ViewRoot
ViewRoot对应于ViewRootImpl类,它是连接WindowManager和DecorView的纽带,也可以说是Window和View的桥梁,他主要功能有:
- 完成View的绘制过程,包括measure、layout、draw过程
- 向DecorView分发Event事件,包括MotionEvent,KeyEvent等
当建立好了decorView与ViewRootImpl的关联后,ViewRootImpl类的requestLayout()方法会被调用,该方法为View绘制的起点方法。
ViewRoot与DectorView的关联过程
我们从Activity的setContentView方法开始分析。该方法会创建DecorView,并将xml布局文件添加到DecorView的content区域,最后调用handleResumeActivity将decorView加入到PhoneWindow中。
ActivityThread调用handleResumeActivity方法将顶层的DecorView添加到PhoneWindow窗口
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume) { if (r.window == null && !a.mFinished && willBeVisible) { //获得当前Activity的PhoneWindow对象 r.window = r.activity.getWindow(); //获得当前phoneWindow内部类DecorView对象 View decor = r.window.getDecorView(); //设置窗口顶层视图DecorView可见度 decor.setVisibility(View.INVISIBLE); //当前Activity的WindowManagerImpl对象 ViewManager wm = a.getWindowManager(); WindowManager.LayoutParams l = r.window.getAttributes(); a.mDecor = decor; l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION; l.softInputMode |= forwardBit; if (a.mVisibleFromClient) { //标记根布局DecorView已经添加到窗口 a.mWindowAdded = true; //将根布局DecorView添加到当前Activity的窗口上面 wm.addView(decor, l); } }
WindowManagerGloba.addView()方法将DectorView与ViewRootlmpl进行关联
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { ............ ViewRootImpl root; View panelParentView = null; ............ //获得ViewRootImpl对象root root = new ViewRootImpl(view.getContext(), display); ........... // do this last because it fires off messages to start doing things try { //将传进来的参数DecorView设置到root中 root.setView(view, wparams, panelParentView); } catch (RuntimeException e) { } }
接下来调用ViewRootImpl类中的setView()方法
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { synchronized (this) { if (mView == null) { //将顶层视图DecorView赋值给全局的mView mView = view; ............. //标记已添加DecorView mAdded = true; ............. //请求布局,View绘制的起点方法 requestLayout(); }
}
可以看出经过以上代码调用,DectorView与ViewRootImpl建立了关联,接下来就通过requestLayout()方法正试开始View的绘制过程了。
View的绘制过程
requestLayout最终会调用performTraversals方法来完成View的绘制。
整个View的绘制流程是在ViewRootImpl类的performTraversals()方法开始的,performTraversals方法会经过measure、layout和draw三个过程。所以View的绘制是ViewRootImpl完成的,另外当手动调用invalidate(主线程),postInvalidate(子线程)也会最终调用performTraversals来重新绘制View。
private void performTraversals() {
......
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
......
mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
......
mView.draw(canvas);
......
}
Measure
measure过程如果是ViewGroup,需要遍历测量其孩子后再测量自己。如果是View则只负责测量自己就行。
- MeasureSpec
MeasureSpec是一个32位整数,由SpecMode和SpecSize两部分组成,其中高2位为SpecMode,低30位为SpecSize。SpecMode为测量模式,SpecSize为相应测量模式下的测量尺寸。 - SpecMode的取值可为以下三种
EXACTLY: 父容器测量出子View的精确大小SpecSize,如match_parent,具体dp值
AT_MOST: 父容器指定子View的大小不得超过SpecSize,如wrap_content
UNSPECIFIED: 父容器不对子View尺寸作限制,通常用于系统内部
在onCreate()中获取View的高宽时不能保证View已测量完毕,因此需要调用view.post(runnable)将测量view的代码投递到消息队列尾部,这样能保证View初始化好之后才回调runnable
Layout
Layout的作用是ViewGroup用来确定子元素位置,当ViewGroup的位置被确定后,它在onLayout中会遍历所有子元素并调用其layout方法,在layout方法中onLayout方法又会被调用。layout方法中通过setFrame来确定View本身的位置,而onLayout方法则会确定所有子元素的位置。
Draw
它的作用是将View绘制到屏幕上,绘制过程有以下几步:
1.绘制背景background
2.绘制自己
2.绘制children(ViewGroup)
3.绘制装饰,滚动条等
onDraw(Canvas canvas)最终是调用canvas.translate() native层的方法进行绘制的
常见自定义View类型
1.继承View重写onDraw方法
2.继承ViewGroup派生自定义Layout
3.继承特定View (Button,TextView)
4.继承特定ViewGroup(LinearLayout等)
自定义View注意事项
1.需要在onMeasure中实现支持wrap_content(指定一个默认大小)
为什么你的自定义View wrap_content不起作用?
https://blog.csdn.net/carson_ho/article/details/62037760
2.需自己实现padding效果
- 如果是View则在ondraw方法里处理padding
- 如果是ViewGroup需要在onMeasure和onLayout中考虑padding和margin的影响
3.不要使用Handler,view本身提供了post方法
4.如果有线程或动画,需要在onDetachedFromWindow中停止
5.如果有滑动冲突,需要处理冲突