Android开发艺术探索 第3章 View事件体系 读书笔记


3.1 View基础知识

  1. View是Android所有控件的基类;View是一种界面层的控件的一种抽象;ViewGroup是空间组,继承自View。

  2. View的位置主要由它的四个定点来决定,分别对应View的四个属性:top、left、right、bottom,这下坐标都是相对父容器而言的。从3.0开始View增加了x、y、translationX、translationY;x和y是View左上角的坐标,translationX和translationY是View左上方相对父容器的偏移量。
    x = left + translationX; y = top + translationY;
    View平移的过程中,top和left表示的是原始左上角的位置信息;其值并不会改变,此时发生改变的是x、y、translationX和translationY。

  3. MotionEvent是指用户手指触摸屏幕产生的一系列事件。
    4 点击屏幕后松开,事件序列 DOWN->UP点击屏幕滑动一会再松开,事件序列为 DOWN->MOVE->...->MOVE->UP。

  4. getX/getY获取相对当前View左上角的x和y坐标;getRawX/getRawY获取相对手机屏幕左上角的x和y坐标。

  5. TouchSlop是系统能识别滑动的最小距离,是系统常量,当手指在屏幕上滑动,小于这个距离,系统不认为你在进行滑动操作;可通过ViewConfiguration.get(getContext()).getScaledTouchSlop()方法来获取;

  6. VelocityTracker用于追踪手指在滑动过程中的速度。使用前在View的onTouchEvent方法中追踪当前单击事件的速度。

VelocityTracker velocityTracker = VelocityTracker.obtain(); 
velocityTracker.addMovement(event); 
//想知道滑动速度时
//获取速度前需计算速度  参数 时间间隔 单位ms
velocityTracker.computeCurrentVelocity(1000);  
//获取速度
int xVelocity = (int)velocityTracker.getXVelocity(); 
int yVelocity = (int)velocityTracker.getYVelocity();

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

  1. GestureDetector用于辅助检测用户的单击、滑动、长按、双击等行为;建议:如果只是监听滑动相关的推荐在onTouchEvent中实现,如果需要监听双击,使用GeststureDetector。
  2. Scroller用来实现View的弹性滑动,View的scrollTo/scrollBy是瞬间完成的,使用Scroller配合View的computeScroll方法配合使用达到弹性滑动的效果

3.2. View的滑动

  1. scrollTo和scrollBy只能改变View内容而不能改变View本身的位置。scrollBy内部也是调用了scrollTo,它是基于当前位置的相对滑动,scrollTo是基于所传递参数的绝对滑动。在滑动过程中mScrollX/mScrollY总是等于View边缘与View内容边缘的距离,这两个属性用getScrollX/getScrollY方法获取。
  2. 使用动画来移动View,主要是操作View的translationX和translationY属性。需要注意的是,View动画只是对View的影像做操作,它并不能真正改变View的位置参数,如果这个View设置了点击事件,点击动画后的新位置无法触发点击事件的,使用属性动画没有此问题,但3.0之前系统无属性动画。
  3. 改变布局参数实现滑动,即改变LayoutParams,如想将一个View右平移100px,只需要将该View的LayoutParams里的marginLeft增加100px即可
 ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) mBtn.getLayoutParams();
params.leftMargin += 100;
mBtn.requestLayout();
//或者mBtn.setLayoutParams(params);

总结:

  1. scrollTo/scrollBy: 操作简单,适合对View内容的滑动
  2. 动画: 操作简单,适合没有交互的View和实现负责的动画效果
  3. 改变布局参数:操作稍微复杂,适合有交互的View

3. 弹性滑动

  1. Scroller不能直接完成View滑动,需要配合View的computeScroll方法才可以完成弹性滑动,它让View不断重绘,每一次重绘有一个时间间隔,通过这个时间间隔Scroller就可以得出View当前滑动的位置,知道了滑动位置就通过scrollTo来完成滑动。每一次滑动都会导致View小幅度滑动,多次小幅度滑动组成了弹性滑动,这就是Scroller的工作机制。
    startScroll()保存了我们传递的几个参数 ——> invalidate()会导致View重绘 ——> draw() ——> computeScroll()该方法为空实现,我们内部调用scrollTo(x,y)实现滑动 和 postInvalidate() 继续重绘,反复下去完成弹性滑动。
  2. 通过动画可以直接实现弹性滑动
  3. 使用延时策略完成滑动,核心思想 就是通过发送一系列延时消息从而达到一种渐进式的效果。用Handler或View的postDelayed方法,postDelayed发送延时消息,然后消息中进行View滑动,接连不断的发送这种延时消息,达到弹性滑动的效果。也可以使用线程的sleep方法来实现。

4. View的事件分发

1. 点击事件的传递规则

  • dispatchTouchEvent(MotionEvent event) 用来处理事件的分发,返回结果受当前View的onTouchEvent和下级View的dispatchTouchEvent方法影响,表示是否消耗该事件。
  • onInterceptTouchEvent(MotionEvent event)
    在dispatchTouchEvent方法内部调用,用来判断是否拦截某个事件,如果当前View拦截了某个事件,那在同一个事件序列中,此方法不会再次调用,返回结果表示是否拦截当前事件
  • onTouchEvent(MotionEvent event)
    在dispatchTouchEvent方法中调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,在同一事件序列里,当前View无法再次接收到事件
  • 三者关系可以用如下伪代码表示
public boolean dispatchTouchEvent(MotionEvent ev){    
    boolean consume = false;
    if(onInterceptTouchEvent(ev)){
        consume = onTouchEvent(ev);
    }else{
        consume = child.dispatchTouchEvent(ev);   
    }
    return consume;
}
  1. 对于一个根ViewGroup,点击事件产生后,首先会传递给它,这时他的dispatchTouchEvent会调用,
    如果它的onInterceptTouchEvent返回true表示要拦截当前事件,接下来事件会交给这个ViewGroup处理,它的onTouchEvent就会被调用,如果这个ViewGroup的onInterceptTouchEvent返回false,则事件会继续传递给子元素,子元素的dispatchTouchEvent会调用,如此反复直到事件被处理。流程图如下


    根ViewGroup事件传递.png
  2. 当一个View需要处理事件时,如果设置了OnTouchListener,那么OnTouchListener的onTouch方法会回调,如果onTouch返回false,则当前View的onTouchEvent方法会被调用;如果返回true,那么onTouchEvent方法将不会调用。由此可见,OnTouchListener优先级高于onTouchEvent。OnClickListener优先级处在事件传递的尾端。
  3. 一个点击事件产生后,传递顺序:Activity->Window->View;如果一个View的onTouchEvent返回false,那么它的父容器的onTouchEvent会被调用,以此类推,所有元素都不处理该事件,最终将传递给Activity处理,即Activity的onTouchEvent会被调用。流程图如下


    点击事件传递顺序.png
  4. 同一个事件序列是指从手指触摸屏幕那一刻开始,到手指离开屏幕那一刻(down->move...move->up)
  5. 一个事件序列只能被一个View拦截且消耗,同一个事件序列所有事件都会直接交给它处理,并且它的onInterceptTouchEvent不会再被调用。
  6. 某个View一旦开始处理事件,如果它不消耗ACTION_DOWN(onTouchEvent返回了false),那么同一事件序列中其他事件都不会再交给它来处理,事件将重新交给他的父元素处理,即父元素的onTouchEvent会被调用。
  7. 如果某个View不消耗除ACTION_DOWN以外的其他事件,那么这个点击事件会消失,此时父元素的onTouchEvent并不会被调用,并且当前View可以收到后续事件,最终这些消失的点击事件会传递给Activity处理
  8. ViewGroup默认不拦截任何事件,ViewGroup的onInterceptTouchEvent方法默认返回false。
  9. View没有onInterceptTouchEvent方法,一旦有事件传递给它,那么它的onTouchEvent方法就会被调用。
  10. View的onTouchEvent方法默认消耗事件(返回true),除非他是不可点击的(clickable和longClickable同时为false)。View的longClickable属性默认都为false,clickable属性分情况,Button默认为true,TextView默认为false。
  11. onClick发生的前提是View可点击,并且它收到了down和up事件。
  12. 事件传递过程是由内而外,事件总是先传递给父元素,然后在由父元素分发给子View,通过requestDisallowInterceptTouchEvent方法可以在子元素干预父元素的事件分发过程,但ACTION_DOWN事件除外。

5. View的滑动冲突

1.常见滑动冲突场景

  • 场景1 —— 外部滑动方向与内部滑动方向不一致,比如ViewPager中包含ListView;
  • 场景2 —— 外部滑动方向与内部滑动方向一致,比如ScrollView中包含ListView;
  • 场景3 —— 上面两种情况的嵌套


    滑动冲突的场景(原书中).jpeg

2.滑动冲突处理规则
通过判断是水平滑动还是竖直滑动来判断到底应该谁来拦截事件;可以根据水平和竖直两个方向的距离差或速度差来做判断

3.滑动冲突解决方式

  • 外部拦截法 —— 即点击事件先经过父容器的拦截处理,如果父容器需要此事件就拦截,不需要就不拦截,需要重写父容器的onInterceptTouchEvent方法;在onInterceptTouchEvent方法中,首先ACTION_DOWN这个事件,父容器必须返回false,即不拦截ACTION_DOWN事件,因为一旦父容器拦截了ACTION_DOWN,那么后续的ACTION_MOVE/ACTION_UP都会直接交给父容器处理;其次是ACTION_MOVE,根据需求来决定是否要拦截;最后ACTION_UP事件,这里必须要返回false,在这里没有多大意义。
  • 内部拦截法 —— 所有事件都传递给子元素,如果子元素需要就消耗掉,不需要就交给父元素处理,需要子元素配合requestDisallowInterceptTouchEvent方法才能正常工作;父元素需要默认拦截除ACTION_DOWN以外的事件,这样子元素调用parent.requestDisallowInterceptTouchEvent(false)方法时,父元素才能继续拦截需要的事件。(ACTION_DOWN事件不受requestDisallowInterceptTouchEvent方法影响,所以一旦父元素拦截ACTION_DOWN事件,那么所有元素都无法传递到子元素去)。

欢迎各位小伙伴一起交流哟。

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

推荐阅读更多精彩内容