《Android开发艺术探索》之View的事件体系和工作原理学习笔记

书中把自定义View分为下面4类:

1、继承View重写onDraw方法

这种方法主要用于实现一些不规则的效果,不方便通过布局的组合方式来达到,一般需要自己绘制图形并实现动画等效果。需要重写onDraw方法。采用该方法需要自己支持wrap_content和padding。

2、继承ViewGroup派生特殊的Layout

这种方法主要用于实现自定义的布局,除了系统的LinearLayout、RelativeLayout、FrameLayout这几种布局外,有时候我们需要重新定义新的布局,把多个View集合到一个ViewGroup里,这个ViewGroup就需要我们自定义。采用这种方式需要处理ViewGroup的测量(onMeasure)、布局(onLayout)这两个过程,并且还要处理子元素的测量和布局过程。

3、继承特定的View(例如TextView)

这种方式的自定义View比较常见,也常用,比如平时项目中自定义的popwindow、progressbar等自定义View,都是继承相应的控件来实现。这种方式不需要自己支持wrap_content和padding等。

4、继承特定的ViewGroup(比如LinearLayout)

这种方式的自定义View也比较常见,当某种效果看起来像几种View组合一起的时候,可以采用这种方式来实现。采用这种方式不需要自己处理ViewGroup的测量和布局这两个过程。比起方法二,此方法更简单,但方法二更灵活,能实现的View自由度高。


大家自定义View的时候可以根据自己需要定义的View需求来确定该使用上述方法的哪一种。


在自定义View前,前期我们需要做大量的自定义View的知识储备,不然实现自定义View效果会很艰难。关于自定义View的知识点,我们需要掌握如下知识点:

1、View的位置参数

2、MotionEvent和TouchSlope

3、Velocity、GestureDetector和Scoller

4、scrollTo和scrollBy

5、弹性滑动

6、View的事件分发机制

7、View的滑动冲突

8、理解MeasureSpec

9、View的自定义过程onMeasure、onLayout和onDraw

不看不知道,一看吓一跳,自定义View的实现需要我们掌握并熟练这么多知识点,这也是实现自定义View的必经之路,静下心来好好学习吧,把每个知识点理解透。接下来,针对上述知识点,我们一个一个看过来吧。

1、View的位置参数

对于View的位置参数,把这几个概念搞清楚就OK了,分别是left、top、right、bottom、x、y、translationX、translationY。left、top、right、bottom四个参数分别代表View的顶点坐标,left即View的左上角横坐标,top即左上角纵坐标,right即右下角横坐标,bottom即右下角纵坐标。注意,这些坐标都是相对于View的父容器来说的,是相对坐标。因此,我们可以根据这4个参数计算出View的宽度和高度:

width = right - left;

height = bottom - top;

在代码中,我们可以分别用getLeft()、getTop()、getRight()、getBottom()方法获取到4个参数的值。

《Android开发艺术探索》之View的事件体系和工作原理学习笔记_第1张图片

x和y两个参数分别代表View左上角的横坐标和纵坐标,translationX和translationY代表View左上角相对于父容器的偏移量。这几个参数的值可以用getX()、getY()、getTranslationX()和getTranslationY()方法获取到。这几个参数的之间的关系可以用下面的式子表示:

x = left + translationX;

y = right + translaitonY;

《Android开发艺术探索》之View的事件体系和工作原理学习笔记_第2张图片


2、MotionEvent和TouchSlope

MotionEvent,在手指接触屏幕后产生的一系列事件,比如

ACTION_DOWN 手指刚好接触屏幕

ACTION_MOVE 手指在屏幕上移动

ACTION_UP 手指从屏幕上松开的一瞬间

等等

正常情况下,一次手指触摸屏幕的行为会产生一系列点击事件,比如

点击屏幕后松开,事件序列为DOWN-->UP;

点击屏幕滑动一会儿后松开,事件序列为DOWN-->MOVE-->MOVE-->....>MOVE-->UP。

等等

在代码中,我们可以根据MotionEvent对象得到点击事件发生的x和y坐标。通过getX()、getY()和getRawX()、getRawY()。两者的区别在于,getX/getY返回的是相对于当前View的左上角的x和y坐标,getRawX/getRawY返回的是相对于手机屏幕左上角的x和y坐标。


TouchSlope是系统所能识别的被认为是滑动的最小距离。这个最小距离被系统定义为了一个常量,常量的值和设备有关,在不同设备上该值会有不同。在代码中,我们可以根据该值来判断当前触摸屏幕的事件是否构成了一次滑动屏幕的行为,如果一次滑动之间的距离小于这个常量值,则系统会判断这不是一次滑动操作。我们可以用下面代码获取到该常量值:

ViewConfiguration.get(getContext()).getScaledTouchSlop()。


3、Velocity、GestureDetector和Scoller

Veloctity,速度追踪,用于追踪手指在屏幕滑动的速度,包括水平方向和竖直方向的速度。在代码中获取手指滑动的水平方向速度和竖直方向速度,我们需要下面几个步骤:

在View的onTouchEvent方法中把event事件绑定到速度追踪对象上:

VelocityTracker vt = VelocityTracker.obtain();

vt.addMoveEvent(event);

获取速度值:

vt.computeCurrentVelocity(1000);

int xVelocity = (int) vt.getXVelocity();

int yVelocity = (int) vt.getYVelocity();

注意,在调用getXVelocity和getYVelocity方法前必须调用computeCurrentVelocity方法;这里获得的速度值是指一段时间内手指滑过的像素数。上面我们设置的时间是1000毫秒,如果在1秒内滑动100像素,那么水平速度就是100像素每秒。速度值可以为负。从右往左滑动,速度值为负,从左往右,速度值为正。当我们不需要计算速度时,要调用clear方法来重置并回收内存:

vt.clear();

vt.recycle();


GestureDetector,手势检测,用于辅助检测用户的单击、滑动、长按、双击等行为。在代码中注入手势检测,我们需要下面几个步骤:

创建一个GestureDetector对象并实现OnGestureListener接口,根据需要我们还可以实现OnDoubleTapListener接口从而能够监听双击行为:

GestureDetector gd = new GestureDetector(this);

//解决长按屏幕后无法拖动的现象

gd.setIsLongpresEnabled(false);

接管目标的onTouchEvent方法,在View的onTouchEvent方法中实现:

gd.onTouchEvent(event);

通过上面两个步骤,我们就把手势检测注入到View了。接下来我们可以有选择的实现OnGestureListener和OnDoubleTapListener接口中的方法了。

OnGestureListener方法介绍
onDown 手指触摸屏幕的瞬间
onShowPress 手指触摸屏幕,尚未松开或拖动
onSingleTapUp 手指触摸屏幕后松开,单击行为
onScroll 手指按下屏幕并拖动,拖动行为
onLongPress 长久按着屏幕不放,长按行为
onFling 按下触摸屏,快速滑动后松开,快速滑动行为

OnDoubleTapListener方法介绍
onDoubleTap 双击行为
onSingleTapConfirmed 严格的单击行为
onDoubleTapEvent 表示发生了双击行为

在实际开发中,可以不使用GestureDetector,也可以在View的onTouchEvent中实现所需要的监听。建议,如果只是监听滑动相关的,在onTouchEvent方法中实现即可,如果要监听双击行为,那么就使用GestureDetector。


Scroller,弹性滑动对象,用于实现View的弹性滑动。Scroller本身不能让View实现弹性滑动,需要View的computeScroll方法配合使用才能实现弹性滑动。在实际开发中,实现的代码基本固定,代码很典型,需要记下:

Scroller scroller = new Scroller(context);

//缓慢滚动到指定位置

private void smoothScrollTo(int destX, int destY){

int scrollX = getScrollX();

int delta = destX - scrollX;

//1000毫秒内慢慢滑向destX

scroller.startSCroll(scrollX, 0, delta, 0, 1000);

invalidate();

}

@Override

public void computeScroll(){

if(scroller.computeScrollOffset()){

scrollTo(scroller.getCurrX(), scroller.getCurrY());

postInvalidate();

}

}


4、scrollTo和scrollBy

从andriod源码可以看出,scrollBy实际上调用了scrollTo方法,实现了对View位置的相对滑动,而scrollTo则实现了View位置的绝对滑动。View滑动的时候有两个参数很重要,mScrollX和mScrollY,这两个属性可以通过getScrollX和getScrollY方法获取。mScrollX的值总是等于View左边缘和View内容左边缘在水平方向的距离,mScrollY的值总是等于View上边缘和View内容上边缘在竖直方向的距离。注意:scrollTo和scrollBy只能改变View内容的位置而不能改变View在布局中的位置。


5、弹性滑动

实现弹性滑动,可以用下面三种方法来实现:

1)使用Scroller 上面第3点已经介绍如何用Scroller实现弹性滑动了;

2)通过动画 关于如何使用动画让View动起来可以参考博客;

3)使用延时策略 思想是通过发送一系列延时消息从而达到一种渐进式效果。可以使用Handler或View的postDelayed方法来实现,也可以用线程的sleep方法来实现。


6、View事件的分发机制和View的滑动冲突

View事件的分发机制以及View的滑动冲突,是重点也是难点,介绍清楚需要大量的篇幅,这里先不做介绍,后面另起博客专门介绍View事件的分发机制和View的滑动冲突。


7、MeasureSpec

可以理解为测量标准。在测量过程中,系统会将View的LayoutParams根据父容器所施加的规则转换成想要的MeasureSpec,然后在根据这个measureSpec来测量View的宽和高。

MeasureSpec分SpecMode和SpecSize两类,前者是测量模式,后者是测量大小。SpecMode分为三类:

UNSPECIFIED 父容器不对View有任何限制;

EXACTLY 父容器已经检测出View所需要的精确大小,这个时候View的最终大小就是SpecSize所指定的值。它对应于LayoutParams中的match_parent和具体的数值这两种模式;

AT_MOST 父容器指定了一个可用大小即SpecSize,View的大小不能大于这个值。他它对应于LayoutParams中的wrap_content。

需要注意一点是:MeasureSpec不是唯一由LayoutParams决定的,LayoutParams需要和父容器一起才能决定View的MeasureSpec,从而进一步决定View的宽和高。


8、View的自定义过程

View的自定义过程分为measure、layout、draw三大流程,即测量、布局、绘制,其中measure决定View的测量宽高,layout决定View的最终宽高和四个顶点的位置,draw则将View绘制到屏幕上。


你可能感兴趣的:(Android)