《Android开发艺术探索》读书笔记--part3 View的事件体系

  • part3-1 View的基础知识

    • View是所有Android控件的基类,ViewGroup也继承自View,而View继承自Object。

    • View的四个位置参数与宽高:

      • top:左上角纵坐标,getTop();

      • left:左上角横坐标,getLeft();

      • right:右下角横坐标 ,getRight();

      • botton:右下角纵坐标,getBottom();

      • width= right-left;

      • height= botton-top;

  • Get:

    • 从Android3.0开始View额外添加了四个参数:

      • x,y:View左上角的坐标 ;

      • translationX,translationY :左上角相对于父容器的偏移量,默认值为0;

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

    • View在平移过程中,top和left并不会发生改变,改变的是x,y,translationX,translationY这几个参数;

  • MotionEvent:手指触摸屏幕事件,典型的事件类型有

    • ACTION_DOEN: 手指刚触摸屏幕;

    • ACTION_MOVE: 手指在屏幕上移动;

    • ACTION_UP: 手指松开屏幕的一瞬;

    • 通过MotionEvent可以获取点击事件产生的x和y坐标

      • getX/getY:返回相对于当前View左上角的x/y坐标;

      • getRawX/getRawY:返回相对于手机屏幕左上角的x/y坐标;

  • TouchSlop:被系统所能识别的滑动最小距离,当滑动距离小于该值时,系统不认为是滑动操作,可通过一下代码获取该距离

    ViewConfiguration.get(getContext()).getScaledTouchSlop();

  • VelocityTracker:用于追踪滑动过程中的速度(一段时间内手指所滑过的像素数),包括水平和竖直方向的速度。使用方式

//初始化velocityTracker
VelocityTracker velocityTracker=VelocityTracker .obtain(); 
velocityTracker.addMovement(event);
//计算速度前要先调用该方法
velocityTracker.computeCurrentVelocity(1000);
//计算速度
int xVelocity=(int)velocityTracker.getXVelocity();
int yVelocity=(int)velocityTracker.getYVelocity();
//回收
velocityTracker.clear();
  • GestureDetector:手势检测,用于检测用户的单击、滑动、长按、双击等行为。使用方式
//实例化
GestureDetector mGestureDetector=new GestureDetector(this);
//解决长按屏幕无法拖动现象
mGestureDetector.setISLongpressEnable(false);

//在目标View的onTouchEvent()中添加
boolean consume=mGestureDetector.onTouchEvent(event);
return consume;
-  然后可以选择性实现OnGestureListenner和OnDoubleTapListenner中的方法,常用方法有:

        - onSingleTapUp()单击

        - onFling()快速滑动  

        - onScroll()拖动 

        - onLongPress()长按 

        - onDoubleTap()双击

    - 建议:如果只是监听滑动相关的事件在onTouchEvent中实现;如果要监听双击这种行为的话,那么就使用GestureDetector。
  • Scroll:滑动对象,用于实现弹性滑动(貌似很少使用,详见原书)

  • part3-2 View的滑动

    • 使用scrollTo(int x,int y)/scrollBy(int x,int y) :只能将View的内容进行移动,并不能将View本身进行移动。操作简单,适合对view内容的滑动。

    • 使用动画移动View,主要操作View的translationX和translationY属性,为了兼容3.0一下版本,需要采用开源动画库nineoldanroids。操作简单,适用于没有交互的view和实现复杂的动画效果。

      • 注意:如果对移动后的View添加点击事件,使用普通的View动画并不能满足需求,因为View动画并没有真正改变View的位置,可以使用属性动画或其他方法解决。
    • 通过改变布局参数LayoutParams进行移动。操作稍微复杂,适用于有交互的view。

  • part3-3 弹性滑动:(详见原书)

    • 使用scroller;

    • 通过动画;

    • 使用延时策略;

  • part3-4 View的事件分发机制

    • 点击事件分发过程的3个重要方法:

      • public boolean dispatchTouchEvent(MotionEvent ev):用来进行事件的分发。如果事件能够传递给当前view,那么此方法一定会被调用,返回结果受当前view的onTouchEvent和下级view的dispatchTouchEvent方法的影响,表示是否消耗当前事件。

      • public boolean onInterceptTouchEvent(MotionEvent ev):在dispatchTouchEvent方法内部调用,用来判断是否拦截某个事件,如果当前view拦截了某个事件,那么在同一个事件序列当中,此方法不会再被调用,返回结果表示是否拦截当前事件。若返回值为True事件会传递到自己的onTouchEvent();若返回值为False传递到子view的dispatchTouchEvent()。

      • public boolean onTouchEvent(MotionEvent ev):在dispatchTouchEvent方法内部调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一个事件序列中,当前view无法再次接收到事件。若返回值为True,事件由自己处理,后续事件序列让其处理;若返回值为False,自己不消耗事件,向上返回让其他的父容器的onTouchEvent接受处理。

    • 上述三个方法的关系可用下面伪代码表示

        public boolean dispatchTouchEvent(MotionEvent ev) {
            boolean consume = false;
            if (onInterceptTouchEvent(ev)) {
                consume = onTouchEvent(ev);
            } else {
                consume = child.dispatchTouchEvent(ev);
            }
            return consume;
        }

Get:

  • 如果给一个需要处理事件的View设置onTouchListenner,那么onTouchListenner中的onTouch方法会被调用,如果onTouch方法返回true,当前View的onTouchEvent方法不会调用,否则则会被调用;在onTouchEvent方法中,如果设置有onClickListenner,那么onClick方法会被调用。由此可见,优先级onTouchListenner>onTouchEvent>onClickListenner

  • 点击事件的传递遵循Activity → Window → View,顶级View接受到事件后,按照事件分发机制去分发事件。如果一个View的onTouchEvent方法返回false,那么事件将交由其父容器处理,如果父容器的onTouchEvent方法返回false,再交由它的父容器处理,以此类推,如果顶级View的onTouchEvent方法返回false,则事件最终会传递给Activity处理,Activity的onTouchEvent方法会被调用。

  • ViewGroup默认不拦截任何事件,View的onTouchEvent默认消耗事件(返回true),除非它是不可点击的

  • View的enable属性不影响onTouchEvent的默认返回值

  • 事件传递过程是由外向内的,先传递给父元素,再由父元素传递给子元素,子元素可以通过requestDisallowInterceptTouchEvent方法干预父元素的分发过程,除了ACTION_DOWN事件。

  • Activity对事件的分发过程:

    事件传递给Activity,调用Activity的dispatchTouchEvent分发,具体是由Activity内部的Window来完成的,Window是抽象类,其实现类是PhoneWindow,PhoneWindow将事件传递给DecorView(当前界面的底层容器,即Activity中setContentView所设置的View的父容器),DecorView再传递给顶级View。

  • 顶级View对事件的分发过程:

    顶级View一般是个ViewGroup,顶级ViewGroup接收到事件后,会调用ViewGroup的dispatchTouchEvent方法,如果顶级ViewGroup拦截了事件即onInterceptTouchEvent返回true,则事件交给顶级ViewGroup处理;如果顶级ViewGroup不拦截事件,事件会传递给它所在点击事件链上的子View,子View的dispatchTouchEvent方法会被调用,并进行下一层分发

    • 注意:如果子View设置了FLAG_DISALLOW_INTERCEPT标记位(通过requestDisallowInterceptTouchEvent方法设置的),ViewGroup将无法拦截处理ACTION_DOWN以外的其他事件,因为ACTION_DOWN会重置该标记
  • View(不包含ViewGroup)对事件的分发过程:

    View接受到事件后,先判断有没有设置onTouchListenner,如果onTouchListenner中的onTouch方法返回true,则该View的onTouchEvent不会被调用。在onTouchEvent处理中,只要View的CLICKABLE和LONG_CLICKABLE有一个为true,onTouchEvent就会返回true,即消耗掉事件,即使View处于Disable状态也会消耗掉事件。如果View设置了onClickListenner,那么onClick方法会被调用,onClickListenner会自动将View的CLICKABLE设置为true

  • part3-5 滑动冲突

    • 滑动冲突场景

      • 外部滑动方向和内部滑动方向不一致,如ScrollView嵌套listview(如果时ViewPager中嵌套则不会发生冲突,因为ViewPager内部以解决了冲突);

      • 外部滑动方向和内部滑动方向一致,如listview嵌套listview;

      • 以上2中冲突的结合;

    • 解决冲突的规则:

      • 根据滑动距离和水平方向形成的夹角;

      • 根绝水平和竖直方向滑动的距离差(开发中常用);

      • 根据两个方向上的速度差

    • 解决冲突的方法(详见原书):

      • 外部拦截法:
        点击事件都先经过父容器的拦截处理,如果父容器需要此事件就拦截,如果不需要就不拦截。该方法需要重写父容器的onInterceptTouchEvent方法,在内部做相应的拦截即可,其他均不需要做修改。
            public boolean onInterceptTouchEvent(MotionEvent event) {
                boolean intercepted = false;
                int x = (int) event.getX();
                int y = (int) event.getY();

                switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN: {
                    intercepted = false;
                    break;
                }
                case MotionEvent.ACTION_MOVE: {
                    int deltaX = x - mLastXIntercept;
                    int deltaY = y - mLastYIntercept;
                    if (父容器需要拦截当前点击事件的条件,例如:Math.abs(deltaX) > Math.abs(deltaY)) {
                        intercepted = true;
                    } else {
                        intercepted = false;
                    }
                    break;
                }
                case MotionEvent.ACTION_UP: {
                    intercepted = false;
                    break;
                }
                default:
                    break;
                }

                mLastXIntercept = x;
                mLastYIntercept = y;

                return intercepted;
            }
  • 内部拦截法:
    父容器不拦截任何事件,所有的事件都传递给子元素,如果子元素需要此事件就直接消耗掉,否则就交给父容器来处理。这种方法和Android中的事件分发机制不一致,需要配合requestDisallowInterceptTouchEvent方法才能正常工作,使用比外部拦截法复杂。
public boolean dispatchTouchEvent(MotionEvent event) {
    int x = (int) event.getX();
    int y = (int) event.getY();

    switch (event.getAction()) {
    case MotionEvent.ACTION_DOWN: {]
        getParent().requestDisallowInterceptTouchEvent(true);
        break;
    }
    case MotionEvent.ACTION_MOVE: {
        int deltaX = x - mLastX;
        int deltaY = y - mLastY;
        if (当前view需要拦截当前点击事件的条件,例如:Math.abs(deltaX) > Math.abs(deltaY)) {
            getParent().requestDisallowInterceptTouchEvent(false);
        }
        break;
    }
    case MotionEvent.ACTION_UP: {
        break;
    }
    default:
        break;
    }

    mLastX = x;
    mLastY = y;
    return super.dispatchTouchEvent(event);
}

使用内部拦截法时父元素也要修改不拦截ACTION_DOWN事件

public boolean onInterceptTouchEvent(MotionEvent ev){
    if(ev.getAction==MotionEvent .ACTION_DOWN){
        return false;
    }else{
        return ture;
    }
}

View的事件体系到此结束,本章中View的分发机制与滑动冲突都是Android中的重点与难点

你可能感兴趣的:(Android开发)