原创安卓手机QQ7.0登录界面动态背景视频实现方案

分析qq7.0:

视频在打开登录界面就开始播放 了,而且期间无黑屏
而且是循环播放的

画质问题这里就不说了,这个看视频源了。

可以让不规则的宽高各种宽高不定的视频比例 以及视频大小都能 适应任意安卓手机的宽高 包括平板,且不留任何缝隙

播放器控件选取:解决的是手机适配的问题,另外是播放器控件,这里选择系统播放器比较好. 因为有些播放器不支持读取asset文件夹的Uri比如七牛的

视频加载速度比较慢第一帧用图片代替且需要耦合视频的第一帧

图片的第一帧截取我用的是一个比较专业的adobe premiere的开发工具 这个你们也可以让ps等后期的去做,这种事情对我来说的话还是小kiss,

技术点:
如何读取资源文件视频
如何测量
如何根据视频大小计算应该缩放的比例大小 解决任意尺寸视频手机不留黑边
如何让图片的封面缩放大小和视频的缩放大小吻合
如何调用onStart短暂黑屏问题

架构搭建

资源的读取


   String VIDEO_PATH = "android.resource://" + BuildConfig.APPLICATION_ID + "/" + R.raw.login;
videoView.setVideoURI(Uri.parse(Constants.VIDEO_PATH));

创建一个自定义视频类 自定义图片类 图片在视频的上面因为视频不是马上播放 加载有一定时间这里也会存在一个黑屏

关于读取视频的问题,之前尝试过读取assests里面的视频失败了,在stackoverflow照的方案也不行,最后还是把视频放到和res/raw文件夹里面了,

具体实现之视频控件

1. 拿到视频的宽高度才能进行测量重新布局

在继承的VideoView里添加setOnPreparedListener方法获取视频宽高度设置给成员变量就可以拿到了

 super.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
            @Override
            public void onPrepared(final MediaPlayer mp) {


      SystemVideoView.this.videoWidth = mp.getVideoWidth();
      SystemVideoView.this.videoHeight = mp.getVideoHeight();

}
}

2. 继承VideoView重写onMeasure测量方法

需要一个完美的算法来解决宽高都铺满屏幕问题

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        MeasureUtil.Size measure = MeasureUtil.measure(displayAspectRatio, widthMeasureSpec, heightMeasureSpec, videoWidth, videoHeight);

        setMeasuredDimension(measure.width, measure.height);
    }

这里的算法比较麻烦,不懂的同学搬用模版代码
测量工具类MeasureUtil.measure方法抽出来
的大致代码是

    public static MeasureUtil.Size measure(int displayAspectRatio, int widthMeasureSpec, int heightMeasureSpec, int videoWidth, int videoHeight) {


 if (widthMode == View.MeasureSpec.EXACTLY && heightMode == View.MeasureSpec.EXACTLY) {  

  if (percentVideo > percentView) {
                            width = widthSize;
                            height = (int) ((float) widthSize / percentVideo);
                        } else {
                            height = heightSize;
                            width = (int) ((float) heightSize * percentVideo);
                        }

    }else if (widthMode == View.MeasureSpec.EXACTLY) {
                width = widthSize;
                height = widthSize * videoHeight / videoWidth;
                if (heightMode == View.MeasureSpec.AT_MOST && height > heightSize) {
                    height = heightSize;
                }
            } else if (heightMode == View.MeasureSpec.EXACTLY) {
                height = heightSize;
                width = heightSize * videoWidth / videoHeight;
                if (widthMode == View.MeasureSpec.AT_MOST && width > widthSize) {
                    width = widthSize;
                }
            } else {
                width = videoWidth;
                height = videoHeight;
                if (heightMode == View.MeasureSpec.AT_MOST && videoHeight > heightSize) {
                    height = heightSize;
                    width = heightSize * videoWidth / videoHeight;
                }

                if (widthMode == View.MeasureSpec.AT_MOST && width > widthSize) {
                    width = widthSize;
                    height = widthSize * videoHeight / videoWidth;
                }
            }

}


public static class Size {
        public final int width;
        public final int height;

        public Size(int width, int height) {
            this.width = width;
            this.height = height;
        }
    }

3. 黑屏问题解决探讨

只要调用start就会有一定概率的黑屏毫秒
先不管测量铺满问题,我们发现会存在一个坑,就是视频黑屏问题,进入这个界面肯定要让它不黑屏的.
1.尝试过在onPrepared里面再在让VideoView显示隐藏结果没卵用
1.直接隐藏控件在方案1的基础上延长几秒,start过程中依然隐藏(不同手机需要的延长时间不同,)但是如果0秒到1秒的过程中如果没有画面动还好,如果动了,延长超过1秒后在显示此控件那么视频就需要留长 不然首帧和此时videoview显示的时间不一致,后面发现这种死办法又没法解决循环播放问题

最后的解决方法通过百度找到 是根据info的视频第一帧来判断:

  mp.setOnInfoListener(new MediaPlayer.OnInfoListener() {
                    @Override
                    public boolean onInfo(MediaPlayer mp, int what, int extra) {
                        if (what == MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START) {
                            if (onCorveHideListener != null) {
                                onCorveHideListener.requestHide();
                            }
                        }
                        if (onInfoListener != null) {
                            onInfoListener.onInfo(mp, what, extra);
                        }
                        return false;
                    }
                });

图片的缩放解决方案和视频缩放一样,你这都需要代码得话打赏一个吧,哈哈,

隐藏的方法在外面了。叫 setOnCorveHideListener ,实际上进入界面就应该马上显示画面的隐藏视频的话是一个白屏,所以这里需要

最后界面activity或者fragment代码

    String VIDEO_PATH = "android.resource://" + BuildConfig.APPLICATION_ID + "/" + R.raw.login;

    loginActivityBinding.videoView.setDisplayAspectRatio(MeasureUtil.ASPECT_RATIO_PAVED_PARENT);
        loginActivityBinding.videoView.setOnCorveHideListener(new SystemVideoView.OnCorveHideListener() {
            @Override
            public void requestHide() {
                loginActivityBinding.corver.setVisibility(View.GONE);
            }
        });
        loginActivityBinding.videoView.setVideoURI(Uri.parse(Constants.VIDEO_PATH));
        loginActivityBinding.videoView.start();
        loginActivityBinding.videoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
            @Override
            public void onCompletion(MediaPlayer mp) {
                loginActivityBinding.videoView.seekTo(0);
                loginActivityBinding.videoView.start();
            }
        });




 @Override
    public void onPause() {
        super.onPause();
        loginActivityBinding.videoView.pause();
    }

    @Override
    public void onResume() {
        super.onResume();
        loginActivityBinding.videoView.start();
    }

完整SystemVideoView代码

public class SystemVideoView extends VideoView {


    private int videoWidth;//width
    private int videoHeight;
    private int displayAspectRatio;

    public SystemVideoView(Context context) {
        super(context);
    }

    public SystemVideoView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public SystemVideoView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);

    }

    protected void init(Context context) {
        this.videoHeight = context.getResources().getDisplayMetrics().heightPixels;
        this.videoWidth = context.getResources().getDisplayMetrics().widthPixels;

        super.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
            @Override
            public void onPrepared(final MediaPlayer mp) {

                SystemVideoView.this.videoWidth = mp.getVideoWidth();
                SystemVideoView.this.videoHeight = mp.getVideoHeight();
                mp.setOnInfoListener(new MediaPlayer.OnInfoListener() {
                    @Override
                    public boolean onInfo(MediaPlayer mp, int what, int extra) {
                        if (what == MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START) {
                            if (onCorveHideListener != null) {
                                onCorveHideListener.requestHide();
                            }
                        }
                        if (onInfoListener != null) {
                            onInfoListener.onInfo(mp, what, extra);
                        }
                        return false;
                    }
                });
            }
        });
    }

    MediaPlayer.OnPreparedListener onPreparedListener = null;

    public interface OnCorveHideListener {
        void requestHide();
    }

    @Override
    public void setOnInfoListener(MediaPlayer.OnInfoListener onInfoListener) {
        this.onInfoListener = onInfoListener;
    }

    MediaPlayer.OnInfoListener onInfoListener;

    public void setOnCorveHideListener(OnCorveHideListener onCorveHideListener) {
        this.onCorveHideListener = onCorveHideListener;
    }

    OnCorveHideListener onCorveHideListener;

    @Override
    public void setOnPreparedListener(MediaPlayer.OnPreparedListener l) {
        this.onPreparedListener = l;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);


        MeasureUtil.Size measure = MeasureUtil.measure(displayAspectRatio, widthMeasureSpec, heightMeasureSpec, videoWidth, videoHeight);
        setMeasuredDimension(measure.width, measure.height);





    public void setDisplayAspectRatio(int var1) {
        displayAspectRatio = var1;
        this.requestLayout();

    }


    @Override
    public boolean isPlaying() {
        return false;
    }

    public int getDisplayAspectRatio() {
        return displayAspectRatio;
    }

    public void setCorver(int resource) {
        BitmapFactory.Options opts = new BitmapFactory.Options();
        opts.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(getResources(), resource, opts);

    }

总结:

由于加载视频比较慢,比如会有黑屏,但是图片的加载速度所产生的黑屏人几乎感受不到,所以采用的方法是 先获取第一张视频第一帧图片 覆盖在视频的区域上面,
当视频加载回调首帧的时候隐藏,
需要解决的问题是图片的缩放方法和视频的一样,这样当图片隐藏后视频的第一帧才可以完美衔接起来.
还需要解决的问题就是 铺满全屏的不留任何空隙的算法问题。

开源项目:
https://github.com/qssq/videoplayer

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,743评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,296评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,285评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,485评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,581评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,821评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,960评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,719评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,186评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,516评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,650评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,329评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,936评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,757评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,991评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,370评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,527评论 2 349

推荐阅读更多精彩内容