Android 列表視頻的全屏、自動小窗口優化實踐

 Hello,愛貓的老司機來埋坑啦<( ̄︶ ̄)>,鑑於以前的《Android 實現視屏播放器、邊播邊緩存功能、外加鏟屎(IJKPlayer)》好像還挺多人關注的,文中一些地方由於篇幅(就是懶)問題一筆帶過,這篇就拓撲聊一聊其中列表全屏,還有播放中的視頻滑出屏幕用小窗口播放的實現,恰好最近有作了一些調整。

 
上例牌 github>>>>>>>> github.com/CarGuo 對,就是這個郭老司機。javascript

本期就不話嘮了,週一誰有精力說話呢┑( ̄Д  ̄)┍,程序猿的週末是什麼?java

列表中播放視頻全屏展現

 
 看太小喵上一篇視頻相關文章的應該知道小喵手賤的用了兩種實現方式,一種是基於懶人的系統層模式;一種是基於單例的UI邏輯播放器的模式的ListVideoUtil。至於爲何是兩種呢?由於手賤啊。(ノಠ益ಠ)ノ彡┻━┻,本文若有不明之處可結合前文一塊兒食用:《Android 實現視屏播放器、邊播邊緩存功能、外加鏟屎(IJKPlayer)》android

一、系統層實現全屏播放

 
 偉人曾經說過,每個Activity都有一個本身的默認佈局,這裏面又包含有了一個com.android.internal.R.id.content,並且是一個FrameLayout(請無視上面的廢話),如此看來用來做爲咱們全屏顯示的父佈局妥妥的。此處手賤的加入了動畫效果的支持,一直以爲5.0的過渡動畫挺高大上的,做爲一個material design的應用必須有這樣的逼格(什麼?你說兼容?這裏美女太多我聽不到····)。git

 做爲一隻內向的程序猿,語言組織能力有限,咱們仍是從代碼上來,從代碼上去吧,註釋滿滿的,順序看下去不難理解(前提是你看的下,確實長♂了點)。github

1.1 進入全屏

 

  • 獲取到了com.android.internal.R.id.content這個ViewGroup。
  • 清除當前列表播放器L上的TextureView渲染控件,等待全屏播放器F的渲染控件。
  • 新建立一個視頻邏輯播放器F,爲它設置一個固定id,這樣幹掉它的時候經過這個id也能快速找到。
  • 保存當前的狀態欄、標題欄信息和列表中在屏幕位置的信息,用於恢復到本來的狀態。
  • 建立一個黑色背景的FrameLayout,充滿屏幕用來承載全屏播放器F,這樣全屏播放器F能夠在其中執行動畫效果。
  • 5.0如下直接加全屏播放器F到ViewGroup居中充滿全屏,5.0以上則執行動畫。
  • 5.0以上先經過margin讓全屏播放器加入到ViewGroup同列表的位置一致,以後經過過渡動畫平移到屏幕中間,居中充滿全屏。

怎麼樣,看起來是否是有些混亂?(ノಠ益ಠ)ノ彡┻━┻,我就說程序猿仍是看代碼好溝通是吧,雖然很長就是。api

 
//得到com.android.internal.R.id.content
private ViewGroup getViewGroup() {    
    return (ViewGroup) (CommonUtil.scanForActivity(getContext())).findViewById(Window.ID_ANDROID_CONTENT);
}

···此處省略無數只草泥馬

//這兩個是TextureView的回調,在這remove和onPause還有add的時候基本會進入
 @Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
    //更新數據到這個surface上渲染
    mSurface = new Surface(surface);
    GSYVideoManager.instance().setDisplay(mSurface);
}

@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
    //告訴數據管理器這個渲染控件放棄了
    GSYVideoManager.instance().setDisplay(null);
    surface.release();
    return true;
}

···此處省略無數只草泥馬

這個開始全屏的頁面邏輯

//將播放的視頻渲染控件移除,進入上面的回調,讓新的邏輯播放器能夠接入
if (mTextureViewContainer.getChildCount() > 0) {
    mTextureViewContainer.removeAllViews();
}

//保存全屏以前的狀態欄和
saveLocationStatus(context, statusBar, actionBar);

try {
    //生成一個播放器,由於繼承關係,會建立一個當前列表item同樣的UI邏輯播放器
    //這些邏輯都是寫在GSYBaseVideoPlayer這個抽象類下
    Constructor<GSYBaseVideoPlayer> constructor = (Constructor<GSYBaseVideoPlayer>) GSYBaseVideoPlayer.this.getClass().getConstructor(Context.class);
    final GSYBaseVideoPlayer gsyVideoPlayer = constructor.newInstance(getContext());
    //給它一個固定的id,在這樣移除的時候就知道在哪裏
    gsyVideoPlayer.setId(FULLSCREEN_ID);
    //獲取屏幕的高度
    WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
    final int w = wm.getDefaultDisplay().getWidth();
    final int h = wm.getDefaultDisplay().getHeight();
    //建立一個層用於加入都window層中,設置爲黑色,用於包含著播放器
    FrameLayout.LayoutParams lpParent = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
    FrameLayout frameLayout = new FrameLayout(context);
    frameLayout.setBackgroundColor(Color.BLACK);
    //若是5.0的機器就執行動畫,這裏其實能夠用VauleAnimaton兼容5.0如下的
    if (mShowFullAnimation && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        //先把播放器的位置設置爲在列表中同樣位置
        FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(getWidth(), getHeight());
        lp.setMargins(mListItemRect[0], mListItemRect[1], 0, 0);
        frameLayout.addView(gsyVideoPlayer, lp);
        vp.addView(frameLayout, lpParent);
        //稍微延時執行動畫
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                //開啓5.0動畫
                TransitionManager.beginDelayedTransition(vp);
                //將播放器跳轉爲充滿居中,系統自動過渡
                resolveFullVideoShow(context, gsyVideoPlayer, h, w);
            }
        }, 300);
    } else {
        //非5.0的直接將播放器的佈局加入到佈局下
        FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(getWidth(), getHeight());
        frameLayout.addView(gsyVideoPlayer, lp);
        vp.addView(frameLayout, lpParent);
        //將播放器跳轉爲充滿居中
        resolveFullVideoShow(context, gsyVideoPlayer, h, w);
    }
    //設置全屏邏輯播放器和當前列表的邏輯狀態一致
    gsyVideoPlayer.setUp(mUrl, mCache, mObjects);
    gsyVideoPlayer.setStateAndUi(mCurrentState);
    //添加上渲染控件,通知數據加載管理器是用這個渲染
    gsyVideoPlayer.addTextureView();
    //配置對應UI
    gsyVideoPlayer.getFullscreenButton().setImageResource(R.drawable.video_shrink);
    gsyVideoPlayer.getFullscreenButton().setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            clearFullscreenLayout();
        }
    });

    gsyVideoPlayer.getBackButton().setVisibility(VISIBLE);
    gsyVideoPlayer.getBackButton().setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            clearFullscreenLayout();
        }
    });
    //將數據加載管理器的接口回到配置到全屏播放器裏面
    GSYVideoManager.instance().setLastListener(this);
    GSYVideoManager.instance().setListener(gsyVideoPlayer);

} catch (Exception e) {
    e.printStackTrace();
}

···此處省略無數只草泥馬

/** * 全屏 */
private void resolveFullVideoShow(Context context, GSYBaseVideoPlayer gsyVideoPlayer) {
    //清除動畫的margin
    FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) gsyVideoPlayer.getLayoutParams();
    lp.setMargins(0, 0, 0, 0);
    //居中充滿
    lp.height = ViewGroup.LayoutParams.MATCH_PARENT;
    lp.width = ViewGroup.LayoutParams.MATCH_PARENT;
    lp.gravity = Gravity.CENTER;
    gsyVideoPlayer.setLayoutParams(lp);
    gsyVideoPlayer.setIfCurrentIsFullscreen(true);
    //加入旋轉工具類
    mOrientationUtils = new OrientationUtils((Activity) context, gsyVideoPlayer);
    mOrientationUtils.setEnable(mRotateViewAuto);
}複製代碼
1.2 退出全屏

 
 既然都進去了♂,出來還難嗎?因此咱們只須要反着來就好了,下面直接長代碼,有註釋。(男人長一點有什麼錯┑( ̄Д  ̄)┍)緩存

  • 是否橫屏,是的話先轉爲豎屏
  • 恢復狀態欄和標題欄
  • 5.0如下直接清除當前列全屏播放器F,恢復視頻狀態
  • 5.0以上顯示讓全屏播放器F過渡到本來的位置,再清除恢復視頻狀態
/** * 退出系統層播放全屏效果 */
public void clearFullscreenLayout() {

    //須要判斷當前是否橫屏,是的話要轉爲界面以後稍等一會在退回,這樣纔不會界面抖動
    int delay = mOrientationUtils.backToProtVideo();
    //關閉旋轉
    mOrientationUtils.setEnable(false);
    mHandler.postDelayed(new Runnable() {
        @Override
        public void run() {
            backToNormal();
        }
    }, delay);
}

/** * 回到正常效果 */
private void backToNormal() {
    //恢復狀態
    showSupportActionBar(mContext, mActionBar, mStatusBar);
    final ViewGroup vp = getViewGroup();
    //拿到content和播放器
    final View oldF = vp.findViewById(FULLSCREEN_ID);
    final GSYVideoPlayer gsyVideoPlayer;
    if (oldF != null) {
        gsyVideoPlayer = (GSYVideoPlayer) oldF;
        if (mShowFullAnimation && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            TransitionManager.beginDelayedTransition(vp);
            //執行動畫回到本來的列表中的位置
            FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) gsyVideoPlayer.getLayoutParams();
            lp.setMargins(mListItemRect[0], mListItemRect[1], 0, 0);
            lp.width = mListItemSize[0];
            lp.height = mListItemSize[1];
            //注意配置回來,否則動畫效果會不對
            lp.gravity = Gravity.NO_GRAVITY;
            gsyVideoPlayer.setLayoutParams(lp);

            new Handler().postDelayed(new Runnable() {
                @Override
                public void run() {
                    resolveNormalVideoShow(oldF, vp, gsyVideoPlayer);
                }
            }, 400);
        } else {
            //直接移除
            resolveNormalVideoShow(oldF, vp, gsyVideoPlayer);
        }

    } else {
        //直接移除
        resolveNormalVideoShow(null, vp, null);
    }
}


/** * 恢復 */
private void resolveNormalVideoShow(View oldF, ViewGroup vp, GSYVideoPlayer gsyVideoPlayer) {
    //移除全屏播放器
    if (oldF.getParent() != null) {
        ViewGroup viewGroup = (ViewGroup) oldF.getParent();
        vp.removeView(viewGroup);
    }
    //拿回狀態
    mCurrentState = GSYVideoManager.instance().getLastState();
    if (gsyVideoPlayer != null) {
        mCurrentState = gsyVideoPlayer.getCurrentState();
    }
    //從新設置回調
    GSYVideoManager.instance().setListener(GSYVideoManager.instance().lastListener());
    GSYVideoManager.instance().setLastListener(null);
    //播放器恢復
    setStateAndUi(mCurrentState);
    //通知數據加載播放器用回列表的渲染
    addTextureView();
    CLICK_QUIT_FULLSCREEN_TIME = System.currentTimeMillis();
}複製代碼

二、ListVideoUtil實現全屏播放

 
 整體上邏輯和上文是一致的,只是這種實如今列表中是不包含邏輯播放器,邏輯播放器和全屏邏輯播放器都是一個單例,須要你手動在list列表的最外層加多一個佈局作全屏播放,在每一個item那裏預留一個位置用於包容列表的播放器,還有一個播放按鈕用於播放。ide

 感受很麻煩是吧,耦合度又高,可是它能夠在視頻滑出界面的時候不被釋放,一直保持在原來的位置。工具

2.1 全屏

 和上面的邏輯基本一致,就不廢話了(能夠偷懶了),只須要注意用的時候操做方式不同,總結起來就是有些麻煩。佈局

//配置好全屏佈局
listVideoUtil.setFullViewContainer(videoFullContainer);
listVideoUtil.setHideStatusBar(true);

···此處省略無數只草泥馬

//增長封面
ImageView imageView = new ImageView(context);
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setImageResource(R.mipmap.xxx1);
//將列表的位置,封面,列表的TAG,列表是的父佈局,播放按鍵傳入進去
listVideoUtil.addVideoPlayer(position, imageView, TAG, holder.videoContainer, holder.playerBtn);

holder.playerBtn.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        //更新其餘item
        notifyDataSetChanged();
        //設置播放器的標誌位,防止錯位
        listVideoUtil.setPlayPositionAndTag(position, TAG);
        //url開始播放
        final String url = "http://baobab.wdjcdn.com/14564977406580.mp4";
        listVideoUtil.startPlay(url);
    }
});複製代碼

列表中播放視頻小窗口播放

 
 有時候咱們會想要視頻滑出屏幕的時候有個小窗口在右下角,最好仍是能夠關閉和拖動的(看視頻的時候能夠快速最小化收起來,不中止,避免尷尬對吧)。邏輯和實現全屏同樣,用系統的content層來承載,不一樣的是利用margin讓視頻出如今右下角,這樣咱們拖動的時候只要改變視頻的margin,就可讓視頻小窗體在它的父佈局內移動啦。

小窗口

/** * 顯示小窗口 */
public void showSmallVideo(Point size, final boolean actionBar, final boolean statusBar) {
    //利用content實現,和全屏同樣,只是大小和背景色不同
    final ViewGroup vp = getViewGroup();

    removeVideo(vp, SMALL_ID);

    if (mTextureViewContainer.getChildCount() > 0) {
        mTextureViewContainer.removeAllViews();
    }

    try {
        Constructor<GSYBaseVideoPlayer> constructor = (Constructor<GSYBaseVideoPlayer>) GSYBaseVideoPlayer.this.getClass().getConstructor(Context.class);
        GSYBaseVideoPlayer gsyVideoPlayer = constructor.newInstance(getContext());
        gsyVideoPlayer.setId(SMALL_ID);

        FrameLayout.LayoutParams lpParent = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
        FrameLayout frameLayout = new FrameLayout(mContext);

        FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(size.x, size.y);
        int marginLeft = CommonUtil.getScreenWidth(mContext) - size.x;
        int marginTop = CommonUtil.getScreenHeight(mContext) - size.y;

        if (actionBar) {
            marginTop = marginTop - getActionBarHeight((Activity) mContext);
        }

        if (statusBar) {
            marginTop = marginTop - getStatusBarHeight(mContext);
        }
        //利用margin讓視頻出如今右下角,這樣咱們拖動的時候只要改變margin就好啦
        lp.setMargins(marginLeft, marginTop, 0, 0);
        frameLayout.addView(gsyVideoPlayer, lp);

        vp.addView(frameLayout, lpParent);
        //繼續播放
        gsyVideoPlayer.setUp(mUrl, mCache, mObjects);
        gsyVideoPlayer.setStateAndUi(mCurrentState);
        gsyVideoPlayer.addTextureView();
        gsyVideoPlayer.onClickUiToggle();
        gsyVideoPlayer.setSmallVideoTextureView(new SmallVideoTouch(gsyVideoPlayer, marginLeft, marginTop));

        GSYVideoManager.instance().setLastListener(this);
        GSYVideoManager.instance().setListener(gsyVideoPlayer);

    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (Exception e) {
        e.printStackTrace();
    }

}

/** * 隱藏小窗口 */
public void hideSmallVideo() {
    final ViewGroup vp = getViewGroup();
    GSYVideoPlayer gsyVideoPlayer = (GSYVideoPlayer) vp.findViewById(SMALL_ID);
    removeVideo(vp, SMALL_ID);
    mCurrentState = GSYVideoManager.instance().getLastState();
    if (gsyVideoPlayer != null) {
        mCurrentState = gsyVideoPlayer.getCurrentState();
    }
    GSYVideoManager.instance().setListener(GSYVideoManager.instance().lastListener());
    GSYVideoManager.instance().setLastListener(null);
    setStateAndUi(mCurrentState);
    addTextureView();
    CLICK_QUIT_FULLSCREEN_TIME = System.currentTimeMillis();
}複製代碼

 這是觸摸邏輯,這拖動的視頻窗體的時候,經過改變margin來實現窗體的移動,注意不要跑飛了就要,加個閾值。多說無益,看代碼(又省下了好多字):

public class SmallVideoTouch implements View.OnTouchListener {

    private int mDownX, mDownY;
    private int mMarginLeft, mMarginTop;
    private int _xDelta, _yDelta;
    private GSYBaseVideoPlayer mGsyBaseVideoPlayer;


    public SmallVideoTouch(GSYBaseVideoPlayer gsyBaseVideoPlayer, int marginLeft,  int marginTop) {
        super();
        mMarginLeft = marginLeft;
        mMarginTop = marginTop;
        mGsyBaseVideoPlayer = gsyBaseVideoPlayer;
    }

    @Override
    public boolean onTouch(View view, MotionEvent event) {
        final int X = (int) event.getRawX();
        final int Y = (int) event.getRawY();
        switch (event.getAction() & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN:
                mDownX = X;
                mDownY = Y;

                FrameLayout.LayoutParams lParams = (FrameLayout.LayoutParams) mGsyBaseVideoPlayer
                        .getLayoutParams();
                _xDelta = X - lParams.leftMargin;
                _yDelta = Y - lParams.topMargin;

                break;
            case MotionEvent.ACTION_UP:
                if (Math.abs(mDownY - Y) < 5 && Math.abs(mDownX - X) < 5) {
                    return false;
                } else {
                    return true;
                }
            case MotionEvent.ACTION_MOVE:
                FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) mGsyBaseVideoPlayer
                        .getLayoutParams();

                layoutParams.leftMargin = X - _xDelta;
                layoutParams.topMargin = Y - _yDelta;

                //不能超過屏幕上下左右的位置
                if (layoutParams.leftMargin >= mMarginLeft) {
                    layoutParams.leftMargin = mMarginLeft;
                }

                if (layoutParams.topMargin >= mMarginTop) {
                    layoutParams.topMargin = mMarginTop;
                }

                if (layoutParams.leftMargin <= 0) {
                    layoutParams.leftMargin = 0;
                }

                if (layoutParams.topMargin <= 0) {
                    layoutParams.topMargin = 0;
                }

                mGsyBaseVideoPlayer.setLayoutParams(layoutParams);

        }
        return false;
    }

}複製代碼

最後

   
 若是你看到這裏,恭喜你看完了<( ̄︶ ̄)>!那麼,下面還有沙發,請問您要坐一坐嗎?d=====( ̄▽ ̄*)b不坐也不要緊,還有github能夠去呢:github.com/CarGuo

來一下嘛
相關文章
相關標籤/搜索