原創(chuàng)安卓手機QQ7.0登錄界面動態(tài)背景視頻實現(xiàn)方案

分析qq7.0:

視頻在打開登錄界面就開始播放 了,而且期間無黑屏
而且是循環(huán)播放的

畫質問題這里就不說了,這個看視頻源了。

可以讓不規(guī)則的寬高各種寬高不定的視頻比例 以及視頻大小都能 適應任意安卓手機的寬高 包括平板,且不留任何縫隙

播放器控件選取:解決的是手機適配的問題,另外是播放器控件,這里選擇系統(tǒng)播放器比較好. 因為有些播放器不支持讀取asset文件夾的Uri比如七牛的

視頻加載速度比較慢第一幀用圖片代替且需要耦合視頻的第一幀

圖片的第一幀截取我用的是一個比較專業(yè)的adobe premiere的開發(fā)工具 這個你們也可以讓ps等后期的去做,這種事情對我來說的話還是小kiss,

技術點:
如何讀取資源文件視頻
如何測量
如何根據(jù)視頻大小計算應該縮放的比例大小 解決任意尺寸視頻手機不留黑邊
如何讓圖片的封面縮放大小和視頻的縮放大小吻合
如何調用onStart短暫黑屏問題

架構搭建

資源的讀取


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

創(chuàng)建一個自定義視頻類 自定義圖片類 圖片在視頻的上面因為視頻不是馬上播放 加載有一定時間這里也會存在一個黑屏

關于讀取視頻的問題,之前嘗試過讀取assests里面的視頻失敗了,在stackoverflow照的方案也不行,最后還是把視頻放到和res/raw文件夾里面了,

具體實現(xiàn)之視頻控件

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就會有一定概率的黑屏毫秒
先不管測量鋪滿問題,我們發(fā)現(xiàn)會存在一個坑,就是視頻黑屏問題,進入這個界面肯定要讓它不黑屏的.
1.嘗試過在onPrepared里面再在讓VideoView顯示隱藏結果沒卵用
1.直接隱藏控件在方案1的基礎上延長幾秒,start過程中依然隱藏(不同手機需要的延長時間不同,)但是如果0秒到1秒的過程中如果沒有畫面動還好,如果動了,延長超過1秒后在顯示此控件那么視頻就需要留長 不然首幀和此時videoview顯示的時間不一致,后面發(fā)現(xiàn)這種死辦法又沒法解決循環(huán)播放問題

最后的解決方法通過百度找到 是根據(jù)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);

    }

總結:

由于加載視頻比較慢,比如會有黑屏,但是圖片的加載速度所產生的黑屏人幾乎感受不到,所以采用的方法是 先獲取第一張視頻第一幀圖片 覆蓋在視頻的區(qū)域上面,
當視頻加載回調首幀的時候隱藏,
需要解決的問題是圖片的縮放方法和視頻的一樣,這樣當圖片隱藏后視頻的第一幀才可以完美銜接起來.
還需要解決的問題就是 鋪滿全屏的不留任何空隙的算法問題。

開源項目:
https://github.com/qssq/videoplayer

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容