View的工作原理
- ViewRoot 和DecorView
ViewRoot对应于ViewRootImpl类,它是连接DecorView和WindowManager的纽带,View的三大绘制流程都是通过ViewRoot来完成。
在ActivityThread中,当Activity对象被创建完毕后,会将DecorView添加到Window中,同时会创建ViewRootImpl,并将ViewRootImpl对象和DecorView对象关联。
Activity中View的绘制流程是从ViewRoot的perfromTraversals()方法开始的,它经过measure layout draw 最终将View绘制出来
- MeasureSpec 代表32位int值,高2位代表specMode,低30位代表specSize
specMode:测量模式
specSize:在某种测量模式下的规格大小
- SpecMode 类型
UNSPECIFIED: 父容器不对View限制,要多大有多大,这种情况一般用于系统内部,表示一种测量状态。
EXACTLY: 父容器已经检测出View所需要的精确大小,这个时候View的最终大小就是SpecSize所指定的值,它对应于LayoutParams中的Match_parent和具体的值。
AT_MOST: 父容器指定了一个可视大小,即SpecSize,View的大小不能大于这个值,具体要看不同View的具体实现,对应于wrap_content
- ******父容器和LayoutParams一起决定View的MeasureSpec
系统会将LayoutParams在父容器的约束下转换成对应的MeasureSpec,然后再根据MeasurSpec来确定View测量后的宽高。
-
父容器测量子View P181
- DecorView对MeasureSpec的转换测量有点差别,其MeasureSpec值由窗口尺寸和其自身的LayoutParams来决定
- View 的mesure方法会调用onMeasure方法,所有只需要实现View的onMeasure就可以了。
View的measure方法又会被所在的ViewGroup的measureChild方法调用。measureChild方法会遍历child,执行child的measure。
- 在onMeasure中又调用了setMeasureDimension()方法设置View宽高的测量值。
- 在measure完以后,通过getMeasuredWidth/Height()可获得测量后的宽/高
- View的Measure过程和Activity的生命周期过程不同步,
-
如何在Activity中获取View的宽/高?
-
Activity/View #onWindowFouceChanged()方法,表示View已经初始化完毕,宽高已经准备好
但该方法会在Acitivty焦点获取或失去时都会调用,也就是onResume和onPause都会调用。
-
view.post(runnable)
view初始化完毕会执行runnable,在run()中view.getMesuredWidth()/height
-
ViewTreeObserver
当View树状态发生改变,或者View树内部的View发生改变,onGlobalLayout方法会被调用,在这里获取View的宽高。
@Override
protected void onStart() {
super.onStart();
ViewTreeObserver viewTreeObserver = vMarqueeView.getViewTreeObserver();
viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
vMarqueeView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
int width = vMarqueeView.getMeasuredWidth();
int height = vMarqueeView.getMeasuredHeight();
}
});
}
- view.measure(int widthMesureSpec,int heightMesureSpec) p192
MATCH_PARENT:不可用,因为不知道父容器的尺寸
WRAP_CONTENT:
int widthMesureSpec = View.MeasureSpec.makeMeasureSpec((1 << 30) - 1, View.MeasureSpec.AT_MOST);
int heightMesureSpec = View.MeasureSpec.makeMeasureSpec((1 << 30) - 1, View.MeasureSpec.AT_MOST);
vMarqueeView.measure(widthMesureSpec, heightMesureSpec);
(1<<30)-1 代表(2^30-1) 即使用最大值去构造MeasureSpec(View的尺寸使用30位二进制表示)
- layout过程和mearsure过程一样,调用父元素的layout方法,layout方法调用onLayout方法,onlayout方法又会遍历子元素,调用子元素的layout,子元素layout又会调用onLayout,如此循环
- View的测量宽高和最终宽高的区别:
getWidth()和getMesuredWidth()
本质上是相等的,只是,getWidht()值mWitdh形成于onLayout之后,而getMesuredWidth()的值形成于mesured之后
(p196)
注意:如过在onLayout中改变了l,r,t,b中某个值,则会影响到getWidth/getHeight,而不会影响getMesuredWidth/Height值
- draw过程有点差别,但大致相同
调用draw方法,draw方法会执行如下几步
1.绘制背景(background.draw(cavans))
2.绘制自己(onDraw)
3.绘制children(dispatchDraw(),dispatchDraw又会循环child执行其draw()方法)
4.绘制装饰(onDrawScrollBars)
- View的setWillNotDraw(boolean willNotDraw) 优化
如果View不做任何绘制内容时,那么设置这个标志位位true后,系统会进行相应的优化,默认情况下,View没有启用这个标志位,但ViewGroup会启用
当自定义View继承ViewGroup且本身不具备绘制功能时,可以开启这个标志位,从而便于系统的后续优化,当明确知道继承ViewGroup的View需要通过onDraw绘制相应内容时,需要显示的关闭这个标志位
-
绘制自定义View须知: p201
- 让View支持wrap_content
直接继承View或ViewGroup的控件,如果不在onMeasure中对wrap_content做特殊处理,那么在外界布局中使用wrap_content就无法达到预期效果。
- 如果有必要,让View支持padding
直接继承View的控件,如果不在draw方法中处理padding,那么padding属性无法起作用。另外,直接继承ViewGroup的控件要在其onMeasure和onLayout中要考虑padding和子元素margin对其造成的影响,不然将导致padding和子元素的margin失效
-
尽量不要在View中使用Handler
View内部本身提供了post方法
-
如果View中有线程或者动画,需及时停止,参考View#onDetachedFromWindow
当Activity退出或者当前View被remove后,View的onDetachedFromWindow将会被调用,对应的方法是onAttachedToWindow(),该方法在onDraw之前任何时候调用,不确定在onMesure之前还是之后调用,当包含该View的Activity被启动时调用。
- View带有滑动嵌套时,需要处理好滑动嵌套
- 再次强调,自定义组件继承View或ViewGroup的,padding值需要自己处理,在onDraw中处理padding,而margin值由父类处理,对于wrap_content,默认是按照match_parent处理,所以也需要自己处理wrap_content情况,设置个最小值。
-
自定义属性 p206