天天看点

基于RelativeLayout实现自动换行标签控件

在开发中,我们有时会遇到显示标签的功能。比如做社交app,用户有标签;做电商app,商品也同样有标签......显示标签时,我们需要一次展示出多个标签,但是单个标签的字数可能不同,当一行不足以容纳时,需要换行显示。系统自带控件无法实现该效果,所以我们需要自定义控件来解决。

实现效果如下:

基于RelativeLayout实现自动换行标签控件

关于自动换行,有多种实现方式,这里基于RelativeLayout来实现。

实现思路:

基于RelativeLayout实现自动换行标签控件

有了思路,下面进入代码实现环节。

首先,我们自定义TagLayout继承自RelativeLayout。

public class TagLayout extends RelativeLayout {
}
           

判断单行空间是否够用,需要用到TagLayout的宽度,我们在onSizeChanged()方法中获取。

private int mWidth;

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    mWidth = w;
}
           

在RelativeLayout中,动态设置child的位置,需要使用到它的addRule()方法。

/**
 * Adds a layout rule to be interpreted by the RelativeLayout. Use this for
 * verbs that take a target, such as a sibling (ALIGN_RIGHT) or a boolean
 * value (VISIBLE).
 *
 * @param verb One of the verbs defined by
 *        {@link android.widget.RelativeLayout RelativeLayout}, such as
 *         ALIGN_WITH_PARENT_LEFT.
 * @param anchor The id of another view to use as an anchor,
 *        or a boolean value (represented as {@link RelativeLayout#TRUE}
 *        for true or 0 for false).  For verbs that don't refer to another sibling
 *        (for example, ALIGN_WITH_PARENT_BOTTOM) just use -1.
 */
public void addRule(int verb, int anchor) {
    // ...
}
           

addRule()方法的使用示例:

layoutParams.addRule(RelativeLayout.BELOW, 1);表示将该子View添加到viewId==1的子View的下边。

layoutParams.addRule(RelativeLayout.ALIGN_TOP, 1);表示该子View与viewId==1的子View顶部对齐。

下面进入该控件的核心方法,将所有的标签逐个添加进来。代码中附带详细注释。

private void addTags() {
    removeAllViews();

    // 初始化各变量
    int total = getPaddingLeft() + getPaddingRight();// 计算单行的宽度
    int index = 1;// child的索引,同时也是child的viewId
    int bottomAnchor = 1;// 添加到底部的child的id
    int alignTopAnchor = 1;// 与之顶部对齐的child的id
    for (String data : mData) {
        // 初始化child视图
        View child = mInflater.inflate(R.layout.tag, null);
        // setId(index)这行代码很重要,将child的索引设为其id,在下面的代码中使用
        child.setId(index);
        TextView textView = (TextView) child.findViewById(R.id.text);
        textView.setText(data);

        // 测量child的宽度
        child.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
        int childWidth = child.getMeasuredWidth();

        // 为每一个child添加底部间距,这样就形成了行间距
        LayoutParams childParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT);
        childParams.bottomMargin = mChildBottomMargin;

        // 如果单行已有的所有child宽度 + 本次需要添加的child的宽度和左间距 > 整个TagLayout的宽度
        // 此时需要将child换行添加
        if (total + mChildLeftMargin + childWidth > mWidth) {
            // 设置LayoutParams:添加到viewId==bottomAnchor的child的底部
            childParams.addRule(RelativeLayout.BELOW, bottomAnchor);
            // total被重新初始化
            total = getPaddingLeft() + getPaddingRight();
            // 给bottomAnchor和alignTopAnchor赋值,后面会使用到
            bottomAnchor = index;
            alignTopAnchor = index;
        } else {// 此时在同一行添加child
            // 设置LayoutParams:与viewId==alignTopAnchor的child顶部对齐
            childParams.addRule(RelativeLayout.ALIGN_TOP, alignTopAnchor);
            // 如果该child不是本行的第一个
            if (index != alignTopAnchor) {
                // 设置LayoutParams:将child添加到前一个child的右侧
                childParams.addRule(RelativeLayout.RIGHT_OF, index - 1);
                // 设置LayoutParams:为child添加左边距,保持child之间的空隙
                childParams.leftMargin = mChildLeftMargin;
                // total累加child的左间距
                total += mChildLeftMargin;
            }
        }
        // 使用上面设置好的childParams,添加child
        addView(child, childParams);
        // total累加child的宽度
        total += childWidth;
        // index索引+1,进入下一轮循环
        index++;
    }
}
           

到这里,核心代码已经完成。再附上该控件的完整代码。

package net.csdn.blog.ruancoder;

import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.RelativeLayout;
import android.widget.TextView;

import java.util.List;

public class TagLayout extends RelativeLayout {
    private LayoutInflater mInflater;

    private boolean mInited = false;

    private int mWidth;
    private int mChildLeftMargin;
    private int mChildBottomMargin;

    private List<String> mData;

    public TagLayout(Context context) {
        super(context);
        init(context);
    }

    public TagLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public TagLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    private void init(Context context) {
        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        mChildLeftMargin = context.getResources().getDimensionPixelSize(R.dimen.tag_leftmargin);
        mChildBottomMargin = context.getResources().getDimensionPixelSize(R.dimen.tag_bottommargin);
        getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                if (!mInited) {
                    mInited = true;
                    addTags();
                }
            }
        });
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        mWidth = w;
    }

    public void setData(List<String> data) {
        this.mData = data;
        addTags();
    }

    private void addTags() {
        if (!mInited) {
            return;
        }

        if (mData == null || mData.isEmpty()) {
            return;
        }

        removeAllViews();

        int total = getPaddingLeft() + getPaddingRight();
        int index = 1;
        int bottomAnchor = 1;
        int alignTopAnchor = 1;
        for (String data : mData) {
            View child = mInflater.inflate(R.layout.tag, null);
            child.setId(index);
            TextView textView = (TextView) child.findViewById(R.id.text);
            textView.setText(data);

            child.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
            int childWidth = child.getMeasuredWidth();

            LayoutParams childParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT);
            childParams.bottomMargin = mChildBottomMargin;

            if (total + mChildLeftMargin + childWidth > mWidth) {
                childParams.addRule(RelativeLayout.BELOW, bottomAnchor);
                total = getPaddingLeft() + getPaddingRight();
                bottomAnchor = index;
                alignTopAnchor = index;
            } else {
                childParams.addRule(RelativeLayout.ALIGN_TOP, alignTopAnchor);
                if (index != alignTopAnchor) {
                    childParams.addRule(RelativeLayout.RIGHT_OF, index - 1);
                    childParams.leftMargin = mChildLeftMargin;
                    total += mChildLeftMargin;
                }
            }
            addView(child, childParams);
            total += childWidth;
            index++;
        }
    }
}
           

最后附上完整工程的代码下载链接:

http://download.csdn.net/detail/ruancoder/9594176