最近在做一個視頻播放器
現在市場上,一個比較完善的視頻播放器
大概具有以下功能:
快進、快退、聲音、亮度控制
這一次就根據這幾個基礎的功能
通過系統的手勢控制類GestureDetector來完成
做了一個實用的工具類
只需要簡單的配置
就可以實作對視頻播放器控制元件的手勢進行監聽
工具類內部實作了相關功能,不需要視頻播放器自己寫
實作了代碼解耦,也方便復用
下面就開始介紹這個工具類的使用,以及實作的原理,
先把工具類的實際代碼貼上來
public class VideoGestureControlUtil {
private static final String TAG = "VideoGestureControlUtil";
private Context mContext;
private GestureDetector mGestureDetector;//手勢類
private MyGestureListener myGestureListener;//手勢監聽
private OnVideoControlListener videoControlListener;//視頻View回呼監聽
private Rect VideoViewRect = null;//視頻控制元件范圍
//狀態相關
private static int SCROLL_FLAG = 0;//記錄狀態
private static final int SCROLL_FLAG_RESET = 0;//初始狀態,無任何操作
private static final int SCROLL_FLAG_TOPBOTTOM_LEFT = 1;//左邊螢屏上下滑動
private static final int SCROLL_FLAG_TOPBOTTOM_RIGHT = 2;//右邊螢屏上下滑動
private static final int SCROLL_FLAG_LEFTRIGHT = 3;//左右滑動
//拖動相關
protected static final float FLIP_DISTANCE = 50;//確定滑動方向的最小滑動距離
private int SCROLL_VIDEO_SCROLL_RANGE = 1000;//拖動范圍 0~1000
private float SCROLL_VIDEO_PLAY_RANGE = 0.25f;//視頻可拖動部分的范圍
//視頻進度相關
private float SCROLL_VIDEO_PLAY_INDEX = 0f;//拖動的視頻進度
private double videoIndex = 0;//拖動的視頻毫秒數
private double newVideoIndex;//拖動結束后視頻的位置,單位毫秒
private double SCROLL_VIDEO_PLAYING_INDEX = 0;//視頻播放位置
private double SCROLL_VIDEO_PLAYING_INDEX_CATCH = 0;//快取的視頻播放位置(手指按下去的視頻播放位置)
private double SCROLL_VIDEO_LENTH = 0;//視頻總長度 單位毫秒
//聲音和亮度相關
private AudioManager systemService;
private int SCROLL_VIDEO_VOICE_INDEX = 0;//拖動的視頻聲音
private double SCROLL_VIDEO_LIGHT_INDEX = 0;//拖動的視頻亮度
private int SCROLL_VOICE_MAX = 0;//聲音總長度
private int SCROLL_LIGHT_MAX = 255;//亮度總長度
private int SCREEN_LIGHT = 0;//螢屏亮度
private int SCREEN_LIGHT_CATCH = 0;//快取的螢屏亮度
private int SCREEN_VOICE = 0;//音量大小
//彈框部分
private Dialog indexDialog;//彈框
private View videoDialogView;//彈框View
private ImageView indeximg;
private LinearLayout indexll, voicell;
private TextView indextv;
private ImageView voiceLightImg;
private SeekBar voiceLightsb;
private WindowManager.LayoutParams indexDialogLp;
private DisplayMetrics displayMetrics;
public VideoGestureControlUtil(Context context, View view) {
myGestureListener = new MyGestureListener();
this.mContext = context;
systemService = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
SCROLL_VOICE_MAX = systemService.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
mGestureDetector = new GestureDetector(context, myGestureListener);
mGestureDetector.setOnDoubleTapListener(myGestureListener);
LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
videoDialogView = layoutInflater.inflate(R.layout.video_gesture_dialog, null);
indexll = videoDialogView.findViewById(R.id.indexll);
voicell = videoDialogView.findViewById(R.id.voicell);
indeximg = videoDialogView.findViewById(R.id.indeximg);
indextv = videoDialogView.findViewById(R.id.indextv);
voiceLightImg = videoDialogView.findViewById(R.id.voice_light_img);
voiceLightsb = videoDialogView.findViewById(R.id.voice_light_sb);
indexDialog = new Dialog(context);
/*隨意定義個Dialog*/
Window dialogWindow = indexDialog.getWindow();
/*實體化Window*/
indexDialogLp = dialogWindow.getAttributes();
/*實體化Window操作者*/
indexDialogLp.x = 0; // 新位置X坐標
indexDialogLp.y = 0; // 新位置Y坐標
dialogWindow.setGravity(Gravity.CENTER);
dialogWindow.getDecorView().setBackground(null);
dialogWindow.setAttributes(indexDialogLp);
/*放置屬性*/
indexDialog.setContentView(videoDialogView, new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT));
dialogWindow.setDimAmount(0);
dialogWindow.setBackgroundDrawableResource(android.R.color.transparent);
try {
int dividerID = indexDialog.getContext().getResources().getIdentifier("android:id/titleDivider", null, null);
View divider = indexDialog.findViewById(dividerID);
if (divider != null) {
divider.setBackgroundColor(Color.TRANSPARENT);
}
} catch (Exception e) {
//上面的代碼,是用來去除Holo主題的藍色線條
e.printStackTrace();
}
indexDialog.setCanceledOnTouchOutside(false);
}
public boolean touch(MotionEvent event) {
boolean detectedUp = event.getAction() == MotionEvent.ACTION_UP;
if (!mGestureDetector.onTouchEvent(event) && detectedUp) {
//手指抬起時觸發
if (SCROLL_FLAG == SCROLL_FLAG_LEFTRIGHT) {
//設定當前播放進度
videoControlListener.setScrollVideoPlayingIndex(SCROLL_VIDEO_PLAYING_INDEX);
}
SCROLL_FLAG = SCROLL_FLAG_RESET;
VideoViewRect = null;
dismissIndexDialog();
} else if (event.getAction() == MotionEvent.ACTION_CANCEL) {
//熄屏時會觸發
SCROLL_FLAG = SCROLL_FLAG_RESET;
VideoViewRect = null;
dismissIndexDialog();
}
return true;
}
/*
* 手勢監聽類
*/
class MyGestureListener extends GestureDetector.SimpleOnGestureListener {
public MyGestureListener() {
super();
}
@Override
public boolean onDoubleTap(MotionEvent e) {
Log.e(TAG, "雙擊");
videoControlListener.onDoubleTap();
return true;
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
Log.e(TAG, "單擊");
videoControlListener.onSingleTap();
return true;
}
@Override
public boolean onDoubleTapEvent(MotionEvent e) {
Log.e(TAG, "onDoubleTapEvent");
return true;
}
@Override
public boolean onContextClick(MotionEvent e) {
Log.e(TAG, "onContextClick");
return true;
}
@Override
public boolean onDown(MotionEvent e) {
Log.e(TAG, "onDown");
return true;
}
@Override
public void onShowPress(MotionEvent e) {
Log.e(TAG, "onShowPress");
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
Log.e(TAG, "onSingleTapUp");
return true;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
switch (SCROLL_FLAG) {
case SCROLL_FLAG_RESET:
WindowManager windowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
displayMetrics = new DisplayMetrics();
windowManager.getDefaultDisplay().getRealMetrics(displayMetrics);
if (e1.getX() < FLIP_DISTANCE || e1.getX() > displayMetrics.widthPixels - FLIP_DISTANCE || e1.getY() > displayMetrics.heightPixels - FLIP_DISTANCE) {
Log.d(TAG, "onScroll: 無效動作" + e1.getX());
//這里主要是處理螢屏邊緣滑動的問題,防止和邊緣的滑動回傳等操作沖突,所以屏蔽了邊緣的部分滑動,
return true;
}
if (VideoViewRect == null) {
VideoViewRect = videoControlListener.getVideoViewRect();
Log.d(TAG, "高度: " + displayMetrics.heightPixels);
Log.d(TAG, "寬度: " + displayMetrics.widthPixels);
if (VideoViewRect != null) {
Log.d(TAG, "left: " + VideoViewRect.left);
Log.d(TAG, "right: " + VideoViewRect.right);
Log.d(TAG, "top: " + VideoViewRect.top);
Log.d(TAG, "bottom: " + VideoViewRect.bottom);
indexDialogLp.x = ((VideoViewRect.right - VideoViewRect.left) / 2) - (displayMetrics.widthPixels / 2);
indexDialogLp.y = ((VideoViewRect.bottom - VideoViewRect.top) / 2) - (displayMetrics.heightPixels / 2) + VideoViewRect.top;
}
}
//初始化,沒有滑動方向
if (e1.getX() - e2.getX() > FLIP_DISTANCE || e2.getX() - e1.getX() > FLIP_DISTANCE) {
Log.i(TAG, "向左右滑...");
SCROLL_FLAG = SCROLL_FLAG_LEFTRIGHT;
SCROLL_VIDEO_PLAY_INDEX = 0f;
videoIndex = 0;
//得到視頻長度和播放位置
SCROLL_VIDEO_LENTH = videoControlListener.getScrollVideoLenth();
SCROLL_VIDEO_PLAYING_INDEX_CATCH = SCROLL_VIDEO_PLAYING_INDEX = videoControlListener.getScrollVideoPlayingIndex();
indexll.setVisibility(View.VISIBLE);
voicell.setVisibility(View.GONE);
showIndexDialog();
return true;
} else if (e1.getY() - e2.getY() > FLIP_DISTANCE || e2.getY() - e1.getY() > FLIP_DISTANCE) {
if (e1.getX() < (displayMetrics.widthPixels / 2)) {
//左邊上下滑動滑動
SCROLL_FLAG = SCROLL_FLAG_TOPBOTTOM_LEFT;
Log.i(TAG, "左螢屏向上下滑...");
SCROLL_VIDEO_LIGHT_INDEX = 0;
voiceLightImg.setImageResource(R.drawable.video_lightimg);
try {
//當前螢屏亮度
SCREEN_LIGHT_CATCH = SCREEN_LIGHT = Settings.System.getInt(mContext.getContentResolver(), Settings.System.SCREEN_BRIGHTNESS);
voiceLightsb.setMax(SCROLL_LIGHT_MAX);
voiceLightsb.setProgress(SCREEN_LIGHT);
} catch (Settings.SettingNotFoundException e) {
e.printStackTrace();
}
} else {
//右邊上下滑動
SCROLL_FLAG = SCROLL_FLAG_TOPBOTTOM_RIGHT;
Log.i(TAG, "右螢屏向上下滑...");
SCROLL_VIDEO_VOICE_INDEX = 0;
//當前聲音大小
SCREEN_VOICE = systemService.getStreamVolume(AudioManager.STREAM_MUSIC);
voiceLightImg.setImageResource(R.drawable.video_voiceimg);
voiceLightsb.setMax(SCROLL_VOICE_MAX);
voiceLightsb.setProgress(SCREEN_VOICE);
}
indexll.setVisibility(View.GONE);
voicell.setVisibility(View.VISIBLE);
showIndexDialog();
return true;
}
break;
case SCROLL_FLAG_TOPBOTTOM_LEFT:
//設定亮度
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!Settings.System.canWrite(mContext)) {
Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS);
intent.setData(Uri.parse("package:" + mContext.getPackageName()));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
} else {
//左螢屏上下滑動
SCROLL_VIDEO_LIGHT_INDEX += distanceY;
Log.e(TAG, "toponScroll:" + SCROLL_VIDEO_LIGHT_INDEX);
double lightIndex = (((double) SCROLL_VIDEO_LIGHT_INDEX / (double) SCROLL_VIDEO_SCROLL_RANGE)) * SCROLL_LIGHT_MAX;
// lightIndex = -lightIndex;//取反
double newLightIndex = lightIndex + SCREEN_LIGHT_CATCH;
if (newLightIndex > SCROLL_LIGHT_MAX) {
//說明拖動到末尾
SCREEN_LIGHT = SCROLL_LIGHT_MAX;
} else if (newLightIndex < 0) {
//說明拖動到開頭
SCREEN_LIGHT = 0;
} else {
SCREEN_LIGHT = (int) newLightIndex;
}
// 申請權限后做的操作
// 設定系統亮度
Settings.System.putInt(mContext.getContentResolver(), Settings.System.SCREEN_BRIGHTNESS, SCREEN_LIGHT);
Log.d(TAG, "螢屏亮度: " + SCREEN_LIGHT);
voiceLightsb.setProgress(SCREEN_LIGHT);
}
}
return true;
// break;
case SCROLL_FLAG_TOPBOTTOM_RIGHT:
//右螢屏上下滑動 設定聲音
SCROLL_VIDEO_VOICE_INDEX += distanceY;
// SCROLL_VIDEO_VOICE_INDEX = (int) distanceY;
double voiceIndex = (((double) SCROLL_VIDEO_VOICE_INDEX / (double) SCROLL_VIDEO_SCROLL_RANGE)) * SCROLL_VOICE_MAX;
// lightIndex = -lightIndex;//取反
if (voiceIndex > 1) {
voiceIndex = 1;
SCROLL_VIDEO_VOICE_INDEX = 0;
} else if (voiceIndex < -1) {
voiceIndex = -1;
SCROLL_VIDEO_VOICE_INDEX = 0;
} else {
voiceIndex = 0;
}
Log.d(TAG, "voiceIndex: " + voiceIndex);
double newVoiceIndex = voiceIndex + SCREEN_VOICE;
if (newVoiceIndex > SCROLL_VOICE_MAX) {
//說明拖動到末尾
SCREEN_VOICE = SCROLL_VOICE_MAX;
} else if (newVoiceIndex < 0) {
//說明拖動到開頭
SCREEN_VOICE = 0;
} else {
SCREEN_VOICE = (int) newVoiceIndex;
}
Log.d(TAG, "結束聲音大小: " + SCREEN_VOICE);
systemService.setStreamVolume(AudioManager.STREAM_MUSIC, SCREEN_VOICE, AudioManager.FLAG_PLAY_SOUND);
voiceLightsb.setProgress(SCREEN_VOICE);
return true;
// break;
case SCROLL_FLAG_LEFTRIGHT:
//左右滑動
if (Math.abs(distanceY) > 1) {
break;
}
SCROLL_VIDEO_PLAY_INDEX += distanceX;
//得到當前視頻進度
videoIndex = (((double) SCROLL_VIDEO_PLAY_INDEX / (double) SCROLL_VIDEO_SCROLL_RANGE)) * (SCROLL_VIDEO_LENTH * SCROLL_VIDEO_PLAY_RANGE);
//說明是進度滑動
videoIndex = -videoIndex;//取反
newVideoIndex = videoIndex + SCROLL_VIDEO_PLAYING_INDEX_CATCH;
// Log.d("print", "videoIndex: " + videoIndex + "-----newVideoIndex:" + newVideoIndex);
if (newVideoIndex > SCROLL_VIDEO_LENTH) {
//說明拖動到末尾
SCROLL_VIDEO_PLAYING_INDEX = SCROLL_VIDEO_LENTH;
} else if (newVideoIndex < 0) {
//說明拖動到開頭
SCROLL_VIDEO_PLAYING_INDEX = 0;
} else {
SCROLL_VIDEO_PLAYING_INDEX = newVideoIndex;
}
if ((videoIndex / 1000) > 0f) {
//快進
indeximg.setImageResource(R.drawable.indeximg_left);
} else {
//快退
indeximg.setImageResource(R.drawable.indeximg_right);
}
// Log.d("print", "滑動結束,拖動進度為" + videoIndex / 1000 + "秒");
// Log.d("print", "滑動結束,當前進度為" + SCROLL_VIDEO_PLAYING_INDEX / 1000 + "秒");
indextv.setText(parse2TimeStr(SCROLL_VIDEO_PLAYING_INDEX) + "/" + parse2TimeStr(SCROLL_VIDEO_LENTH));
return true;
// break;
}
return false;
}
@Override
public void onLongPress(MotionEvent e) {
Log.e(TAG, "onLongPress");
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
Log.e(TAG, "onFling");
return false;
}
}
private void showIndexDialog() {
indexDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
indexDialog.show();
}
private void dismissIndexDialog() {
if (indexDialog != null) {
indexDialog.dismiss();
}
}
public interface OnVideoControlListener {
//得到視頻總長度
double getScrollVideoLenth();
//得到視頻當前播放位置
double getScrollVideoPlayingIndex();
//設定視頻當前播放位置
void setScrollVideoPlayingIndex(double playIndex);
//雙擊螢屏
void onDoubleTap();
//單擊螢屏
void onSingleTap();
//得到視頻控制元件的大小范圍
Rect getVideoViewRect();
}
public void setOnVideoControlListener(OnVideoControlListener videoControlListener) {
this.videoControlListener = videoControlListener;
}
private String parse2TimeStr(double timeStr) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("HH:mm:ss");
simpleDateFormat.setTimeZone(TimeZone.getTimeZone("GMT+00:00"));
if (timeStr / 1000 > 3600) {
//說明超過一小時
simpleDateFormat.applyPattern("HH:mm:ss");
} else {
//說明沒超過一小時
simpleDateFormat.applyPattern("mm:ss");
}
return simpleDateFormat.format(timeStr);
}
}
使用起來其實也很簡單,我們這里自定義了一個控制元件MyVideoView
假設這個MyVideoView就是我們的視頻控制元件
那么如何讓這個控制元件使用我們的工具類
從而實作手勢控制播放進度,聲音,亮度的功能呢?
看下面的代碼
public class MyVideoView extends View {
private VideoGestureControlUtil videoGestureControlUtil;
private double playIndex = 0;//視頻當前播放位置
public MyVideoView(Context context) {
this(context, null);
}
public MyVideoView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public MyVideoView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
videoGestureControlUtil = new VideoGestureControlUtil(context, this);
videoGestureControlUtil.setOnVideoControlListener(new VideoGestureControlUtil.OnVideoControlListener() {
@Override
public double getScrollVideoLenth() {
return 36 * 60 * 1000;//36分鐘的視頻
}
@Override
public double getScrollVideoPlayingIndex() {
return playIndex;
}
@Override
public void setScrollVideoPlayingIndex(double playIndex) {
MyVideoView.this.playIndex = playIndex;
}
@Override
public void onDoubleTap() {
Log.d("print", "onSingleTap: 雙擊");
}
@Override
public void onSingleTap() {
Log.d("print", "onSingleTap: 單擊");
}
@Override
public Rect getVideoViewRect() {
Rect rect = new Rect();
boolean localVisibleRect = getGlobalVisibleRect(rect);
if (localVisibleRect) {
return rect;
}
return null;
}
});
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return videoGestureControlUtil.touch(event);
}
}
可以看到實作起來其實很簡單
核心在onTouchEvent里面
把控制元件的觸摸監聽反饋給這個工具類即可
同時需要實作五個介面
這五個介面
需要回傳視頻的時長,當前播放進度
同時拖動完成后,會回傳當前應該播放的進度
單擊,雙擊的監聽也都已經實作
這邊需要重點介紹getVideoViewRect這個方法
這個方法是用來回傳這個控制元件在螢屏上的位置的
具體可以參考該篇博客
(轉載)Android:6種方式讓你高效 & 正確地獲取View的坐標位置
為什么需要控制元件把位置傳遞過去呢?
那是因為我們的工具類里面
已經實作了手指拖動時播放進度、亮度,聲音變化時的彈窗
需要根據用戶的位置,來調整這個彈窗的位置
當然,也可以去掉這個功能,自己注釋掉工具類的dialog代碼即可
關于dialog彈窗位置如何設定,可以參考這篇博客或者百度搜索下即可
(轉載)Android 關于dialog的顯示位置設定
這樣,這個視頻播放器就實作了通過手勢控制播放進度,亮度,聲音,以及單擊,雙擊的功能
說完了基本使用
再來說說原理
首先是手勢的監聽
可以參考這篇博客
(轉載)Android GestureDetector詳解
接下來分析我們工具類里的具體實作
parse2TimeStr方法主要是對時間進行格式化
方便彈窗的時候,播放進度的文字顯示一致
touch方法主要是用來監聽手指抬起(MotionEvent.ACTION_UP)
或者手指按住但此時息屏(MotionEvent.ACTION_CANCEL)的情況
這個時候需要關閉彈窗,同時根據拖動的距離,去回呼方法
告訴視頻播放器當前拖動到的播放進度
核心監聽MyGestureListener類
就是對雙擊,單擊,拖動進行處理的地方了
注意,這邊將拖動進行了不同狀態的劃分
分別是
SCROLL_FLAG_RESET 初始狀態,也就是手指沒按下去的狀態
SCROLL_FLAG_TOPBOTTOM_LEFT 左邊螢屏上下滑動,控制亮度
SCROLL_FLAG_TOPBOTTOM_RIGHT 右邊螢屏上下滑動,控制聲音
SCROLL_FLAG_LEFTRIGHT 左右滑動,控制播放進度
手指拖動時會判斷狀態,抬起后狀態復原
另外注意這段代碼
if (e1.getX() < FLIP_DISTANCE || e1.getX() > displayMetrics.widthPixels - FLIP_DISTANCE || e1.getY() > displayMetrics.heightPixels - FLIP_DISTANCE) {
Log.d(TAG, "onScroll: 無效動作" + e1.getX());
//這里主要是處理螢屏邊緣滑動的問題,防止和邊緣的滑動回傳等操作沖突,所以屏蔽了邊緣的部分滑動,
return true;
}
主要來防止在螢屏邊緣(比如底部或者兩側)進行拖動,不需要時可以注釋掉
dialog的位置設定主要是以下代碼,可以根據實際需要進行修改
indexDialogLp.x = ((VideoViewRect.right - VideoViewRect.left) / 2) - (displayMetrics.widthPixels / 2);
indexDialogLp.y = ((VideoViewRect.bottom - VideoViewRect.top) / 2) - (displayMetrics.heightPixels / 2) + VideoViewRect.top;
最后,因為彈窗需要用到一些圖片什么的,這里也一并上傳
在網盤里下載即可
鏈接:
https://pan.baidu.com/s/1_E8IeJhXz2jTwdigLGgkaA
提取碼:
kkzu
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/252707.html
標籤:其他
