Android中系统触摸相关辅助类总结

Android中的触摸事件,我们可以通过重写View的OnTouchEvent()等事件,通过事件类型MotionEvent来进行我们想要实现的逻辑操作,有时候一些简单的需求很容易实现,但是有时候,一些很困难的需求,我们需要编写大量的代码来实现。好在Android官方就给我们提供了很丰富的触摸相关的辅助类,今天,我就把我知道的分享给大家。

1、GestureDetector(手势探测器)

顾名思义,GestureDetector可以很方便的帮我们识别出我们当前在屏幕上的手势操作,比如按下、长按、抬起、双击、滚动、快速滑动(惯性滑动)等,甚至它还能识别出是否是鼠标右键点击的动作,省去了我们大量的手势判断语句,非常的实用。

1.1 相关的方法及接口简介

GestureDetector的手势识别有三个接口来处理我们当前的手势动作,还有一个集成了三个接口的实现类方便我们去处理多种事件。

1.1.1 GestureDetector.OnGestureListener

作用: 用来监听单击、长按、滑动等操作,我们可以在这些操作的回调方法中处理我们自己的逻辑。

具体实现方法:
见下面代码,注释已经写的很清楚了,示例代码中仅仅对滚动进行了处理。

    private static class MyGestureDetectorListener implements GestureDetector.OnGestureListener{        private GestureDetectorView mView;        public MyGestureDetectorListener(GestureDetectorView view){            this.mView = view;        }        /**         * 用户按下屏幕的时候回调         * @param motionEvent         * @return         */        @Override        public boolean onDown(MotionEvent motionEvent) {            Log.e("ll", "MyGestureDetectorListener : onDown");            return true;        }        /**         * 用户按下屏幕100ms后,如果还没有松手或者移动就会回调         * @param motionEvent         */        @Override        public void onShowPress(MotionEvent motionEvent) {            Log.e("ll", "MyGestureDetectorListener : onShowPress");        }        /**         * 单纯的点击再抬手时调用。用户手指松开(UP事件)的时候如果没有执行onScroll()和onLongPress()这两个回调的话,就会回调         * @param motionEvent         * @return         */        @Override        public boolean onSingleTapUp(MotionEvent motionEvent) {            Log.e("ll", "MyGestureDetectorListener : onSingleTapUp");            return false;        }        /**         * 屏幕拖动事件,如果按下的时间过长,调用了onLongPress,再拖动屏幕不会触发onScroll。         * 拖动屏幕会多次触发         * @param motionEvent 开始拖动的第一次按下down操作,也就是第一个ACTION_DOWN         * @param motionEvent1 触发当前onScroll方法的ACTION_MOVE         * @param distanceX 当前的x坐标与最后一次触发scroll方法的x坐标的差值。         * @param distanceY 当前的y坐标与最后一次触发scroll方法的y坐标的差值。         * @return         */        @Override        public boolean onScroll(MotionEvent motionEvent, MotionEvent motionEvent1, float distanceX, float distanceY) {            Log.e("ll", "MyGestureDetectorListener : onScroll : " + distanceY);            mView.scrollBy(0, (int) distanceY);            return false;        }        /**         * 用户长按后(好像不同手机的时间不同,源码里默认是100ms+500ms)触发,触发之后不会触发其他回调,直至松开(UP事件)。         * @param motionEvent         */        @Override        public void onLongPress(MotionEvent motionEvent) {            Log.e("ll", "MyGestureDetectorListener : onLongPress");        }        /**         * 按下屏幕,在屏幕上快速滑动后松开,由一个down,多个move,一个up触发         * @param motionEvent 开始快速滑动的第一次按下down操作,也就是第一个ACTION_DOWN         * @param motionEvent1 触发当前onFling方法的move操作,也就是最后一个ACTION_MOVE         * @param velocityX X轴上的移动速度,像素/秒         * @param velocityY Y轴上的移动速度,像素/秒         * @return         */        @Override        public boolean onFling(MotionEvent motionEvent, MotionEvent motionEvent1, float velocityX, float velocityY) {            Log.e("ll", "MyGestureDetectorListener : onFling");            return false;        }    }

注意事项:

1、onDown()方法返回true时,onScroll()onFling()方法才能回调。
2、onLongPress()若触发了,则不会再触发其他回调。
3、事件回调顺序:

  • 快速点击:onDown() > onSingleTapUp()
  • 慢按屏幕:onDown() > onShowPress() > onSingleTapUp()
  • 长按屏幕:onDown() > onShowPress() > onLongPress()
  • 滑动屏幕:onDown() > onShowPress() > onScroll()(多个) > onFling()
  • 快速拖动屏幕后松手:onDown() > onScroll()(多个) > onFling()

1.1.2 GestureDetector.OnDoubleTapListener

作用: 用来监听双击事件操作,我们可以在这些操作的回调方法中处理我们自己的逻辑。

具体实现方法:
见下面代码,注释已经写的很清楚了。

private static class MyGestureDetectorDoubleTapListener implements GestureDetector.OnDoubleTapListener{    /**     * 单击事件。用来判定该次点击是单纯的SingleTap而不是DoubleTap,如果连续点击两次就是DoubleTap手势,     * 如果只点击一次,系统等待一段时间后没有收到第二次点击则判定该次点击为SingleTap而不是DoubleTap,然后触发SingleTapConfirmed事件。     * @param motionEvent     * @return     */    @Override    public boolean onSingleTapConfirmed(MotionEvent motionEvent) {        Log.e("ll","MyGestureDetectorDoubleTapListener : onSingleTapConfirmed");        return false;    }    /**     * 双击触发     * @param motionEvent     * @return     */    @Override    public boolean onDoubleTap(MotionEvent motionEvent) {        Log.e("ll","MyGestureDetectorDoubleTapListener : onDoubleTap");        return false;    }    /**     * 双击间隔中发生的动作。指触发onDoubleTap以后,在双击之间发生的其它动作,包含down、up和move事件     * @param motionEvent     * @return     */    @Override    public boolean onDoubleTapEvent(MotionEvent motionEvent) {        Log.e("ll","MyGestureDetectorDoubleTapListener : onDoubleTapEvent");        return false;    }}

注意事项:
1、onSingleTapConfirmed()用来判断是不是单点事件,双击不会执行此方法。
2、事件调用顺序:

  • 单击屏幕时: OnDown() > OnsingleTapUp() > OnsingleTapConfirmed()
  • 双击屏幕时: OnDown() > onSingleTapUp() > onDoubleTap() > onDoubleTapEvent() > onDown() > onDoubleTapEvent()

1.1.3 GestureDetector.OnContextClickListener

作用: 用来监听鼠标右键按下的事件,我们可以在这些操作的回调方法中处理我们自己的逻辑。

具体实现方法:
见下面代码,注释已经写的很清楚了。

private static class MyGestureDetectorContextClickListener implements GestureDetector.OnContextClickListener{    /**     * 当鼠标/触摸板,右键点击时候的回调。     * @param motionEvent     * @return     */    @Override    public boolean onContextClick(MotionEvent motionEvent) {        return false;    }}

注意事项:
1、OnContextClickListener接口最低支持的API是23。

1.1.4 GestureDetector.SimpleOnGestureListener(实现类)

SimpleOnGestureListener实现了上述三个接口,我们可以直接使用此类,并重写我们想要的方法就行。

GestureDetector.SimpleOnGestureListener simpleOnGestureListener = new GestureDetector.SimpleOnGestureListener(){    @Override    public boolean onDown(MotionEvent e) {        return true;    }};

1.2 GestureDetector使用方法

GestureDetector可以作用于多个地方,比如Activity的OnTouchEvent()方法,View的onTouch()方法和onTouchEvent()方法上,其中,以 后两者使用较多。
这里以View.onTouchEvent()方法举例:

1.2.1 在View的初始化时初始化GestureDetector

public GestureDetectorView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {    super(context, attrs, defStyleAttr);    init(context);}private void init(Context context){    //传入View方便对View进行操作    mGestureDetector = new GestureDetector(context, new MyGestureDetectorListener(this));    //设置双击事件监听    mGestureDetector.setOnDoubleTapListener(new MyGestureDetectorDoubleTapListener());}

1.2.1 在View的onTouchEvent()方法中,将触摸事件交给GestureDetector去处理

@Overridepublic boolean onTouchEvent(MotionEvent event) {    return mGestureDetector.onTouchEvent(event);}

此处展示一个使用示例,仅仅监听了OnGestureListeneronScroll()方法:

Android中系统触摸相关辅助类总结_第1张图片 监听onScroll()

1.3 小结

GestureDetector可以很方便的为我们检测各种触摸事件,我们只需要再相应的回调方法中处理我们自己的逻辑即可,可以说是触摸辅助类第一神器!

2、ViewDragHelper(子控件拖拽帮手)

顾名思义,ViewDragHelper肯定是用来帮助View进行Drag(拖拽)的。它主要是用来给ViewGroup处理它内部子View的拖拽动作使用的,有了它,我们可以省去写ViewGroup中烦人的触摸拦截方法(onInterceptTouchEvent())和子控件的onTouchEvent()方法了!

2.1 相关方法及接口介绍

2.1.1 ViewDragHelper.Callback

ViewDragHelper的主要方法都在它内部类ViewDragHelper.Callback中,这里,我写了一个类MyViewDragHelperCallback实现了ViewDragHelper.Callback中的比较重要的方法,每个方法的具体作用,都已在注释中写明:

private static class MyViewDragHelperCallback extends ViewDragHelper.Callback{    //传入父ViewGroup和ViewDragHelper,便于对子View的操作进行判断    private RelativeLayout mViewGroup;    private ViewDragHelper mDragHelper;    public MyViewDragHelperCallback(ViewDragHelper dragHelper, RelativeLayout view){        this.mDragHelper = dragHelper;        this.mViewGroup = view;    }    /**     * 根据传入的child确定是否需要捕获此child(对它进行操作)     * @param child 被触摸的子View     * @param pointerId 按下手指的id,一般多点触摸时会用来判断是哪根手指触摸     * @return 返回true表示将要捕获这个child     */    @Override    public boolean tryCaptureView(View child, int pointerId) {        return true;    }    /**     * 返回被横向移动的子控件child的上坐标top,和移动距离dy,我们可以根据这些值来返回child的新的top。     * @param child     * @param top 拖拽动作后系统建议的距离父布上侧的距离     * @param dy Y轴方向拖拽移动的距离     * @return 返回拖拽后child距离父布局上侧的距离     */    @Override    public int clampViewPositionVertical(View child, int top, int dy) {        return top;    }    /**     * 返回被横向移动的子控件child的左坐标left,和移动距离dx,我们可以根据这些值来返回child的新的left。     * @param child     * @param left 拖拽动作后系统建议的距离父布局左侧的距离     * @param dx X轴方向拖拽移动的距离     * @return 返回拖拽后child距离父布局左侧的距离     */    @Override    public int clampViewPositionHorizontal(View child, int left, int dx) {        return left;    }    /**     * 这个用来控制垂直移动的边界范围,单位是像素。     * @param child     * @return 返回值大于0时才能捕获子View     */    @Override    public int getViewVerticalDragRange(View child) {        return super.getViewVerticalDragRange(child);    }    /**     * 这个用来控制横向移动的边界范围,单位是像素。     * @param child     * @return 返回值大于0时才能捕获子View     */    @Override    public int getViewHorizontalDragRange(View child) {        return super.getViewHorizontalDragRange(child);    }    /**     * 当releasedChild被释放的时候回调     * @param releasedChild     * @param xvel x轴方向的加速度     * @param yvel y轴方向的加速度     */    @Override    public void onViewReleased(View releasedChild, float xvel, float yvel) {        super.onViewReleased(releasedChild, xvel, yvel);    }    /**     * 若ViewDragHelper设置了setEdgeTrackingEnabled()此方法,则调用此方法     * @param edgeFlags 边缘触摸方向     * @param pointerId     */    @Override    public void onEdgeDragStarted(int edgeFlags, int pointerId) {        super.onEdgeDragStarted(edgeFlags, pointerId);    }}

2.1.2 ViewDragHelper.captureChildView()

直接对子View进行捕获,可以绕开Callback.tryCaptureView()方法。

2.1.3 ViewDragHelper.setEdgeTrackingEnabled()

设置ViewGroup的边缘可以被拖拽,可以设置左、上、右、下四个方向或者任意几个方向,一般配合Callback.onEdgeDragStarted()ViewDragHelper.captureChildView()使用(后面有示例)。

2.1.4 ViewDragHelper.settleCapturedViewAt()

对ViewGroup的某子View进行回弹处理,需要配合ViewGroup的computeScroll()方法和invalidate()方法。

2.2 ViewDragHelper使用方式

2.2.1 初始化ViewDragHelper

在ViewGroup的初始化时,通过ViewDragHelper的静态初始化方法create()进行创建:

public ViewDragHelperView(Context context, AttributeSet attrs, int defStyleAttr) {    super(context, attrs, defStyleAttr);    init();}private void init(){    mViewDragHelper = ViewDragHelper.create(this,1.0f, new MyViewDragHelperCallback(this));}

创建时的三个参数的意义分别是:

  • forParent:给哪个ViewGroup使用的,一般传入当前的ViewGroup;
  • sensitivity:拖拽灵敏度,传入值越大,灵敏度越小,一般传入1.0f即可;
  • ViewDragHelperCallback:ViewDragHelper的实现类Callback,基本所有的拖拽后的回调都在此方法内。

2.2.2 重写onInterceptTouchEvent()

重写ViewGroup的onInterceptTouchEvent()方法,把它交给ViewDragHelper的shouldInterceptTouchEvent()来处理。

@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {    return mViewDragHelper.shouldInterceptTouchEvent(ev);}

注意:ViewDragHelper只对ViewGroup有效,并且它也不能直接操作子View,而是借助的ViewGroup!

2.2.3 重写ViewGroup的onTouchEvent()

重写ViewGroup的onTouchEvent(),把它交给ViewDragHelper的processTouchEvent()来处理,并返回true

@Overridepublic boolean onTouchEvent(MotionEvent event) {    mViewDragHelper.processTouchEvent(event);    return true;}

使用方式是非常简单的,最重要的是在Callback中实现自己的拖拽逻辑。

2.3 使用场景简介

使用场景都是在ViewDragHelper的实现类Callback中进行处理的。

2.3.1 对子View进行拖拽到任意位置

效果展示:

Android中系统触摸相关辅助类总结_第2张图片 捕捉子View

通过重写tryCaptureView()clampViewPositionVertical()clampViewPositionHorizontal()对子View进行拖拽处理。
其中,tryCaptureView()用来判断是否是需要拖拽的子View,clampViewPositionVertical()clampViewPositionHorizontal()用来返回子View拖拽后的位置。

示例代码表示,可以拖拽任意子View到任意位置,但是不能超过父ViewGroup的左侧和右侧:

    @Override    public boolean tryCaptureView(View child, int pointerId) {        //直接返回true,表示可以拖拽任意子View        return true;    }    @Override    public int clampViewPositionVertical(View child, int top, int dy) {        //直接返回top,不对垂直方向的拖拽进行拦截,交给ViewDragHelper去处理        return top;    }    @Override    public int clampViewPositionHorizontal(View child, int left, int dx) {        //不能超过父View左侧        if(left < mViewGroup.getPaddingLeft()){            left = mViewGroup.getPaddingLeft();        }        //不能超过父View右侧        if(left + child.getWidth() > mViewGroup.getWidth() - mViewGroup.getPaddingRight()){            left = mViewGroup.getWidth() - mViewGroup.getPaddingRight() - child.getWidth();        }        return left;    }

2.3.2 对ViewGroup边缘触摸事件进行捕获子View的行为

效果展示:

边缘捕获

通过ViewDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT)对左侧边缘进行触摸,通过Callback.onEdgeDragStarted()ViewDragHelper.captureChildView()对需要捕获的子View使用操作。

示例代码表示,可以在左侧或者上侧边缘拖拽id为tv2的子View:

ViewDragHelper:

//对屏幕左侧或上侧进行边缘触摸mViewDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT|ViewDragHelper.EDGE_TOP);

Callback:

@Overridepublic void onEdgeDragStarted(int edgeFlags, int pointerId) {    super.onEdgeDragStarted(edgeFlags, pointerId);    View childView = mViewGroup.findViewById(R.id.tv2);    mDragHelper.captureChildView(childView,pointerId);}

2.3.3 子View拖拽,在松手后回到原位

效果展示:

Android中系统触摸相关辅助类总结_第3张图片 回弹效果

这里需要重写ViewGroup的computeScroll()方法,并且调用ViewDragHelper的continueSettling()方法对子View滚动位置实时刷新。
除此之外,还需要在tryCaptureView()的时候记录子View的初始lefttop,并且调用ViewDragHelpersettleCapturedViewAt()方法对子View进行复位。

示例代码是复位子View的id为tv1的View:

ViewGroup:
对子View的位置进行判断,并不断刷新。

@Overridepublic void computeScroll() {    if(mViewDragHelper.continueSettling(true)){        invalidate();    }    super.computeScroll();}

Callback:

//捕获View时记录其left和top@Overridepublic boolean tryCaptureView(View child, int pointerId) {    if(child.getId() == R.id.tv1){        originLeft = child.getLeft();        originTop = child.getTop();    }    return true;}//松手时交给ViewDragHelper的settleCapturedViewAt()进行复位。@Overridepublic void onViewReleased(View releasedChild, float xvel, float yvel) {    super.onViewReleased(releasedChild, xvel, yvel);    if(releasedChild.getId() == R.id.tv1){        mDragHelper.settleCapturedViewAt(originLeft,originTop);        mViewGroup.invalidate();    }}

2.4 注意事项

在我们的举例中,如果你将我们的子控件换成Button或者将子控件的clickable设置为true(子View能消费触摸事件),你会发现子View无法被拖拽了!原因在于在ViewGroup的onInterceptTouchEvent()方法中,我们返回的是ViewDragHelper.shouldInterceptTouchEvent(ev),这样会导致ViewGroup的onTouchEvent()不被调用,这样就导致我们的ViewDragHelper.processTouchEvent()不被调用!

如果将onInterceptTouchEvent()的返回值直接为true的话,又会导致子View的点击事件被拦截不被触发!

查看ViewDragHelper.shouldInterceptTouchEvent(ev)的源码:

public boolean shouldInterceptTouchEvent(MotionEvent ev) {    final int action = MotionEventCompat.getActionMasked(ev);    switch (action) {        ...        case MotionEvent.ACTION_MOVE: {                      final int pointerCount = ev.getPointerCount();            for (int i = 0; i < pointerCount; i++) {                           final int horizontalDragRange = mCallback.getViewHorizontalDragRange(                            toCapture);                final int verticalDragRange = mCallback.getViewVerticalDragRange(toCapture);                // 如果getViewHorizontalDragRange和getViewVerticalDragRange的返回值都为0,则break                if (horizontalDragRange == 0 && verticalDragRange == 0) {                    break;                }                                // tryCaptureViewForDrag方法中会设置mDragState=STATE_DRAGGING                if (pastSlop && tryCaptureViewForDrag(toCapture, pointerId)) {                    break;                }            }            break;        }    }    ...    return mDragState == STATE_DRAGGING;}

ViewDragHelper.shouldInterceptTouchEvent(ev)的返回为true的条件是ViewDragHelper.mDragState == STATE_DRAGGING,然而mDragState是在tryCaptureViewForDrag()方法中被设置为STATE_DRAGGING的。其中,若horizontalDragRangeverticalDragRange一直为0,则mDragState无法设置为STATE_DRAGGING。并且horizontalDragRangeverticalDragRange是在ViewDragHelper.getViewVerticalDragRange()ViewDragHelper.getViewHorizontalDragRange()设置的,因此,只要这两个方法的返回值大于0就可以正常捕获了!

    @Override    public int getViewVerticalDragRange(View child) {        return 1;    }    /**     * 这个用来控制横向移动的边界范围,单位是像素。     * @param child     * @return 返回值大于0时才能捕获子View     */    @Override    public int getViewHorizontalDragRange(View child) {        return 1;    }

2.5 ViewDragHelper的一些其它回调及回调调用顺序

2.5.1 其它回调方法

/** * mDragState改变时回调 * STATE_IDLE:所有的View处于静止空闲状态 * STATE_DRAGGING:某个View正在被用户拖动(用户正在与设备交互) * STATE_SETTLING:某个View正在安置状态中(用户并没有交互操作),就是自动滚动的过程中 * @param state */@Overridepublic void onViewDragStateChanged(int state) {    super.onViewDragStateChanged(state);}/** * 当捕获的子View的位置发生改变时回调 * @param changedView * @param left * @param top * @param dx * @param dy */@Overridepublic void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {    super.onViewPositionChanged(changedView, left, top, dx, dy);}/** * 当子View被捕获时回调 * @param capturedChild * @param activePointerId */@Overridepublic void onViewCaptured(View capturedChild, int activePointerId) {    super.onViewCaptured(capturedChild, activePointerId);}/** * 当触摸到边界时回调。 * @param edgeFlags * @param pointerId */@Overridepublic void onEdgeTouched(int edgeFlags, int pointerId) {    super.onEdgeTouched(edgeFlags, pointerId);}/** * * @param edgeFlags * @return true的时候会锁住当前的边界,false则unLock。 */@Overridepublic boolean onEdgeLock(int edgeFlags) {    return super.onEdgeLock(edgeFlags);}/** * 改变同一个坐标(x,y)去寻找captureView位置的方法。(具体作用暂时未知) * @param index * @return */@Overridepublic int getOrderedChildIndex(int index) {    return super.getOrderedChildIndex(index);}

2.5.2 回调调用顺序:

shouldInterceptTouchEvent:

  • ACTION_DOWNgetOrderedChildIndex(findTopChildUnder) > onEdgeTouched()
  • ACTION_MOVEgetOrderedChildIndex(findTopChildUnder) > getViewHorizontalDragRange() & getViewVerticalDragRange(checkTouchSlop)(MOVE中可能不止一次) > clampViewPositionHorizontal() & clampViewPositionVertical() > onEdgeDragStarted() > tryCaptureView() > onViewCaptured() > onViewDragStateChanged()

processTouchEvent:

  • ACTION_DOWNgetOrderedChildIndex(findTopChildUnder) > tryCaptureView() > onViewCaptured() > onViewDragStateChanged() > onEdgeTouched()
  • ACTION_MOVESTATE == DRAGGING > STATE!=DRAGGING > onEdgeDragStarted > getOrderedChildIndex(findTopChildUnder) > getViewHorizontalDragRange& getViewVerticalDragRange(checkTouchSlop) > tryCaptureView() > onViewCaptured() > onViewDragStateChanged()

2.6 小结

ViewDragHelper是一个强大的拖拽子View的辅助神器,大家用好这个类,可以大大提高我们的触摸手势的效率!

3、ItemTouchHelper(item触摸帮手)

ItemTouchHelper是一个触摸item时对item进行处理的类。既然涉及到条目,那肯定是和一些和item有关的控件有关了,没错,这个类就是一个针对RecyclerView的工具类,用来处理RecyclerView内部的条目拖拽和滑动事件的,它可以让条目拖拽到一个新的位置,也可以让条目滑动出来删除等等场景!

3.1 相关方法和接口介绍

ItemTouchHelper类的具体实现方法都在它的内部类ItemTouchHelper.Callback中,ItemTouchHelper.Callback中有三个必须重写的方法,也是核心方法。
我写了一个类MyItemTouchHelperCallback继承ItemTouchHelper.Callback,它们的意义都在注释中已标明:

public static class MyItemTouchHelperCallback extends ItemTouchHelper.Callback{    //RecyclerView的Adapter    private ItemTouchHelperAdapter mAdapter;    public MyItemTouchHelperCallback(ItemTouchHelperAdapter adapter){        this.mAdapter = adapter;    }    /**     * 允许哪个方向的拖拽和滑动,一般配合makeMovementFlags(int,int)去使用     * @param recyclerView     * @param viewHolder     * @return     */    @Override    public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {        //允许上下拖拽,和从右向左滑动        int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;        int swipeFlags = ItemTouchHelper.LEFT;        //计算拖拽和滑动的方向        return makeMovementFlags(dragFlags,swipeFlags);    }    /**     * 拖拽一个item到新位置时会调用此方法,一般配合Adapter的notifyItemMoved()方法来交换两个ViewHolder的位置     * @param recyclerView     * @param viewHolder     * @param target     * @return true表示已经到达移动目的地     */    @Override    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {        //交换item位置        Collections.swap(mAdapter.mDatas,viewHolder.getAdapterPosition(),target.getAdapterPosition());        mAdapter.notifyItemMoved(viewHolder.getAdapterPosition(),target.getAdapterPosition());        return true;    }    /**     * 当滑动到一定程度时,松手会继续滑动,然后调用此方法,反之item会回到原位,不调用此方法     * @param viewHolder     * @param direction 滑动的方向     */    @Override    public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {        Log.e("zw", "direction : " + direction);        //删除此item        mAdapter.mDatas.remove(viewHolder.getAdapterPosition());        mAdapter.notifyItemRemoved(viewHolder.getAdapterPosition());    }}

ItemTouchHelper.Callback中其它可以选择重写的方法:

    /**     * 是否长按时才使拖拽生效     * @return 默认返回true     */    @Override    public boolean isLongPressDragEnabled() {        return super.isLongPressDragEnabled();    }    /**     * 是否可以进行滑动时的删除动作,也就是是否能调用onSwiped()方法     * @return 默认返回true     */    @Override    public boolean isItemViewSwipeEnabled() {        return super.isItemViewSwipeEnabled();    }    /**     * 静止状态变为拖拽或者滑动的时候会回调     * @param viewHolder     * @param actionState 当前的状态     */    @Override    public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {        super.onSelectedChanged(viewHolder, actionState);    }    /**     * 当用户操作完毕某个item并且其动画也结束后会调用该方法,一般我们在该方法内恢复ItemView的初始状态,防止由于复用而产生的显示错乱问题。     * @param recyclerView     * @param viewHolder     */    @Override    public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {        super.clearView(recyclerView, viewHolder);    }    /**     * 实现我们自定义的交互规则或者自定义的动画效果     * @param c     * @param recyclerView     * @param viewHolder     * @param dX     * @param dY     * @param actionState     * @param isCurrentlyActive     */    @Override    public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {        super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);    }

还有一些方法没有做过多研究,这里就不列举了。

注意事项:调用onSwiped()方法时,如果没有配合RecyclerViewAdapternotifyItemRemoved()方法则会让Item的内容给移除出去,但是此Item还在原地,也就是会留下一个空白的view!

3.2 ItemTouchHelper的使用方式

3.2.1 RecyclerView初始化

实现RecyclerViewAdapterViewHolder类,RecyclerView设置LayoutManager等,很常规的写法,这里就不贴代码了。

3.2.1 RecyclerView设置ItemTouchHelper

ItemTouchHelperAdapter.MyItemTouchHelperCallback touchHelperCallback        = new ItemTouchHelperAdapter.MyItemTouchHelperCallback(adapter);ItemTouchHelper itemTouchHelper = new ItemTouchHelper(touchHelperCallback);itemTouchHelper.attachToRecyclerView(recyclerView);

3.3 ItemTouchHelper的使用场景简介

3.3.1 实现上下拖拽换位置功能和向左滑动删除功能

此功能需要重写ItemTouchHelper.CallbackgetMovementFlags()方法、onMove()方法和onSwiped()方法,并且要配合RecyclerViewAdapternotifyItemMoved()notifyItemRemoved()方法使用。

ItemTouchHelper.Callback关键代码:

    //RecyclerView的Adapter    private ItemTouchHelperAdapter mAdapter;    public MyItemTouchHelperCallback(ItemTouchHelperAdapter adapter){        this.mAdapter = adapter;    }    /**     * 允许哪个方向的拖拽和滑动,一般配合makeMovementFlags(int,int)去使用     * @param recyclerView     * @param viewHolder     * @return     */    @Override    public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {        //允许上下拖拽,和从右向左滑动        int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;        int swipeFlags = ItemTouchHelper.LEFT;        //计算拖拽和滑动的方向        return makeMovementFlags(dragFlags,swipeFlags);    }    /**     * 拖拽一个item到新位置时会调用此方法,一般配合Adapter的notifyItemMoved()方法来交换两个ViewHolder的位置     * @param recyclerView     * @param viewHolder     * @param target     * @return true表示已经到达移动目的地     */    @Override    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {        //交换item位置        Collections.swap(mAdapter.mDatas,viewHolder.getAdapterPosition(),target.getAdapterPosition());        mAdapter.notifyItemMoved(viewHolder.getAdapterPosition(),target.getAdapterPosition());        return true;    }    /**     * 当滑动到一定程度时,松手会继续滑动,然后调用此方法,反之item会回到原位,不调用此方法     * @param viewHolder     * @param direction 滑动的方向     */    @Override    public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {        Log.e("zw", "direction : " + direction);        //删除此item        mAdapter.mDatas.remove(viewHolder.getAdapterPosition());        mAdapter.notifyItemRemoved(viewHolder.getAdapterPosition());    }

实现效果:

Android中系统触摸相关辅助类总结_第4张图片 拖拽换位置和滑动删除功能

3.3.1 向左滑动删除时带有动画功能(透明度)

此示例为向左滑动时,先显示黑色的View,当黑色的TextView显示完全时,再向右滑动时,更改黑色TextView的文字颜色透明度。

先上显示效果:

Android中系统触摸相关辅助类总结_第5张图片 带动画的效果

更改我们的布局文件,在item布局的屏幕右边放置我们的TextView,布局文件如下:

        

其中用到了ConstraintLayout,这个控件对于布局十分的方便,并且性能也很优秀,关于此控件的更多使用方法可以参考我这篇博客内容: ConstraintLayout——约束性布局学习

这时候我们会发现向右侧滑动时,第二个TextView根本出不来,系统调用的是当前屏幕可见部分布局的setTranslationX()方法,因此无论如何,我们的第二个TextVeiw都不会显示,因此我们要将setTranslationX()改变成scrollTo()方法。

另外,我们还要重写clearView()方法,来将有动画效果的条目给复位到初始化状态,以免复用的时候产生显示误差。

ItemTouchHelper.Callback关键代码:

@Overridepublic void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {    //仅仅针对Item的滑动事件    if(actionState == ItemTouchHelper.ACTION_STATE_SWIPE){        TextView alphaView = viewHolder.itemView.findViewById(R.id.tv_item1);        int viewWidth = alphaView.getWidth();        if(Math.abs(dX) < viewWidth){            //滚动            viewHolder.itemView.scrollTo((int) -dX,0);        } else if(Math.abs(dX) < recyclerView.getWidth() / 2){            //滑动的透明度            float childAlpha = Math.abs(dX) - viewWidth;            float fatherAlpha = recyclerView.getWidth() / 2 - viewWidth;            float alpha = childAlpha / fatherAlpha;            int color = Color.argb((int)(alpha * 255),255,255,255);            alphaView.setTextColor(color);        }    } else {        super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);    }}@Overridepublic void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {    super.clearView(recyclerView, viewHolder);    //复位    viewHolder.itemView.setScrollX(0);    TextView alphaView = viewHolder.itemView.findViewById(R.id.tv_item1);    alphaView.setTextColor(Color.parseColor("#00FFFFFF"));}

3.4 小结

ItemTouchHelper是RecyclerView自带的一个帮助我们实现拖拽和滑动的工具类,通过这个类我们可以很轻松的实现在RecyclerView上炫酷的拖拽和滑动效果!

总结

上面介绍了三种和触摸有关的辅助类:

  • GestureDetector:用来监听我们触摸屏幕的手势。

  • ViewDragHelper:帮助我们处理子控件的拖拽事件。

  • ItemTouchHelper:RecyclerView上帮助我们拖拽和滑动子条目的利器。

相信通过上述的例子,你应该也学会了怎么使用它们,在工作中用上这些辅助类,可以让我们的工作效率大大提高,实现事半功倍的效果!

最后,本示例所有效果,见此GitHub Demo

更多相关文章

  1. Android中触摸事件传递机制
  2. Android使用JNI生成.so文件并调用(使用传统生成.h的方法)
  3. android studio运行编译速度慢的解决方法
  4. Android模拟产生事件
  5. android中共享全局数据的方法
  6. Android的内存优化方法
  7. Android中事件分发机制
  8. 原来Android还可以这样通过反射,获取jar包属性及方法

随机推荐

  1. Android反射机制技术的使用示例
  2. android res目录下存放图片文件夹 i m h
  3. Only the original thread that created
  4. Android读取系统时间
  5. avd安装apk
  6. Android剪贴板学习
  7. Android 之 自定义适配器
  8. Android判断界面
  9. Android 自定义view的简单应用(2) 毛玻璃效
  10. android 进度条垂直动态上升下降达到警告