美文网首页Android开发Android技术知识Android开发
Android 关于手势(GestureLayout)的思考及问

Android 关于手势(GestureLayout)的思考及问

作者: 289346a467da | 来源:发表于2018-06-29 16:44 被阅读10次

先看实现的效果

device-2018-06-29-161828.gif device-2018-06-29-161958.gif

GestureDetector相关

GestureDetector 提供的MotionEvents检测各种手势和事件。当发生特定的运动事件时,GestureDetector.OnGestureListener回调将通知用户。

GestureDetector 的内部实现其实就是将onTouchEvent(MotionEvent ev) 将一系列的touch事件处理然后回调给用户。然而GestureDetector extends Object ,它本身是没有touch事件的。 所以,需要重写view的onTouch(MotionEvents event) 将event传递给GestureDetector.onTouchEvent(MotionEvent ev),说白了 GestureDetector 就是对一系列的touch事件的一个封装。

如何使用GestureLayout

使用GestureLayout 就很简单了

 mGestureDetector = new GestureDetector(context, gestureListener);
 
  /** 手势监听 */
    GestureDetector.SimpleOnGestureListener gestureListener = new GestureDetector.SimpleOnGestureListener() {
        @Override
        public boolean onDown(MotionEvent e) {
            
        }

        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {//滑动事件
            
        }

        @Override
        public boolean onDoubleTap(MotionEvent e) {//双击事件
            
        }

        @Override
        public boolean onSingleTapUp(MotionEvent e) {//点击事件
            
        }
    };
    
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        boolean touchEvent = mGestureDetector.onTouchEvent(event);
    
        return touchEvent;
    }

实践GestureLayout在视频的重要作用,改变音量、亮度和进度

这块感觉没什么可讲的,主要就是拿到手势回调 然后换算一下,然后通过监听回调给视频的控制器,让控制器继承ControllerGestureLayout就可以了,直接上代码了

public abstract class ControllerGestureLayout extends FrameLayout implements View.OnTouchListener {

    public static final int NONE = 0, VOLUME = 1, BRIGHTNESS = 2, POSITION = 3;

    private @ScrollMode
    int mScrollMode = NONE;


    @IntDef({NONE, VOLUME, BRIGHTNESS, POSITION})
    @Retention(RetentionPolicy.SOURCE)
    private @interface ScrollMode {
    }

    protected GestureDetector mGestureDetector;

    public VideoGestureListener mVideoGestureListener;

    private int offX = 1;
    private int offInX = 10;

    private int oldVolume = 0;

    private int maxVolume = 0;

    private float brightness = 1;

    private static final String TAG = "ControllerGestureLayout";

    private BrightnessHelper brightnessHelper;

    private AudioHelper audioHelper;

    private long currentPosition, duration;

    private float oldPosition = 0;

    private boolean isGesture = true;

    public ControllerGestureLayout(@NonNull Context context) {
        super(context);
        init(context);
    }

    public ControllerGestureLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public ControllerGestureLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    private void init(Context context) {
        setOnTouchListener(this);
        mGestureDetector = new GestureDetector(context, gestureListener);
        brightnessHelper = new BrightnessHelper(context);
        audioHelper = new AudioHelper(context);
    }

    /** 手势监听 */
    GestureDetector.SimpleOnGestureListener gestureListener = new GestureDetector.SimpleOnGestureListener() {
        @Override
        public boolean onDown(MotionEvent e) {
            if (builder().isGesture()) {
                down();
                return true;
            } else {
                return false;
            }
        }

        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {//滑动事件
            if (builder().isGesture()) {
                return gestureMove(e1, e2, distanceX, distanceY);
            } else {
                return false;
            }
        }

        @Override
        public boolean onDoubleTap(MotionEvent e) {//双击事件
            if (builder().isGesture()) {
                onDouble();
                return true;
            } else {
                return false;
            }
        }

        @Override
        public boolean onSingleTapUp(MotionEvent e) {//点击事件
            if (builder().isGesture()) {
                if (null != mVideoGestureListener) {
                    mVideoGestureListener.onSingleTapUp();
                }
                return true;
            } else {
                return false;
            }
        }
    };

    /** 双击事件 */
    private void onDouble() {
        if (!builder().isDouble()) {
            return;
        }
        if (null != mVideoGestureListener) {
            mVideoGestureListener.onDoubleTap();
        }
    }

    /** 手指按下 */
    private void down() {
        brightness = brightnessHelper.getAppBrightness(getContext());
        if (brightness == -1) {
            brightness = brightnessHelper.getSystemBrightness() / 255;
        }
        mScrollMode = NONE;
        oldVolume = audioHelper.getCurrentVolume();
        maxVolume = audioHelper.getMaxVolume();
        currentPosition = getVideoCurrentPosition();
        duration = getVideoDuration();
        oldPosition = 1000L * currentPosition / duration;
    }

    public abstract long getVideoCurrentPosition();

    public abstract long getVideoDuration();

    /** 手势移动 */
    private boolean gestureMove(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        if (!builder().isMove()) {
            return false;
        }
        switch (mScrollMode) {
            case NONE:
                return noneChange(e1, distanceX, distanceY);
            case VOLUME:
                return changeVolume(e1, e2);
            case BRIGHTNESS:
                return changeBrightness(e1, e2);
            case POSITION:
                return changePosition(e1, e2);
            default:
                return false;
        }
    }

    /** 改变进度 */
    private boolean changePosition(MotionEvent e1, MotionEvent e2) {
        if (!builder().isPosition()) {
            if (mVideoGestureListener != null) {
                mVideoGestureListener.onGesture(false);
            }
            return false;
        }
        if (mVideoGestureListener != null) {
            mVideoGestureListener.onGesture(true);
        }
        float offX = e2.getX() - e1.getX();
        float movePercent = (1000L * (offX / getWidth()) * duration) / duration;
        float value = movePercent + oldPosition;
        if (value < 0) {
            value = 0;
        } else if (value > 1000) {
            value = 1000;
        }
        if (mVideoGestureListener != null) {
            mVideoGestureListener.onMovePosition((long) (duration * value / 1000), value);
        }
        return true;
    }

    /** 改变亮度 */
    private boolean changeBrightness(MotionEvent e1, MotionEvent e2) {
        if (!builder().isBrightness()) {
            if (mVideoGestureListener != null) {
                mVideoGestureListener.onGesture(false);
            }
            return false;
        }
        if (mVideoGestureListener != null) {
            mVideoGestureListener.onGesture(true);
        }
        float newBrightness = (e1.getY() - e2.getY()) / getHeight();
        newBrightness += brightness;
        if (newBrightness < 0) {
            newBrightness = 0;
        } else if (newBrightness > 1) {
            newBrightness = 1;
        }
        Window window = Utils.scanForActivity(getContext()).getWindow();
        WindowManager.LayoutParams attributes = window.getAttributes();
        attributes.screenBrightness = newBrightness;
        window.setAttributes(attributes);
        if (mVideoGestureListener != null) {
            mVideoGestureListener.showBrightness((int) (newBrightness * 100));
        }
        return true;

    }

    /** 改变声音 */
    private boolean changeVolume(MotionEvent e1, MotionEvent e2) {
        if (!builder().isVolume()) {
            return false;
        }
        getParent().requestDisallowInterceptTouchEvent(false);//touch hand over parent view handle
        int value = getHeight() / maxVolume;
        int newVolume = (int) ((e1.getY() - e2.getY()) / value + oldVolume);
        int volumeProgress = (int) (newVolume / Float.valueOf(maxVolume) * 100);
        audioHelper.setVolume(newVolume);
        if (mVideoGestureListener != null) {
            mVideoGestureListener.showVolume(volumeProgress);
        }
        return true;
    }

    /** 没有改变 */
    private boolean noneChange(MotionEvent e1, float distanceX, float distanceY) {
        if (mVideoGestureListener != null) {
            mVideoGestureListener.onGesture(true);
        }
        if ((Math.abs(distanceX) - Math.abs(distanceY)) > offX) {
            mScrollMode = POSITION;
        } else {
            if (e1.getX() < getWidth() / 2) {
                mScrollMode = BRIGHTNESS;
            } else {
                mScrollMode = VOLUME;
            }
        }
        return false;
    }

    private ViewParent getControllerParent() {
        try {
            return getParent().getParent();
        } catch (NullPointerException e) {
            return null;
        }
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        boolean touchEvent = mGestureDetector.onTouchEvent(event);
        switch (event.getAction()) {
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                if (mVideoGestureListener != null) {
                    mVideoGestureListener.onTouchCancle(mScrollMode);
                }
                break;
        }
        return touchEvent;
    }

    protected void actionUpGesture() {
        if (mVideoGestureListener != null) {
            mVideoGestureListener.onTouchCancle(mScrollMode);
        }
    }

    public void hideChangePosition() {
        if (mVideoGestureListener != null) {
            mVideoGestureListener.onTouchCancle(POSITION);
        }
    }

    public void doDown() {
        down();
    }

    public boolean doChangePosition(float offX) {
        if (!builder().isPosition()) {
            if (mVideoGestureListener != null) {
                mVideoGestureListener.onGesture(false);
            }
            return false;
        }
        if (mVideoGestureListener != null) {
            mVideoGestureListener.onGesture(true);
        }
        float movePercent = (1000L * (offX / getWidth()) * duration) / duration;
        float value = movePercent + oldPosition;
        if (value < 0) {
            value = 0;
        } else if (value > 1000) {
            value = 1000;
        }
        if (mVideoGestureListener != null) {
            mVideoGestureListener.onMovePosition((long) (duration * value / 1000), value);
        }
        return true;
    }

    public void setVideoGestureListener(VideoGestureListener mVideoGestureListener) {
        this.mVideoGestureListener = mVideoGestureListener;
    }

    public interface VideoGestureListener {
        void onDoubleTap();

        void onSingleTapUp();

        void onMovePosition(long position, float value);

        void showBrightness(int position);

        void showVolume(int position);

        void onTouchCancle(int scrollMode);

        void onGesture(boolean gesture);
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
    }

    public Builder builder = null;

    public Builder builder() {
        if (builder == null) {
            builder = new Builder();
        }
        return builder;
    }

    public static class Builder {

        Builder() {
        }

        /** 手势监听默认开启 */
        private boolean isGesture = true;

        /** 手势移动默认开启 */
        private boolean isMove = true;

        /** 手势移动音量默认开启 */
        private boolean isVolume = true;

        /** 手势移动亮度默认开启 */
        private boolean isBrightness = true;

        /** 手势移动进度默认开启 */
        private boolean isPosition = true;

        private boolean isDouble = true;

        boolean isGesture() {
            return isGesture;
        }

        boolean isMove() {
            return isMove;
        }

        boolean isVolume() {
            return isVolume;
        }

        boolean isBrightness() {
            return isBrightness;
        }

        public boolean isPosition() {
            return isPosition;
        }

        public boolean isDouble() {
            return isDouble;
        }

        public Builder setGestureDetector(boolean isGesture) {
            this.isGesture = isGesture;
            return this;
        }

        public Builder setGestureMove(boolean isMove) {
            this.isMove = isMove;
            return this;
        }

        public Builder setGestureVolume(boolean isVolume) {
            this.isVolume = isVolume;
            return this;
        }

        public Builder setGestureBrightness(boolean isBrightness) {
            this.isBrightness = isBrightness;
            return this;
        }

        public Builder setGesturePosition(boolean isPosition) {
            this.isPosition = isPosition;
            return this;
        }

        public Builder setDoublePlayOrPuse(boolean isDouble) {
            this.isDouble = isDouble;
            return this;
        }
    }
}

调节屏幕亮度的辅助类

public class BrightnessHelper {

    private ContentResolver resolver;

    private int maxBrightness = 255;

    private static final String TAG = "BrightnessHelper";

    public BrightnessHelper(Context context) {
        resolver = context.getContentResolver();
    }

    /*
    * 调整亮度范围
    */
    private int adjustBrightnessNumber(int brightness) {
        if (brightness < 0) {
            brightness = 0;
        } else if (brightness > 255) {
            brightness = 255;
        }
        return brightness;
    }

    /*
     * 关闭自动调节亮度
     */
    public void offAutoBrightness() {
        try {
            if (Settings.System.getInt(resolver, Settings.System.SCREEN_BRIGHTNESS_MODE) == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC) {
                Settings.System.putInt(resolver,
                        Settings.System.SCREEN_BRIGHTNESS_MODE,
                        Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
            }
        } catch (Settings.SettingNotFoundException e) {
            e.printStackTrace();
        }
    }

    /** 获取系统亮度 */
    public int getSystemBrightness() {
        return Settings.System.getInt(resolver, Settings.System.SCREEN_BRIGHTNESS, 255);
    }

    /** 获取APP内的亮度 */
    public float getAppBrightness(Context context) {
        float screenBrightness = Utils.scanForActivity(context).getWindow().getAttributes().screenBrightness;
        Log.e(TAG, "getAppBrightness: " + screenBrightness);
        if (screenBrightness == -1) {
            //一开始是默认亮度的时候,获取系统亮度,计算比例值
            screenBrightness = getSystemBrightness() / 255f;
        }
        return screenBrightness;
    }
}

调节音量的辅助类

public class AudioHelper {
    protected AudioManager mAM;

    public AudioHelper(Context context) {
        mAM = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
    }

    public int getCurrentVolume() {
        return mAM.getStreamVolume(AudioManager.STREAM_MUSIC);
    }

    public int getMaxVolume() {
        return mAM.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
    }

    public void setVolume(int volume) {
        mAM.setStreamVolume(AudioManager.STREAM_MUSIC, volume, AudioManager.FLAG_PLAY_SOUND);
    }
}

实现效果如下:

device-2018-06-29-161828.gif

RecyclerView的item中播放视频View使用GestureLayout横向滑动改变进度存在的问题

如果item中要进行GestureLayout 手势操作,也许你会想这很简单吗,在播放视频的view中实现GestureLayout监听就行了?

但是,如果item消费了onTouch事件,那么RecyclerView就不能滑动了,因为由子View消费掉了。

还有一种想法,在播放视频的view中实现GestureLayout监听中横向滑动就消费掉onTouch事件竖向滑动就拦截掉?

但是,经过一系列测试,还是无法实现,原因是什么呢?

GestureLayout 监听的 onDown() 要想监听滑动事件 也就是MoationEvent.ACTION_MOVE,就必须返回true,也就相当于MoationEvent.ACTION_DOWN必须返回true,否则无法检测到滑动事件,简单来说,父view和子view必须有一个去消费掉onTouch事件,这样不管如何拦截都不能达成你想要的效果,横向滑动改变进度,竖向滑动交给RecyclerView。

那么如何在列表中实现以下效果呢?


device-2018-06-29-161958.gif

如何解决上述问题呢?

查找了大量的资料看到了RecyclerView有一个这样的方法:

RecyclerView.addOnItemTouchListener(itemTouchListener);

这个方法就是监听item的触摸事件的,那么我们是否可以在这上面进行修改呢?

先看一下,这个监听方法:

 public static interface OnItemTouchListener {
        
        //这个方法中设有拦截处理
        public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e);

        //当某个事件被拦截时,会走这个方法
        public void onTouchEvent(RecyclerView rv, MotionEvent e);

        // 当RecyclerView的一个孩子不想要RecyclerView及其祖先时调用
        public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept);
    }

看到这个监听方法我已经有了思路了:

上述问题无法处理的原因的是,子View和父view无法解决的冲突问题,那么我们就用一个view来进行滑动判断,这样就没有冲突了,这个view当然是RecyclerView,那么我们如何通过触摸来拿到正在播放的item的视频的view呢?

当然很简单RecyclerView 是很强大的存在,它有一个这样的方法:

通过触摸的x,y的坐标可以拿到触摸的itemView

View childViewUnder = rv.findChildViewUnder(e.getX(), e.getY());

然后我们再根据,childViewUnder 拿到当前的位置position和已知正在播放的item的位置进行判断。

 if (childViewUnder != null) {
                            //get item view position in the list
                            int childAdapterPosition = rv.getChildAdapterPosition(childViewUnder);
                            //if touch item view position equals record the position of the video being played in the list
                            if (itemPosition == childAdapterPosition) {
                                RecyclerView.ViewHolder childViewHolder = rv.getChildViewHolder(childViewUnder);
                                if (childViewHolder instanceof VideoFeedHolder) {//get ViewHolder
                                    VideoFeedHolder feedHolder = (VideoFeedHolder) childViewHolder;
                                    FrameLayout ll_video = feedHolder.ll_video;
                                    //Judging whether the touch is a video playback view
                                    if (isTouchPointInView(ll_video, e.getX(), e.getY())) {
                                        intercept = true;
                                    }
                                }
                            }
                        }

然后在通过ViewHolder拿到视频的view,通过x,y坐标来判断是否触摸在视频的view上面,如果在上面就拦截开始滑动改变播放进度

  private boolean isTouchPointInView(View view, float x, float y) {
        if (view == null) {
            return false;
        }
        int[] location = new int[2];
        view.getLocationOnScreen(location);
        int left = location[0];
        int top = location[1];
        int right = left + view.getMeasuredWidth();
        int bottom = top + view.getMeasuredHeight();
        //view.isClickable() &&
        if (y >= top && y <= bottom && x >= left
                && x <= right) {
            return true;
        }
        return false;
    }

具体如何改变播放进度,要根据你具体的实现的播放器view代码来实现

下面是完整的代码:

public class RecyclerItemTouchListener extends RecyclerView.SimpleOnItemTouchListener {

    /**
     * whether to intercept touch event
     */
    private boolean intercept = false;

    /**
     * record touch down x , y
     */
    private float downX, downY;

    /**
     * video view
     */
    private VideoPlayerView videoPlayerView;

    /**
     * record the position of the video being played in the list
     */
    private int itemPosition = 0;

    /**
     * touch move offset
     */
    private static final int MOVE_OFFSET = 20;

    public RecyclerItemTouchListener(VideoPlayerView videoPlayerView) {
        this.videoPlayerView = videoPlayerView;
    }

    /**
     * set position of the video being played
     *
     * @param itemPosition
     *         int - position
     */
    public void setItemPosition(int itemPosition) {
        this.itemPosition = itemPosition;
    }

    @Override
    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
        switch (e.getAction()) {
            case MotionEvent.ACTION_DOWN:
                downX = e.getX();
                downY = e.getY();
                if (videoPlayerView != null) {
                    videoPlayerView.doDown();
                }
                break;
            case MotionEvent.ACTION_MOVE:
                float dX = downX - e.getX();
                float dY = downY - e.getY();
                if (Math.abs(dX) > MOVE_OFFSET && Math.abs(dY) > MOVE_OFFSET) {//correct sliding operation
                    if (Math.abs(dX) - Math.abs(dY) > MOVE_OFFSET) {//if horizontal slide
                        //According to coordinates get recyclerView touch item View
                        View childViewUnder = rv.findChildViewUnder(e.getX(), e.getY());
                        if (childViewUnder != null) {
                            //get item view position in the list
                            int childAdapterPosition = rv.getChildAdapterPosition(childViewUnder);
                            //if touch item view position equals record the position of the video being played in the list
                            if (itemPosition == childAdapterPosition) {
                                RecyclerView.ViewHolder childViewHolder = rv.getChildViewHolder(childViewUnder);
                                if (childViewHolder instanceof VideoFeedHolder) {//get ViewHolder
                                    VideoFeedHolder feedHolder = (VideoFeedHolder) childViewHolder;
                                    FrameLayout ll_video = feedHolder.ll_video;
                                    //Judging whether the touch is a video playback view
                                    if (isTouchPointInView(ll_video, e.getX(), e.getY())) {
                                        intercept = true;
                                    }
                                }
                            }
                        }
                    } else {
                        intercept = false;
                    }
                } else {
                    intercept = false;
                }
                break;

        }
        return intercept;
    }

    @Override
    public void onTouchEvent(RecyclerView rv, MotionEvent e) {
        switch (e.getAction()) {
            case MotionEvent.ACTION_MOVE:
                float dX = downX - e.getX();
                float dY = downY - e.getY();
                if (Math.abs(dX) > MOVE_OFFSET && Math.abs(dY) > MOVE_OFFSET) {
                    if (Math.abs(dX) - Math.abs(dY) > MOVE_OFFSET) {
                        if (videoPlayerView != null) {
                            videoPlayerView.doChangePosition(-dX);
                        }
                    }
                }
                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                intercept = false;
                if (videoPlayerView != null) {
                    videoPlayerView.hideChangePosition();
                }
                break;
        }
    }

    /**
     * Judging whether the touch view
     *
     * @param view
     *         View
     * @param x
     *         float
     * @param y
     *         float
     *
     * @return boolean
     */
    private boolean isTouchPointInView(View view, float x, float y) {
        if (view == null) {
            return false;
        }
        int[] location = new int[2];
        view.getLocationOnScreen(location);
        int left = location[0];
        int top = location[1];
        int right = left + view.getMeasuredWidth();
        int bottom = top + view.getMeasuredHeight();
        //view.isClickable() &&
        if (y >= top && y <= bottom && x >= left
                && x <= right) {
            return true;
        }
        return false;
    }
}

思考View的事件分发机制

所谓的事件分发就是MoationEvent事件的分发过程,当MoationEvent 产生后,系统就需要把这个事件传递给一个具体的view,这个传递的过程就是分发过程。

完成分发过程主要有以下三个重要的方法完成:

方法 描述
dispatchTouchEvent 用来进行事件的分发。如果事件能够传递给当前view,那么此方法一定会调用,返回的结果受当前view的onTouchEvent和下级View的dispatchTouchEvent方法的影响,表示是否消耗了当前事件。
onInterceptTouchEvent 用来判断是否拦截某个事件,如果当前view拦截了某个事件,那么在同一事件序列中,此方法不会再调用,返回结果表示是否拦截当前事件。
onTouchEvent 在dispatchTouchEvent方法中调用,用来处理触摸事件,返回结果表示是否消耗当前事件。

可以用一段伪代码来表示事件的分发机制:

public boolean dispatchTouchEvent(MoationEvent event){
    boolean coustom = false;
    if(onInterceptTouchEvent(event)){
        coustom = onTouchEvent(event);
    }else{
        coustom = cilde.dispatchTouchEvent(event);
    }
    return coustom;
}

不管多么复杂发滑动冲突都是遵循事件的分发机制来进行的,可以用许多思路来解决问题。

注意调节亮度后需要恢复亮度

 /**
     * What is changed is the brightness of the activity of the current window.
     * The brightness of other activities does not change,and it need to be reset
     * to the previous brightness when exiting.
     */
    public void recoverBrightness() {
        if (isRecordBrightness) {
            brightnessHelper.setAppBrightness(WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE, getContext());
        }
    }

相关文章

网友评论

    本文标题:Android 关于手势(GestureLayout)的思考及问

    本文链接:https://www.haomeiwen.com/subject/irvayftx.html