我的“View的事件体系”知识点总结

文章总结自《android开发艺术探索》一书。

3.1 View基础知识

1、View是Android中所有控件的基类,是一种界面层的控件的一种抽象,也代表了一种控件。

2、在Android中View呈现出树的结构,ViewGroup继承了View,这代表View本身可以是单个控件也可以是多个控件组成的控件组

3、View中的四个属性:top、left、right、bottom,top是左上角纵坐标,left是左上角横坐标,right是右下角横坐标,bottom是右下角纵坐标;Android3.0开始增加了x、y、translationX、translationY四个参数,x、y是View左上角的坐标,另外两个代表View左上角相对于父容器的偏移量,View在平移的时候top、left不会变,x、y等4个参数变化;另外View中坐标系X轴正方向向右,Y轴正方向向下。

4、获取属性的方法:

  • Left =getLeft(); 剩下几个类比;
  • getX/getY,返回的是控件或事件相对于当前View左上角的坐标
  • getRawX/getRawY,返回的是控件或事件相对于手机屏幕左上角的坐标

5、 MotionEvent典型事件类型:

  • ACTION_DOWN
  • ACTION_MOVE
  • ACTION_UP

6、 TouchSlop:滑动最小距离,即两次滑动之间的距离小于这个值,系统默认不是滑动操作,获取方式:ViewConfiguration.get(getContext()).getScaledTouchSlop()

7、 VelocityTracker,速度追踪器,追踪手指在滑动过程中的速度,使用方法:

//build
VelocityTracker vt = VelocityTracker.obtain();
VelocityTracker.addMovement(event);

//use
vt.computeCurrentVelocity(1000);
int Vx = (int) vt.getXVelocity();
int Vy = (int) vt.getYVelocity();

//recyle
vt.clear();
vt.recyle();

8、GestureDetector,手势检测器,辅助检测单击、滑动等事件,用法:

//build
GestureDetector mGestureDetector = new GestureDetector(this);

//implement Interface: OnGestureListener || OnDoubleTapListener
    ....

//code in Method: mView.onTouchEvent
boolean consume = mGestureDetector.onTouchEvent(event);
return consume;

9、Scroller,用于实现View的弹性滑动。View的srcollTo/scrollBy过程是瞬间的,Scroller本身无法滑动View,需要和View的computeScroll配合使用,用法:

Scroller mScroller = new Scroller(mContext);

private void smoothScrollTo(int destX,int destY){
    int scrollX = getScrollX();
    int delta = destX - scrollX;
    //slide slowly to destX in 1000ms
    mScroller.startScroll(scrollX,0,delta,0,1000);
    invalidate();
}

@Override
public void computeScroll(){
    if(mScroller.computeScrollOffset()){
        scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
        postInvalidate();
    }
}

startScroll方法只是做一些赋值计算的工作,实际上起作用的是下面的invalidate,该方法导致View重绘,View的draw方法中会去调用computeScroll方法,computeScroll去获取Scroller当前的属性,然后开始滑动,再通知重绘,如此反复。

computeScrollOffset()方法是根据当前时间流逝的百分比来计算scrollX和scrollY改变的百分比并计算当前值,类似于属性动画中的插值器和估值器。返回值决定滑动是否结束。

3.2 View的滑动

三种方式实现View的滑动:

  • View自身提供的scrollTo/scrollBy方法
  • 动画给View施加平移效果
  • 改变View的LayoutParams使View重新布局

1、scrollTo/scrollBy
scrollBy调用了scrollTo,前者是基于当前距离的相对滑动,后者是绝对滑动。其源码中的mScrollX总是等于View左边缘和View内容左边缘的水平距离,mScrollY上边缘和内容上边缘垂直距离,从左往右滑,mScrollX为负,从上往下滑,mScrollY为负,并且只能改变View内容的位置而不能改变View在布局中的位置。

2、使用动画
android3.0以上可以使用属性动画,基本没缺点,3.0以下版本的View动画也可以移动,但是不能改变对象的属性,所以onClick等事件需要解决。

3、改变布局参数
改变LayoutParams中的marginXXX参数的值或者在需要的地方放上空的View,需要时改变View的宽度高度用来“挤走”目标控件,例如:

MarginLayoutParams params = (MarginLayoutParams)mButton.getLayoutParams();
params.width += 100;
params.height += 100;
mButton.requestlayout();

4、三者比较
scrollTo/scrollBy唯一缺点:只能滑动内容,不能改变View本身。
使用动画:3.0以上属性动画没有明显缺点,3.0以下View动画不能改变View的属性。动画缺点:不适用于与用户交互的控件。
改变布局:操作复杂,没有明显缺点。

5、使用延时策略
发送一系列的延时消息,可以使用Handler或View的postDelayed方法,也可以使用线程的sleep方法。

3.4 View的事件分发机制

1、针对MotionEvent的事件分发,主要由三个方法完成:

  • public boolean dispatchTouchEvent(MotionEvent ev),用来进行当前事件的分发,如果事件能传递给当前View,此方法一定会调用,返回结果受当前View的onTouchEvent和下级的dispatchTouchEvent方法影响,表示是否消耗该事件
  • public boolean onInterceptTouchEvent(MotionEvent event),在上述方法内部调用,用来表示是否拦截该事件,如果View拦截了该事件,那么同一事件序列的所有事件都交给该View处理,并且此方法不会再被调用
  • public boolean onTouchEvent(MotionEvent event),在dispatchTouchEvent方法中调用,处理点击事件,返回结果表示是否消耗该事件。

关系伪代码:

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

2、点击事件的激发顺序:onTouchListener—>onTouchEvent—>onClickListener

3、点击事件的传递顺序: Activity—>Window—>View,顶层View拿到事件以后进行分发。分发时如果子View无法处理事件,会上抛事件,父View的onTouchEvent会被调用,如果所有元素都不处理这件事,就会抛给Activity,Activity的onTouchEvent会被调用。

4、同一事件序列:指的是从手指触摸屏幕的那一刻,中间经历了若干个move事件,到手指离开屏幕的那一刻,中间产生的所有事件。

5、正常情况下,同一事件序列的所有事件只能被一个View拦截并且消耗。

6、某个View一旦开始处理事件,如果onTouchEvent返回false,即不消耗事件,那么同一事件序列中剩下的所有事件都不交给它处理,转交给父View去处理。

7、ViewGroup默认不拦截任何事件,View没有onInterceptTouchEvent方法,事件传给它直接调用onTouchEvent。

8、View的onTouchEvent默认都会消耗事件,除非是不可点击的,View的enable属性不影响onTouchEvent的默认返回值。

9.onClick事件发生的前提是当前View可点击,并且接收到了downup事件。

10、可以通过requestDisallowInterceptTouchEvent方法在子元素中干预父元素的分发过程,但是ACTION_DOWN事件除外。

3.5 View的滑动冲突

1、常见的滑动冲突场景

  • 外部滑动方向和内部滑动方向不一致,比如外层一个ScrollView,内层一个ListView
  • 外部滑动方向和内部滑动方向一致,比如内外层同时能上下滑动或者左右滑动
  • 上面两种情况的嵌套

第一种的处理规则是:根据用户不同的滑动方向,让不同层的View拦截点击事件。具体来说根据滑动过程中两个点的坐标来判断,比如说两点坐标的竖直距离差与水平距离差的大小比较,水平竖直方向的速度差等。

第二、三种的处理规则:根据业务逻辑,在View的拦截中加上相应的业务逻辑判断是否拦截处理。

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:{
            if(父容器需要当前点击事件){
                intercepted = true;
            }else{
                intercepted = false;
            }
            break;
        }
        case MotionEvent.ACTION_UP:{
            intercepted = false;
            break;
        }
        default:
            break;
    }
    mLastXIntercept = x;
    mLastYIntercept = y;
    return intercepted;
}

ACTION_DOWN事件一定不能拦截,否则后续事件都交给父容器处理,如果父容器处理不好会上抛给上层父容器,不会传递到其子元素,另外ACTION_UP事件没有多大意义,也要返回false。

内部拦截法
父容器不拦截任何事件,所有事件都传递给子元素,如果子元素需要此事件就消耗掉,否则就上抛给父容器进行处理。需要配合requestDisallowInterceptTouchEvent方法并重写子元素的dispatchTouchEvent

public boolean dispatchTouchEvent(MotionEvent event){
    int x = (int) event.getX();
    int y = (int) event.getY();

    switch(event.getAction()){
        case MotionEvent.ACTION_DOWN:{
            parent.requestDisallowInterceptTouchEvent(true);
            break;
        }
        case MotionEvent.ACTION_MOVE:{
            int deltaX = x - mLastX;
            int deltaY = y - mLastY;
            if(父类需要此类点击事件){
                parent.requestDisallowInterceptTouchEvent(false);
            }
            break;
        }
        case MotionEvent.ACTION_UP:{
            break;
        }
        default:
            break;
    }
    mLastX = x;
    mLastY = y;
    return super.dispatchTouchEvent(event);
}

父元素默认拦截除了ACTION_DOWN以外的其他事件:

public boolean onInterceptTouchEvent(MotionEvent event){
    int action = event.getAction();
    if(action==MotionEvent.ACTION_DOWN){
        return false;
    }else{
        return true;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值