Android面试题(三)——View的事件体系和工作原理

Android面试题(三)——View的事件体系和工作原理

2023年7月15日发(作者:)

Android⾯试题(三)——View的事件体系和⼯作原理引⾔View在Android的地位堪⽐四⼤组件,Android为我们提供了很多的系统控件。但是为了区别⼀般性,我们往往需要⾃定义View,这就要求我们对View的事件体系和⼯作原理有深⼊的理解,只有这样才能做出完美的⾃定义控件⾯试题1.

View中onTouch,onTouchEvent和onClick的执⾏顺序onTouch->onTouchEvent->onClick1. 当⼀个View需要处理事件时,如果它设置了OnTouchListener,那么OnTouchListener的onTouch⽅法会被回调。2. 这时事件如何处理还得看onTouch的返回值,如果返回false,则当前View的onTouchEvent⽅法会被调⽤;如果返回true,那么onTouchEvent⽅法将不会被调⽤。由此可见,给View设置的onTouchListener,其优先级⽐onTouchEvent要⾼。3. 如果当前⽅法中设置了onClickListener,那么它的onClick⽅法会被调⽤。可以看出,常⽤的OnClickListener,其优先级别最低。2.

View的滑动⽅式1. 三种⽅式:

a. 通过View本⾝提供的scrollTo/scrollBy⽅法

移动的是View的内容,View本⾝不移动

b. 通过动画给View施加平移效果实现滑动

移动的View的影像,View本⾝位置不发⽣改变。Android3.0以下,移动后的View点击事件发⽣的位置不会改变

c. 通过改变View的LayoutParams使View重新布局实现滑动

改变布局参数,代码如下:MarginLayoutParams params = (MarginLayoutParams) outParams(); += 10; += 10;tLayout();//获知 outParams(params);- **三种⽅法的使⽤对⽐** - scrollTo/scrollBy:操作简单,适合对View内容的滑动; - 动画:操作简单,主要适合于没有交互的View和实现复杂的动画效果; - 改变布局参数:操作稍微复杂,适⽤于有交互的View。1. ### View的事件分发机制

事件的分发机制由三个重要⽅法来共同完成:dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent

1. 事件分发:public boolean dispatchTouchEvent(MotionEvent ev)

⽤来进⾏事件的分发。如果事件能够传递给当前View,那么此⽅法⼀定会被调⽤,返回结果受当前View的onTouchEvent和下级View的DispatchTouchEvent⽅法的影响,表⽰是否消耗当前事件。2. 事件拦截:public boolean onInterceptTouchEvent(MotionEvent event)

在上述⽅法内部调⽤,⽤来判断是否拦截某个事件,如果当前View拦截了某个事件,那么在同⼀个事件序列当中,此⽅法不会被再次调⽤,返回结果表⽰是否拦截当前事件。3. 事件响应:public boolean onTouchEvent(MotionEvent event)

在dispatchTouchEvent⽅法中调⽤,⽤来处理点击事件,返回结果表⽰是否消耗当前事件,如果不消耗,则在同⼀个事件序列中,当前View⽆法再次接收到事件。4. 三者的关系可以总结为如下伪代码: public boolean dispatchTouchEvent(MotionEvent ev) { boolean consume = false; if (onInterceptTouchEvent(ev)) { consume = onTouchEvent(ev); } else { consume = chTouchEvent(ev); } return consume; }- 事件传递机制的11个结论: 1. 同⼀个事件序列是从⼿指触摸屏幕的那⼀刻起,到⼿指离开屏幕那⼀刻结束,这个过程中所产⽣的⼀系列事件。这个事件序列以down事件开始,中间含有数量不定的move事件,最终以up事件结束。 2. ⼀个事件序列只能被⼀个View拦截且消耗,不过通过事件代理TouchDelegate,可以将onTouchEvent强⾏传递给其他View处理。 3. 某个View⼀旦决定拦截,那么这⼀事件序列就都只能由它来处理 4. 某个View⼀旦开始处理事件,如果不消耗ACTION_DOWN事件(onTouchEvent返回了false),那么事件会重新交给它的⽗元素处理,即⽗元素的onTouchEvent会被调⽤。 5. 如果View不消耗除ACTION_DOWN以外的事件,那么这个点击事件会消失,此时⽗元素的onTouchEvent并不会调⽤,并且当前View可以持续收到后续的事件,最终这些消失的事件会传递到Activity。 6. ViewGroup默认不拦截任何事件。Android源码中ViewGroup的onInterceptTouchEvent⽅法默认返回false。 7. View没有onIntercepteTouchEvent⽅法,⼀旦有点击事件传递给它,那么它的onTouchEvent⽅法就会被调⽤。 8. View的onTouchEvent默认都不会消耗事件(返回false),除⾮它是可点击的(clickable和longClickable有⼀个为true)。View的longClickable默认都为false,clickable要分情况看,⽐如Button默认为true,TextView默认为false。 9. View的enable属性不影响onTouchEvent的默认返回值。哪怕⼀个View是disable状态,只要它的clickable或者longClickable有⼀个为true,那么它的onTouchEvent就返回true。 10. onClick会发⽣的前提是当前View是可点击的,并且它受到down和up的事件。 11. 事件传递是由外向内的,即事件总是先传递给⽗元素,然后再由⽗元素分发给⼦View,通过requestDisallowInterceptTouchEvent⽅法就可以在⼦元素中⼲扰⽗元素的事件分发过程,但ACTION_DOWN事件除外。1.

View的绘制流程1. 三个过程1. measure:测量View的宽和⾼2. layout:确定View在⽗控件中的放置位置3. draw:负责将View绘制在屏幕上。2. ⼏个常⽤回调⽅法1. 构造⽅法2. onAttachToWindow:在包含View的Activity启动时调⽤3. onDetachFromWindow:在包含View的Activity退出或者View被remove时回调4. onVisibilityChanged:当View的可见状态发⽣改变时调⽤3. 两个重要概念1. ViewRoot:连接WindowManager(外界访问Window的⼊⼝)和DecorView(顶级View)的纽带,View的三⼤流程均是通过ViewRoot来完成的。2. DecorView:顶级View4. View的绘制流程

View的绘制流程是从ViewRoot的PerformTraversals⽅法开始的。

如上图所⽰:1. performTraversals会依次调⽤performMeasure, performLayout, performDraw三个⽅法,这三个⽅法分别完成顶层View的measure,layout,draw⽅法,onMeasure⼜会调⽤所有⼦元素的measure过程,直到完成整个View树的遍历。同理,performLayout, performDraw的传递流程与performMeasure相似。唯⼀不同在于,performDraw的传递过程在draw⽅法中通过dispatchDraw实现,但没有本质区别。2. Measure过程后可以调⽤getMeasureWidth和getMeasureHeight⽅法获取View测量后的宽⾼,与getWidth和getHeight的区别是:getMeasuredHeight()返回的是原始测量⾼度,与屏幕⽆关,getHeight()返回的是在屏幕上显⽰的⾼度。实际上在当屏幕可以包裹内容的时候,他们的值是相等的,只有当view超出屏幕后,才能看出他们的区别。当超出屏幕后,getMeasuredHeight()等于getHeight()加上屏幕之外没有显⽰的⾼度。3. Layout过程确定View四个顶点的位置和实际的宽⾼。4. Draw过程确定View的显⽰,只有draw⽅法完成后View的内容才会出现在屏幕上。2.

MeasureSpec的使⽤measureSpec的作⽤:很⼤程度上决定了⼀个View的尺⼨规格

下⾯是它的⼀些常量和⽅法: private static final int MODE_SHIFT = 30; private static final int MODE_MASK = 0x3 << MODE_SHIFT; /** * Measure specification mode: The parent has not imposed any constraint * on the child. It can be whatever size it wants. */ public static final int UNSPECIFIED = 0 << MODE_SHIFT; /** * 精确模式,对应LayoutParams中的match_parent和具体数值这两种模式 */ public static final int EXACTLY = 1 << MODE_SHIFT; /** * 最⼤模式,⼤⼩不定,但是不能超过窗⼝的⼤⼩ */ public static final int AT_MOST = 2 << MODE_SHIFT; public static int makeMeasureSpec(int size, int mode) { if (sUseBrokenMakeMeasureSpec) { return size + mode; } else { return (size & ~MODE_MASK) | (mode & MODE_MASK); } } public static int getMode(int measureSpec) { return (measureSpec & MODE_MASK); } public static int getSize(int measureSpec) { return (measureSpec & ~MODE_MASK); }MeasureSpec和LayoutParams的关系

View的MeasureSpec由⽗容器的MeasureSpec和⾃⾝的LayoutParams决定。

View的masure过程由ViewGroup传递,具体观察ViewGroup的measureChildWithMargins⽅法

DecorView的MeasureSpec由窗⼝尺⼨和⾃⾝的LayoutParams决定。

⼦元素的MeasureSpec还和View的margin和padding有关。

具体情况如下图:

1. ### 如何让⾃定义View⽀持⾃定义属性

1. 在values⽬录下创建⾃定义属性的XML,⽐如,format负责定义属性的格式,可以是“color”代表颜⾊,也可以是reference代表资源id,dimension代表尺⼨。 2. 在View的构造函数中解析⾃定义属性的值并做相应处理,解析circle_color这个属性的值: public CircleView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray a = StyledAttributes(attrs, View); mColor = or(View_circle_color, ); e(); init(); }3. 在布局⽂件中使⽤⾃定义属性,使⽤前需要在布局⽂件中添加schemas⽣命:`xmlns:app="/apk/res-auto"`在这个声明中app是⾃定义属性的前缀,也可以换成其他名字,不过要与CircleView中的⾃定义属性⼀致。 1.

重写View应该注意哪些⽅⾯⾃定义View的分类⼤致可以分为4类:1. 继承View重写onDraw⽅法2. 继承ViewGroup派⽣特殊的Layout3. 继承特定的View(⽐如TextView)4. 继承特定的ViewGroup(⽐如LinearLayout)5. ⾃定义View须知:1. 让View⽀持wrap_content2. 如果有必要,让View⽀持padding3. 尽量不要在View中使⽤Handler,没必要,有post系列⽅法4. View中如果有线程或动画,需要及时停⽌,参考View#onDetachedFromWindow5. View带有滑动嵌套情形时,需要处理好滑动冲突实⽤范围继承View重写onDraw⽅法注意事项不规则效果继承ViewGroup派⽣特殊的Layou实⽤范围继承特定的View(⽐如TextView)继承特定的ViewGroup(⽐如LinearLayout)⾃定义布局注意事项扩展已有的View的功能⾃定义布局推荐知识点View的事件分发机制滑动冲突解决⽅案View的测量、布局以及绘制流程View的常见回调⽅法⾃定义View实现的⼀般步骤参考资料《android开发艺术探究》

发布者:admin,转转请注明出处:http://www.yc00.com/news/1689430633a247040.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信