美文网首页
自定义侧滑删除带你详解事件分发一二

自定义侧滑删除带你详解事件分发一二

作者: 嗯哼嗯哼嗯哼嗯哼 | 来源:发表于2017-07-18 23:01 被阅读0次

    自定义侧滑删除带你详解事件分发一二

    android中的事件分发算是我第一次详细的了解android的某种机制,那时我是一直查博客,打印日志,来详细的分析了每个事件分发的流程,也得益于当时学的比较认真,所以我现在的记忆还是很清晰。记得当时为什么会学事件分发,就是因为我们当时的一个项目里面,商品详情页面是模仿京东的,我是从那个控件开始详细了解android事件分发的。今天就通过一个非常简单的自定义侧滑删除控件来了解事件分发的机制。

    先上效果:


    event_1.gif

    侧滑删除大家都不陌生,实现的方式也是多种多样,结合今天的主题,我们就以一个自定义的LinearLayout来实现侧滑删除。如果要自定义滑动相关的自定义控件,就一定要对View的坐标关系有比较清晰的了解,比如调用了scrollTo(),scrollBy()后变化的是scrollX和scrollY,而left,right,top,bottom,x,y等坐标并没有改变,因为根据官方文档知道,scrollTo()和scrollBy()移动的是View的内容,而并没有影响到View相对于父View的左上角的相对关系。还有MotionEvent中的getRawX(),getRawY(),这些是相对整个屏幕左上角的坐标,至于该使用相对父View的还是使用相对的整个屏幕的左上角的就要视情况而定。只要用的一套对应的就行。

    我们想象一下,这个自定义View可以让它继承LinearLayout,相比于直接继承View这样我们就能省去很多事,省去Measure和Layout的步骤。这个时候我们就能专注于处理滑动和事件分发。学习View的事件分发一般都是用来处理滑动冲突的,并且一般都会结合Scroller和VelocityTracker。侧滑删除控件主要就是侧滑吗,我们想办法把这个LinearLayout滑动起来就好了,这样就把原先看不到View显示出来了。好了基本的思路就是这样,很简单。那么下面就来看一下事件分发吧。

    android中事件分发我们一般只考虑View和ViewGroup就可以了,先说一下大概的流程,首先事件分发一定会先传到Activity,然后这个Activity会持有一个Window,这时就会把这个事件传递给相应的Window,这个Window里面有一个DecorView,这个时候这个事件就传递到了View层。一个事件传递到ViewGroup首先会进入DispatchEvent方法,这个时候首先会有一个标志位,这个标志位就是这个DispatchEvent的返回值,用来告诉上一层的View是否处理这个MotionEvent。首先这个标志位会先置为false,然后问这个ViewGroup是否需要拦截这个MotionEvent,这个时候就是进入了onInterceptEvent(),这个函数只有在ViewGroup内会被调用,这个函数的返回值就是告诉这个ViewGroup是否把这个事件向下传,自己不处理。还是选择自己处理不再向下传。这个函数返回false的话,就表示不拦截这个事件,会把这个事件向下传递。如果返回的是true,这个时候这个ViewGroup就会变成了View,表示它自己会处理这个事件。不再向下传递。当一个事件传递到View的时候,首先会进入dispatchEvent()方法,然后会进入onTouchEvent()方法,在onTouchEvenet里面会调用onTouch和onClick方法,前提是你设置了这两个方法。

    好了,有了上面的基础我们就开始动手吧,侧滑删除吗,我就让这个LinearLayout滑动就行了。首先会判断在什么时候我需要拦截这个事件,应该就是当用户是在左右滑动的时候,我们会认为用户是想使用侧滑删除的功能,那么这时候应该拦截这个事件,也就是让我们的LinearLayout来处理。

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        int action = ev.getAction();
        boolean intercepted = false;
        int x = (int) ev.getX();
        if (!mScroller.isFinished()) {
            mScroller.abortAnimation();
            return true;
        }
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                mLastX = (int) ev.getX();
                intercepted = false;
                break;
            case MotionEvent.ACTION_MOVE:
                if (Math.abs(x - mLastX) > mTouchSlop) intercepted = true;
                else intercepted = false;
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                intercepted = false;
    
                break;
        }
        mLastX = x;
        return intercepted;
    }
    
    mTouchSlop:是系统认定是滑动的最小值。
    mScroller:是用来处理滑动的,用这个处理滑动会有一个动画的效果,没那么突然,虽然它的内部实现不是动画。
        可以看到在这个方法里面只有当用户在X轴方向的滑动分距离大于系统认为的最小滑动的 时候才会拦截事件。事件拦截写来以后就会交给onTouchEvent处理。那么这时我们就会在这个函数处理相关的滑动。
    
    
    
    @Override
    public boolean onTouchEvent(MotionEvent event) {
    
        getParent().requestDisallowInterceptTouchEvent(false);
        if (!mScroller.isFinished()) {
            mScroller.abortAnimation();
            return true;
        }
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mLastX = (int) event.getX();
                break;
            case MotionEvent.ACTION_MOVE:
                int dx = (int) (event.getX() - mLastX);
                mLastX = (int) event.getX();
                scrollBy(-dx, 0);
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                int scrollX = getScrollX();
                if (scrollX > MAXWIDTH / 3) {
                    mScroller.startScroll(scrollX, 0, MAXWIDTH - scrollX, 0, 200);
                } else {
                    mScroller.startScroll(scrollX, 0, -scrollX, 0, 200);
                }
                invalidate();
                break;
        }
        mLastX = (int) event.getX();
        return true;
    }
    
        mScroller.startScroll(scrollX, 0, -scrollX, 0, 2000);这个函数表示
    的意思就是从(scrollX,0)点滑动到(scrollX + (-scrollX),0 + 0)点,并且
    耗费的时间是200ms,如果使用Scroller来处理滑动就一定需要重写
    computeScroll(),并且一定别忘记了postInvalidate(),因为Scroller只是负
    责计算在相应的时间这个x应该变化到什么值。它只是这个功能,所以我们拿到这个值后需要自己去scrollTo,并且需要调用 postInvalidate();这样他才会继续调用
    computeScroll()来完成滑动,这样不停的重新绘制,显示出来的就是动画效果。
    
    
     @Override
    public void computeScroll() {
        super.computeScroll();
        if (mScroller.computeScrollOffset()) {
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            postInvalidate();
        }
    }
    

    上面就算侧滑删除的功能实现了,可以看出非常简单,整个文件就一百多行代码,项目非常简单,但是也能体现我们对事件分发各个环节的掌握,把对的代码写在对的地方。

    下面总结一下事件分发的知识点

    1.同一个事件序列是指从手指接触屏幕的那一刻起到手指离开屏幕的那一刻结束,在这个过程中所产生的一系列事件,这个事件序列以DOWN事件开始,中间含有数量不定的MOVE事件,最终以UP事件结束。

    2.正常情况下,一个事件序列只能被一个View拦截且消耗,因为一个元素拦截了某次事件那么同一个事件序列内的所有事件都会直接交给它处理。当然不包括父View从中间拦截。

    3.某个View决定拦截,那么这一个事件序列都只能由它来处理(如果事件序列能够传递给它的话),并且它的onInterceptTouchEvent不会再被调用,这个很好理解,就是说,当一个View决定拦截一个事件后,那么系统就会把同一个事件序列内的其他方法都直接交给他来处理。因此就不用再调用这个View的onInterceptTouchEvent去询问它是否要拦截了。

    4.某个View一旦开始处理事件,如果它不消耗DOWN事件,(onTouchEvent返回了fasle),那么同一事件序列中的其他事件都不会再交给它处理,并且事件将重新交由它的父元素去处理。即父元素的onTouchEvent会被调用。

    5.如果View不消耗除DOWN以外的其他事件,那么这个点击事件会消失,此时父元素的onTouchEvent并不会被调用,并且当前View可以持续收到后续的事件追踪这些消失的点击事件会传递给Activity处理。

    6.ViewGroup默认不拦截任何事件,android源码中ViewGroup的onInterceptTouchEvent方法默认返回false。

    7.View没有onInterceptTouchEvent方法,一旦有点击事件传递给它,那么它的onTouchEvent方法就会被调用。

    8.View的onTouchEvent默认都会消耗事件(返回true),除非它是不可点击的(clickable和longClickable同时为false),View的longClickable属性默认都为false,clickable属性要分情况,比如Button的clickable属性默认为true,而TextView的clickable属性默认为false。

    9.View的enable属性不影响onTouchEvent的默认返回值,哪怕一个View是disable状态的,只要它的clickable或者longclickable又一个为true,那么它的onTouchEvent就返回true。

    10.onClick会发生的前提是当前View是可点击的,并且他收到了down和up的事件。

    11.事件传递过程是由外向内的,即事件总是先传递给父元素,然后再由父元素分发子View,通过requestDisAllowInterceptTouchEvent方法可以再子元素中干预父元素的事件分发过程。当时DOWN事件除外。

    12.CANCEL事件,官方文档讲的是当前手势被释放,你将不会接收到其他事件,应该像UP一样对待它,那到底什么情况会触发这个事件呢?当前控件(子控件)收到前驱事件(DOWN或者MOVE)后,它的父控件突然插手拦截了事件的传递,这时当前控件就会收到CANCEL,收到此事件后,不管子控件此时返回true或者false,都认为这一个动作已经完成,不会再回传到父控件的onTouchEvent中处理,同时后续事件会通过dispatchEvent方法直接传递到父控件这里来处理。

    13.父控件不拦截DOWN事件,如果子控件的onTouchEvent返回了true,在MOVE时父控件拦截,此时CANCEL会传递到子控件,并且子控件只会收到这一个CANCEL事件,后续的事件序列都会进入到父控件的onTouchEvent,且不再走父控件的onInterceptTouchEvent方法,因为此时父控件相当于View,mTarget为空时直接进入onTouchEvent方法,所以此时在onTouchEvent可以监听到后续的事件是因为没有控件会处理这个事件。如果父控件的onTouchEvent返回false,此时会上传到父控件的父控件,最终Activity会处理。如果父控件的onTouchEvent返回true表明后续事件会由这个父控件来处理。

    14.最后一点时触摸区域的问题,如果触摸区域在子控件内,同时父控件没有拦截事件传递,则不管子控件是否拦截此事件都会传递到子控件的onTouchEvent中处理,可以看成一种责任吧,因为我点的就是你,你父亲没有拦截说明它不想处理了,那就会传递到你这里。

    相关文章

      网友评论

          本文标题: 自定义侧滑删除带你详解事件分发一二

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