将在上一章中,我们知道了App的启动流程以及UI的绘制的整体流程,这么这片文章将带领大家继续深入UI是如何绘制.进行了哪些步骤.
我们知道在Activity调用onResume()方法后,会对UI进行绘制(PS:这里在强调一遍不能在onResume()方法中做大量耗时的操作,否则页面会白屏,界面出现慢等情况),绘制时主要调用了persormTraversals()方法开启了UI绘制, performMeasure -> performLayout -> performDraw.这三个方法分别调用了View的方法:view.measure() -> view.layout() -> view.drow(),View的绘制流程是:先测量 -> 在摆放 -> 再绘制的流程.
本篇主要讲解performMeasure() performLayout performDraw 如何对view进行测量、摆放、绘制的.
测量流程
下面我们来看以下performTraversals()中是如何调用performMeasure()的,代码如下:
下述代码中,将布局的宽和高以及lp.width 和 lp.height 它传入的是MATCH_PARENT
或者 WRAP_CONTENT
,交给getRootMeasureSpec()
方法进行处理,我们且看这个方法是如何处理的?
//getRootMeasureSpec ?? 它是干什么的呢?mWidth 和 mHeight 它是哪里来的呢?
//它是我们基础布局的宽和高
//getRootMeasureSpec 传入宽和高的测量规格
//mWidth 和 mHeight是具体的宽和高有一个具体的宽度值和高度值
//lp.width 和 lp.height 它传入的是MATCH_PARENT 或者 WRAP_CONTENT
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// Ask host how big it wants to be
//TODO 3测量View
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
getRootMeasureSpec()方法如下:首先我们先来解析以下参数到底是什么意思.windowSize:表示布局的宽度或高度的具体值.rootDimension:表示:父容器说施加的规则 MATCH_PARENT 或 WRAP_CONTENT 或 自己设置的具体的值 三种规则,也就是LayoutParams的layout_width layout_height.从代码中我们也可以看出来在switch中的判断 rootDimension,在case中对windowSize和rootDimension进行了打包处理,通过MeasureSpec类,它会返回一个值,这个值包括布局的具体的宽度或高度以及施加的规则,Google在这里处理的非常巧妙,MeasureSpec类是非常值得学习和借鉴的地方,接下来我们看看MeasureSpec做了什么?
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
//windowSize 窗口的可用宽度或高度 rootDimension 到底是什么? 窗口的一维(宽度或高度)的布局参数。 跟view的布局参数
int measureSpec;
switch (rootDimension) {
//MeasureSpec.makeMeasureSpec 到底是干什么的
//MeasureSpec 在Measure流程中,系统将View的LayoutParams的layout_width layout_height
//也就是传递进来的rootDimension 根据父容器说施加的规则 MATCH_PARENT WRAP_CONTENT 和 自己的值 三种规则
//转换成对应的MeasureSpec(规格) 然后在onMeasure中根据这个MeasureSpec来确定view的测量宽高
//MeasureSpec 是干什么的?具体的模式加上windowSize 是具体的大小, 宽高的具体数值加上模式 然后进行了打包。把两个变量变成一个变量 一次性传递过去。
//其实就是将高的显示模式和高的具体数值 或 宽的显示模式和宽的具体数值进行了打包 变成一个变量传递给了onMeasure了 这个方法就是干了这一件事
case ViewGroup.LayoutParams.MATCH_PARENT://layout_height或layout_width 是MATCH_PARENT 规则
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT://layout_height或layout_width 是WARP_CONTENT 规则
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default://layout_height或layout_width 是一个具体的数值 这时候 windowSize = rootDimension
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
//返回对应的度量规格
return measureSpec;
}
首先我们先看一下MeasureSpec类的几个变量是干什么的? 首先我们需要注意几种模式.
UNSPECIFIED 模式:父view不对子View做任何限制,子View需要多大就多大,一般是ScrollView 或者 ListView等这些View.
EXACTLY 模式:父View已经测量出子View需要的精确大小,这时候View的最终大小就是SpecSize所指定的值.注意:它对应这match_parent和设置精确数值这两种模式.
AT_MOST 模式:子View最终大小是父View指定的SpecSize的值,且子view的大小不能大于这个值对应warp_content这种模式.
看上述的几种模式其中EXACTLY和AT_MOST模式分别对应这,layout_width和layout_height的值,根据设置的值来确定哪种模式.
UNSPECIFIED模式:对应着个别的view如:ScrollView、ListView、RecyclerView.
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;//0x3 << MODE_SHIFT 0x3 转换成二进制就是 然后左移30位
//MODE_MASK = 30个0 ~MODE_MASK 求反
/** @hide */
@IntDef({UNSPECIFIED, EXACTLY, AT_MOST})
@Retention(RetentionPolicy.SOURCE)
public @interface MeasureSpecMode {}
/**
* Measure specification mode: The parent has not imposed any constraint
* on the child. It can be whatever size it wants.
* UNSPECIFIED 模式:父view不对子View做任何限制,子View需要多大就多大
* ScrollView 或者 ListView等这些View
* 00 后面 30个0 32位的二进制前两位是00 后面全是0
* 整型值 32位的值
*/
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
/**
* Measure specification mode: The parent has determined an exact size
* for the child. The child is going to be given those bounds regardless
* of how big it wants to be.
* EXACTLY 模式:
* 父View已经测量出子View需要的精确大小,这时候View的最终大小就是SpecSize所指定的值
* 注意:它对应这match_parent和设置精确数值这两种模式
* 01 后面 30个0 32位的二进制前两位01 后面全是0
*/
public static final int EXACTLY = 1 << MODE_SHIFT;
/**
* Measure specification mode: The child can be as large as it wants up
* to the specified size.
* AT_MOST 模式:
* 子View最终大小是父View指定的SpecSize的值,且子view的大小不能大于这个值
* 对应warp_content这种模式
*
* 10 后面 30个0, 32位的二进制前两位是10 后面全是0
*/
public static final int AT_MOST = 2 << MODE_SHIFT;
接下来我们需要普及一下二进制的一些算法,才能往下进行,在上述代码中我们看到 0 << MODE_SHIFT 0x3 << MODE_SHIFT这些是什么意思呢? 其实就是转换成了二进制.
关于二进制的转换算法
int类型移位运算
public class BinaryTest {
public static void main(String[] args) {
int number = 1;
printlnInfo(number);
number = number << 10;//左移10位
printlnInfo(number);
number = number >> 5;//右移5位
printlnInfo(number);
}
private static void printlnInfo(int number) {
System.out.println(Integer.toBinaryString(number));
}
}
上述代码中,我们对int进行了左移和右移运算,运算结果很简单.输出结果为:
1
10000000000
100000
简单移位运算我们就已经学会了,我们再回到我们的代码中:
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
上述代码,对0进行了左移运算,MODE_SHIFT = 30,也就是0左移30位,那么结果就是: 二进制30个0
public static final int EXACTLY = 1 << MODE_SHIFT;
1 << MODE_SHIFT 结果就是:1 00000 00000 00000 00000 00000 00000
public static final int AT_MOST = 2 << MODE_SHIFT;
2 << MODE_SHIFT结果就是:10 00000 00000 00000 00000 00000 00000
其实我们可以将AT_MOST EXACTLY UNSPECIFIED,看成32位的二进制数,不够末尾补0就可以了.
关于十六进制转二进制运算
我们在代码中看到这样一段: 很显然0x3就是十六进制,那么基础的问题16进制如何转换为十进制和二进制呢?
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
上述代码中,如何0x3转换成二进制呢? 我们先将十六进制转换成十进制,如何转换:
进制转换
将十六进制的(2B)H转换为十进制的步骤如下:
1. 第0位 B x 16^0 = 11;
2. 第1位 2 x 16^1 = 32;
3. 读数,把结果值相加,11+32=43,即(2B)H=(43)D。
那么按照上述步骤将0x3转换为10进制就是: 3 x 16 ^ 0 = 3
3 转换为二进制就是: 11
也就是 11 << 30 左移30位得到:11 00000 00000 00000 00000 00000 00000
关于二进制的& | ~ 运算
我们还需要学会二进制& 与 | 和 ~ 运算
关于 & 运算,1 & 1 = 1; 0 & 1 = 0; 0 & 0 = 0; 1 & 0 = 0
关于 | 运算, 0 | 0 = 0; 0 | 1 = 1; 1 | 0 = 1 ; 1 | 1 = 1
关于 ~ 运算, 0 = 1 ; 1 = 0
逻辑加法(“或”运算)
逻辑加法通常用符号“+”或“∨”来表示。逻辑加法运算规则如下:
0+0=0, 0∨0=0
0+1=1, 0∨1=1
1+0=1, 1∨0=1
1+1=1, 1∨1=1
从上式可见,逻辑加法有“或”的意义。也就是说,在给定的逻辑变量中,A或B只要有一个为1,其逻辑加的结果就为1;只有当两者都为0时逻辑加的结果才为0。
逻辑乘法(“与”运算)
逻辑乘法通常用符号“×”或“∧”或“·”来表示。逻辑乘法运算规则如下:
0×0=0, 0∧0=0, 0·0=0
0×1=0, 0∧1=0, 0·1=0
1×0=0, 1∧0=0, 1·0=0
1×1=1, 1∧1=1, 1·1=1
不难看出,逻辑乘法有“与”的意义。它表示只当参与运算的逻辑变量都同时取值为1时,其逻辑乘积才等于1。
逻辑否定("非"运算)
逻辑非运算又称逻辑否运算。其运算规则为:
0=1 “非”0等于1
1=0 “非”1等于0
OK,了解了二进制的运算,我们回到代码继续进行分析,我们来看makeMeasureSpec()方法
public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
@MeasureSpecMode int mode) {
//将大小和模式进行打包
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
//将具体的宽高大小和对应的模式进行了二进制的操作
//size
//size & ~MODE_MASK size 先进行了一个运算 比如宽或高具体的数值是100,~符号是对MODE_MASK进行了求反操作
//mode & MODE_MASK 传进来的具体的mode也就是
return (size & ~MODE_MASK) | (mode & MODE_MASK); // | 运算 0 | 1 = 1 前两位作为mode 后30位作为size
}
}
我们主要看的就是 (size & ~MODEMASK) | (mode & MODE_MASK); 这一段代码,它将具体的宽高大小和对应的模式进行了二进制类似一个打包的操作,用到的时候在将它解开.
我们通过一个具体的例子来理解: 例如我们通过计算得到的具体的 宽度=100dp,layout_width = 100dp.
也就是说
size = 100
根据我们上述的代码可以知道使用的模式为
mode = _EXACTLY ( _10 00000 00000 00000 00000 00000 00000)._
_MODE_MASK = _11 00000 00000 00000 00000 00000 00000
~MODE_MASK = 00 11111 11111 11111 11111 11111 11111
_size 转换为二进制: 00 00000 00000 00000 00000 000_11 00100
size & ~MODE_MASK = _00 00000 00000 00000 00000 000_11 00100 其实还是size
mode & MODE_MASK = 10 00000 00000 00000 00000 00000 00000 其实还是mode
进行 | 操作 得到的最终结果 measureSpec= _10 00000 00000 00000 00000 000_11 00100 得到的结果 前两位是mode,后30位是size.这种模式
我们再看一下解包的操作:
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);//与0为参照 只会得到前两位
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);//与1为参照,只会得到后30位
}
比如解码mode,通过得到的
measureSpec= 10 00000 00000 00000 00000 000_11 00100
_MODE_MASK =_11 00000 00000 00000 00000 00000 00000
~MODE_MASK=00 11111 11111 11111 11111 11111 11111
measureSpec & _MODE_MASK = 10 00000 00000 00000 00000 00000 00000 = EXACTLY 模式解包正确
measureSpec & ~MODE_MASK = 0_0 00000 00000 00000 00000 000_11 00100 = size = 100 宽度解包正确
这也进一步证实了 measureSpec 是一个32位的二进制,前两位表示模式,后30位表示具体的大小. 这样将两个值打包成了一个值.这段代码设计的非常的巧妙,看起来没有几行代码,但是值得我们每个程序员深思这种设计的原理.
我们继续回到代码中的performMeasure()方法中,在getRootMeasureSpec()方法中分别得到布局宽度及模式以及高度及模式的打包值.然后传递给了performMeasure()方法.看下面代码中,调用了view的measure()方法,我们继续看measure()方法中做了什么?
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
//调用了View里面的测量方法 DecorView 中的measure 将打包好的规格传入 然后我们在看measure做了什么
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
在view的measure()方法中我们看到了熟悉的方法onMeasure(),这个方法是我们经常在自定义控件中调用的方法
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
//最终调用onMeasure() 方法,而这个方法恰巧是我们可以自定义的 如果顶层是FrameLayout 那么就会调到FrameLayout的onMeasure方法
//可以看出我们自己的xml的顶层就是FrameLayout,根据自己的业务规则进行设置
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
long value = mMeasureCache.valueAt(cacheIndex);
// Casting a long to int drops the high 32 bits, no mask needed
//setMeasuredDimensionRaw() 确定最终的长度
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
}
我们的顶层view是FrameLayout,我们来看FrameLayout中重写的onMeasure()方法,根据自己的业务逻辑实现了布局的测量,代码如下:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//可以看出 FrameLayout 重写了onMeasure()方法
int count = getChildCount();
final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
mMatchParentChildren.clear();
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
maxWidth = Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
childState = combineMeasuredStates(childState, child.getMeasuredState());
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}
// Account for padding too
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
// Check against our minimum height and width
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
// Check against our foreground's minimum height and width
final Drawable drawable = getForeground();
if (drawable != null) {
maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
}
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
count = mMatchParentChildren.size();
if (count > 1) {
for (int i = 0; i < count; i++) {
final View child = mMatchParentChildren.get(i);
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec;
if (lp.width == LayoutParams.MATCH_PARENT) {
final int width = Math.max(0, getMeasuredWidth()
- getPaddingLeftWithForeground() - getPaddingRightWithForeground()
- lp.leftMargin - lp.rightMargin);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
width, MeasureSpec.EXACTLY);
} else {
childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
lp.leftMargin + lp.rightMargin,
lp.width);
}
final int childHeightMeasureSpec;
if (lp.height == LayoutParams.MATCH_PARENT) {
final int height = Math.max(0, getMeasuredHeight()
- getPaddingTopWithForeground() - getPaddingBottomWithForeground()
- lp.topMargin - lp.bottomMargin);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
height, MeasureSpec.EXACTLY);
} else {
childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
lp.topMargin + lp.bottomMargin,
lp.height);
}
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}
在上述代码中,实际上就是循环了它的子view,然后让每个子view调用measure()方法,自己进行测量.
layout 布局摆放流程
我们来看performLayout 的布局摆放,最终调到了view中的layout方法,代码如下:
//宽高测量完毕以后就开始摆放
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
mLayoutRequested = false;
mScrollMayChange = true;
mInLayout = true;
final View host = mView;
if (host == null) {
return;
}
if (DEBUG_ORIENTATION || DEBUG_LAYOUT) {
Log.v(mTag, "Laying out " + host + " to (" +
host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")");
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
try {
//host 就是 DecorView 调用了layout方法 对顶层View的位置进行初始化
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
}
......
}
我们再看一下,view中的layout方法做了什么? 最终调到了onLayout方法,这个方法也是我们所熟悉的自定义view中所重新的方法.
public void layout(int l, int t, int r, int b) {
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
//上下左右四个点的初始化是通过setFrame进行初始化的
//注意 setFrame的作用
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
//最终调到了onLayout方法,但是在View中是个空的方法,需要具体的View实现 也可以看作FrameLayout
onLayout(changed, l, t, r, b);
if (shouldDrawRoundScrollbar()) {
if(mRoundScrollbarRenderer == null) {
mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
}
} else {
mRoundScrollbarRenderer = null;
}
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLayoutChangeListeners != null) {
ArrayList<OnLayoutChangeListener> listenersCopy =
(ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
int numListeners = listenersCopy.size();
for (int i = 0; i < numListeners; ++i) {
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
}
}
}
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) {
mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
notifyEnterOrExitForAutoFillIfNeeded(true);
}
}
在View中的onLayout()方法,是一个空方法,也就是所需要我们自己去实现布局的摆放,继续看FrameLayout中的onLayout的实现
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
看如下代码,其实在FrameLayout根据自己的业务来实现布局的摆放,循环子view 用自己的布局进行布局摆放.其实最终还是让子view调用layout()方法自己去实现摆放.
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
//在onLayout 中根据自己具体的业务来进行摆放
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
final int count = getChildCount();
final int parentLeft = getPaddingLeftWithForeground();
final int parentRight = right - left - getPaddingRightWithForeground();
final int parentTop = getPaddingTopWithForeground();
final int parentBottom = bottom - top - getPaddingBottomWithForeground();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);//迭代子view
//让子view 自己去摆放
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
int childLeft;
int childTop;
int gravity = lp.gravity;
if (gravity == -1) {
gravity = DEFAULT_CHILD_GRAVITY;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
if (!forceLeftGravity) {
childLeft = parentRight - width - lp.rightMargin;
break;
}
case Gravity.LEFT:
default:
childLeft = parentLeft + lp.leftMargin;
}
switch (verticalGravity) {
case Gravity.TOP:
childTop = parentTop + lp.topMargin;
break;
case Gravity.CENTER_VERTICAL:
childTop = parentTop + (parentBottom - parentTop - height) / 2 +
lp.topMargin - lp.bottomMargin;
break;
case Gravity.BOTTOM:
childTop = parentBottom - height - lp.bottomMargin;
break;
default:
childTop = parentTop + lp.topMargin;
}
//循环子view 用自己的布局进行布局摆放
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
其实整个布局摆放的流程是很简单的.包括绘制流程最终是通过Surface,调用native层的C++代码来实现图形的显示,这里需要注意的是Android是使用Skia来进行图形渲染的,这一点和Flutter一样的.感兴趣的同学可以研究一下Skia如何进行图形渲染.这里就不再进行深入讲解了