美文网首页
View的事件机制

View的事件机制

作者: Lucida_star | 来源:发表于2018-04-03 18:21 被阅读3次

    微信公众号:Lucidastar
    如有问题或建议,请公众号留言
    最近更新:2018-04-03

    View的认识

    1、View的位置参数

    图注:Lucidastar
    width = right - left
    height = bottom - top
    
    Left = getLeft();
    Right = getRight();
    Top = getTop();
    Bottom = getBottom();
    

    从Android3.0开始,View增加了额外的几个参数。x、y、translationX和translationY,其中x和y是View左上角的坐标,translationX和translationY是View左上角相当于父容器的偏移量。translationX和translationY默认值是0,

    x = left + translationX;
    y = top + translationY;
    

    left和top表示的是原始左上角的位置信息,其值是不会改变,改变的是x、y、translationX和translationY这四个参数。

    2、MotionEvent和TouchSlop

    • MotionEvent对象可以得到点击事件发生的x和y的坐标。
    • getX/getY和getRawX/getRawY。
      getX/getY返回的是相当于当前View左上角的x和y的坐标
      getRawX/getRawY返回的是相当与手机屏幕左上角的x和y的坐标
      TouchSlop是系统所能识别的被认为是滑动的最小距离

    3、VelocityTracker、GestureDetector和Scroller

    • VelocityTracker 速度追踪,包括水平和竖直
    VelocityTracker velocityTracker = VelocityTracker.obtain();
    velocityTracker.addMovement(event);
    
    • GestureDetector 手势监测
    GestureDetector mDestureDetector = new GestureDetector (this);
    mDestureDetector.setIsLongpressEnabled(false);//解决长按屏幕后无法拖动的现象
    boolean consume = mDestureDetector.onTouchEvent(event);
    return consume;
    
    • Scroller弹性滑动对象,用于实现View的弹性滑动
    Scroller scroller = new Scroller(mContext);
    

    4、View的滑动
    可以通过三种方式进行滑动

    1. 是通过View本身提供的scrollTo/scrollBy方法来实现滑动;
    2. 通过动画给View施加平移效果来实现滑动
    3. 通过改变View的layoutParams使得View重新布局从而实现滑动

    View的事件分发机制

    View的点击分发过程由三个很重要的方法来公共完成:dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent;

    public boolean dispatchTouchEvent(MotionEvent ev)
    //用来进行事件的分发。如果事件能够传递给当前View,那么此方法一定会被调用,返回结果受当前View的onTouchEvent和下级View的dispatchTouchEvent方法的影响,表示是否消耗当前事件。
    
    public boolean onInterceptTouchEvent(MotionEvent ev)
    //在上述方法内部调用,用来判断是否拦截某个事件,如果当前View拦截了某个事件,那么在同一个事件序列当中,此方法不会被再次调用,返回结果表示是否拦截当前事件。
    
    public boolean onTouchEvent(MotionEvent event)
    //在dispatchTouchEvent方法中调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一个事件序列中,当前View无法再次接收到事件。
    
    下面用伪代码表示一下他们的关系
    public boolean dispatchTouchEvent(MotionEvent ev){
    boolean consume = false;
    if(onInterceptTouchEvent(ev)){  //拦截了
         consume = onTouchEvent(ev);//判断onTouchEvent是否消耗了
    }else{//没有拦截,交给子View继续事件的分发
         consume = child.dispatchTouchEvent(ev);
    }
         return consume;
    }
    

    1、分发 2、是否拦截3、是否消耗 没有拦截就交个子View继续分发,拦截了就自己处理。
    当一个点击事件产生后,它的传递过程遵循如下顺序Activity-->Window-->View

    事件传递过程是由外向内的,即事件总先传递给父元素,然后再由父元素分发给子View,通过requestDisallowInterceptTouchEvent方法可以在子元素中干预父元素的事件分发过程,但是ACTION_DOWN事件除外。

    Activity对点击事件的分发过程
    Activity#dispatchTouchEvent   getWindow().superDispatchTouchEvent(ev)
    Window#superDispatchTouchEvent(ev)   抽象类
    PhoneWindow#superDispatchTouchEvent(ev)   具体实现Window   mDecor.superDispatchTouchEvent(event)   mDecor是一个view应该说是一个FrameLayout
    这样就把事件传递到了顶级的View了。
    

    View的滑动冲突
    常见冲突场景

    1. 场景1---外部滑动方向和内部滑动方向不一致
    2. 场景2---外部滑动方向和内部滑动方向一致
    3. 场景3---上面两种情况的嵌套

    自定义一个水平滑动的容器(ViewGroup)

    当给里头放listView时,为了解决水平和竖直滑动的冲突,该如何进行解决??
    ①首先解决的思路是外部拦截法

     @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
            boolean intercept = false;
            int x = (int) ev.getX();
            int y = (int) ev.getY();
            switch (ev.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    intercept = false;
                    if (!mScroller.isFinished()) {
                        mScroller.abortAnimation();
                        intercept = true;
                    }
                    break;
                case MotionEvent.ACTION_MOVE:
                    int delayX = x - mLastXIntercept;
                    int delayY = y - mLastYIntercept;
                    if (Math.abs(delayX) > Math.abs(delayY)) {
                        intercept = true;
                    } else {
                        intercept = false;
                    }
                    break;
                case MotionEvent.ACTION_UP:
                    intercept = false;
                    break;
                default:
                    break;
            }
            mLastYIntercept = y;
            mLastXIntercept = x;
            mLastX = x;
            mLastY = y;
            return intercept;
        }
    

    首先父容器会拦截到事件

    1. 按下,父容器肯定不会拦截,如果拦截了,那么后续的滑动和抬起都会交由父容器处理,这个时候事件没法再传递给子元素了。
    2. 滑动,这个事件可以根据需要来决定是否拦截。根据我们的需要,通过左右和上下滑动的大小来判读是否拦截,当我们是左右滑动时就需要拦截,反 之不拦截,因为我们的需求就是左右滑动时就交由父容器来处理。
    3. 抬起,抬起时,父容器就不管了,由子元素来做处理就可以了
     @Override
        public boolean onTouchEvent(MotionEvent event) {
            mVelocityTracker.addMovement(event);
            int x = (int) event.getX();
            int y = (int) event.getY();
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    if (!mScroller.isFinished()) {
                        mScroller.abortAnimation();
                    }
                    break;
                case MotionEvent.ACTION_MOVE:
                    int deltaX = x - mLastX;
                    int deltaY = y - mLastY;
                    scrollBy(-deltaX,0);
                    break;
                case MotionEvent.ACTION_UP://主要做一些child的位置判定
                    int scrollX = getScrollX();
                    int scrollToChildIndex = scrollX / mChildWidth;
                    mVelocityTracker.computeCurrentVelocity(1000);
                    float xVelocity = mVelocityTracker.getXVelocity();
                    if (Math.abs(xVelocity) >= 50){
                        mChildIndex = xVelocity > 0 ? mChildIndex - 1 : mChildIndex + 1;
                    }else {
                        mChildIndex = (scrollX + mChildWidth / 2) / mChildWidth;
                    }
                    mChildIndex = Math.max(0, Math.min(mChildIndex, mChildrenSize - 1));
                    int dx = mChildIndex * mChildWidth - scrollX;
                    smoothScrollBy(dx, 0);
                    mVelocityTracker.clear();
                    break;
                default:
                    break;
            }
            mLastX = x;
            mLastY = y;
            return true;
        }
    

    当父容器拦截成功了,我就把事件进行了消耗。

    ②内部拦截法
    重写listView,复写dispatchTouchEvent,因为父容器除了按下不拦截,其他的事件都会拦截掉,不拦截时就把事件进行分发

     @Override
        public boolean dispatchTouchEvent(MotionEvent event) {
            int x = (int) event.getX();
            int y = (int) event.getY();
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN: {
                    mHorizontalScrollViewEx2.requestDisallowInterceptTouchEvent(true);
                    break;
                }
                case MotionEvent.ACTION_MOVE: {
                    int deltaX = x - mLastX;
                    int deltaY = y - mLastY;
                    Log.d(TAG, "dx:" + deltaX + " dy:" + deltaY);
                    if (Math.abs(deltaX) > Math.abs(deltaY)) {
                        mHorizontalScrollViewEx2.requestDisallowInterceptTouchEvent(false);
                    }
                    break;
                }
                case MotionEvent.ACTION_UP: {
                    break;
                }
                default:
                    break;
            }
            mLastX = x;
            mLastY = y;
            return super.dispatchTouchEvent(event);
        }
    

    当按下的时候,告诉父容器不拦截mHorizontalScrollViewEx2.requestDisallowInterceptTouchEvent(true);

    当滑动的时候,当判断是左右滑动的时候,告诉父容器进行拦截mHorizontalScrollViewEx2.requestDisallowInterceptTouchEvent(false);

    这样解决了滑动冲突,这就是内部拦截法

    看完第三章的总结

    1. View的位置坐标了解
    2. 方法及参数的了解(getX()、getY()、getRawX()、getRawY() 、translationX translationY)
    3. 理解MotionEvent TouchSlop
    4. VelocityTracker(速度) 、GestureDetector(手势)、Scroller(弹性滑动)
    5. View的滑动方式(3种)
    6. 事件分发机制,三个方法的理解及它们的关系(dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent)分发,拦截及消费。可以看上面的伪代码
    7. 点击事件的分发过程(由外向内的)看源码理解一下
    8. 通过例子来理解分发过程及处理事件的冲突的解决方案
    image

    相关文章

      网友评论

          本文标题:View的事件机制

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