最近项目上有一个需求,需要实现一个类似于csdn选择标签的功能:
下面是最终的效果:
采用edittext图文混标的思路,在edittext中插入图片,这样可以实现一次添加,一次删除。
edittext中插入的图片可以是bitmap,也可以是drawable。因为drawable的可塑性比较强,而且tag中的内容是动态的,所以我选择了drawable来做处理
public class TagDrawable extends Drawable {
@Override
public void draw(@NonNull Canvas canvas) {
//TODO 画图
}
@Override
public void setAlpha(int i) {
//TODO 设置画笔的透明度
}
@Override
public void setColorFilter(@Nullable ColorFilter colorFilter) {
//TODO 设置画笔的颜色过滤器
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
}
一个tag包含了多种属性,比如文本颜色,大小,背景颜色等。。。。,所以需要一个实体类来保存
public class Tag {
private int bgColor;
private int textColor;
private String text;
private float textSize;
Tag(Builder builder) {
this.bgColor = builder.bgColor;
this.textColor = builder.textColor;
this.text = builder.text;
this.textSize = builder.textSize;
}
public void setBgColor(int bgColor) {
this.bgColor = bgColor;
}
public int getBgColor() {
return bgColor;
}
public void setTextColor(int textColor) {
this.textColor = textColor;
}
public int getTextColor() {
return textColor;
}
public void setText(String text) {
this.text = text;
}
public String getText() {
return text;
}
public void setTextSize(float textSize) {
this.textSize = textSize;
}
public float getTextSize() {
return textSize;
}
public static class Builder {
private int bgColor;
private int textColor;
private String text;
private float textSize;
public Builder bgColor(int bgColor) {
this.bgColor = bgColor;
return this;
}
public Builder textColor(int textColor) {
this.textColor = textColor;
return this;
}
public Builder text(String text) {
this.text = text;
return this;
}
public Builder textSize(int textSize) {
this.textSize = textSize;
return this;
}
public Tag build() {
return new Tag(this);
}
}
}
属性的名称都写的比较见名知意,这儿就不解释了。
参考这张图我们可以很清楚的知道其实需要绘制的就两个部分
1.背景(圆角矩形)
2.前端的文字
绘制之前需要先搞定以下几件事:
1.初始化画笔和数据(Tag实体类)
2.计算drawable的宽高
drawable的宽高是由文字的大小来决定的。
宽度要比文字的宽度多一点,因为两边要留一些空隙
高度是文字高度的1.5倍
//测量文字的宽高
Rect textRect = new Rect();
textPaint.getTextBounds(text, 0, text.length(), textRect);
//背景宽度要比文字宽度宽一些
width = textRect.width() + (textRect.width() / text.length() * 2);
//背景高度是文字高度的1.5倍
height = (int) (textRect.height() * 1.5);
3.文字居中显示
我们有时候会发现android的文字会自带“margin”,因为需要考虑到“h”,‘g’这种特殊一点的字符,如果直接设置Paint.Align.CENTER属性是不会居中的
具体的代码如下:
Paint.FontMetrics fontMetrics = textPaint.getFontMetrics();
float top = fontMetrics.top;//为基线到字体上边框的距离,即上图中的top
float bottom = fontMetrics.bottom;//为基线到字体下边框的距离,即上图中的bottom
int baseLineY = (int) (rectF.centerY() - top / 2 - bottom / 2);//基线中间点的y轴计算公式
canvas.drawText(text, rectF.centerX(), baseLineY, textPaint);
搞定了以上几个需求,代码写起来就比较简单了,我这儿贴一下完整的代码(不足的地方请指正)
public class TagDrawable extends Drawable {
private Paint bgPaint;
private Paint textPaint;
private int width;
private int height;
private String text;
private Tag tag;
TagDrawable(Tag tag) {
init(tag);
}
private void init(Tag tag) {
//初始化数据
this.tag = tag;
this.text = tag.getText();
if (TextUtils.isEmpty(text))
return;
int bgColor = tag.getBgColor() == 0 ? Color.CYAN : tag.getBgColor();
int textColor = tag.getTextColor() == 0 ? Color.BLACK : tag.getTextColor();
float textSize = tag.getTextSize() == 0 ? 40 : tag.getTextSize();
//背景画笔
bgPaint = new Paint();
bgPaint.setAntiAlias(true);
bgPaint.setStyle(Paint.Style.FILL);
bgPaint.setColor(bgColor);
//文字画笔
textPaint = new Paint();
textPaint.setAntiAlias(true);
textPaint.setColor(textColor);
textPaint.setTextAlign(Paint.Align.CENTER);
textPaint.setStyle(Paint.Style.FILL);
textPaint.setTextSize(textSize);
//测量文字的宽高
Rect textRect = new Rect();
textPaint.getTextBounds(text, 0, text.length(), textRect);
//背景宽度要比文字宽度宽一些
width = textRect.width() + (textRect.width() / text.length() * 2);
//背景高度是文字高度的1.5倍
height = (int) (textRect.height() * 1.5);
}
@Override
public void draw(@NonNull Canvas canvas) {
Rect rect = getBounds();
//画背景(圆角矩形框)
RectF rectF = new RectF(getBounds());
rectF.bottom = height;
//留出8px空白,以免添加多个tag时出现挤在一起的情况
rectF.right = rectF.right - 8;
canvas.drawRoundRect(rectF, 360, 360, bgPaint);
int count = canvas.save();
canvas.translate(rect.left, rect.top);
//画文字(居中)
Paint.FontMetrics fontMetrics = textPaint.getFontMetrics();
float top = fontMetrics.top;//为基线到字体上边框的距离,即上图中的top
float bottom = fontMetrics.bottom;//为基线到字体下边框的距离,即上图中的bottom
int baseLineY = (int) (rectF.centerY() - top / 2 - bottom / 2);//基线中间点的y轴计算公式
canvas.drawText(text, rectF.centerX(), baseLineY, textPaint);
canvas.restoreToCount(count);
}
@Override
public void setAlpha(int i) {
textPaint.setAlpha(i);
}
@Override
public void setColorFilter(@Nullable ColorFilter colorFilter) {
textPaint.setColorFilter(colorFilter);
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
@Override
public int getIntrinsicWidth() {
return width;
}
@Override
public int getIntrinsicHeight() {
return height;
}
@Override
public void setBounds(int left, int top, int right, int bottom) {
//设置drawable的宽高
super.setBounds(left, top, width, height);
}
/**
* 改变tag的状态
*
* @param tag
*/
public void invalidateDrawable(Tag tag) {
init(tag);
invalidateSelf();
}
public Tag getTag() {
return tag;
}
}
在插入图片的时候实际上也是对字符串的插入,如果这张图代表一个空字符串是没有意义的。我们先给定一张图实际代表的内容,然后再用drawable去替换
1.先生成一个随机内容的TagDrawable
/**
* 随机获取一个tag
* @return
*/
private TagDrawable getRandomTagDrawable() {
String text = Math.random() + "";
Tag tag = new Tag.Builder().bgColor(Color.RED).text(text).textColor(Color.BLACK).textSize(60).build();
TagDrawable tagDrawable = new TagDrawable(tag);
//drawable在使用前需要重新测量宽高
tagDrawable.setBounds(0, 0, tagDrawable.getIntrinsicWidth(), tagDrawable.getIntrinsicHeight());
return tagDrawable;
}
2.进行插入操作
/**
* 插入一个tag
* @param view
*/
public void insert(View view) {
TagDrawable randomTagDrawable = getRandomTagDrawable();
ImageSpan imageSpan = new ImageSpan(randomTagDrawable, ImageSpan.ALIGN_BASELINE);
String tempDrawableText = randomTagDrawable.getTag().getText();
SpannableString spannableString = new SpannableString(tempDrawableText);
//用drawable替换相应的文字
spannableString.setSpan(imageSpan, 0, tempDrawableText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
int index = editText.getSelectionStart();
Editable editableText = editText.getEditableText();
if (index < 0 || index > editText.getText().length()) {
editableText.append(spannableString);
} else {
editableText.insert(index, spannableString);
}
System.out.println("length==============" + editText.getText().toString().length());
}
如果我们在每次插入后获取内容的长度,你会发现drawable的长度就就是他所代表内容的长度
(完)