用原生VideoView进行全屏播放时的问题_videoview mvideoview.setzorderontop(true);其他界面显示不出-程序员宅基地

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

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/eouaq448466/article/details/54636230

智能推荐

一对一直播源码开发,疑难杂症排查— 播放杂音、噪音、回声问题_开发音频 播放 伴随嘶哑声-程序员宅基地

文章浏览阅读203次。今天要跟大家分享的是一对一直播源码开发,疑难杂症排查— 播放杂音、噪音、回声问题,相比于一对一直播源码开发中的视频而言,音频要敏感得多,视频画面有噪点、马赛克都还是可以勉强被接受,而声音一旦有任何瑕疵,人耳都会特别容易感觉到,而且难以忍受。一、问题现象常见的一对一直播源码开发中音频问题现象描述如下:电流音,爆音,滋滋声或者嘟嘟声声音断断续续,听不清楚回声,能听到自己说话的声音二、 问题排查1、参数配置问题在一对一直播源码开发中,音频是一个特别敏感的东西,涉及到许多参数配置,一_开发音频 播放 伴随嘶哑声

15张超详细的Python学习路线图,纯良心分享,零基础学习宝典-程序员宅基地

文章浏览阅读5.6k次,点赞3次,收藏23次。这是一篇 Python 入门指南,针对那些没有任何编程经验,从零开始学习 Python 的同学。不管你学习的出发点是兴趣驱动、拓展思维,还是工作需要、想要转行,都可以用此文作为一个参考。在这个信息爆炸的时代,以 “Python入门” 为关键字搜索出的结果成千上万。不少小白选手难免会东一榔头西一棒槌,最终看了很多文章,却仍没跨过新手那道门槛。结合自身的学习经验以及与很多自学者的沟通了解,我们整理出,供诸位尚未入门或刚入门不久的同学参考。..._python学习路线图

干货全拿走-用Excel构建股票量化交易模型_个人炒股模型-程序员宅基地

文章浏览阅读5.1k次,点赞3次,收藏23次。ExcelVBA写的几个量化算法和模型_个人炒股模型

mysql导出数据库报错,navicat 导入导出数据库报错的解决-程序员宅基地

文章浏览阅读1.1k次。在使用navicat导入导出数据功能时,导出没问题,导入总是报错,还遇到了中文乱码。在网上查了很多资料,比如使用了navicat的数据传输功能,但仍然报错,弄了几个小时都没解决。最后换了种思路,我直接用mysql自带的命令行来尝试导入导出,结果一试就成功无任何报错、乱码!这里顺便贴出网上的教程:MySQL命令行导出数据库:1,进入MySQL目录下的bin文件夹:cdMySQL中到bin文件夹的目..._mysqldump导入navicat错误

java.sql.SQLException: java.lang.ClassCastException: java.math.BigInteger cannot be cast to java.lan_mysql中java.lang.classcastexception: java.math.bigi-程序员宅基地

文章浏览阅读4.7k次。换个jar包就好了_mysql中java.lang.classcastexception: java.math.biginteger cannot be cast to

【计算机网络】电脑无法搜索到蓝牙设备,但是手机可以搜索到蓝牙设备-程序员宅基地

文章浏览阅读100次。电脑无法搜索到蓝牙设备,但是手机可以搜索到蓝牙设备,我出现这个问题是电脑默认设置下只能搜索到常用的连接配件,而高级设置可以搜索到全部的蓝牙设备。设置方法 Win11系统,设置->蓝牙和其他设备->设备->设备设置->蓝牙设备设置->高级。

随便推点

java实现医药网站_JavaWeb版医药管理系统[中英文版]-程序员宅基地

文章浏览阅读168次。【实例简介】数据库为GBK 请大家建立数据库时注意数据库配置 :hibernate.cfg.xml参数配置 :MessageResources_zh_CN.properties首页登录 :http://localhost:8080/MedicineManager/用户登录页面 可以切换中英文:测试了将近2个小时 基本跑的不错 但是貌似有乱码问题、大家试着解决吧【实例截图】【核心代码】edb8d3e..._医学医药管理网页代码

Typescript 之接口 interface(详解)_ts interface-程序员宅基地

文章浏览阅读5.3k次,点赞3次,收藏11次。你可以把它理解为形状,一个对象需要有什么样的属性,函数需要什么参数或返回什么样的值,数组应该是什么样子的,一个类和继承类需要符合什么样的描述等等。需要注意的是类 Interface 只会检查实例的属性,静态属性是需要额外定义一个 Interface;接口就是用来定义一个类结构,定义一个类中应该包含哪些属性和方法,同时接口也可以当成类型声明去使用。混合类型的接口就是使用同一个 Interface 来描述函数或者对象的属性或方法。接口可以约束对象,函数,类的结构和类型,是一种代码协作必须遵守的契约。_ts interface

springcloud之admin的搭建使用_spring cloud admin-程序员宅基地

文章浏览阅读1.6k次。微服务的优点是服务模块化,高内聚,低耦合。随着服务数量的增多,服务的监控也变得异常困难。springcloud引入提供了admin组件,人性化UI管理界面可以对所有服务的运行状态进行监控,并提供告警等功能。一.springboot-admin简介显示健康状况显示详细信息,例如JVM和内存指标数据源指标缓存指标关注并下载日志文件查看jvm系统和环境属性查看Spring Boot配置..._spring cloud admin

/etc/fstab 参数详解及如何设置开机自动挂载_fstab defaults-程序员宅基地

文章浏览阅读6.5w次,点赞5次,收藏52次。某些时候当Linux系统下划分了新的分区后,需要将这些分区设置为开机自动挂载,否则,Linux是无法使用新建的分区的。 /etc/fstab 文件负责配置Linux开机时自动挂载的分区。Windows的文件结构是多个并列的树状结构,最顶部的是不同的磁盘(分区),如:C,D,E,F等。Linux的文件结构是单个的树状结构。最顶部的为根目录,即/。在根目录下,分为多个子目录,包括/bi_fstab defaults

LNK1104:Cannot open file 'glut32.lib'_vs2022 错误lnk1104无法打开文件“glut32.lib-程序员宅基地

文章浏览阅读2.8k次。前言:好久没有写CSDN的blog了,刚刚写blog的时候,我还是一个hello world的水平。真可谓“条条大路通CS”,花了一年时间,从EE专业自学到了CS了。也当是小有所成。希望,一起在路上的朋友,我们一起加油!问题背景在使用QT开发window应用程序的时候,需要使用到OpenGL的库来绘制3D地球。问题详细使用QT creator这个IDE,在build项目的时候,报下面的错误LNK1_vs2022 错误lnk1104无法打开文件“glut32.lib

正则基础知识-程序员宅基地

文章浏览阅读4.1k次,点赞4次,收藏18次。正则 RegExp:由相关元字符和修饰符组成的一个规则,匹配 验证和捕获(只用来处理字符串)可以理解为两个斜杠中间包含一些内容就是正则元字符:/元字符/ 两个斜杠之间包起来的内容正则:它就是用来处理字符串的一个规则;●正则匹配:编写一个规则,验证某个字符串是否符合这个规则,正则匹配使用的是test方法●正则捕获:编写一个规则,在一个字符串中把复合规则的内容都获取到 正则捕获使用的方法 正则的exec方法、字符串中的split、replace、match等方法都支持正则正则创建● 字面_正则