为什么学习
自从学了Android自定义控件的一些知识,总是处于似懂非懂状态,说都说了上来,自己在项目里封装了一些自定义控件,但是还是缺乏一个很直观的了解。所以去了解学习下Android是如何封装控件的,就从简单的入手,分析下LinearLayout是如何实现的
什么是LinearLayout
作为最基础的布局,所以从事过Android开发的同学都应该非常了解
中文解释应该叫做线性布局,相比如RelativeLayout,LinearLayout更简单,在没有weight的情况也每次只要测量一次就够,而RelativeLayout每次都需要测量两次
一些LinearLayout需要注意的属性
orientation 纵向排布或者水平排布
weight 权重,用于分配LinearLayout剩下的空间(会详细介绍)
measureWithLargestChild 这个属性不常见,如果赋值为true的话,所有
weight子View都会采用最大View的最小尺寸(为什么Android要设计这个属性,我也不是很理解)
源码分析
一般所有控件类的源码,都会从 measure, layout和draw3个方法入手,查看他们的回调函数onMeasure, onLayout和onDraw
只要明白这3个流程,一般控件的整个实现也就明白了
LinearLayout作为一个ViewGroup的子类,主要作为一个布局容器出现,所以我们需要重点查看写onMeasure方法
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
从上面代码看到,LinearLayout的onMeasure方法实现非常简洁,根据布局方向分为measureVertical和measureHorizontal。下后面的onLayout和onDraw也是如此。鉴于内部实现基本一模一样,我在这只分析纵向的实现
/**
* Measures the children when the orientation of this LinearLayout is set
* to {@link #VERTICAL}.
*
* @param widthMeasureSpec Horizontal space requirements as imposed by the parent.
* @param heightMeasureSpec Vertical space requirements as imposed by the parent.
*
* @see #getOrientation()
* @see #setOrientation(int)
* @see #onMeasure(int, int)
*/
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
mTotalLength = 0;
int maxWidth = 0;
int childState = 0;
int alternativeMaxWidth = 0;
int weightedMaxWidth = 0;
boolean allFillParent = true;
float totalWeight = 0;
final int count = getVirtualChildCount();
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
boolean matchWidth = false;
boolean skippedMeasure = false;
final int baselineChildIndex = mBaselineAlignedChildIndex;
final boolean useLargestChild = mUseLargestChild;
int largestChildHeight = Integer.MIN_VALUE;
// See how tall everyone is. Also remember max width.
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
if (child == null) {
mTotalLength += measureNullChild(i);
continue;
}
if (child.getVisibility() == View.GONE) {
i += getChildrenSkipCount(child, i);
continue;
}
if (hasDividerBeforeChildAt(i)) {
mTotalLength += mDividerHeight;
}
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
totalWeight += lp.weight;
if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {
// Optimization: don't bother measuring children who are going to use
// leftover space. These views will get measured again down below if
// there is any leftover space.
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
skippedMeasure = true;
} else {
int oldHeight = Integer.MIN_VALUE;
if (lp.height == 0 && lp.weight > 0) {
// heightMode is either UNSPECIFIED or AT_MOST, and this
// child wanted to stretch to fill available space.
// Translate that to WRAP_CONTENT so that it does not end up
// with a height of 0
oldHeight = 0;
lp.height = LayoutParams.WRAP_CONTENT;
}
// Determine how big this child would like to be. If this or
// previous children have given a weight, then we allow it to
// use all available space (and we will shrink things later
// if needed).
measureChildBeforeLayout(
child, i, widthMeasureSpec, 0, heightMeasureSpec,
totalWeight == 0 ? mTotalLength : 0);
if (oldHeight != Integer.MIN_VALUE) {
lp.height = oldHeight;
}
final int childHeight = child.getMeasuredHeight();
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
lp.bottomMargin + getNextLocati