"三天不学习,赶不上阿凡提" --巴依老爷
1. 先从setContentView(R.layout.main)说起吧
Android 常规的布局是用xml来配置的,需要通过反射来初始化
void setContentView(int resId){
// 实际将xml初始化View
LayoutInflater.from(this).inflate(resId,viewGroup)
}
-> LayoutInflate
View inflate(resId,viewGroupRoot,isAttachRoot){
Resource res = context.resource
//根据Id获取到此layout的解析器
XmlResourceParser parser = res.getLayout(resId)
return inflate(parser,viewGroupRoot,isAttachRoot)
}
->// 由于XML文件的解析很费时,所以在构建时用aapt工具,将XMl预处理成Parser等压缩的二进制格式
View inflate(parser,rootView,isAttach){
rInflate(parse,rootView) -> rInflateChildren()...
->
String name = parser.getName()
final View temp = createViewFromTag(rootView,name)
// 这里生成只有宽高的LayoutParams
final ViewGroup.LayoutParams params = rootView.generateLayoutParams(attrs)
//递归到子View
rInflateChildren(...)
//最后组成完整View树
rootView.addView(view,params)
-> rootView.mChildren[].add(view.apply{ setLayoutParams(params))
}
至此,从一个的Xml文件转换到一个View树,每层View都设置了LayoutParams,只不过,还没有画出到屏幕上。在这个过程中,LayoutParams作为一个包含了在Xml中配置的各种参数的容器对象,成为View的参数。
2. View树的开始绘制
这中间忽略掉Window-ViewRoot的创建,绑定等过程,来到ViewRootImpl。View树的绘制从这里开始。
2.1 Measure - 测量过程
// 执行遍历
void performTraversals(){
// 先进行测量的分析
performMeasure(childWidthMeasureSpec,childWidthMeasureSpec)
measureHierarchy()
performLayout()
performDraw()...
}
// 遍历执行Measure,调用View的measure()
void measure(int widthMeasureSpec,int heightMeasureSpec){
//此时主要是对width/heightMeasureSpec的再调整,以及对比新旧参数的变化
//之后调用onMeasure()进入执行测量的实际工作
onMeasure(widthMeasureSpec,heightMeasureSpec)
}
// 此时传入的是当前View自身的初始化时设置的LayoutParams中的wrap match xxdp...
void onMesure(int widthMeasureSpec,int heightMeasureSpec){
//1.从这两个int参数中获取MeasureMode,MeasureSize等信息
int mWidth = MeasureSpec.getSize(widthMeasureSpec)
int mWidthMode MeasureSpec.getMode(widthMeasureSpec)
int mHeight = ..
int mHeightMode ...
----
//2.如果是ViewGroup有子View,在这一步会递归到子View的measure操作中去
int childMeasureSpec = child.getLayoutParams()...
child.measure(childWidthMeasureSpec,childHeightMeasureSpec)
//3.在这一步中,会综合协商子View和自身ViewGroup的MeasureSpec
//比如 AT_MOST下需要参考将子View的宽高作为自身的宽高,EXACTLY就不需要参考
//加上padding等附加的参数,最后得出本ViewGroup自身应该设置的实际宽高
// 当然,这个步骤是高度自定义的,不同的模式有不同的测量方式,比如可以将AT_MOST就设置宽高成定值等等
.... //省略掉这个过程
//设置自身宽高
setMeasureDimension(mMeasureWidth,mMeasureHeight)
}
Measure 过程综述:整体而言就是,将View树的每个节点View,根据其自身设置的LayoutParams,同时考虑到其自身作为ViewGroup和子View之间的布局关系,来测量出每个节点View的宽高 measureWidth,measureHeight。
2.2 Layout - 布局过程
void performLayout(){
host.layout(0,0,host.getMeasureWidth(),host.getMeasureHeight())
}
->View的layout()方法
//参数就是上层ViewGroup计算好的上下左右边界
void layout(int l,int t,int r,int b){
onLayout(changed,l,t,r,b)
->layoutChildren() ...
//计算好子View的上下左右边界,递归到子View的layout过程
}
// 这部分没有什么需要分析得细节,因为在之前的measure过程,都已经确定了每一层的宽高
Layout 过程概述:将测量好的宽高,结合每层ViewGroup的布局关系,进而确定成整个屏幕坐标系下的绝对坐标
测量好了坐标才可以绘制
3.Draw - 绘制
此部分涉及到的较为复杂 分析待续...