美文网首页
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