之前参加了一个课程,里面有一节讲到了用视频作为启动界面。讲师用的是自定义VIDeoVIEw,重写onMeasure方法,因为原生的VIDeoVIEw在那情况下不能实现全屏播放。当时没有深入研究,现在补回来。
用的是36氪之前的视频(608×1080)和Genymotion中的Google Nexus 5(1080×1920)。
一、效果图
1、原生VIDeoVIEw的效果,这里没有让底部的导航栏也变透明。因为截图上来很难看到差别,后面会解释。
xml
<?xml version="1.0" enCoding="utf-8"?><linearLayout xmlns:androID="http://schemas.androID.com/apk/res/androID" androID:orIEntation="vertical" androID:layout_wIDth="match_parent" androID:layout_height="match_parent"> <VIDeoVIEw androID:ID="@+ID/vIDeo_vIEw" androID:layout_wIDth="match_parent" androID:layout_height="match_parent" androID:clickable="false" androID:focusable="false" androID:focusableIntouchMode="false"/></linearLayout>
java
public class VIDeoVIEwActivity extends AppCompatActivity { private VIDeoVIEw mVIDeoVIEw; @OverrIDe protected voID onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentVIEw(R.layout.activity_vIDeo_vIEw); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { getwindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);// getwindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); } mVIDeoVIEw = (VIDeoVIEw) findVIEwByID(R.ID.vIDeo_vIEw); mVIDeoVIEw.setVIDeoURI(Uri.parse("androID.resource://" + getPackagename() + "/" + R.raw.kr36)); mVIDeoVIEw.start(); mVIDeoVIEw.setonCompletionListener(new MediaPlayer.OnCompletionListener() { @OverrIDe public voID onCompletion(MediaPlayer mp) { mVIDeoVIEw.start(); } }); }}
2、自定义的VIDeoVIEw
布局文件基本同上,除了控件名和ID
...<com.example.test.test_fitstatusbar.CustomVIDeoVIEw androID:ID="@+ID/custom_vIDeo_vIEw"...
Activity.java也是基本同上。这里是自定义VIDeoVIEw的java代码,只重写了onMeasure方法。
public class CustomVIDeoVIEw extends VIDeoVIEw { public CustomVIDeoVIEw(Context context) { super(context); } public CustomVIDeoVIEw(Context context,AttributeSet attrs) { super(context,attrs); } public CustomVIDeoVIEw(Context context,AttributeSet attrs,int defStyleAttr) { super(context,attrs,defStyleAttr); } @OverrIDe protected voID onMeasure(int wIDthMeasureSpec,int heightmeasureSpec) { int wIDth = getDefaultSize(0,wIDthMeasureSpec); int height = getDefaultSize(0,heightmeasureSpec); setMeasuredDimension(wIDth,height); }}
二、在对比原生VIDeoVIEw的onMeasure方法之前,先了解一些事情。
1、这里涉及到MeasureSpec类,这个类代码不多,但很精妙。我也有很多地方没弄懂。不过在这里,只需了解它的三种mode就可以了。
/** * 1、UnspecIFIED * 根据源码的注释,其大概意思是parent不对child做出限制.它想要什么size就给什么size.看了一些教程,都说用得很少,或者是系统内部才用得上.所以先不管了 * 2、EXACTLY * 对应于match_parent和给出具体的数值 * 3、AT_MOST * 对应于wrap_content */ public static class MeasureSpec { private static final int MODE_SHIFT = 30; private static final int MODE_MASK = 0x3 << MODE_SHIFT; public static final int UnspecIFIED = 0 << MODE_SHIFT; public static final int EXACTLY = 1 << MODE_SHIFT; public static final int AT_MOST = 2 << MODE_SHIFT; ...... public static int getMode(int measureSpec) { return (measureSpec & MODE_MASK); } public static int getSize(int measureSpec) { return (measureSpec & ~MODE_MASK); } ...... }
而这里,我所有控件的wIDth和height都是mach_parent,所以以下分析都是基于MeasureSpec.EXACTLY这个mode。
2、getDefaultSize
public static int getDefaultSize(int size,int measureSpec) { int result = size; int specMode = MeasureSpec.getMode(measureSpec); int specsize = MeasureSpec.getSize(measureSpec); switch (specMode) { case MeasureSpec.UnspecIFIED: result = size; break; case MeasureSpec.AT_MOST: case MeasureSpec.EXACTLY: result = specsize; break; } return result; }
因为都是MeasureSpec.EXACTLY,所以最终返回的结果是MeasureSpec.getSize(measureSpec),与size,也就是第一个参数无关。
3、setMeasuredDimension
protected final voID setMeasuredDimension(int measureDWIDth,int measuredHeight) { boolean optical = isLayoutModeOptical(this); if (optical != isLayoutModeOptical(mParent)) { Insets insets = getopticalinsets(); int opticalWIDth = insets.left + insets.right; int opticalHeight = insets.top + insets.bottom; measureDWIDth += optical ? opticalWIDth : -opticalWIDth; measuredHeight += optical ? opticalHeight : -opticalHeight; } setMeasuredDimensionRaw(measureDWIDth,measuredHeight); }
中间的判断语句,涉及到VIEwGroup的LayoutMode,它有两个值,一个是默认值clipBounds,效果就是保留子vIEw之间的空白,因为有些控件看上去要比实际的小,但它仍然是占了给定的大小,只是系统让它的一部分边缘变成留白,这样的话,不至于子vIEw真的是连接在一起;另一个是opticalBounds,它就是用来消除clipBounds的效果。一般情况下,都不会进入判断语句块里。
而这里要关注的其实是最后一句代码,setMeasuredDimensionRaw。
4、setMeasuredDimensionRaw
private voID setMeasuredDimensionRaw(int measureDWIDth,int measuredHeight) { mMeasureDWIDth = measureDWIDth; mMeasuredHeight = measuredHeight; mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET; }
这个方法就是将最终的测量结果赋值给对应的vIEw的全局变量,意味着measure部分结束。
三、对比原生VIDeoVIEw的onMeasure方法
@OverrIDe protected voID onMeasure(int wIDthMeasureSpec,int heightmeasureSpec) {// Log.i("@@@@","onMeasure(" + MeasureSpec.toString(wIDthMeasureSpec) + ","// + MeasureSpec.toString(heightmeasureSpec) + ")"); int wIDth = getDefaultSize(mVIDeoWIDth,wIDthMeasureSpec); int height = getDefaultSize(mVIDeoHeight,heightmeasureSpec); if (mVIDeoWIDth > 0 && mVIDeoHeight > 0) { int wIDthSpecMode = MeasureSpec.getMode(wIDthMeasureSpec); int wIDthSpecsize = MeasureSpec.getSize(wIDthMeasureSpec); int heightSpecMode = MeasureSpec.getMode(heightmeasureSpec); int heightSpecsize = MeasureSpec.getSize(heightmeasureSpec); if (wIDthSpecMode == MeasureSpec.EXACTLY && heightSpecMode == MeasureSpec.EXACTLY) { // the size is fixed wIDth = wIDthSpecsize; height = heightSpecsize; // for compatibility,we adjust size based on aspect ratio if ( mVIDeoWIDth * height < wIDth * mVIDeoHeight ) { //Log.i("@@@","image too wIDe,correcting"); wIDth = height * mVIDeoWIDth / mVIDeoHeight; } else if ( mVIDeoWIDth * height > wIDth * mVIDeoHeight ) { //Log.i("@@@","image too tall,correcting"); height = wIDth * mVIDeoHeight / mVIDeoWIDth; } } else if (wIDthSpecMode == MeasureSpec.EXACTLY) { ...... } else if (heightSpecMode == MeasureSpec.EXACTLY) { ...... } else { ...... } } else { // no size yet,just adopt the given spec sizes } setMeasuredDimension(wIDth,height); }
为了方便对比,再贴出onMeasure方法。我在这个方法中,打印过wIDth和height的值,它们的值就是屏幕显示部分的分辨率。意思是说,按这里的情况来讲,当状态栏和底部的导航栏都是透明时,wIDth是1080,height是1920,正好是Google Nexus 5的分辨率。
当底部的导航栏不是透明时,height就是1776。
@OverrIDe protected voID onMeasure(int wIDthMeasureSpec,int heightmeasureSpec) { int wIDth = getDefaultSize(0,height); }
现在对比原生的onMeasure方法来分析。
首先是通过getDefaultSize来得到wIDth和height。上面说过,在我这个例子中,getDefaultSize的返回值只与第二个参数有关,即wIDthMeasureSpec和heightmeasureSpec,而这两个参数都是从相同的VIEwGroup传进来的,所以无论是原生还是重写,其从getDefaultSize中得到的值都是一样的。然后进入第一层判断语句块,在这里通过MeasureSpec.getMode()和getSize(),再次取得控件的mode和size。其实这在getDefaultSize里也有实现,所以外层的wIDth和wIDthSpecsize的值是相同的,height也是这种情况。
根据之前的说明,可以知道进入的是第一个判断语句块,而其它情况也被我省略了。
再到下面的判断语句,比较乘积之后,就修改wIDth或height,对比重写的方法可以判断,导致效果不同的地方就是这里。代码的逻辑很清晰简单。这里直接取具体值来分析。这里的视频资源的帧宽度是608,帧高度是1080。用来测试的Google Nexus 5是1080×1920。
mVIDeoWIDth * height = 608 × 1920 = 1,167,360,mVIDeoHeight * wIDth= 1080 × 1080 = 1,166,400,所以修改的是height,等于1,918.4。所以开头说不让底部的导航栏变透明,因为只差两个像素左右,截图看不清。而当底部导航栏不是透明的时候,height是1776。这时候修改的就是wIDth,等于999.8,所以如上面的截图,差别就比较明显了。这么看来,这部分代码就是把VIDeoVIEw的宽或高给修改了,因为我是指定match_parent的,也就应该是屏幕显示部分的大小。而重写的方法就是跳过了这部分,让VIDeoVIEw的宽高仍然是match_parent。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持编程小技巧!
总结以上是内存溢出为你收集整理的用原生VideoView进行全屏播放时的问题全部内容,希望文章能够帮你解决用原生VideoView进行全屏播放时的问题所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)