最近在做项目时候遇到了标签布局问题,当时我首选的是用流式布局TagFlowLayout的控件,可是发现不能满足我项目的需求,于是翻看了一下网页写了一个用配合ChekBox来实现多选标签的布局,在这写出来,希望对其他伙伴们有些帮助!
需取效果:
首先:我们需要一个实体类TagBean,确保有id,和name(显示的内容)
public class TagBean {
private String id;
private String name;
public void setId(String id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public TagBean(String id, String name) {
this.id = id;
this.name = name;
}
}
然后就可以写我们的自定义布局了,重写ViewGroup,采用onDraw,onMeasure,onLayout等方法类似于流式布局,熟悉流式布局的小伙伴应该so easy,这里就不说太多了,直接上代码:
public class LabelLayout extends ViewGroup {
private int mMaxCheckCount = Integer.MAX_VALUE;
/**
* 竖直方向间距, default is 8.0dp.
*/
private int horizontalSpacing;
/**
* 水平方向间距, default is 4.0dp.
*/
private int verticalSpacing;
//whether or not to draw the divider between labels at horizon.
private boolean enableDivider = false; //是否允许显示分割线 默认不显示
private int dividerColor = 0xffECECEC;
private float dividerHeight;
private int checkboxLayoutId;
//nark checked labels.
private Map<String, Boolean> labelcheckMap;
//mark the first position in a row.
private Set<Integer> rowPositons = new HashSet<>();
//The paint to draw the divider.
Paint dividerPaint;
public LabelLayout(Context context) {
this(context, null);
}
public LabelLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public LabelLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setWillNotDraw(false);
labelcheckMap = new HashMap<>();
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LabelLayout, defStyleAttr, R.style.LabelLayoutDefault);
try {
horizontalSpacing = (int) a.getDimension(R.styleable.LabelLayout_label_horizontalSpacing, dp2px(8.0f));
verticalSpacing = (int) a.getDimension(R.styleable.LabelLayout_label_verticalSpacing, dp2px(4.0f));
checkboxLayoutId = a.getResourceId(R.styleable.LabelLayout_label_checkboxLayout, R.layout.view_label_common);
enableDivider = a.getBoolean(R.styleable.LabelLayout_label_enableDivider, false);
dividerHeight = a.getDimension(R.styleable.LabelLayout_label_dividerHeight, dp2px(2));
dividerColor = a.getColor(R.styleable.LabelLayout_label_dividerColor, 0xffECECEC);
} finally {
a.recycle();
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (enableDivider) {
if (dividerPaint == null) {
dividerPaint = new Paint();
dividerPaint.setAntiAlias(true);
dividerPaint.setColor(dividerColor);
dividerPaint.setStyle(Paint.Style.FILL);
}
for (Integer top : rowPositons) {
if (top != 0) {
//draw lines between labels.
canvas.drawRect(0, top - dividerHeight / 2, getMeasuredWidth(), top + dividerHeight / 2, dividerPaint);
}
}
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
measureChildren(widthMeasureSpec, heightMeasureSpec);
int width = 0;
int height = 0;
int row = 0; // The row counter.
int rowWidth = 0; // Calc the current row width.
int rowMaxHeight = 0; // Calc the max tag height, in current row.
final int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
if (child.getVisibility() != GONE) {
rowWidth += childWidth;
if (rowWidth > widthSize) { // Next line.
rowWidth = childWidth; // The next row width.
height += rowMaxHeight + verticalSpacing;
rowMaxHeight = childHeight; // The next row max height.
row++;
} else { // This line.
rowMaxHeight = Math.max(rowMaxHeight, childHeight);
}
rowWidth += horizontalSpacing;
}
// System.out.println("measured height:" + height);
rowPositons.add(height - verticalSpacing / 2);
}
// Account for the last row height.
height += rowMaxHeight;
// Account for the padding too.
height += getPaddingTop() + getPaddingBottom();
// If the tags grouped in one row, set the width to wrap the tags.
if (row == 0) {
width = rowWidth;
width += getPaddingLeft() + getPaddingRight();
} else {// If the tags grouped exceed one line, set the width to match the parent.
width = widthSize;
}
setMeasuredDimension(widthMode == MeasureSpec.EXACTLY ? widthSize : width,
heightMode == MeasureSpec.EXACTLY ? heightSize : height);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int parentLeft = getPaddingLeft();
final int parentRight = r - l - getPaddingRight();
final int parentTop = getPaddingTop();
final int parentBottom = b - t - getPaddingBottom();
int childLeft = parentLeft;
int childTop = parentTop;
int rowMaxHeight = 0;
final int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
if (child.getVisibility() != GONE) {
if (childLeft + width > parentRight) { // Next line
childLeft = parentLeft;
childTop += rowMaxHeight + verticalSpacing;
rowMaxHeight = height;
} else {
rowMaxHeight = Math.max(rowMaxHeight, height);
}
child.layout(childLeft, childTop, childLeft + width, childTop + height);
childLeft += width + horizontalSpacing;
}
}
}
/**
* set the default labels that you wanna to add into LabelLayout.
*
* @param labels A collection contains objects that implement ILabel interface.
*/
public void setLabels(List<TagBean> labels) {
labelcheckMap.clear();
removeAllViews();
if (labels == null || labels.size() == 0) return;
for (final TagBean label : labels) {
final CheckBox tagView = (CheckBox) View.inflate(getContext(), checkboxLayoutId, null);
tagView.setText(label.getName());
addView(tagView);
tagView.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {//选中时添加到map
if (labelcheckMap.size() > mMaxCheckCount - 1) {
if (checkListener != null) {
checkListener.onBeyondMaxCheckCount();
}
tagView.setChecked(false);
} else {
labelcheckMap.put(label.getId(), true);
if (checkListener != null) {
checkListener.onCheckChanged(label, true);
Drawable drawable = getResources().getDrawable(R.mipmap.select_tag);
tagView.setCompoundDrawablePadding(10);
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
//设置选中头部图片
tagView.setCompoundDrawables(drawable, null, null, null);
}
}
} else {//否则及时清理map
if (labelcheckMap.containsKey(label.getId())) {
labelcheckMap.remove(label.getId());
if (checkListener != null) {
checkListener.onCheckChanged(label, false);
//设置未选中头部图片
tagView.setCompoundDrawables(null, null, null, null);
}
}
}
}
});
}
}
/**
* set the maximum numbers of checked labels.
*/
public void setMaxCheckCount(int count) {
mMaxCheckCount = count;
}
/**
* Get the current selected tag number.
*/
public int getCheckedLabelsCount() {
int count = 0;
for (Map.Entry<String, Boolean> m : labelcheckMap.entrySet()) {
if (m.getValue()) {
count++;
}
}
return count;
}
public List<String> getCheckedLabelIds() {
List<String> chechedLabelIds = new ArrayList<>();
for (Map.Entry<String, Boolean> m : labelcheckMap.entrySet()) {
if (m.getValue()) {
chechedLabelIds.add(m.getKey());
}
}
return chechedLabelIds;
}
/**
* To serialize checked-label ids as json, make benefit for use.
*
* @return json string
*/
public String getCheckedIdsAsJson() {
List<String> chechedId = new ArrayList<>();
for (Map.Entry<String, Boolean> m : labelcheckMap.entrySet()) {
if (m.getValue()) {
chechedId.add(m.getKey());
}
}
return new JSONArray(chechedId).toString();
}
private float dp2px(float dp) {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,
getResources().getDisplayMetrics());
}
//监听方法
private OnCheckChangeListener checkListener;
public interface OnCheckChangeListener {
void onCheckChanged(TagBean label, boolean isChecked);
void onBeyondMaxCheckCount();
}
public void setOnCheckChangedListener(OnCheckChangeListener checkListener) {
this.checkListener = checkListener;
}
上面是布局类代码我全展示出来,
还有就是xml文件的代码:
ChekBox的:
<?xml version="1.0" encoding="utf-8"?>
<CheckBox xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="32dp"
android:layout_margin="10dp"
android:background="@drawable/label_selector"
android:button="@null"
android:paddingBottom="4dp"
android:paddingLeft="13dp"
android:paddingRight="13dp"
android:paddingTop="4dp"
android:text="hello"
android:textColor="@drawable/label_gray_selector"
android:textSize="16sp" />
背景选中效果代码:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="true">
<shape android:shape="rectangle">
<corners android:radius="6dp" />
<solid android:color="#fff" />
</shape>
</item>
<item>
<shape android:shape="rectangle">
<corners android:radius="6dp" />
<stroke android:width="1dp" android:color="#fff" />
</shape>
</item>
</selector>
字体选中效果:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="#FF9400" android:state_checked="true" />
<item android:color="#434343" />
</selector>
主布局 :
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.l.tag_label.MainActivity">
<com.example.l.tag_label.LabelLayout
android:id="@+id/label_me"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
>
</com.example.l.tag_label.LabelLayout>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="bt"
android:text="点击"/>
</LinearLayout>
然后就是activity中的代码了,
首先添加数据:
private ArrayList<TagBean> initList() {
ArrayList<TagBean> list = new ArrayList<>();
for (int i = 0; i < 8; i++) {
TagBean bean = new TagBean(i + "", "hello" + i);
list.add(bean);
}
return list;
}
然后再把数据传过去,
tab.setLabels(list);// 设置数据
tab.setMaxCheckCount(4);// 设置最大选中数量
// 点击选中取消监听
tab.setOnCheckChangedListener(new LabelLayout.OnCheckChangeListener() {
@Override
public void onCheckChanged(TagBean label, boolean isChecked) {
String name = label.getName();
Toast.makeText(MainActivity.this, isChecked + "==" + name, Toast.LENGTH_SHORT).show();
Log.d("dd", name + "");
// 在这块可以自己写个集合储存一下
if (isChecked)
name_liat.add(name);
else
name_liat.remove(name);
}
@Override
public void onBeyondMaxCheckCount() {
}
});
最后执行自己的button按钮监听方法就可以把集合里面的东西拿出来了。
public void bt(View view) {
for (int i = 0; i <name_liat.size() ; i++) {
Log.d("ee", name_liat.get(i).toString() + "");
}
}
写的够详细,简单了!如果感觉有帮助,请点赞!!^_^
效果图: