播放器手势:基于GestureDetector实现(双击、快进快退、音量亮度调节)

最近有个播放器手势滑动快进快退的需求,研究了一下Android中GestureDetector的用法,封装了一个工具类,实现了单击、双击、横向滑动(快进快退)、竖向滑动(亮度音量调节)手势。代码参见:https://github.com/ChenSWD/PlayerGestureDetector

1、在播放器中,常用的一些功能一般有:快进快退、亮度音量调节、双击暂停/播放、单击隐藏/显示播放器的UI。。。其中单击与双击事件一般会存在冲突,GestureDetectoronSingleTapConfirmed()方法在单击且不是双击事件时回调,可以很好的避免这个问题。

2、另外代码中实现了根据滑动的速度改变快进/快退速率的功能,参考代码中updateScrollRatio()方法的实现。这里还是贴出代码实现,相应解释参见代码中的注释。

3、需要说明的是GestureDetectorCompat不能检测手势抬起,所以需要单独监听ACTION_UP手势,以识别结束了快进/快退的手势,相关使用参见https://github.com/ChenSWD/PlayerGestureDetector中MainActivity的说明

/**
 * 播放器常用手势监听:单击、双击、横向滑动、左右两边边竖向滑动(亮度和声音)
 */

public class PlayerGestureListener extends GestureDetector.SimpleOnGestureListener {
    private WeakReference<VideoGestureListener> listener;
    private int screenWidth, centerW;
    //播放器View的长宽DP
    private float dpVideoWidth, dpVideoHeight;
    private ScrollMode scrollMode = ScrollMode.NONE;
    private long timeStamp;
    private int mTouchSlop;
    private int scrollRatio = 1;    //快进的比率,速度越快,值越大
    private int preDpVideoDuration = 0;     //每一dp,快进的时长,ms
    private float density;
    private int totalDuration = 0;      //单次快进快退累计值
    private float leftTBValue = 0;      //单次左边累计值(一般是亮度)
    private float rightTBValue = 0;     //单次右边累计值(一般是声音)

    public PlayerGestureListener(VideoGestureListener listener, Context context) {
        this.listener = new WeakReference<>(listener);
        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics outMetrics = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(outMetrics);

        screenWidth = outMetrics.widthPixels;
        centerW = screenWidth / 2;
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();

        density = context.getResources().getDisplayMetrics().density;
    }

    //设置播放器SurfaceView的宽高
    public void setVideoWH(int w, int h) {
        dpVideoWidth = w / density;
        dpVideoHeight = h / density;
        //默认基础总共2分钟,代表的意义:从屏幕一边滑动到另一边,总共可以快进2分钟
        preDpVideoDuration = (int) (2 * 60 * 1000 / dpVideoWidth);
    }

    @Override
    public boolean onDown(MotionEvent e) {
        scrollMode = ScrollMode.NONE;
        timeStamp = System.currentTimeMillis();
        totalDuration = 0;
        leftTBValue = 0;
        rightTBValue = 0;
        if (listener.get() != null) {
            listener.get().onGestureDown();
        }
        return true;
    }

    @Override
    public boolean onSingleTapUp(MotionEvent e) {
        return super.onSingleTapUp(e);
    }

    @Override
    public void onShowPress(MotionEvent e) {
        super.onShowPress(e);
    }

    @Override
    public void onLongPress(MotionEvent e) {
        super.onLongPress(e);
    }

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        long time = System.currentTimeMillis();
        distanceX = -distanceX;
        distanceY = -distanceY;
        float dpX = distanceX / density;
        float dpY = distanceY / density;
        updateScrollRatio(dpX, time - timeStamp);
        timeStamp = time;
        float xDiff = e2.getX() - e1.getX();
        float yDiff = e2.getY() - e1.getY();
        if (scrollMode == ScrollMode.NONE) {
            //横向滑动
            if (Math.abs(xDiff) > mTouchSlop) {
                scrollMode = ScrollMode.HORIZONTAL_S;
                updateVideoTime((int) (preDpVideoDuration * xDiff));
            }
            //纵向滑动
            else if (Math.abs(yDiff) > mTouchSlop) {
                if (e1.getX() < centerW) {
                    scrollMode = ScrollMode.LEFT_TB;
                } else {
                    scrollMode = ScrollMode.RIGHT_TB;
                }
            }
        }
        //快进快退
        else if (scrollMode == ScrollMode.HORIZONTAL_S) {
            updateVideoTime((int) (preDpVideoDuration * scrollRatio * dpX));
        } else if (scrollMode == ScrollMode.LEFT_TB) {
            updateVideoLeftTB(dpY / dpVideoHeight);
        } else if (scrollMode == ScrollMode.RIGHT_TB) {
            updateVideoRightTB(dpY / dpVideoHeight);
        }
        return true;
    }

    @Override
    public boolean onDoubleTap(MotionEvent e) {
        if (listener.get() != null) {
            listener.get().onGestureDoubleClick();
        }
        //双击事件
        return super.onDoubleTap(e);
    }

    @Override
    public boolean onDoubleTapEvent(MotionEvent e) {
        return super.onDoubleTapEvent(e);
    }

    @Override
    public boolean onSingleTapConfirmed(MotionEvent e) {
        if (listener.get() != null) {
            listener.get().onGestureSingleClick();
        }
        //单击事件,在双击事件发生时不会产生这个事件,所以用这个回调作为播放器单击事件
        return super.onSingleTapConfirmed(e);
    }

    /***
     * 根据滑动速度更新速度比率值,这样可以在滑动速率较快时,快进的速度也会变快,可以根据需要调整
     * 根据实验,正常速度一般在10~40dp/s
     * @param dpX:横向滑动距离 dp
     * @param duration:时间间隔 ms
     */
    private void updateScrollRatio(float dpX, long duration) {
        int ratio = (int) ((Math.abs(dpX) / duration) * 1000);
        if (ratio < 20) {
            scrollRatio = 1;
        } else if (ratio < 40) {
            scrollRatio = 3;
        } else if (ratio < 70) {
            scrollRatio = 7;
        } else if (ratio < 100) {
            scrollRatio = 13;
        } else if (ratio < 300) {
            scrollRatio = 18;
        } else if (ratio < 500) {
            scrollRatio = 24;
        } else if (ratio < 800) {
            scrollRatio = 31;
        } else if (ratio < 1000) {
            scrollRatio = 40;
        } else {
            scrollRatio = 60;
        }
    }

    //累积快进进度,totalDuration:当前快进的总值,负值代表是要快退
    private void updateVideoTime(int duration) {
        totalDuration += duration;
        if (listener.get() != null) {
            listener.get().onGestureUpdateVideoTime(totalDuration);
        }
    }

    //累积亮度
    private void updateVideoLeftTB(float ratio) {
        leftTBValue += ratio;
        if (listener.get() != null) {
            listener.get().onGestureLeftTB(leftTBValue);
        }
    }

    //累积声音
    private void updateVideoRightTB(float ratio) {
        rightTBValue += ratio;
        if (listener.get() != null) {
            listener.get().onGestureRightTB(rightTBValue);
        }
    }

    public interface VideoGestureListener {
        /***
         * 手指在Layout左半部上下滑动时候调用,一般是亮度手势
         * 从View底部滑动到顶部,代表从0升到1
         * @param ratio:0-1 之间,1代表最亮,0代表最暗
         */
        void onGestureLeftTB(float ratio);

        /***
         * 手指在Layout右半部上下滑动时候调用,一般是音量手势
         * 从View底部滑动到顶部,代表从0升到1
         * @param ratio:0-1 之间,1代表音量最大,0代表音量最低
         */
        void onGestureRightTB(float ratio);

        /**
         * @param duration :快进快退,大于0快进,小于0快退
         */
        void onGestureUpdateVideoTime(int duration);

        //单击手势,确认是单击的时候调用
        void onGestureSingleClick();

        //双击手势,确认是双击的时候调用,可用于播放器暂停
        void onGestureDoubleClick();

        void onGestureDown();
    }

    private enum ScrollMode {
        NONE,               //初始值
        LEFT_TB,            //左边上下滑动(调节亮度)
        RIGHT_TB,           //右边上下滑动(调节声音)
        HORIZONTAL_S,       //横向滑动(快进快退)
        SINGLE_CLICK,       //单击
        DOUBLE_CLICK        //双击
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_38062353/article/details/82194600