android View的事件体系

View的事件体系

View的基础知识

Android中所有控件的基类。

View的位置主要由它的四个顶点来决定的,分别对应View的四个属性:left:左上角横坐标,top:左上角纵坐标,right:右下角横坐标,bottom:右下角纵坐标

获取这四个参数通过getLeft,getTop,getRight,getBottom得到,在3.0版本后,新增了几个参数:x,y:自身View的坐标;和translationX,translationY:相对于父容器的偏移量

MotionEvent和TouchSlop

通过MotionEvent对象,我们可以得到点击事件发生的x、y坐标。为此,系统提供了两个组方法:getX、getY(相对于当前View)和getRawX、getRawY(相对于手机屏幕)

TouchSlop是系统所能识别出的最小滑动距离,它是一个常量,和设备有关,不同设备的值不同。通过ViewConfiguration.get(getContext()).getScaledTouchSlop();获得。

作用:处理滑动时,可利用这个常量做一些过滤,比如滑动距离小于此值,我们就可以认为未达到滑动距离的临界值。

 

VelocityTracker、GestureDetector和Scroller

Ø  VelocityTracker:速度追踪

用于追踪手指在滑动过程中的速度。它的使用过程很简单,首先,在View的onTouchEvent方法中追踪当前单击事件的速度:

通过以下方法获得当前的滑动速度:

要注意的是,要获得速度之前必须要计算,这里的速度是指手指滑动的像素数,1000表示时间间隔为1秒,取值:左负右正。

                     速度 = (终点位置 - 起点位置) / 时间段

使用完毕后需调用clear和recycle将其重置并回收。

当然,在ViewDragHelper中重写onViewReleased 方法直接获得了速度:

public void onViewReleased(ViewreleasedChild, float xvel, float yvel);

*ViewDragHelper解决了android中手势处理过于复杂的问题

本质其实是分析onInterceptTouchEvent和onTouchEvent的MotionEvent参数,然后根据分析的结果去改变一个容器中被拖动子View的位置( 通过offsetTopAndBottom(int offset)和offsetLeftAndRight(int offset)方法 ),他能在触摸的时候判断当前拖动的是哪个子View。

Ø  GestureDetector:手势检测

用于辅助检测用户的单击、滑动、长按、双击等行为。

首先,创建GestureDetector对象并实现OnGestureListener接口(OnDoubleTapListener:监听双击)

接着,接管目标View的onTouchEvent方法,代码如下:

做完了上面两步,就可以有选择性的实现OnGestureListener和OnDoubleTapListener的方法了,两个接口的方法介绍:

方法名

描述

所属接口

onDown

手指触摸屏幕的一瞬间: ACTION_DOWN

OnGestureListener

onShowPress

手指轻轻触摸屏幕尚未松开或 拖动的状态

OnGestureListener

onSingleTapUp

手指松开,伴随着1个ACTION_UP而触发,这是单击行为

OnGestureListener

onScroll

手指按下屏幕并拖动,拖动行为

OnGestureListener

onLongPress

长久的按着屏幕不放,即长按

OnGestureListener

onFling

快速滑动后松开

OnGestureListener

onSingleTapConfirmed

严格的单击行为,注意与onSingleTapUp不同,如果触发了此方法,那么不可能在紧跟着另一个单击行为,即这只能是单击,不是双击中的一次单击

OnDoubleTapListener

onDoubleTap

双击,由两次连续的单击组成不可能和onSingleTapConfirmed方法共存

OnDoubleTapListener

onDoubleTapEvent

表示发生了双击行为,在双击的期间,ACTION_DOWN/MOVE/UP都会触发此回调

OnDoubleTapListener

 

建议的GestureDetector使用场景:监听双击行为时可使用

Ø  Scroller:弹性滑动

用于实现View的弹性滑动,我们都知道,scrollTo和scrollBy方法滑动时,其过程是瞬间完成的,体验不是很好,这个就可以使用Scroller可以实现过渡效果的滑动(在一定时间间隔内完成的)它本身无法让View弹性滑动,需要和View的computeScrllo方法配合才能完成,如下:

    android View的事件体系_第1张图片  

View的滑动

三种方式实现滑动:

Ø  第一种:通过View本身提供的scrollTo和scrollBy方法实现滑动

²  scrollBy实际也是调用了scrollTo方法,它实现了基于当前位置的相对滑动,而scrollTo则是基于所传递参数的绝对滑动。

Ø  第二种:通过动画给View施加平移效果来实现。

²  主要操作View的translationX和translationY属性,既可以采用传统的View动画,也可以采用属性动画(属性动画为了兼容3.0以下,使用开源动画库:nineoldandroids)需要注意一点:View动画是对View影像做操作,并不是真正的改变View位置参数,如果希望动画后的状态得以保留,我们还必须将fillAfter设置为true。

Ø  第三种:通过改变View的LayoutParams使得View重新布局从而实现

²  如果想将一个Button向右平移100px,只需通过Button.getLayoutParams()得到对象,并且设置其leftMargin属性增加100px即可,最后别忘记button.requestLayout();或者直接setLayoutParams(params对象);

这是一种很灵活的做法。

       针对上面的分析,得出结论:

l  scrollTo和scrollBy:操作简单,适合对View内容的滑动

l  动画:操作简单,主要适用于没有交互的View和实现复杂的动画效果

l  改变布局参数:操作稍微复杂,适用于有交互的View

View的事件分发机制

所谓点击事件的分发,其实就是对MotionEvent事件的分发过程,即当一个MotionEvent产生了以后,系统将事件传递给一个具体的View,这个过程就是分发。点击事件的分发过程由三个重要的方法共同完成:

l  dispatchTouchEvent

²  用来进行事件分发,如果事件能够传递给当前View,那此方法一定被调用,返回结果受当前View的onTouchEvent和下级View的dispatchTouchEvent影响,表示是否消耗当前事件。

l  onInterceptTouchEvent

²  在dispatchTouchEvent方法内部调用,用来判断是否拦截某个事件。如果拦截,调用onTouchEvent方法

l  onTouchEvent

²  在dispatchTouchEvent方法中调用,用来处理点击事件。

点击事件传递规则:对于一个根ViewGroup来说,点击事件产生后,首先会传递给它,这时它的dispatchTouchEvent就会被调用,如果这个ViewGroup的onInterceptTouchEvent返回的是true表示要拦截,接着就自然调它的onTouchEvent;

如果返回false,就表示不拦截并传递给它的子元素并调用子元素的dispatchTouchEvent,如此反复直到事件被最终处理。

这其中有一点要注意: 如果View设置了事件监听:OnTouchListener,那么其中的onTouch方法优先级比上述的第三个重要方法:onTouchEvent要高!就是说:onTouch返回true,那onTouchEvent方法将不会被调用了!

而我们平时经常设置的OnClickListener是在onTouchEvent中的,由此可见,我们平时用的优先级是最低的!即处于事件传递的尾端。

如果一个View不消耗这个事件,那么就不会再传递给它了,并且没有其他子View消耗,那这个事件最终会消失。

ViewGroup默认不拦截任何事件;View没有onInterceptTouchEvent方法,View的onTouchEvent事件默认返回true,消耗。

事件传递过程是由外向内的,即事件总是先传递给父元素。不过可以通

getParent().requestDisallowInterceptTouchEvent(true);////不让父拦截

在子元素中干预父元素的事件分发过程,但是ACTIVITY_DOWN事件除外。

View的滑动冲突:只要内外层同时可以滑动,就会产生冲突。例如ViewPager中嵌套横向ListView?

 

View的工作原理

ViewRoot和DecorView概念

介绍View的三大流程之前必须要了解一些基本概念。ViewRoot和DecorView

ViewRoot对应于ViewRootImpl类,是连接WindowManager和DecorView的纽带,View的三大流程均是由ViewRoot来完成的。当Activity被创建后,会将DecorView添加到Window中,同时创建ViewRootImpl对象,并将这个实现类与DecorView关联起来。

View的绘制流程是从ViewRoot的performTraversals方法开始的,他经过measure、layout和draw三个过程才能最终将一个View绘制出来。

 android View的事件体系_第2张图片                                                                  

关于DecorView和“测量方式”的介绍请看上次总结的文档:

View的工作流程

主要是指measure(测量)、layout(布局)、draw(绘制)这三大流程

Ø  Measure过程要分情况来看,如果是一个原始的View,通过measure方法就完成了测量过程;若是ViewGroup,除了完成自己的测量外,还要遍历去调用子元素的measure方法

²  View的measure方法是final的,不能被重写,在方法中会调用onMeasure方法,它里面有两个重要的方法:setMeasureDimension()和getDeaultSize(),

直接继承View的自定义控件需要重写onMeasure并设置wrap_content时的自身大小,否则在布局中使用wrap_content就相当于使用match_parent。为什么呢?通过查看:getDeaultSize的源码就明白了(自己看吧)。

²  ViewGroup和View不同的是:他是抽象类,没有重写onMeasure方法,但是他提供了一个measureChhildren方法会在遍历子View的时候调用,并调用其中measureChild

²  这里有个问题!:

我们想在Activity启动的时候获取某个View的宽高,不要觉得直接getXXX就可以的!事实上在OnCreate、Start、Resume中均无法正确的得到。因为View的measure过程和Activity的生命周期不是同步执行的,因此无法保证get之前测量过程完毕,那怎么办呢?!我们可以通过下面四种方式:

l  Activity中重写onWindowFocusChanged()

含义为View初始化完毕了,需要注意的是,Activity得到焦点和失去焦点均会调用此方法,所以可能会被拼频繁调用。代码如下:

l  view . post(runnable)

通过post可以将一个runnable投地到消息队列的尾部,然后等待Looper调用的时候,View也已经初始化好了:

l  ViewTreeObserver

使用ViewTreeObserver的众多回调也可完成这个功能,比如使用OnGlobalLayoutListener这个接口,当View树发生改变时,onGlobalLayout方法将会被调用,伴随着View树的改变,此方法会被调用多次:

l  view . measure(int w,int h)

通过手动对view进行测量来得到宽高,这种方式比较复杂,需要根据View的LayoutParams来分情况处理。

match_parent:无法测量

具体数值(dp/px):

wrap_content:理论上支持的最大值

Ø  Layout:作用是ViewGroup用来确定子元素的位置,ViewGroup位置被确定后会在onLayout中遍历调用子元素的layout方法,比measure简单的多,只要记住layout确定view本身位置,而onLayout确定子View位置。

Layout中通过setFrame方法设置四个顶点位置

这里也有个问题!?:View的测量宽高和最终宽高有何区别?

也就是getMeasureWidth和getWidth的区别?

其实,测量宽高形成于measure过程,最终宽高形成于layout过程。

 

Ø  Draw:这个过程就比较简单了,作用是将View绘制到屏幕上,绘制过程遵循如下几步:

²  绘制背景background . draw(canvas)

²  绘制自己(onDraw)

²  绘制children(dispatchDraw)

²  绘制装饰(onDrawScrollBars)

View的绘制过程的传递是通过dispatchDraw实现的!它会遍历子view的draw方法。

自定义View

关于自定义View可大致分为四类:

Ø  继承View重写onDraw方法

Ø  继承ViewGroup派生特殊的layout

Ø  继承特定的View(比如TextView)

Ø  继承特定的ViewGroup(比如LinearLayout)

自定义view须知

1.     让View支持wrap_content

2.     如果有必要,让你的View支持padding

3.     尽量不要在View中使用Handler,没必要

4.     View中如果有线程或者动画,需要及时停止,参考View#onDetachedFromWindow

5.     View带有滑动嵌套情形时,妥善处理滑动冲突


你可能感兴趣的:(android View的事件体系)