相关内容后续GitHub更新,想冲击金三银四的小伙伴可以找找看看,欢迎star
(顺手留下GitHub链接,需要获取相关面试等内容的可以自己去找)
https://github.com/xiangjiana/Android-MS
(VX:mm14525201314)
一、View绘制的流程框架
View的绘制是从上往下一层层迭代下来的。DecorView
–>ViewGroup
(— >ViewGroup
)–>View
,按照这个流程从上往下,依次measure
(测量),layout
(布 局),draw
(绘制)
二、Measure流程
顾名思义,就是测量每个控件的大小。
调用measure()
方法,进行一些逻辑处理,然后调用onMeasure()
方法,在其中调用 setMeasuredDimension()
设定View的宽高信息,完成View的测量操作。
public final void measure(int widthMeasureSpec, int heightMeasur eSpec) {
}
measure()
方法中,传入了两个参数 widthMeasureSpec, heightMeasureSpec
表示 View的宽高的一些信息。
protected void onMeasure(int widthMeasureSpec, int heightMeasure Spec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumW idth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heig htMeasureSpec));
}
由上述流程来看Measure
流程很简单,关键点是在于widthMeasureSpec
, heightMeasureSpec
这两个参数信息怎么获得?
如果有了widthMeasureSpec
, heightMeasureSpec
,通过一定的处理(可以重写,自 定义处理步骤),从中获取View的宽/高,调用setMeasuredDimension()
方法,指定 View的宽高,完成测量工作。
MeasureSpec
的确定
先介绍下什么是MeasureSpec
?
MeasureSpec
由两部分组成,一部分是测量模式,另一部分是测量的尺寸大
小。 其中,Mode模式共分为三类
UNSPECIFIED
: 不对View进行任何限制,要多大给多大,一般用于系统内部
EXACTLY
: 对应LayoutParams
中的match_parent
和具体数值这两种模式。检测到 View所需要的精确大小,这时候View的最终大小就是SpecSize
所指定的值,
AT_MOST
: 对应LayoutParams
中的wrap_content
。View的大小不能大于父容器 的大小。
那么MeasureSpec
又是如何确定的?
对于DecorView
,其确定是通过屏幕的大小,和自身的布局参数LayoutParams
。
这部分很简单,根据LayoutParams
的布局格式(match_parent
,wrap_content
或 指定大小),将自身大小,和屏幕大小相比,设置一个不超过屏幕大小的宽高,以 及对应模式。 对于其他View(包括ViewGroup
),其确定是通过父布局的MeasureSpec
和自身的 布局参数LayoutParams
。 这部分比较复杂。以下列图表表示不同的情况:
当子View的LayoutParams
的布局格式是wrap_content
,可以看到子View的大小 是父View的剩余尺寸,和设置成match_parent
时,子View的大小没有区别。为了 显示区别,一般在自定义View时,需要重写onMeasure
方法,处理wrap_content
时的情况,进行特别指定。
从这里看出MeasureSpec
的指定也是从顶层布局开始一层层往下去,父布局影响 子布局。
可能关于MeasureSpec
如何确定View大小还有些模糊,篇幅有限,没详细具体展开介绍
View的测量流程:
三、Layout流程
测量完View大小后,就需要将View布局在Window中,View的布局主要通过确定上 下左右四个点来确定的。
其中布局也是自上而下,不同的是ViewGroup
先在layout()
中确定自己的布局,然 后在onLayout()
方法中再调用子View的layout()
方法,让子View布局。在Measure 过程中,ViewGroup
一般是先测量子View的大小,然后再确定自身的大小。
public void layout(int l, int t, int r, int b) {
// 当前视图的四个顶点
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
// setFrame() / setOpticalFrame():确定View自身的位置
// 即初始化四个顶点的值,然后判断当前View大小和位置是否发生了变化并返回
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
//如果视图的大小和位置发生变化,会调用onLayout()
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PF LAG_LAYOUT_REQUIRED) {
// onLayout():确定该View所有的子View在父容器的位置
onLayout(changed, l, t, r, b);
…
}
上面看出通过 setFrame()
/ setOpticalFrame()
:确定View自身的位置,通过 onLayout()
确定子View的布局。 setOpticalFrame()
内部也是调用了 setFrame()
,所以具体看setFrame()
怎么确定自身的位置布局。
protected boolean setFrame(int left, int top, int right, int bot tom) {
…
// 通过以下赋值语句记录下了视图的位置信息,即确定View的四个顶点
// 即确定了视图的位置
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBott om);
}
确定了自身的位置后,就要通过onLayout()
确定子View的布局。onLayout()
是一个 可继承的空方法。
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
如果当前View就是一个单一的View,那么没有子View,就不需要实现该方法。
如果当前View是一个ViewGroup
,就需要实现onLayout
方法,该方法的实现个自 定义ViewGroup
时其特性有关,必须自己实现。
由此便完成了一层层的的布局工作。 View的布局流程:
四、Draw过程
View的绘制过程遵循如下几步:
①绘制背景 background.draw(
canvas
)
②绘制自己(onDraw
)
③绘制Children(dispatchDraw
)
④绘制装饰(onDrawScrollBars
)
从源码中可以清楚地看出绘制的顺序。
public void draw(Canvas canvas) {
// 所有的视图最终都是调用 View 的 draw ()绘制视图( ViewGroup 没有复写 此方法)
// 在自定义View时,不应该复写该方法,而是复写 onDraw(Canvas) 方法进行绘 制。
// 如果自定义的视图确实要复写该方法,那么需要先调用 super.draw(canvas)完 成系统的绘制,然后再进行自定义的绘制。
…
int saveCount;
if (!dirtyOpaque) {
// 步骤1: 绘制本身View背景
drawBackground(canvas);
最后为了帮助大家深刻理解Android相关知识点的原理以及面试相关知识,这里放上相关的我搜集整理的24套腾讯、字节跳动、阿里、百度2020-2021面试真题解析,我把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包知识脉络 + 诸多细节。
还有 高级架构技术进阶脑图、Android开发面试专题资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。
网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!
究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!