View的事件体系

2018-09-25  本文已影响0人  要学的东西太多了

1.View的位置参数top(左上角纵坐标),left(左上角横坐标),bottom(右下角纵坐标),right(右下角横坐标)是相对于它的父容器来说的,右、下为正。

2.View在平移过程中,top和left的值不会改变,变的是translationX和translationY,导致x和y的值发生改变(x=left+translationX,y=top+translationY),这几个值也是相对父容器的。

3.MotionEvent事件中我们能得到x,y坐标,getX/Y拿到的是触摸点相对当前View左上角的坐标,getRawX/getRawY返回的是相对屏幕左上角的坐标。

4.TouchSlop是系统所能识别的最小滑动距离,在作滑动处理的时候可以用这个值做过滤,通过ViewConfiguration.get(Context context).getScaledTouchSlop()方法获取。

5.VelocityTracker用于追踪手指在滑动过程中的速度,通过如下代码获取:

@Override
    public boolean onTouchEvent(MotionEvent event) {
        VelocityTracker velocityTracker = VelocityTracker.obtain();
        velocityTracker.addMovement(event);
        //要获取速度必须先计算速度,这里是1S内滑动的像素点
        velocityTracker.computeCurrentVelocity(1000);
        float velocityX = velocityTracker.getXVelocity();
        float velocityY = velocityTracker.getYVelocity();
        //不用的时候回收内存
        velocityTracker.clear();
        velocityTracker.recycle();
        return super.onTouchEvent(event);
    }

6.GestureDetector辅助检测手势(单击,双击,滑动,长按等行为)。

GestureDetector detector = new GestureDetector(gestureListener);
        detector.setIsLongpressEnabled(false);//解决长按后无法监听的问题

@Override
    public boolean onTouchEvent(MotionEvent event) {
        boolean b = detector.onTouchEvent(event);
        return b;
    }

监听事件回调

OnGestureListener:
onDown  轻触屏幕的一瞬间
onShowPress  未松开或拖动的状态
onSingleTapUp  单击
onScroll  拖动
onLongPress  长按
onFling  快速滑动

OnDoubleTapListener:
onSingleTapConfirmed  单击
onDoubleTap  双击
onDoubleTapEvent  双击期间

7.ScrollTo(绝对距离)和ScrollBy(相对距离)只能移动View的内容,View本身是不会移动的,mScrollX的值随移动而变,从右往左为正,反之为负;mScrollY从下往上为正,反之为负。scroll值指的是View内部滑动的值,View初始化的scroll值为0,延View坐标轴正反向滑动,scroll值为负,反之为正。它们用在父布局上就是移动子View,用在View上就是移动View的内容(比如TextView移移动的是文本,ImageView移动的是内部的Drawable对象)

8.View动画平移的只是影像,真身还在原来的位置,绑定的事件依然在原位置才能触发,属性动画在3.0以上可以解决这个问题。View平移后要设置fillafter为true,否则影像会在完成动画的瞬间回到原点。

9.Scroller的典型代码如下:

private void init(){
        scroller = new Scroller(context);
    }

    public void smoothScrollTo(int desX,int desY){
        int scrollX = getScrollX();
        int scrollY = getScrollY();
        int x = desX-scrollX;
        int y = desY-scrollY;
        //Scroller本身不滑动,这里是设置起点位置、位移距离和滑动事件间隔,在startScroll方法内部,finalX=scrollX+x。
        scroller.startScroll(scrollX,scrollY,x,y,1000);
        //这里调用View的重绘,在onDraw方法中会去调用computeScroll方法
        invalidate();
    }

    @Override
    public void computeScroll() {
        //computeScrollOffset方法会根据流逝时间百分比,计算scroller的目标位置,然后调用scrollTo进行滑动,这个方法为true时表示还在滑动,为false时表示滑动完成
        if(scroller.computeScrollOffset()){
            scrollTo(scroller.getCurrX(),scroller.getCurrY());
            postInvalidate();//下一次重绘
        }
    }

10.弹性滑动的主要思想都是计算时间百分比来计算滑动位置,然后用scrollto方法来实现滑动。

11.事件的点击传递流程是activity-window-decorview-view,事件点击触发流程如下核心代码所示:
(1)view的分发流程(view其实只能消费事件)

public boolean dispatchTouchEvent(MotionEvent event) {
        boolean enable = ENABLED_MASK == ENABLED;
        boolean b = false;
        if(ListenerInfo!=null && enable ){//ListenerInfo在set各种listener的时候会自动初始化
            if(mOnTouchListener!=null){
                b = mOnTouchListener.onTouch(view,event);
            }else{
                b = onTouchEvent(event);
            }
        }
        return b;
    }

    public boolean onTouchEvent(MotionEvent event) {
        //clickable只要CLICKABLE、LONG_CLICKABLE、CONTEXT_CLICKABLE任一可用就为true
        boolean clickable = CLICKABLE || LONG_CLICKABLE || CONTEXT_CLICKABLE;
        if(enabled == disabled){
            return clickable;
        }
        //View默认消耗事件(返回true),除非是不可点击的(clickable和longclickable都为false)
        if(clickable){
            switch(event.getAction()){
                case MotionEvent.ACTION_UP:
                    performClick();
                    break;
            }
            return true;
        }
        return false;
    }

    public void performClick() {
        if (mOnClickListener != null) {
            mOnClickListener.onClick(view);
        }
    }

(2)viewGroup的分发流程:

public boolean dispatchTouchEvent(MotionEvent event) {
        final boolean intercepted;
        //如果是按下或者mFirstTouchTarget 不为空,会再次判断是否需要拦截事件
        if (event.getAction() == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
            //FLAG_DISALLOW_INTERCEPT可以用requestDisallowInterceptTouchEvent()方法改变
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            //如果这个标志为false,再判断onInterceptTouchEvent()方法是否需要拦截
            if (!disallowIntercept) {
                intercepted = onInterceptTouchEvent(ev);
            } else {
                intercepted = false;
            }
        } else {
            intercepted = true;
        }
        //没拦截才走下面的流程
        if (!canceled && !intercepted) {
            //注意这里只有按下的时候才会走,如果子view的ACTION_DOWN都返回false了,那么ACTION_MOVE都不会传递给子view
            if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                if (newTouchTarget == null && childrenCount != 0) {
                    //这里会根据子view的Z轴值来组装子view列表
                    final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                    final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled();
                    for (int i = childrenCount - 1; i >= 0; i--) {
                        //根据可见性和优先级,找到子view分发事件
                        final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
                        final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
                        //dispatchTransformedTouchEvent里面,cancel为false,不会传递ACTION_CANCEL事件,接下来,child不为null,会调用child的dispatchTouchEvent方法
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                            //如果子view消费了事件,跳出循环
                            //addTouchTarget方法会给mFirstTouchTarget 和newTouchTarget 赋值,他们俩是相同的对象,这个对象的next是null
                            newTouchTarget = addTouchTarget(child, idBitsToAssign);
                            alreadyDispatchedToNewTouchTarget = true;
                            break;
                        }
                    }
                }
            }
        }
        //拦截的时候,mFirstTouchTarget 为null,直接调用view的分发流程
        if (mFirstTouchTarget == null) {
            handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);
        } else {
            //ACTION_MOVE和ACTION_UP的分发都是走的这里的逻辑
            TouchTarget target = mFirstTouchTarget;
            while (target != null) {
                //next 肯定为空,循环只会跑一次
                final TouchTarget next = target.next;
                //这里只有ACTION_DOWN被子view消费了,判断才为真,不会再分发一次
                if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                    handled = true;
                } else {
                    //如果拦截了,或者 需要通知子view  ACTION_CANCEL事件
                    final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted;
                    if (dispatchTransformedTouchEvent(ev, cancelChild,target.child, target.pointerIdBits)) {
                        handled = true;
                    }
                    //如果取消了,会重置mFirstTouchTarget,所以前面判断到一旦拦截,这个事件序列就不会再判断是否需要拦截
                    if (cancelChild) {
                            if (predecessor == null) {
                                mFirstTouchTarget = next;
                            } else {
                                predecessor.next = next;
                            }
                            target.recycle();
                            target = next;
                            continue;
                       }
                target = next;
            }
        }
        return handled ;
    }

12.子View可以通过requestDisallowInterruptTouchEvent方法干预父元素除action_down以外的事件分发过程。

13.viewgroup不是每次都调用onInterceptTouchEvent方法,一旦viewgroup决定拦截,后面所有事件都交给它处理,不会再调用onInterceptTouchEvent判断,只有dispatchTouchEvent方法确保每次都会调用。

14.滑动冲突解决方案只是滑动规则不同而已,解决方案一般是外部拦截法和内部拦截法。伪代码如下:

外部拦截法:
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean intercept = false;
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                intercept = false;
                break;
            case MotionEvent.ACTION_MOVE:
                if(父容器需要拦截){//根据不同滑动规则判断
                    intercept = true;
                }else{
                    intercept = false;
                }
                break;
            case MotionEvent.ACTION_UP:
                intercept = false;
                break;
        }
        return intercept;
    }

内部拦截法(注意父布局的onInterceptTouchEvent方法除Action_Down返回false外都返true):
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                parent.requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:
                if(父容器需要拦截){
                    parent.requestDisallowInterceptTouchEvent(false);
                }
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return super.dispatchTouchEvent(ev);
    }
上一篇下一篇

猜你喜欢

热点阅读