Android自定义组件-TagView

前言

简单的Tag组件

  • 实现了Tag的选中和非选中状态
  • 支持流式排布和固定列个数排布

具体代码

数据实体ZTagVo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class ZTagVo {
private Integer id;
private String text;
private boolean seleced = false;

public ZTagVo() {
}

public ZTagVo(Integer id, String text, boolean seleced) {
this.id = id;
this.text = text;
this.seleced = seleced;
}

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getText() {
return text;
}

public void setText(String text) {
this.text = text;
}

public boolean isSeleced() {
return seleced;
}

public void setSeleced(boolean seleced) {
this.seleced = seleced;
}
}

ZTagItem

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.support.annotation.IntDef;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

public class ZTagItem extends android.support.v7.widget.AppCompatTextView {

// 3种模式:圆角矩形、圆弧、直角矩形
public final static int MODE_ROUND_RECT = 1;
public final static int MODE_ARC = 2;
public final static int MODE_RECT = 3;

private Paint mPaint;

private int mTextSize = 14;
private int mTextColor = Color.parseColor("#ff333333");
// 背景色
private int mBgColor = Color.parseColor("#fff7f7f7");
// 边框颜色
private int mBorderColor = Color.parseColor("#fff7f7f7");

// 边框大小
private float mBorderWidth = 1;
// 边框角半径
private float mRadius;
// Tag内容
private int mTagIndex;

private ZTagVo mTagVo;
// 字体水平空隙
private int mHorizontalPadding;
// 字体垂直空隙
private int mVerticalPadding;
// 边框矩形
private RectF mRect;
// 调整标志位,只做一次
private boolean mIsAdjusted = false;
// 点击监听器
private OnTagClickListener mTagClickListener;
// 显示模式
private int mTagMode = MODE_ARC;


public ZTagItem(Context context, ZTagVo tagVo) {
super(context);
setText(tagVo.getText());
_init(context);
}

public ZTagItem(Context context, int tagIndex, ZTagVo tagVo, int textSize, int textColor, int bgColor, int borderColor) {
super(context);
mTagIndex = tagIndex;
mTextSize = textSize;
mTextColor = textColor;
mBgColor = bgColor;
mBorderColor = borderColor;
mTagVo = tagVo;
setText(tagVo.getText());
_init(context);
}

public ZTagItem(Context context, AttributeSet attrs) {
super(context, attrs);
_init(context);
}

public int dp2px(Context context, float dpVal) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
dpVal, context.getResources().getDisplayMetrics());
}

/**
* 初始化
*
* @param context
*/
private void _init(Context context) {
mHorizontalPadding = dp2px(context, 16);
mVerticalPadding = dp2px(context, 6);
mRect = new RectF();
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
// 设置字体占中
setGravity(Gravity.CENTER);
setPadding(mHorizontalPadding, mVerticalPadding, mHorizontalPadding, mVerticalPadding);
setTextSize(mTextSize);
setTextColor(mTextColor);
setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (mTagClickListener != null) {
mTagClickListener.onTagClick(mTagIndex, mTagVo);
}
}
});
setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
if (mTagClickListener != null) {
mTagClickListener.onTagLongClick(mTagIndex, mTagVo);
}
return true;
}
});
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
_adjustText();
}

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// 设置矩形边框
mRect.set(mBorderWidth, mBorderWidth, w - mBorderWidth, h
- mBorderWidth);
}

@Override
protected void onDraw(Canvas canvas) {
// 绘制背景
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(mBgColor);
float radius = mRadius;
if (mTagMode == MODE_ARC) {
radius = mRect.height() / 2;
} else if (mTagMode == MODE_RECT) {
radius = 0;
}
canvas.drawRoundRect(mRect, radius, radius, mPaint);
// 绘制边框
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(mBorderWidth);
mPaint.setColor(mBorderColor);
canvas.drawRoundRect(mRect, radius, radius, mPaint);

super.onDraw(canvas);
}

/**
* 调整内容,如果超出可显示的范围则做裁剪
*/
private void _adjustText() {
if (mIsAdjusted) {
return;
}
mIsAdjusted = true;
// 获取可用宽度
int availableWidth = ((ZTagView) getParent()).getAvailableWidth();
mPaint.setTextSize(getTextSize());

// 计算字符串长度
float textWidth = mPaint.measureText(mTagVo.getText());
// 如果可用宽度不够用,则做裁剪处理,末尾不3个.
if (textWidth + mHorizontalPadding * 2 > availableWidth) {
float pointWidth = mPaint.measureText(".");
// 计算能显示的字体长度
float maxTextWidth = availableWidth - mHorizontalPadding * 2
- pointWidth * 3;
float tmpWidth = 0;
StringBuilder strBuilder = new StringBuilder();
CharSequence mTagText = mTagVo.getText();
for (int i = 0; i < mTagText.length(); i++) {
char c = mTagText.charAt(i);
float cWidth = mPaint.measureText(String.valueOf(c));
// 计算每个字符的宽度之和,如果超过能显示的长度则退出
if (tmpWidth + cWidth > maxTextWidth) {
break;
}
strBuilder.append(c);
tmpWidth += cWidth;
}
// 末尾添加3个.并设置为显示字符
strBuilder.append("...");
setText(strBuilder.toString());
}
}

/******************************************************************/

public int getBgColor() {
return mBgColor;
}

public void setBgColor(int bgColor) {
mBgColor = bgColor;
}

public int getBorderColor() {
return mBorderColor;
}

public void setBorderColor(int borderColor) {
mBorderColor = borderColor;
}

public float getBorderWidth() {
return mBorderWidth;
}

public void setBorderWidth(float borderWidth) {
mBorderWidth = borderWidth;
}

public float getRadius() {
return mRadius;
}

public void setRadius(float radius) {
mRadius = radius;
}

public int getHorizontalPadding() {
return mHorizontalPadding;
}

public void setHorizontalPadding(int horizontalPadding) {
mHorizontalPadding = horizontalPadding;
setPadding(mHorizontalPadding, mVerticalPadding, mHorizontalPadding,
mVerticalPadding);
}

public int getVerticalPadding() {
return mVerticalPadding;
}

public void setVerticalPadding(int verticalPadding) {
mVerticalPadding = verticalPadding;
setPadding(mHorizontalPadding, mVerticalPadding, mHorizontalPadding,
mVerticalPadding);
}

/********************************* 点击监听 *********************************/
public void setTagClickListener(OnTagClickListener tagClickListener) {
mTagClickListener = tagClickListener;
}

/**
* 点击监听器
*/
public interface OnTagClickListener {
void onTagClick(int index, ZTagVo tagVo);

void onTagLongClick(int index, ZTagVo tagVo);
}

/********************************* 显示模式 *********************************/

public int getTagMode() {
return mTagMode;
}

public void setTagMode(@TagMode int tagMode) {
mTagMode = tagMode;
}

@IntDef({MODE_ROUND_RECT, MODE_ARC, MODE_RECT})
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.PARAMETER)
public @interface TagMode {
}
}

ZTagView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;

import java.util.List;

public class ZTagView extends ViewGroup {
// 点击监听器
private ZTagItem.OnTagClickListener mTagClickListener;
// Tag之间的垂直间隙
private int mVerticalInterval;
// Tag之间的水平间隙
private int mHorizontalInterval;

//标签的列数 设为0则根据标签自身的宽度换行
private int columnNum = 3;

private int mTextSize = 14;
private int mTextColor = Color.parseColor("#ff000000");
private int mTextSelectColor = Color.parseColor("#ff219EFF");
private int mBgColor = Color.parseColor("#ffF7F7F7");
private int mBgSelectColor = Color.parseColor("#ffEBF8FF");
private int mBorderColor = Color.parseColor("#ffF7F7F7");
private int mBorderSelectColor = Color.parseColor("#ffEBF8FF");


public ZTagView(Context context) {
this(context, null);
}

public ZTagView(Context context, AttributeSet attrs) {
this(context, attrs, -1);
}

public ZTagView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
_init(context);
}

public void init(int textSize, int textColor, int textSelectColor, int bgColor, int bgSelectColor, int borderColor, int borderSelectColor) {
mTextSize = textSize;
mTextColor = textColor;
mTextSelectColor = textSelectColor;
mBgColor = bgColor;
mBgSelectColor = bgSelectColor;
mBorderColor = borderColor;
mBorderSelectColor = borderSelectColor;
}

public void setTagClickListener(ZTagItem.OnTagClickListener tagClickListener) {
mTagClickListener = tagClickListener;
}

private int dp2px(Context context, float dpVal) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
dpVal, context.getResources().getDisplayMetrics());
}

private void _init(Context context) {
mHorizontalInterval = dp2px(context, 10);
L.e("mHorizontalInterval:" + mHorizontalInterval);
mVerticalInterval = dp2px(context, 6);
// 如果想要自己绘制内容,则必须设置这个标志位为false,否则onDraw()方法不会调用
setWillNotDraw(false);
setPadding(mHorizontalInterval, mVerticalInterval, mHorizontalInterval, mVerticalInterval);
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
// 计算可用宽度,为测量宽度减去左右padding值
int availableWidth = widthSpecSize - getPaddingLeft() - getPaddingRight();
// 测量子视图
measureChildren(widthMeasureSpec, heightMeasureSpec);
int childCount = getChildCount();
int tmpWidth = 0;
int measureHeight = 0;
int maxLineHeight = 0;
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
// 记录该行的最大高度
if (maxLineHeight == 0) {
maxLineHeight = child.getMeasuredHeight();
} else {
maxLineHeight = Math.max(maxLineHeight, child.getMeasuredHeight());
}

int width = child.getMeasuredWidth();
if (columnNum > 0) {
width = (int) ((double) (availableWidth - mHorizontalInterval * (columnNum - 1)) / columnNum);
}
// 统计该行TagView的总宽度
tmpWidth += width + mHorizontalInterval;
// 如果超过可用宽度则换行
if (tmpWidth - mHorizontalInterval > availableWidth) {
// 统计TagGroup的测量高度,要加上垂直间隙
measureHeight += maxLineHeight + mVerticalInterval;
maxLineHeight = child.getMeasuredHeight();
tmpWidth = 0;
}

}
// 统计TagGroup的测量高度,加上最后一行
measureHeight += maxLineHeight;
measureHeight += getPaddingTop() + getPaddingBottom();
// 设置测量宽高,记得算上padding
if (childCount == 0) {
setMeasuredDimension(0, 0);
} else if (heightSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(widthSpecSize, measureHeight);
} else {
setMeasuredDimension(widthSpecSize, heightSpecSize);
}
}

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childCount = getChildCount();
if (childCount <= 0) {
return;
}
int totalWidth = getMeasuredWidth();
int availableWidth = totalWidth - getPaddingLeft() - getPaddingRight();
// 当前布局使用的top坐标
int curTop = getPaddingTop();
// 当前布局使用的left坐标
int curLeft = getPaddingLeft();
int maxHeight = 0;
for (int i = 0; i < childCount; i++) {
View itemChildView = getChildAt(i);

if (maxHeight == 0) {
maxHeight = itemChildView.getMeasuredHeight();
} else {
maxHeight = Math.max(maxHeight, itemChildView.getMeasuredHeight());
}

int width = itemChildView.getMeasuredWidth();
if (columnNum > 0) {
width = (int) ((double) (availableWidth - mHorizontalInterval * (columnNum - 1)) / columnNum);
}
int height = itemChildView.getMeasuredHeight();
// 超过一行做换行操作
if (curLeft + width > totalWidth - getPaddingRight()) {
curLeft = getPaddingLeft();
// 计算top坐标,要加上垂直间隙
curTop += maxHeight + mVerticalInterval;
maxHeight = itemChildView.getMeasuredHeight();
}
// 设置子视图布局
itemChildView.layout(curLeft, curTop, curLeft + width, curTop + height);
// 计算left坐标,要加上水平间隙
curLeft += width + mHorizontalInterval;
}
}

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

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}


/******************************************************************/
public void cleanTags() {
removeAllViews();
postInvalidate();
}

/**
* 添加Tag
*
* @param tag tag内容
*/
public void addTag(ZTagVo tag) {
int tagIndex = getChildCount();
ZTagItem tagItem = null;
if (tag.isSeleced()) {
tagItem = new ZTagItem(
getContext(),
tagIndex,
tag,
mTextSize,
mTextSelectColor,
mBgSelectColor,
mBorderSelectColor
);

} else {
tagItem = new ZTagItem(
getContext(),
tagIndex,
tag,
mTextSize,
mTextColor,
mBgColor,
mBorderColor
);
}
tagItem.setTagClickListener(new ZTagItem.OnTagClickListener() {
@Override
public void onTagClick(int index, ZTagVo tagVo) {
if (mTagClickListener != null) {
mTagClickListener.onTagClick(index, tagVo);
}
}

@Override
public void onTagLongClick(int index, ZTagVo tagVo) {
if (mTagClickListener != null) {
mTagClickListener.onTagLongClick(index, tagVo);
}
}
});

addView(tagItem);

}

public void addTags(List<ZTagVo> tags) {
for (ZTagVo tag : tags) {
addTag(tag);
}
}

public void setTags(List<ZTagVo> tags) {
cleanTags();
addTags(tags);
}

public int getAvailableWidth() {
return getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
}
}

使用

xml

1
2
3
4
5
6
<com.xhkjedu.sxb.widget.tag.ZTagView
android:id="@+id/paixv_tagview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="10dp"
android:paddingRight="10dp">

调用

1
2
3
4
5
6
7
8
9
10
paixv_tags.add(ZTagVo(1, "综合排序", true))
paixv_tags.add(ZTagVo(2, "浏览量排序", false))
paixv_tagview.setTags(paixv_tags)
paixv_tagview.setTagClickListener(object : ZTagItem.OnTagClickListener {
override fun onTagLongClick(index: Int, tagVo: ZTagVo?) {
}

override fun onTagClick(index: Int, tagVo: ZTagVo?) {
}
})