前言
通过学习Android官方Layout的源码,可以帮助自己更好的理解Android的UI框架系统,了解内部便捷的封装好的API调用,有助于进行布局优化和自定义view实现等工作。这里把学习结果通过写博客进行总结,便于记忆,不至于将来遗忘。
本篇博客中源码基于Android 8.1
FrameLayout特点
FrameLayout是Android开发中最常用的Layout之一,它的特点就是子view们是层叠覆盖,后添加的子view会覆盖在其他子view之上。
源码探究
构造函数
FrameLayout的构造函数很简单,处理一个FrameLayout的属性measureAllChildren:
public FrameLayout(@NonNull Context context, @Nullable AttributeSet attrs,
@AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
final TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.FrameLayout, defStyleAttr, defStyleRes);
if (a.getBoolean(R.styleable.FrameLayout_measureAllChildren, false)) {
setMeasureAllChildren(true);
}
a.recycle();
}
measureAllChildren属性作用是设置是否在测量宽高时计算所有的子view。默认为false,即在measure阶段不会考虑状态为GONE的子view。
LayoutParams
FrameLayout中定义了静态内部类LayoutParams继承自MarginLayoutParams,含有一个成员gravity:
public int gravity = UNSPECIFIED_GRAVITY;
因此支持子view设置父布局对齐方式。
测量onMeasure
由于FrameLayout帧布局的特点,它不像LinearLayout和RelativeLayout需要权重或相对关系等,只需要遍历子view,依次调用child测量,然后设置自身尺寸即可。但是也有细分不同情况,当FrameLayout的MeasureSpec模式为EXACTLY时,只需按常规流程进行即可。当模式为AT_MOST时,意味着FrameLayout自身尺寸不明确,需要反向依赖最大的那个child的尺寸,因此在遍历的同时需要记录最大尺寸。若同时存在child的LayoutParams设置了MATCH_PARENT,则意味着child又依赖父布局尺寸,因此在FrameLayout设置完自身尺寸后,需要再对它们进行一次测量。
FrameLayout中的宽高测量分为两部分。上部分为计算子view中的最大宽高,从而设置自身宽高。下部分为二次计算在上部分中未能精确计算宽高的子view的宽高,此时传给child的测量规格是根据FrameLayout测量后的宽高生成。
一、上部分:计算最大宽高
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
// 该变量用于判断是否记录需要二次测量子view(若FrameLayout的父布局给定的测量规格中未指明精确的大小,则为true)。
final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
// mMatchParentChildren为一个ArrayList集合,用于缓存需要二次测量宽高的子view。
mMatchParentChildren.clear();
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
// 遍历子view
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
// 判断child是否为GONE,或设置了measureAllChildren属性。
if (mMeasureAllChildren || child.getVisibility() != GONE) {
// 调用child测量。
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
final LayoutParams lp = (LayoutParams) child.