Recently working on a video player
Now on the market, a relatively complete video player
Probably has the following functions:
Fast forward, fast reverse, sound, brightness control
This time based on these basic functions
Through the system's gesture control class GestureDetector to complete
Made a practical tool class
Only need simple configuration
You can monitor the gestures of the video player controls
Relevant functions are implemented in the tool class, no need for the video player to write by itself
Realizes code decoupling and facilitates reuse
Now let's introduce the use of this tool class and the principle of implementation.
Paste the actual code of the tool class first
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);
}
}
It is actually very simple to use, we have customized a control MyVideoView here
Suppose this MyVideoView is our video control
So how to make this control use our tool class
So as to realize the function of gesture control playback progress, sound and brightness?
Look at the code below
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);
}
}
You can see that the implementation is actually very simple
The core is in onTouchEvent
Feedback the touch monitoring of the control to this tool class
Need to implement five interfaces at the same time
These five interfaces
Need to return the length of the video, the current playback progress
After dragging at the same time, it will return to the current progress
Single click and double click monitoring have also been implemented
Here we need to focus on the getVideoViewRect method
This method is used to return the position of the control on the screen
For details, please refer to this blog
(Reprinted) Android: 6 ways to get the coordinate position of View efficiently & correctly
Why do you need a control to pass the position?
That’s because in our tool class
The playback progress and brightness when dragging with the finger, and the pop-up window when the sound changes have been realized
Need to adjust the position of this pop-up window according to the user's position
Of course, you can also remove this function by commenting out the dialog code of the tool class.
For how to set the position of the dialog pop-up window, please refer to this blog or Baidu search.
(Reproduced) Android settings on the display position of the dialog
In this way, this video player realizes the function of controlling playback progress, brightness, sound, and single click and double click through gestures
Finished talking about basic use
Let me talk about the principle
The first is the monitoring of gestures
You can refer to this blog
(Reproduced) Detailed Android GestureDetector
Next, analyze the specific implementation in our tool class
The parse2TimeStr method is mainly to format the time
When the pop-up window is convenient, the text display of the playback progress is consistent
The touch method is mainly used to monitor the finger up (MotionEvent.ACTION_UP)
Or when the finger is pressed and the screen is stopped (MotionEvent.ACTION_CANCEL)
At this time, you need to close the pop-up window and go to the callback method according to the drag distance
Tell the video player the playback progress of the current drag
The core listener MyGestureListener class
This is the place to deal with double-click, click, and drag
Note that the drag is divided into different states here
Respectively are
SCROLL_FLAG_RESET initial state, which is the state where the finger is not pressed
SCROLL_FLAG_TOPBOTTOM_LEFT Slide up and down on the left screen to control the brightness
SCROLL_FLAG_TOPBOTTOM_RIGHT Slide up and down on the right screen to control the sound
SCROLL_FLAG_LEFTRIGHT slide left and right to control the playback progress
The state will be judged when the finger is dragged, and the state will be restored after lifting
Also note this code
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;
}
Mainly to prevent dragging at the edge of the screen (such as the bottom or both sides), you can comment out when you don’t need it
The location setting of dialog is mainly the following code, which can be modified according to actual needs
indexDialogLp.x = ((VideoViewRect.right - VideoViewRect.left) / 2) - (displayMetrics.widthPixels / 2);
indexDialogLp.y = ((VideoViewRect.bottom - VideoViewRect.top) / 2) - (displayMetrics.heightPixels / 2) + VideoViewRect.top;
Finally, because the pop-up window needs some pictures and so on, they are also uploaded here
Download it in the network disk
link:
https://pan.baidu.com/s/1_E8IeJhXz2jTwdigLGgkaA
Extraction code:
kkzu