• 用原生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的效果。不过,这种情况只在Holo Theme下才能有效。可以参考官方文档https://developer.android.com/about/versions/android-4.3.html#UI。但现在应该是用AppCompat Theme的比较多,至于怎么在AppCompat Theme下也实现这种效果,还有待解决。

    一般情况下,都不会进入判断语句块里。

    而这里要关注的其实是最后一句代码,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。

  • 相关阅读:
    0152 日期对象Date:实例化,属性和方法,操作总毫秒数【时间戳】,案例
    0151 Math对象:random、round、floor、ceil、abs、max、min、随机整数、案例
    0150 内置对象概述、查阅MDN文档
    0149 遍历对象:for...in
    0148 JavaScript 的 new关键字
    0147 JavaScript创建对象的三种方式 之 构造函数
    0146 JavaScript创建对象的三种方式 之 new Object
    0145 JavaScript创建对象的三种方式 之 字面量:创建,访问对象的属性&方法,变量、属性、函数、方法总结
    0144 对象:相关概念、对象的优势
    0143 JavaScript预解析:概念、变量预解析、函数预解析、函数表达式声明函数问题、案例
  • 原文地址:https://www.cnblogs.com/xiongwo/p/6262595.html
Copyright © 2020-2023  润新知