Android 自定义控件 layout
Read The Fucking Source Code
引言
Android自定义控件涉及View的绘制分发流程
源码版本(Android Q — API 29)
本文涉及Android绘制流程
1. 顶层视角预览 layout
2. layout
2.1 View和ViewGroup的区别
View:View主要执行layout方法,使用 serFrame 方法来设置本身 View 的四个顶点的位置,确定View本身的位置。
ViewGroup:ViewGroup主要执行onLayout方法,递归遍历所有子View,确定子View的位置。
2.2 View和ViewGroup的汇总
2.2.1 View的 layout 过程
2.2.2 ViewGroup的 layout 过程
2.3 layout自顶向下
2.3.1 最顶层(DecorView)分发的layout
我们来看ViewRootImpl中的performLayout()方法
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
//代码省略……
//mView即DecorView
final View host = mView;
//代码省略……
//进行布局分发
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
//代码省略……
}
看到这里,那host.getMeasuredWidth() / host.getMeasuredHeight()是什么?它是直接调用View中的方法,其实就是经过measure后的DecorView的测量宽度和高度。在 Android 自定义控件 measure 中有说明。
2.3.2 ViewGroup(举例:LinearLayout)分发的layout
2.3.2.1 我们先来看ViewGroup中的layout()方法
@Override
public final void layout(int l, int t, int r, int b) {
//ViewGoup的LayoutTransition动画,动画相关,可忽略
if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
if (mTransition != null) {
mTransition.layoutChange(this);
}
//调用父类(View)的layout方法
super.layout(l, t, r, b);
} else {
// record the fact that we noop'd it; request layout when transition finishes
mLayoutCalledWhileSuppressed = true;
}
}
ViewGroup里面的layout最终会调入到父类View中的layout,View的layout后面讲解。这里可以先告诉大家,最终会调用View的onLayout方法,而ViewGroup的onLayout是抽象方法,所以它的子类LinearLayout必须要实现。
2.3.2.2 我们再来看LinearLayout中的onLayout()方法。
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mOrientation == VERTICAL) {
//纵向布局,对应xml中的:android:orientation="vertical"
layoutVertical(l, t, r, b);
} else {
//横向布局,对应xml中的:android:orientation="horizontal"
layoutHorizontal(l, t, r, b);
}
}
2.3.2.3 挑一个纵向的吧,我们再来看LinearLayout中的layoutVertical()方法。
void layoutVertical(int left, int top, int right, int bottom) {
//代码省略……
//每个子View经过测量,都确定了尺寸,即宽和高,那么布局的确定只需要 left和top的位置。
int childTop;
int childLeft;
//代码省略……
//遍历子View
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child == null) {
childTop += measureNullChild(i);
} else if (child.getVisibility() != GONE) {
//计算子View的测量宽 / 高值
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
//代码省略……
//确定自身子View的位置(递归调用子View的setChildFrame(),实际上是调用了子View的layout)
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
//代码省略……
}
}
}
2.3.2.4 我们再来看LinearLayout中的setChildFrame()方法。
private void setChildFrame(View child, int left, int top, int width, int height) {
//开始递归调用View的layout方法
child.layout(left, top, left + width, top + height);
}
又一次回到了View的layout方法,接下来就看View分发的layout。
2.3.3 View分发的layout
我们先来看View中的layout()方法。
public void layout(int l, int t, int r, int b) {
//代码省略……
// 当前视图的四个顶点
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
// 初始化四个顶点的值、判断当前View大小和位置是否发生了变化
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
//若视图的大小 & 位置发生变化。会重新确定该View所有的子View在父容器的位置:onLayout()
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
//重新确定该View所有的子View在父容器的位置
onLayout(changed, l, t, r, b);
//代码省略……
}
}
我们先来看View中的onLayout()方法。
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
空空如也,其实View的布局由父容器决定,所以空实现是正常的,当然也可以在自定义View中进行更改。
小编的扩展链接
优秀博客推荐
Android开发之自定义控件(二)---onLayout详解
自定义View Layout过程 - 最易懂的自定义View原理系列(3)