文字后面紧跟标签SpareLayout

来一波需求

有这样一种需求,前面一个View,后面要带着几个标签,如果前面的View不太大,那么标签紧跟标签向前移动(后三个条目),如果前面的View就很大,余下来足够的空间放标签(第一个条目)


�酷我音乐的标签.png

有一个想法

自己定义一个ViewGroup,类似于水平布局的LinearLayout,优先测量后面的View,最后将剩余的空间给第一个View,在layout的时候从左向右摆放,最终实现效果,给新的ViewGroup起名叫SpareLayout

  • 测量的时候从最后一个子View开始测量
  • 累加后面所有View的宽度,将剩余空间给第一个View
  • 高度使用最大高度的View
  • layout时从左向右摆放测量好的View

做一点实现

按上面的思路重写onMeasure和onLayout方法

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    //余下的空间
    int spareWidth = 0;
    //最大高度
    int maxHeight = 0;
    //子view从后向前测量
    for (int i = getChildCount() - 1; i > 0; i--) {
        View child = getChildAt(i);
        //不可见的跳过
        if (child.getVisibility() == GONE) {
            continue;
        }
        //测量一个子View,并处理padding,margin
        measureChild(child, widthMeasureSpec, heightMeasureSpec);
        FrameLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams();
        int marginWidth = lp.leftMargin + lp.rightMargin;
        int marginHeight = lp.topMargin + lp.bottomMargin;
        spareWidth += child.getMeasuredWidth() + marginWidth;
        maxHeight = Math.max(maxHeight, child.getMeasuredHeight() + marginHeight);
    }
    //最后来测量第一个View,使用的方式是AT_MOST,宽度是剩余空间
    View firstChild = getChildAt(0);
    FrameLayout.LayoutParams lp = (LayoutParams) firstChild.getLayoutParams();
    int marginWidth = lp.leftMargin + lp.rightMargin;
    int marginHeight = lp.topMargin + lp.bottomMargin;
    int paddingWidth = getPaddingLeft() + getPaddingRight();
    int paddingHeight = getPaddingTop() + getPaddingBottom();
    int firstViewWidthSpec =
         MeasureSpec.makeMeasureSpec(widthSize - spareWidth - marginWidth - paddingWidth,
            MeasureSpec.AT_MOST);
    measureChild(firstChild, firstViewWidthSpec, heightMeasureSpec);
    maxHeight = Math.max(firstChild.getMeasuredHeight() + marginHeight, maxHeight);
    //储存测量结果
    setMeasuredDimension(spareWidth + firstChild.getMeasuredWidth() + paddingWidth,
        maxHeight + paddingHeight);
}

测量得到了每一个View应该的大小,接下来就是摆放所有的子View,看过来onLayout()

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    int left = 0;
    //从左向右排放View
    for (int i = 0; i < getChildCount(); i++) {
        View child = getChildAt(i);
        if (child.getVisibility() == GONE) {
            continue;
        }
        FrameLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams();
        int leftStart = left + lp.leftMargin + getPaddingLeft();
        int topStart;

        //处理vertical的gravity
        final int verticalGravity = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
        switch (verticalGravity) {
            case Gravity.TOP:
                //从上向下计算
                topStart = lp.topMargin + getPaddingTop();
                break;
            case Gravity.CENTER_VERTICAL:
                //vertical的居中,是指view居中(除去这个SpareLayout的padding和子View的margin居中)
                topStart = (t + getPaddingTop() + lp.topMargin +    //可以放view的空间上边
                    b - getPaddingBottom() - lp.bottomMargin        //可以放view的空间下边
                    - child.getMeasuredHeight()) / 2                //中心线
                    - t;                                            //计算出view的上边
                break;
            case Gravity.BOTTOM:
                //从下向上算的
                topStart =
                    b - lp.bottomMargin - getPaddingBottom() - child.getMeasuredHeight() - t;
                break;
            default:
                //默认是在上面
                topStart = lp.topMargin + getPaddingTop();
        }
        child.layout(leftStart, topStart, leftStart + child.getMeasuredWidth(),
            topStart + child.getMeasuredHeight());
        //累加左边已经使用的空间
        left += child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
    }
}

这样实现的效果:


�文本比较长的时候.png

�文本比较短的情况.png

提一些Tips

如果后面几个标签已经很大的情况没有处理
以前为这个效果试验了各种方式,跑包的时间都比停下来写这个控件时间长得多,以后注意不要再做这样的事
使用linearLayout的weight属性并没有成功

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 127,645评论 18 546
  • 在Android知识体系中,Android系统提供了一个GUI库,里面有很多原生控件,但是很多时候我们并不满足于系...
    ForeverCy阅读 4,125评论 7 49
  • 在《世间事》专题改版成《故事》专题之际,也迎来了《故事烩》第十六期。 文友们在阅读《故事烩》,参与活动的同时,也惊...
    孤独一刀阅读 134评论 17 19
  • 刚结婚时,两人的爱情就像玫瑰花一样,炽热,激情。 有了孩子以后,两人的关系像狂风中的纤草,震颤,摇摆。 老了以后,...
    之末阅读 56评论 3 3
  • 古殿空山裹,名王有旧茔。秦陵和汉寝,不及此幽情 仿佛一个垂垂老矣的剑客,白发横额,皱纹交错,却掩不住那提剑时...
    shindowy阅读 156评论 0 1