美文网首页
View的事件体系

View的事件体系

作者: 要学的东西太多了 | 来源:发表于2018-09-25 17:40 被阅读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);
        }
    

    相关文章

      网友评论

          本文标题:View的事件体系

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