android VIew 相关面试题及答案

View绘制

onmeasume()主要是度量子view的大小来确定自己的大小

1.View绘制流程

www.jianshu.com/p/03d8601cc…

  • 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创建规则是什么

image.png image.png

  • 4.自定义Viewwrap_content不起作用的原因

wrap_content起到与match_parent相同的作用, 从上面发现:

  • getDefaultSize()的默认实现中,当View的测量模式是AT_MOST或EXACTLY时,View的大小都会被设置成子View MeasureSpec的specSize。
  • 因为AT_MOST对应wrap_content;EXACTLY对应match_parent,所以,默认情况下,wrap_contentmatch_parent是具有相同的效果的。

解决了问题2:wrap_content起到与match_parent相同的作用

那么有人会问:wrap_content和match_parent具有相同的效果,为什么是填充父容器的效果呢?

  • 由于在getDefaultSize()的默认实现中,当View被设置成wrap_contentmatch_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有两个子类GLSurfaceViewVideoView

10.SurfaceViewView的区别:

  1. View主要适用于主动更新的情况下,而SurfaceView主要适用于被动更新,例如频繁地刷新
  2. View在主线程中对画面进行刷新,而SurfaceView通常会通过一个子线程来进行页面的刷新
  3. 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事件分发机制

image.png 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里执行

Guess you like

Origin juejin.im/post/7031766846638571550