android VIew 相关面试题及答案
View绘制
onmeasume()主要是度量子view的大小来确定自己的大小
1.View绘制流程
- 2.MeasureSpec是什么
MeasureSpec 封装了父布局传递给子布局的布局要求,每个 MeasureSpec 由
mode
和size
组成,包含了父布局对子布局相应的宽高要求。
MeasureSpec代表一个32位int值,高2位代表SpecMode,低30位代表SpecSize,SpecMode是指测量模式,而specSize是指在某种测量模式下的规格大小。 这个类就是负责将 <size, mode> 的元组转换为 int 值,高 2 位表示 specMode,低 30 位表示 specSize。
一个 View 的大小并不是由它自己确定的,而是由其自身的 LayoutParams 以及父布局的 MeasureSpec 确定的。
SpecMode分三种,分别是
-
EXACTLY: 对子View提出了一个确切的建议尺寸(SpecSize);(fill_parent)(100dp) 父布局对子布局的宽高大小有明确的要求,不管子布局想要多大,它都不能超过父布局对它的限制;
-
AT_MOST: 子View的大小不得超过SpecSize;(warp_content)
-
UNSPECIFIED: 对子View的尺寸不作限制,通常用于系统内部。
3.子View创建MeasureSpec创建规则是什么
- 4.自定义Viewwrap_content不起作用的原因
wrap_content
起到与match_parent
相同的作用,
从上面发现:
- 在
getDefaultSize()
的默认实现中,当View的测量模式是AT_MOST或EXACTLY时,View的大小都会被设置成子View MeasureSpec的specSize。 - 因为AT_MOST对应wrap_content;EXACTLY对应match_parent,所以,默认情况下,
wrap_content
和match_parent
是具有相同的效果的。
解决了问题2:
wrap_content
起到与match_parent
相同的作用
那么有人会问:wrap_content和match_parent具有相同的效果,为什么是填充父容器的效果呢?
- 由于在
getDefaultSize()
的默认实现中,当View被设置成wrap_content
和match_parent
时,View的大小都会被设置成子View MeasureSpec的specSize。 - 所以,这个问题的关键在于子View MeasureSpec的specSize的值是多少
我们知道,子View的MeasureSpec值是根据子View的布局参数(LayoutParams)和父容器的MeasureSpec值计算得来,具体计算逻辑封装在getChildMeasureSpec()里。
6.为什么onCreate获取不到View的宽高
view的绘制流程是在perfromTraversals()中进行的,而他是在onresume()之后才调用的,所以无法在oncreate和onresume中拿到
- 7.View#post与Handler#post的区别 当View已经attach到了window,两者是没有区别的,都是调用UI线程的Handler发送runnable到MessageQueue,最后都是由handler进行消息的分发处理。
但是如果View尚未attach到window的话,runnable被放到了ViewRootImpl#RunQueue中,最终也会被处理,但不是通过MessageQueue。
当视图树尚未attach到window的时候,整个视图树是没有Handler的(其实自己可以new,这里指的handler是AttachInfo里的),这时候用RunQueue来实现延迟执行runnable任务,并且runnable最终不会被加入到MessageQueue里,也不会被Looper执行,而是等到ViewRootImpl的下一个performTraversals时候,把RunQueue里的所有runnable都拿出来并执行,接着清空RunQueue。
由此可见RunQueue的作用类似于MessageQueue,只不过,这里面的所有 runnable最后的执行时机,是在下一个performTraversals到来的时候,MessageQueue里的消息处理的则是下一次loop到来的时候。
- 8.Android绘制和屏幕刷新机制原理
9.什么事surfaceView
SurfaceView
继承之View
,但拥有独立的绘制表面,即它不与其宿主窗口共享同一个绘图表面,可以单独在一个线程进行绘制,并不会占用主线程的资源。这样,绘制就会比较高效,游戏,视频播放,还有最近热门的直播,都可以用SurfaceView
SurfaceView
有两个子类GLSurfaceView
和VideoView
10.SurfaceView
和View
的区别:
View
主要适用于主动更新的情况下,而SurfaceView
主要适用于被动更新,例如频繁地刷新View
在主线程中对画面进行刷新,而SurfaceView
通常会通过一个子线程来进行页面的刷新- View在绘图时没有使用双缓冲机制,而
SufaceView
在底层实现机制中就已经实现了双缓冲机制
如果自定义View需要频繁刷新,或者刷新时数据处理量比较大,就 可以考虑使用SurfaceView来取代View了
- 16.getWidth()方法和getMeasureWidth()区别 getMeasureWidth()在走完onMeasure()方法之后有值
getWidth()在layout()之后有值,是布局完成之后的确切值
所以在onLayout之后去调用getWidth()方法
在onMeasure()之后调用getMeasureWidth()方法
getMeasuredWidth()获取的是view原始的大小,也就是这个view在XML文件中配置或者是代码中设置的大小。getWidth()获取的是这个view最终显示的大小,这个大小有可能等于原始的大小也有可能不等于原始大小。
View事件分发
- 1.View事件分发机制
activity的dispathTouchEvent->phoneWindow的super.dispathTouchEvent,调用decorView的dispathTouchEvent,调用ViewGroup的dispathTouchEvent,然后调用getparent.disallowIntercept(内部拦截法,由子类来确认父类是否拦截),如果返回true,副类不能拦截,交给子view处理,如果返回false,子类不处理,交给父类,那么接着就执行父类的onInterceptTouchEvent判断父类是否拦截,如果返回false,表示父类不拦截,接着往下分发给子view的dispatchTouchEvent进行处理,如果返回true,那么表示父类拦截此事件,就会调用父类的onTouchEvent进行处理,如果返回true,那么就会消费该事件,如果返回true,那么就会顶层atvitity的onTouchEvent进行处理,事件流失。回过头来看,如果事件分发给了子view处理,那么会调用子view的dispatchTouchEvent()。然后判断mOntouchListener是否为null,如果为null,那么会执行onTouchEvent(),返回false,表示不消费此事件,返回给父view的onTouchEvent,父view再返回给顶层activity,返回true代表消费此事件,执行onclick();如果mOntouchListener为不为null,那么会执行onTouch,如果返回false,那么会执行ontouchevent,如果返回true,那么会消费此事件
前面ViewGroup 说到的disallowIntercept变量是通过parent.requestDisallowInterceptTouchEvent(boolean)设置的,设置是否禁止拦截事件的意思,一般子view不希望父类拦截事件的话可以调用该方法,在处理滑动冲突的时候会经常用到这个方法,他的优先级也是最高的。
2.view的onTouchEvent,OnClickListerner和OnTouchListener的onTouch方法 三者优先级
ontouch 大于 ontouchevent 大于 onclicklistener
3.onTouch 和onTouchEvent 的区别 首先,onTouchEvent是安卓事件分发里面的核心方法之一。我们经常接触的Activity和View,里面都有重写这个方法。由于ViewGroup继承View,所以onTouchEvent确定存在Activity、View和ViewGroup这三者中,是专门用来处理事件分发的。MotionEvent.ACTION_DOWN,MotionEvent.ACTION_MOVE,MotionEvent.ACTION_UP。 onTouch方法是View设置了触摸监听事件后,需要重写的方法,是OnTouchListener接口中的方法。
4.ACTION_CANCEL什么时候触发
- 在子View处理事件的过程中,父View对事件拦截
- ACTION_DOWN初始化操作
- 在子View处理事件的过程中被从父View中移除时
- 子View被设置了PFLAG_CANCEL_NEXT_UP_EVENT标记时 子View在处理一个Touch事件中,父View的onInterceptTouchEvent返回true,此时子View会接收到MotionEvent.Action_Cancel。
5.事件是先到DecorView还是先到Window
- 因为解耦的原因,所以要DecorView -> Activity -> PhoneWindow -> DecorView传递事件。ViewRootImpl并不知道有Activity这种东西存在!它只是持有了DecorView。所以,不能直接把触摸事件送到Activity.dispatchTouchEvent();不直接分发给DecorView,而是要通过PhoneWindow来间接发送也是因为Activity不知道有DecorView!但是,Activity持有PhoneWindow ,而PhoneWindow当然知道自己的窗口里有些什么了,所以能够把事件派发给DecorView。在Android中,Activity并不知道自己的Window中有些什么,这样耦合性就很低了。不管Window里面的内容如何,只要Window仍然符合Activity制定的标准,那么它就能在Activity中很好的工作。当然,这就是解耦所带来的扩展性的好处。
6.点击事件被拦截,但是想传到下面的View,如何操作 重写子类的requestDisallowInterceptTouchEvent()方法返回true就不会执行父类的onInterceptTouchEvent(),即可将点击事件传到下面的View
通过内部拦截法,让子view控制父view不能拦截此事件
7.如何解决View的事件冲突 使用内部拦截法和外部拦截法,具体怎么用就要考虑场景了
8.在 ViewGroup 中的 onTouchEvent 中消费 ACTION_DOWN 事件,ACTION_UP事件是怎么传递
-> Activity.dispatchTouchEvent()
-> ViewGroup1.dispatchTouchEvent()
-> ViewGroup1.onTouchEvent()
9.## activity、 ViewGroup和 View 都不消费 ACTION_DOWN,那么 ACTION_MOVE 和 ACTION_UP 事件是怎么传递的?
-> Activity.dispatchTouchEvent()
-> Activity.onTouchEvent();
-> 消费
- 10.同时对父 View 和子 View 设置点击方法,优先响应哪个
答案是优先响应子 view,原因很简单,如果先响应父 view,那么子 view 将永远无法响应,父 view 要优先响应事件,必须先调用 onInterceptTouchEvent 对事件进行拦截,那么事件不会再往下传递,直接交给父 view 的 onTouchEvent 处理。
- 11.requestDisallowInterceptTouchEvent的调用时机
我们一个手势的操作,会经历down,move,up等操作,子view调用requestDisallowInterceptTouchEvent(true)的时间,是必须在能拿到点击事件,比如我们在down的时候调用了方法,接下来的move,up都会传到子view上了,如果是在子view的move方法中调用的话,那么要确认父view在move的过程中,能将事件传递给子view就好了。
我们需要再父view的dispatchTouchEvent里进行判断,如果是down,那么不拦截事件,是为了让事件分发给view,然后子view的ACTION_DOWN里调用父view的requestDisallowInterceptTouchEvent(true)方法,不然父view进行拦截,这样的话子view的move 和up就可以在子view里执行
转载自:https://juejin.cn/post/7031766846638571550