自定义布局之树形布局(三):测量宽高

上一篇讲了总体的思路及自定义属性的声明,本篇讲解树形布局的宽高测量。前面讲到树形布局分为水平方向和竖直方向(类似于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项目里看看完整的代码。

下一篇讲解子控件在布局中的摆放。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值