当前位置: 移动技术网 > IT编程>移动开发>Android > 用原生VideoView进行全屏播放时的问题

用原生VideoView进行全屏播放时的问题

2019年07月24日  | 移动技术网IT编程  | 我要评论

生活大爆炸第四季19,李翊君的歌,实达bp3000

之前参加了一个课程,里面有一节讲到了用视频作为启动界面。讲师用的是自定义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。

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持移动技术网!

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网