用原生VideoView进行全屏播放时的问题
之前参加了一个课程,里面有一节讲到了用视频作为启动界面。讲师用的是自定义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, widthmeasurespec); int height = getdefaultsize(0, heightmeasurespec); setmeasureddimension(width, 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。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持!