(原创)视频播放器的手势控制工具类

最近在做一个视频播放器

现在市场上,一个比较完善的视频播放器

大概具有以下功能:

快进、快退、声音、亮度控制

这一次就根据这几个基础的功能

通过系统的手势控制类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

猜你喜欢

转载自blog.csdn.net/Android_xiong_st/article/details/113116891