欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2024-02-18 18:21:46
之前参加了一个课程,里面有一节讲到了用视频作为启动界面。讲师用的是自定义videoview,重写onmeasure方法,因为原生的videoview在那情况下不能实现全屏播...

之前参加了一个课程,里面有一节讲到了用视频作为启动界面。讲师用的是自定义videoview,重写onmeasure方法,因为原生的videoview在那情况下不能实现全屏播放。当时没有深入研究,现在补回来。

用的是36氪之前的视频(608×1080)和genymotion中的google nexus 5(1080×1920)。

一、效果图

1、原生videoview的效果,这里没有让底部的导航栏也变透明。因为截图上来很难看到差别,后面会解释。

用原生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

用原生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。

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