您的位置:首页 > 移动开发 > Android开发

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

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

实现效果如下:



关于自动换行,有多种实现方式,这里基于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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息