一款自定義的MediaControl組件,替換Android系統自帶的控制組件,控制視頻播放、快進、暫停等功能
Android系統自帶的MediaControl組件過于難看,自己寫個替換,使用簡單,順便也可以學到東西,查看源碼請點擊這里
效果圖
使用方法
- 實現自定義組件必須的接口ControlOper,接口內部邏輯自己實現,該接口功能主要用于完成播放、快進和全屏等功能
public interface ControlOper {
void start();
void pause();
int getDuration();
int getCurPosition();
void seekTo(int pos);
boolean isPlaying();
int getBufPercent();
boolean isComplete();
boolean canPause();
boolean canSeekBackward();
boolean canSeekForward();
boolean isFullScreen();
void fullScreen();
}
- 初始化自定義組件
jMediaControl = new JMediaControl(this);
jMediaControl.setmPlayerCtr(mPlayerControl); //設置接口
jMediaControl.setAnchorView(mSurfaceContainer); //設置組件的依附視圖
jMediaControl.startLoadingAnimation(); //視頻播放之前的動畫效果,防止黑屏造成不友好
- 開啟你自己的MediaPlayer邏輯,如設置數據源、緩沖監聽、預備播放監聽和生命周期等,后完成播放即可
- 在事件onTouchEvent里面調用
- 最后在銷毀的時候,調用自定義組件的銷毀方法,防止內存溢出
public void recycleSelf();
實現原理
根據功能需求決定實現的方法和原理,該組件主要是將自定義組件依附在播放的頁面上,并且執行自定義組件上面的監聽動作,當非全屏時,點擊播放部分頁面會展示自定義組件,非播放頁面點擊不展示;當控制界面展示時,3秒無操作自動掩藏,并且掩藏后自動展示默認的進度條(視頻最下方的小橫條),下次再次點擊視頻時,又展示控制欄
所以主要功能需求有三:
- 依附組件
- 執行播放、暫停和全屏等監聽
- 兩種進度條的顯示和掩藏邏輯
依附組件
- 將依附的根布局viewgroup傳入自定義組件中,通過setAnchorView
- 依附過程,主要是加載xml,移除我們自定義viewgroup的allviews子視圖,將加載的視圖xml添加addview自定義組件viewgroup中
- 最后,再將自定義組件addview到根布局視圖中去
注意,最好每次添加視圖前,都要先移除當前視圖的內容
下面展示添加控制視圖代碼:
/**
* 綁定控制視圖到當前的自定義視圖
*/
private void bindingContrlView(){
if(mAnchorVGroup == null){
return;
}
mAnchorVGroup.removeView(this);
FrameLayout.LayoutParams frameParams = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
Gravity.BOTTOM
);
removeAllViews();
if(mCtrlView == null){
createCtrlView();
}
addView(mCtrlView, frameParams);
}
/**
* 添加控制視圖
*/
private void addCtrViewToMediaView(){
bindingContrlView();
if (mCtrlView != null) {
setProgress(mSeekBar);
if (mBtnPause != null && mBtnPause1 != null) {
mBtnPause.requestFocus();
mBtnPause1.requestFocus();
}
disableUnsupportedButtons();
FrameLayout.LayoutParams tlp = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT,
Gravity.BOTTOM
);
mAnchorVGroup.addView(this, tlp);
mIsCtlShowing = true;
}
}
執行播放、暫停和全屏等監聽
- 該部分功能主要是通過回調接口ControlOper完成,比如點擊暫停時就去掉用該接口的pause方法,這部分邏輯有許多需要用戶自己完成
這部分代碼比較簡單,稍稍看下代碼就明白了:
mBtnPause = (ImageButton)view.findViewById(R.id.pause);
mBtnPause1 = (ImageButton)view.findViewById(R.id.btnPause);
if(mBtnPause != null && mBtnPause1 != null){
mBtnPause.setOnClickListener(mPauseClickListener);
mBtnPause1.setOnClickListener(mPauseClickListener);
}
private OnClickListener mPauseClickListener = new OnClickListener() {
@Override
public void onClick(View view) {
mIsPause = !mIsPause;
doPauseOrResume();
updateBtnPauseStatus();
}
};
進度條邏輯
嚴格來說,此自定義組件一共加載了3中xml視圖進入內存了:
(1)播放前的動畫,由于是一次性的動作,后續不在處理可以忽略;
(2)控制操作界面的進度條,你可以點擊暫停播放拉取等操作的進度條,這個視圖3秒顯示,超出時間后消失掩藏
(3)無任何操作時,自動顯示在視頻最下方方便用戶查看進度,這個界面不能操作,只能查看,并且它不能和條件2同時存在
此處,只講解2和3;
a. 當視頻MediaPlayer預備播放完成后,可以播放時,先讓視頻暫停并顯示我們的操作界面,暫停狀態下,不消失操作界面,這里調用show方法:
if (!mIsCtlShowing && mAnchorVGroup != null) {
//先移除掉默認顯示的進度條
if(mIsDefaultProgressShowing){
mAnchorVGroup.removeView(this);
mIsDefaultProgressShowing = false;
}
addCtrViewToMediaView();
}
updateBtnPauseStatus();
mHandler.sendEmptyMessage(SHOW_SEEKBAR);
//暫停狀態不掩藏進度條等
if(mIsPause && !mPlayerCtr.isComplete()){
return;
}else if(mPlayerCtr.isComplete()){ //結束時,跳轉到暫停狀態
mIsPause = true;
}
Message msg = mHandler.obtainMessage(FADE_OUT);
if (timeout != 0) {
mHandler.removeMessages(FADE_OUT);
mHandler.sendMessageDelayed(msg, timeout);
}
這里先判斷是否已經添加視圖,添加了就不用再添加了;后面發送SHOW_SEEKBAR的handler消息,用于更新seekbar的進度條,并且會發送一個延時的messgae,用于控制界面的消失邏輯處理;中間的mISPause判斷邏輯用處是:暫停狀態一直顯示界面,不消失,還有當視頻結束時,也自動讓界面顯示不消失
b. 在看看handler接收消息邏輯
private void myHandlerMsg(JMediaControl control, Message msg){
switch (msg.what){
case SHOW_PROGRESSBAR:
control.setProgress(control.mProgressBar);
if((!control.mIsCtlShowing) && (!control.mPlayerCtr.isComplete())){
sendEmptyMessageDelayed(SHOW_PROGRESSBAR, 1000);
}
break;
case FADE_OUT:
if(!control.mPlayerCtr.isComplete()){
control.hide();
}
break;
//更新顯示進度條
case SHOW_SEEKBAR:
control.setProgress(control.mSeekBar);
if(!control.mIsDragging && control.mIsCtlShowing && !control.mPlayerCtr.isComplete()){
msg = obtainMessage(SHOW_SEEKBAR);
sendMessageDelayed(msg, 1500);
}
break;
}
}
消息分為三種,顯示控制界面消息、移除控制界面消息和顯示默認的進度條消息;默認進度條顯示的時機在哪里?
起初,我的想法是將顯示控制界面和顯示默認進度條設置兩種狀態量,根據狀態量然后在show方法里面去調用各種進度條顯示移除等,后來思緒很久,發現狀態太多很難進行判斷控制,邏輯過于復雜;
后面干脆就跟著邏輯走就行了,不需要狀態判斷顯示,一個原則:只要控制狀態視圖消失移除的時候就是我要添加默認顯示視圖的時候;只要點擊事件傳遞到我自定義組件的時候,就是我要添加控制視圖的時候,所以就不需要狀態來控制顯示,邏輯控制即可,關鍵邏輯調用代碼:
添加默認進度視圖:
public void hide(){
if(mAnchorVGroup == null){
return;
}
try{
mAnchorVGroup.removeView(this);
addDefaultProgessToMediaView();
if(mHandler != null){
mHandler.removeMessages(SHOW_SEEKBAR);
mHandler.sendEmptyMessage(SHOW_PROGRESSBAR);
}
}catch (IllegalArgumentException ex){
ex.printStackTrace();
}
mIsCtlShowing = false;
}
添加控制界面視圖:
@Override
public boolean onTouchEvent(MotionEvent event) {
show();
return true;
}
這里,關于事件如何傳遞到我們的視圖中,就需要在添加視圖時,將我們的視頻布滿在MediaPlayer的整個播放視圖里面才行,就是布局要完全覆蓋MediaPlayer的視頻視圖,這樣才能保證點擊事件才能傳遞下來;還有一點,這兩種視圖在添加時,要注意移除上一個視圖;
最后,其他的seekbar進度邏輯、更新緩存狀態等邏輯就相對簡單了,可以自己看下我的源代碼,其實還可以繼續添加手勢監聽,調節亮度音量等,由于沒有時間,就暫時沒完成;這部分在onTouchEvent里面去完成就行了;