上一篇讲了总体的思路及自定义属性的声明,本篇讲解树形布局的宽高测量。前面讲到树形布局分为水平方向和竖直方向(类似于LinearLayout),这里以水平方向为例来讲解。
总体思路
红色是根结点,绿色是子结点,其中子结点是竖直方向线性摆放的。而蓝色就是整个TreeLayout的宽高范围。
如此一来,TreeLayout的宽度如下:
(根结点的宽度 + 根结点的水平方向Margin) + (层级间隔LevelInterval) + (最宽子结点的宽度 + 它的水平方向Margin)
TreeLayout的高度分两种情况:
第一种情况:(根结点高度 + 根结点的竖直方向Margin ) < (子结点高度总和 + 它们的竖直方向Margin总和),高度如下:
子结点高度总和 + 它们的竖直方向Margin总和
第二种情况:(根结点高度 + 根结点的竖直方向Margin ) >(子结点高度总和 + 它们的竖直方向Margin总和),高度如下:
根结点高度 + 根结点的竖直方向Margin
测量流程
不同于普通的控件,布局控件在决定自身宽高前还得先参考各子控件的宽高。当布局自己使用了wrap_content,它的宽高就取决于子控件了。下面是测量的关键代码。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//1
measureChildren(widthMeasureSpec,heightMeasureSpec);
if(mTreeDirection == DIRECTION_LEFT_TO_RIGHT || mTreeDirection == DIRECTION_RIGHT_TO_LEFT){
measureHorizontal(widthMeasureSpec,heightMeasureSpec);
}else{
measureVertical(widthMeasureSpec,heightMeasureSpec);
}
}
代码1的measureChildren是ViewGroup自带的方法,调用它就能让布局的所有子控件进行一次测量(也就是调用了它们的onMeasure方法)。得知了各子控件的宽高后再来决定布局自身的宽高。
上面讲过水平树的宽度测量思路,下面就是具体的代码实现。注意代码1处,测量时不需要考虑GONE的子控件。
protected int measureWrapContentWidthHorizontal(){
int childCount = getChildCount();
if(childCount <= 0){
return 0;
}
View root = getChildAt(0);
int wrapWidth = 0;
LayoutParams rootLayoutParams = (LayoutParams) root.getLayoutParams();
wrapWidth += (rootLayoutParams.leftMargin + rootLayoutParams.rightMargin + root.getMeasuredWidth());
if(childCount == 1){
return wrapWidth;
}
int maxWidth = 0;
for(int i = 1;i < childCount;i++){
View child = getChildAt(i);
//1
if(child.getVisibility() == View.GONE){
continue;
}
LayoutParams layoutParams = (LayoutParams) child.getLayoutParams();
int widthNeeded = layoutParams.leftMargin + layoutParams.rightMargin + child.getMeasuredWidth();
maxWidth = Math.max(maxWidth,widthNeeded);
}
wrapWidth += maxWidth;
wrapWidth += mLevelInterval;
return wrapWidth;
}
而高度的测量也是差不多的,照着思路来实现就可以了,下面是高度测量代码。
protected int measureWrapContentHeightHorizontal(){
int childCount = getChildCount();
if(childCount <= 0){
return 0;
}
View root = getChildAt(0);
LayoutParams rootLayoutParams = (LayoutParams) root.getLayoutParams();
int rootHeightNeed = rootLayoutParams.topMargin + rootLayoutParams.bottomMargin + root.getMeasuredHeight();
int childrenHeightNeeded = 0;
for(int i = 1;i < childCount;i++){
View child = getChildAt(i);
if(child.getVisibility() == View.GONE){
continue;
}
LayoutParams layoutParams = (LayoutParams) child.getLayoutParams();
childrenHeightNeeded += (layoutParams.topMargin + layoutParams.bottomMargin + child.getMeasuredHeight());
}
return Math.max(rootHeightNeed,childrenHeightNeeded);
}
下面是水平树的onMeasure方法。还是那句话,如果布局使用了wrap_content,宽高取决于子控件。可如果布局的宽高是具体值,比如宽200dp,高300dp,那就和子控件没什么关系了。
protected void measureHorizontal(int widthMeasureSpec, int heightMeasureSpec){
int width = MeasureSpec.getSize(widthMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
mWrapWidth = measureWrapContentWidthHorizontal();
mWrapHeight = measureWrapContentHeightHorizontal();
if(widthMode == MeasureSpec.AT_MOST){
width = mWrapWidth;
}
if(heightMode == MeasureSpec.AT_MOST){
height = mWrapHeight;
}
setMeasuredDimension(MeasureSpec.makeMeasureSpec(width,widthMode),MeasureSpec.makeMeasureSpec(height,heightMode));
}
总的来说就是先测量子控件高度,如果布局使用了wrap_content,则计算出子控件们需要的尺寸大小,如果布局宽高为具体值,就不必考虑子控件的尺寸了。
最后
本篇文章对水平树的测量进行讲解,竖直方向的测量原理其实也是一样的,感兴趣的朋友可以到Github项目里看看完整的代码。
下一篇讲解子控件在布局中的摆放。