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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/a8341025123/article/details/79291467

本文是《Android开发艺术探索》读书笔记系列第三篇,来看看本章的思维导图。
View的事件体系

一、View基础知识

View和ViewGroup

View是Andorid中所有控件的基类,ViewGroup可以翻译为控件组,其内部可以包含多个View,它也是继承了View,这意味着View本身就可以是单个控件也可以是多个控件组成的一个控件。

View的位置参数

View的位置主要由四个参数来确定,分别是top、left、right、bottom。
top:左上角的纵坐标
left:左上角的横坐标
right:右下角的横坐标
bottom:右下角的纵坐标

需要注意的是,上述的四个参数都是相对于View的父容器来说的,如下图所示。在Android中,X轴的正方向是向右的,Y轴的正方向是向下的。(不仅仅是Android,其他大部分显示系统都是这样定义的),这四个参数的具体数值可以在View通过getTop(),getB

View的四个位置参数

在Android3.0后,为View额外增加了几个新参数:x、y、translationX和translationY。
x和y是View的左上角的坐标,而translationX和translationY是View左上角相对于父容器的偏移量,这几个参数也是相对于父容器的坐标。一般默认情况下translationX和translationY为0。x、top和translationX之间有如下的关系。

x = left + translationX
y = top + translationY

为什么会多出这几个参数呢?因为在View平移的过程中,可能会出现View的实际内容没变只是可视内容在平移,此时top和left任然表示原始左上角的位置信息,并不发生改变,此时发生改变的是translationX和translationY,所以x和y也发送了改变,表示平移后的位置。

MotionEvent

MotionEvent代表在手指接触屏幕后所产生的一系列事件,典型的时间类型有以下几种:

  • ACTION_DOWN —— 手指刚接触屏幕
  • ACTION_MOVE —— 手指在屏幕上移动
  • ACTION_UP —— 手指在屏幕上松开的一瞬间

正常情况下,一次手指触摸屏幕的行为会出发一系列点击事件,考虑如下几种情况

  • 点击屏幕后离开松开,事件序列为DOWN -> UP
  • 点击屏幕滑动一会再松开,事件序列为DOWN -> MOVE -> MOVE -> …. -> MOVE -> UP

我们可以通过MotionEnvent对象得到点击事件发生的x和y坐标,有两组方法,getX/getY返回的是相对于当前View左上角的x和y坐标,而getRawX/getRawY返回的是相对于手机屏幕左上角的x和y坐标。

TouchSlop

TouchSlopsided系统所能识别出的被认为是滑动的最小距离。这是一个常量,和设备有关,在不同的设备上这个值可能是不同的。可以通过下面这个方法获得

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

这个常量的意义是我们在处理滑动时,可以利用这个常量来做过滤,过滤一些滑动距离小于TouchSlop的滑动,这可以提高用户体验。

VelocityTracker

速度追踪,用于追踪手指在滑动过程中的速度,包括水平和竖直方向的速度。使用VelocityTracker有以下几个步骤

  1. 首先,在View的onTouchEvent方法中追踪当前单击事件的速度

    VelocityTracker velocityTracker = VelocityTracker.obtain();
    velocityTracker.addMovement(event);

  2. 接着,设置毫秒数t,就可以获得x和y方向的速度了。这里得到的x和y的含义是:经过了t毫秒,在x(或y)方向上滑动过的像素数。可以为负数,表示和坐标轴相反方向。

    velocityTracker.computeCurrentVelocity(1000);
    int xVelocity = (int) velocityTracker.getXVelocity();
    int yVelocity = (int) velocityTracker.getYVelocity();

  3. 最后回收资源

    velocityTracker.clear();
    velocityTracker.recycle();

GestureDetector

手势检测,用于辅助检测用户的单击、滑动、长按、双击等行为,通过创建GestureDetector对象并实现OnGestureListener接口,并在需要接管的View的onTouchEvent方法中调用GestureDector的onTouchEvent(event)就可以实现监听。

Scroller对象

弹性滑动对象,用于实现View的弹性滑动,Scroller本身无法让View弹性滑动,它需要和View的computeScroll方法配合使用才能共同完成这个功能。后续内容会再次提到。

View的滑动

View的滑动可以用过三种方式实现

使用scrollTo/scrollBy

scrollTo/scrollBy是View本身提供的专门的方法。scrollTo是滑动到绝对位置,scrollBy是基于当前位置滑动。需要注意,这两个方法只能改变View内容的位置而不能改变View在布局中的位置。

使用动画

使用动画来移动View,主要是操作View的translationX和translationY属性,既可以采用传统的View动画,也可以采用属性动画,如果采用属性动画的话,为了兼容3.0以下版本,需要采用开源动画库nineoldandroids。

在Android3.0以下的手机上通过nineoldandroids来实现的属性动画本质上仍然是View动画。

View动画是对View的影像做操作,它并不能真正改变View的位置参数,包括宽/高,并且如果希望动画后的状态得以保留还必须将fillAfter属性设置为true,否则动画完成后其动画结果会消失。

改变布局参数

改变布局参数,即改变LayoutParams

MarginLayoutParams params = (MarginLayoutParams)mButton.getLayoutParams();  
params.width += 100;  
params.leftMargin += 100;  
mButton.requestLayout();  
//或者mButton.setLayoutParams(params);  

各种滑动方式的对比

  • scrollTo/scrollBy:操作简单,适合对View内容的滑动;
  • 动画:操作简单,主要适用于没有交互的View和实现复杂的动画效果;
  • 改变布局参数:操作稍微复杂,适用于有交互的View。

弹性滑动

使用Scroller

Scroller的工作原理:Scroller本身并不能实现View的滑动,它需要配合View的computeScroll方法才能完成弹性滑动的效果,它不断地让View重绘,而每一次重绘距滑动起始时间会有一个时间间隔,通过这个时间间隔Scroller就可以得出View当前的滑动位置,知道了滑动位置就可以通过scrollTo方法来完成View的滑动。就这样,View的每一次重绘都会导致View进行小幅度的滑动,而多次的小幅度滑动就组成了弹性滑动,这就是Scroller的工作机制。

通过动画

动画本身就是一种渐近的过程,因此通过它来实现的滑动天然就具有弹性效果。

使用延时策略

延时策略的核心思想是通过发送一系列延时消息从而达到一种渐近式的效果,具体来说可以使用Handler或View的postDelayed方法,也可以使用线程的sleep方法。

View的事件分发机制

点击事件的传递规则与方法介绍

  1. public boolean dispatchTouchEvent(MotionEvent ev)
    用来进行事件的分发。如果事件能够传递给当前的View,那么此方法一定会被调用,返回结果受当前View的onTouchEvent和下级View的dispatchTouchEvent方法的影响,表示是否消耗当前事件。
  2. public boolean onInterceptTouchEvent(MotionEvent ev)
    在dispatchTouchEvent方法内部调用,用来判断是否拦截某个事件,如果当前View拦截了某个事件,那么在同一个事件序列当中,此方法不会被再次调用,返回结果表示是否拦截当前事件。
  3. public boolea onTouchEvent(MotionEvent ev)
    在dispatchTouchEvent方法中调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一个事件序列中,当前View无法再次接收到事件。

三种基础方法讲完了,上一张图解,这张图出自图解 Android 事件分发机制 作者:Kelin
这里写图片描述

View的滑动冲突

滑动冲突的常见场景

  1. 场景1:外部滑动方向和内部滑动方向不一致;
  2. 场景2:外部滑动方向和内部滑动方向一致;
  3. 场景3:上面两种情况的嵌套。

滑动冲突的处理规则

  1. 对于场景1,它的处理规则是:根据滑动是水平滑动还是竖直滑动来判断到底由谁来拦截事件。
  2. 对于场景2,它无法根据滑动的角度、距离差以及速度差来做判断,但是这个时候一般都能在业务上找到突破点,比如业务上有规定:当处于某种状态时需要外部View响应用户的滑动,而处于另外一种状态是则需要内部View来响应View的滑动,根据这种业务上的需求我们也能得出相应的处理规则。
  3. 场景3跟场景2一样,只能从业务上找到突破点,具体方法和场景2一样,都是从业务的需求上得出相应的处理规则。

外部拦截法

所谓外部拦截法是指点击事件都先经过父容器的拦截处理,如果父容器需要此事件就拦截,如果不需要此事件就不拦截,这样就可以解决滑动冲突的问题,这种方法比较符合点击事件的分发机制。

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;  
}  

内部拦截法

内部拦截法是指父容器不拦截任何事件,所有的事件都传递给子元素,如果子元素需要此事件就直接消耗掉,否则就交由父容器进行处理,这种方法和Android中的事件分发机制不一致,需要配合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);  

父容器也需要重写

public boolean onInterceptTouchEvent(MotionEvent event) {
    int action = event.getAction();
    if (action == MotionEvent.ACTION_DOWN) {
        return false;
    } else {
        return true;
    }
}

猜你喜欢

转载自blog.csdn.net/a8341025123/article/details/79291467
今日推荐