文字后面紧跟标签SpareLayout

2017-11-14  本文已影响43人  常强儿

来一波需求

有这样一种需求,前面一个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属性并没有成功

上一篇 下一篇

猜你喜欢

热点阅读