View事件体系

View参数概念

1.位置参数

由顶点决定,对应于View的四个属性: top(左上角纵坐标)left(左上角横坐标)bottom(右下角纵坐标)right(右下角横坐标),相对于View的父容器,view的宽高width = right – leftheight= bottom – top

获取:getLeft(),getRight(), getTop(), getBottom();

Android3.0开始新增x, y, translationX, translationY

x,y表示view相对于父容器的左上角坐标,translationX, translationY表示view相对于父容器的偏移量,默认值为0

topleft表示原始的位置信息,在平移过程中,translationXtranslationY发生改变,进而x, y改变。

2.MotionEvent(位移事件)

ACTION_DOWN:手指刚接触屏幕

ACTION_MOVE:手指在屏幕上移动

ACTION_UP:手指离开屏幕

getX(), getY()点击事件相对于当前View的左上角的xy坐标getRawX(), getRawY()点击事件相对于屏幕左上角的xy坐标

3.TouchSlop系统能识别的最小滑动距离

通过ViewConfiguration.get(getContext()).getScaledTouchSlop获取

4.VelocityTracker追踪手指滑动过程中的速度

onTouchEvent中追踪点击事件的速度

VelocityTrackervelocityTracker = VelocityTracker.obtain();

velocityTracker.addMovement(event);

velocityTracker.computeCurrentVelocity(1000), 1000ms内划过的像素

velocityTracker.getXVelocity();水平速度

velocityTracker.getXVelocity();竖直速度

velocityTracker.clear();

velocityTracker.recycle();

5.GestureDetector手势检测检测单击、双击、长按、滑动

onSingleTapUp(单击)onFailing(快速滑动)onScroll(拖动)onLongPress(长按)onDoubleTap(双击)

6.Scroller弹性滑动对象有过渡效果的滑动

View的滑动

1.scrollTo& scrollBy

scrollTo()方法
scrollBy()方法

关键属性mScrollX,mScrollY分别通过getScrollX()getScrollY()得到

mScrollXView左边缘与View内容左边缘在水平方向的距离

mScrollYView上边缘与View内容上边缘在竖直方向的距离

View左移时,mScrollX为正,反之为负

View上移时,mScrollY为正,反之为负

scrollToscrollBy只能改变View内容的位置,不能改变View在布局中的位置。

2.使用动画View动画和属性动画

View动画向右下角平移100

View动画

View动画是对View的影像做操作,并没有真正改变View的位置参数,动画完成后结果会消失,设置fillAfter属性为true会保留动画后的状态,移动后的View无法触发onClick事件

属性动画View从原位置向右平移100像素

属性动画

属性动画后动画结果不会消失,并且在平移动画之后,能够触发onClick事件

3.改变布局参数

改变LayoutParams,通过设置margin参数即可达到移动的效果。

改变LayoutParams

scrollTo/scrollBy:适合对View内容的滑动

动画:适合没有交互的View和复杂的动画效果

改变布局参数:适合有交互的View

弹性滑动

1.探究Scoller

当构造一个Scroller对象并调用它的startScroll方法时,Scroller内部其实什么也没做,只是传递了几个参数并保存了,而invalidate方法会导致View重绘,draw方法又会调用computeScroll方法,而在computeScroll中通过scrollTo方法让View滑动,接着又调用postInvalidate方法再次重绘,直到滑动过程结束。

而在computeScroll方法中,通过computeScrollOffset判断当前是否重绘结束,通过时间的流逝来计算当前View的位置坐标。如果经过的时间小于总时间,继续重绘,直到时间达到动画的总时间,重绘结束,同时滑动完成。

View的重绘距滑动起始会有一个时间间隔,通过这个时间间隔就能得出当前View的滑动位置,然后通过scrollTo方法完成View的滑动,这样每次重绘都会让View小幅度滑动,多次就成了弹性滑动。

2.使用动画

通过动画的每一帧的到来获取动画完成的比例,然后根据这个比例计算View应该滑动的距离。

动画滑动

3.延时策略

通过HandlerpostDelayed,或者在线程中通过whilesleep来不断的发送消息,在消息中进行View的滑动。

View的事件分发机制

1.传递规则

分发过程即将MotionEvent传递到一个具体的View,由三个重要的

方法共同完成,dispatchTouchEventonInterceptTouchEventonTouchEvent

public booleandispatchTouchEvent(MotionEvent ev)

事件传递给当前View一定会执行这个方法,返回结果与ViewonTouchEvent和下级ViewdispatchTouchEvent有关,表示是否消耗当前的事件。

public booleanonInterceptTouchEvent(MotionEvent ev)

判断是否拦截某个事件,在dispatchTouchEvent方法中调用。如果View拦截了某个事件,那么在同一事件序列当中,该方法不会再被调用

public boolean onTouchEvent(MotionEventev)

处理点击事件,判断是否消耗了事件,如果不消耗,那在同一事件序列中,当前View不会再接收到事件。

这三个方法的关系可以用以下伪代码表示

事件分发的关系

事件的传递规则:对于根ViewGroup来说,点击事件首先会传给它,此时调用它的dispatchTouchEvent方法,然后执行onInterceptTouchEvent方法,如果该方法返回true,表示它要拦截该事件,然后事件就会交给ViewGroup处理,然后它的onTouchEvent方法就会被调用,如果返回false表示它不拦截当前事件,这时事件就会传递给它的子元素,接着调用子元素的dispatchTouchEvent方法,如此反复直到事件被处理。

监听事件优先级OnTouchListener > OnTouchEvent > OnclickListener

当产生点击事件后,总是先传递给Activity,再传递给Window,最后传递给顶级View,然后按照事件的传递规则去分发事件。如果所有View都不处理这个事件,那么这个事件最终会传递给Activity处理,ActivityonTouchEvent方法会被调用。

总结

a.同一个时间序列是指从手指接触屏幕开始,到手指离开屏幕,中间有一系列的move事件

b.一般一个事件序列只能被一个View拦截消耗,因为一旦View截了某事件,那么同一事件序列内的所有事件都会直接交给它处理,但是View可以将本应自己处理的事件通过onTouchEvent强行传递给其它View处理。

c.某个View一旦拦截那么该事件序列都只能由它来处理,并且它onInterceptTouchEvent不会被调用。即不会再被拦截。

d.某个View一旦开始处理事件,如果不消耗ACTION_DOWN件,即onTouchEvent返回了false,那么同一事件序列中的其它事件不会再交给它处理,而是由它的父元素处理,即父元素的onTouchEvent会被调用。

e.某个View不消耗除ACTION_DOWN以外的其它事件,那么这个点击事件会消失,但父元素的onTouchEvent不会被调用,并且当前View会持续收到后续事件,最终传递给Activity处理。

f.ViewGroup默认不拦截任何事件,即onInterceptTouchEvent默认返回false

g.View没有onInterceptTouchEvent方法,一旦事件传递给它,那么它的onTouchEvent方法就会被调用。

h.ViewonTouchEvent方法默认都是消耗事件,除非它是不可点击的(clickablelongClickable都为falseViewlongClickable默认为false)。

i.事件的传递过程是由外向内的,总是由父元素开始,再传递到子View,子View可以通过requestDisallowInterceptTouchEvent干预事件的分发,但是ACTION_DOWN除外。

2.源码解析

1.Activity的分发过程

当点击操作发生时,事件最先传递给当前的Activity,由ActivitydispatchTouchEvent进行事件派发,具体是由Activity内的Window来完成的,Window会将事件传递给decor view(setContentView设置的View的父容器)


Activity#dispatchTouchEvent

2.Window是如何将事件传递给ViewGroup

Window是个抽象类,WindowsuperDispatchTouchEvent方法也是个抽象方法,PhoneWindowWindow的唯一实现类。

PhoneWindow#dispatchTouchEvent

PhoneWindow直接将事件传给了DecorViewDecorViewsetContentView所设置View的父容器,所以事件肯定会传递给setContentViewView,也叫顶级View

3.顶级View对事件的分发过程

对于根ViewGroup来说,点击事件首先会传给它,此时调用它的dispatchTouchEvent方法,然后执行onInterceptTouchEvent方法,如果该方法返回true,表示它要拦截该事件,然后事件就会交给ViewGroup处理,然后它的onTouchEvent方法就会被调用,如果返回false表示它不拦截当前事件,这时事件就会传递给它的子元素,接着调用子元素的dispatchTouchEvent方法,如此反复直到事件被处理。

这个方法较长,我们分段来看

ViewGroup#dispatchTouchEvent

ViewGroup的子元素成功处理时,mFirstTouchTarget会被赋值,并指向子元素,即ViewGroup不拦截事件并交给子元素处理时mFirstTouchTarget != null

ViewGroup拦截时,mFirstTouchTarget就为空,当后续事件到来时actionMasked != MotionEvent.ACTION_DOWN并且mFirstTouchTarget== null,所以该条件为false,所以ViewGrouponInterceptTouchEvent不会再被调用,并且同一事件序列中的其它事件都将交给它来处理。

特殊情况:标记位FLAG_DISALLOW_INTERCEPT,子View可以通过设置requestDisallowInterceptTouchEvent方法,使得ViewGroup无法拦截除了ACTION_DOWN以外的其它事件。如果是ACTION_DOWN事件,这个标记位就会被重置。

重置标记位

上面代码说明了ViewGroup会在ACTION_DOWN事件到来之前重置标记位的状态。当ViewGroup不再拦截事件的时候,事件就会下发至它的子View处理。源码如下所示

遍历ViewGroup所有子元素,判断子元素是否能够接收点击事件并且点击事件是否落在子元素的区域内。如果都满足,那么事件就交给它来处理,在dispatchTransformedTouchEvent方法中有这样一段代码。

dispatchTransformedTouchEvent

如果子元素不为空,直接调用它的dispatchTouchEvent方法,如果返回true,那么mFirstTouchEvent会被赋值并且跳出循环。如果返回falseViewGroup会把事件分发给下一个元素。

如果遍历完所有的元素后时间都没有被处理,那么有两种情况,一是ViewGroup没有子元素,二是子元素处理了点击事件,但是dispatchTouchEvent返回了false,一般是因为在TouchEvent中返回了false,这两种情况下ViewGroup会自己处理点击事件。

这里dispatchTransformedTouchEvent方法中第三个参数为null,由之前的代码分析,它会调用super.dispatchTouchEvent方法,即交给View处理,接着看View的处理。

4.View对点击事件的处理过程。

View#dispatchTouchEvent
View#dispatchTouchEvent

首先判断有没有OnTouchListener,如果OnTouchListener中的onTouch方法返回true,那么onTouchEvent就不会被调用,所以OnTouchListener优先级高于onTouchEvent

接下来分析onTouchEvent,这里面主要分三个过程: View处于不可用状态,View设置有TouchDelegateView处于可用状态。

a.View不可用

View在不可用状态下依然会消耗点击事件

b.View设置了TouchDelegate

TouchDelegate可以让某个控件处理比它实际占用空间更大的触

摸消息。具体可以参考官方文档:TouchDelegate,具体使用:TouchDelegate的使用

c.View可用

整个过程分为ACTION_UPACTION_DOWNACTION_CANCELACTION_MOVE四个状态,我们就最有代表性的ACTION_UP来分析

ACTION_UP

只要ViewCLICKABLELONG_CLICKABLEtrue,就会消耗这个事件,并返回true,然后触发performClick方法,如果View设置了onClick事件,那么onClick就会被调用。

performClick

可点击的ViewCLICKABLEtrue,不可点击的为false

ViewLONG_CLICKABLE默认为false

setClickablesetLongClickable可以分别改变其属性。

setOnClickListenersetOnLongClickListener可以分别将CLICKABLELONG_CLICKABLE改为true

到这里View的事件体系基本就分析完了,以上所有内容参考自Android开发艺术探索。

2017.2.16  21:39

------END

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,311评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,339评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,671评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,252评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,253评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,031评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,340评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,973评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,466评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,937评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,039评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,701评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,254评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,259评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,497评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,786评论 2 345

推荐阅读更多精彩内容