先上图:
最近产品需要一个编辑标签的页面如图所示。
需要实现可以输入标签,按回车键或逗号生成标签。并且可以移动光标删除标签,和插入标签。
并且单个标签有字符限制(英文算一个中文算两个字符)
直接上代码
activity的代码
package com.fan.tagtest;
import android.content.Context;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;
import android.text.Editable;
import android.text.InputFilter;
import android.text.Selection;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextPaint;
import android.text.TextWatcher;
import android.text.style.ImageSpan;
import android.util.TypedValue;
import android.view.KeyEvent;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class MainActivity extends AppCompatActivity {
List source = new ArrayList<>();
private static final int maxLength = 8;
private static final boolean isChinese2English = true;
public static void setEditTextInhibitInputSpeChat(EditText editText) {
InputFilter filter = new InputFilter() {
@Override
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
String chars = "\r\n\t ";
String speChat = "[" + chars + "]";
Pattern pattern = Pattern.compile(speChat);
Matcher matcher = pattern.matcher(source.toString());
if (matcher.find()) {
String str = source.toString();
char[] charArr = toCharArray(chars);
for (char c : charArr) {
str = str.replaceAll(new String(new char[]{c}), "");
}
return str;
} else return null;
}
};
InputFilter emojiFilter = new InputFilter() {
@Override
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
for (int index = start; index < end; index++) {
int type = Character.getType(source.charAt(index));
if (type == Character.SURROGATE) {
return "";
}
}
return null;
}
};
editText.setFilters(new InputFilter[]{filter, emojiFilter});
}
@NonNull
private static MyImageSpanImage[] getSortedImageSpans(final Editable text) {
MyImageSpanImage[] spans = text.getSpans(0, text.length(), MyImageSpanImage.class);
Arrays.sort(spans, new Comparator() {
@Override
public int compare(MyImageSpanImage o1, MyImageSpanImage o2) {
int start1 = text.getSpanStart(o1);
int start2 = text.getSpanStart(o2);
if (start1 > start2) {
return 1;
} else if (start1 < start2) {
return -1;
}
return 0;
}
});
return spans;
}
EditText etTags;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTitle("编辑标签");
setContentView(R.layout.activity_main);
etTags = (EditText) findViewById(R.id.et_tags);
findViewById(R.id.ll).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
setSoftInputVis(etTags, true);
}
});
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ok();
}
});
initHint();
init();
etTags.setOnKeyListener(new View.OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_NUMPAD_COMMA) {
initTags();
return true;
}
return false;
}
});
etTags.addTextChangedListener(new TextWatcher() {
int start;
int count;
int before;
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
this.start = start;
this.before = before;
this.count = count;
}
@Override
public void afterTextChanged(Editable editable) {
if (count <= 0) {
return;
}
etTags.removeTextChangedListener(this);
if (Selection.getSelectionEnd(editable) != start + count) {//如果光标位置和最新变化的结尾不相等说明不是正常输入的 全部作废处理
editable.replace(start, start + count, "");
} else {
onChange(editable);
}
etTags.addTextChangedListener(this);
}
private void onChange(Editable editable) {
String changeString = editable.subSequence(start, start + count).toString();
int sumOfComma = removeAllComma(editable);
count -= sumOfComma;
count -= delIfOverMax();
if (sumOfComma > 0) {
initTags();
return;
}
if (count == 0) {
return;
}
setTextSpan(editable);
}
private void setTextSpan(Editable editable) {
CharSequence string = editable.subSequence(start, start + count);
char[] chars = toCharArray(string);
int i = 0;
for (char c : chars) {
editable.setSpan(new MyImageSpanText(MainActivity.this, getImage(new String(new char[]{c}), false)), start + i, start + i + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
i++;
}
}
private int removeAllComma(Editable editable) {
int sum = 0;
while (editable.toString().contains(",")) {//删除所有逗号
int selEndIndex = Selection.getSelectionEnd(editable);
int i = editable.toString().indexOf(",");
editable.replace(i, i + 1, "");
Selection.setSelection(editable, selEndIndex - (selEndIndex >= i ? 1 : 0));
sum++;
}
return sum;
}
});
setEditTextInhibitInputSpeChat(etTags);
}
private int delIfOverMax() {
final Editable text = etTags.getText();
int selEndIndex = Selection.getSelectionEnd(text);
int lastEnd = 0;
MyImageSpanImage[] spans = getSortedImageSpans(text);
for (MyImageSpanImage span : spans) {
int start = text.getSpanStart(span);
int length = init(text, selEndIndex, lastEnd, start);
if (length > 0)
return length;
int end = text.getSpanEnd(span);
lastEnd = Math.max(end, lastEnd);
}
int length = text.length();
length = init(text, selEndIndex, lastEnd, length);
if (length > 0)
return length;
return 0;
}
private int init(Editable text, int selectedIndex, int start, int end) {
if (start >= end) {
return 0;
}
if (selectedIndex >= start && selectedIndex <= end) {//因为只会正常输入不会异常插入所以可以用光标位置判断某块是否超长
String blockString = text.subSequence(start, end).toString();
int length = calculateLength(blockString);
if (length > maxLength) {
//光标右边的字符串
String rightString = text.subSequence(selectedIndex, end).toString();
//光标左边的字符串
String leftString = text.subSequence(start, selectedIndex).toString();
//光标右边的字符串英文长度
int rightLength = calculateLength(rightString);
//光标左边的字符串英文长度
int leftLength = calculateLength(leftString);
//
char[] leftStringChars = toCharArray(leftString);
int okIndex = selectedIndex;
int charSum = 0;
int leaveLength = maxLength - rightLength;
for (int i = leftStringChars.length - 1; i >= 0; i--) {
char c = leftStringChars[i];
if ((c & 0xffff) <= 0xff) {
charSum += 1;
okIndex--;
} else {
charSum += 2;
okIndex--;
}
int nowLeaveLength = leftLength - charSum;
if (nowLeaveLength <= leaveLength) {
break;
}
}
text.replace(okIndex, selectedIndex, "");
return selectedIndex - okIndex;
}
} else {
setImageSpan(text, start, end);
}
return 0;
}
private void initHint() {
String string = "多个标签用逗号或回车分割";
SpannableString text = new SpannableString(string);
Bitmap image = getImage(string, true);
text.setSpan(new MyImageSpanText(this, image), 0, text.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
etTags.setHint(text);
}
private void initTags() {
final Editable text = etTags.getText();
int lastEnd = 0;
MyImageSpanImage[] spans = getSortedImageSpans(text);
for (MyImageSpanImage span : spans) {
int start = text.getSpanStart(span);
int end = text.getSpanEnd(span);
if (lastEnd < start) {//如果lastEnd 不等于 起始位置 中间的一段就是普通字符串
setImageSpan(text, lastEnd, start);
}
lastEnd = Math.max(end, lastEnd);
}
if (lastEnd != text.length()) {
setImageSpan(text, lastEnd, text.length());
}
}
private static char[] toCharArray(CharSequence str) {
if (str instanceof SpannableStringBuilder) {
str.length();
}
char[] charArray = new char[str.length()];
for (int i = 0; i < str.length(); i++) {
charArray[i] = str.charAt(i);
}
return charArray;
}
private void setImageSpan(Editable text, int start, int end) {
Bitmap tagImage = getTagImage(text.subSequence(start, end).toString());
for (MyImageSpanText span2 : text.getSpans(0, etTags.length(), MyImageSpanText.class)) {
text.removeSpan(span2);
}
text.setSpan(new MyImageSpanImage(this, tagImage), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
private void init() {
StringBuilder sb = new StringBuilder();
for (String str : source) {
str = str.trim().replaceAll(",", "").replaceAll("\n", "").replaceAll("\r", "").replaceAll("\t", "");
if (str.length() == 0) {
continue;
}
sb.append(str);
}
etTags.setText(sb.toString());
etTags.setSelection(etTags.length());
int length = 0;
Editable text = etTags.getText();
for (String str : source) {
str = str.trim().replaceAll(",", "").replaceAll("\n", "").replaceAll("\r", "").replaceAll("\t", "");
if (str.length() == 0) {
continue;
}
int strLength = str.length();
Bitmap tagImage = getTagImage(str);
text.setSpan(new MyImageSpanImage(this, tagImage), length, strLength + length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
length += strLength;
}
}
private void ok() {
initTags();
processImageSpan();
}
private void processImageSpan() {
final Editable text = etTags.getText();
MyImageSpanImage[] spans = getSortedImageSpans(text);
source.clear();
int lastEnd = 0;
for (MyImageSpanImage span : spans) {
int start = text.getSpanStart(span);
int end = text.getSpanEnd(span);
if (lastEnd == end || start == end) {
lastEnd = end;
continue;
}
lastEnd = end;
source.add(text.toString().substring(start, end));
}
//结果
this.source = source;
finish();
}
private Bitmap getImage(String string, boolean isHint) {
if (string == null) {
return null;
}
FrameLayout fl = new FrameLayout(this);
fl.setPadding(0, getPx(6), 0, getPx(6));
TextView tv = new TextView(this);
tv.setMaxLines(1);
tv.setLines(1);
tv.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14);
tv.setText(string);
tv.setTextColor(isHint ? 0xff888888 : 0xff444444);
fl.addView(tv);
return getBitmapViewByMeasure(fl, (int) getTextViewLength(tv, string), getPx(32));
}
// 计算出该TextView中文字的长度(像素)
public static float getTextViewLength(TextView textView, String text) {
if (text == null) {
return 0;
}
TextPaint paint = textView.getPaint();
// 得到使用该paint写上text的时候,像素为多少
float textLength = paint.measureText(text);
return textLength;
}
private Bitmap getTagImage(String string) {
if (string == null) {
return null;
}
string = string.replaceAll(",", "");
if (string.length() == 0) {
return null;
}
FrameLayout fl = new FrameLayout(this);
fl.setPadding(getPx(4), getPx(2), getPx(4), getPx(2));
TextView tv = new TextView(this);
tv.setPadding(getPx(4), getPx(4), getPx(4), getPx(4));
tv.setMaxLines(1);
tv.setLines(1);
tv.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14);
tv.setText(string);
tv.setTextColor(0xffffffff);
tv.setBackgroundResource(R.drawable.shape_edit_v1_tag);
fl.addView(tv);
return getBitmapViewByMeasure(fl, (int) getTextViewLength(tv, string) + getPx(16), getPx(32));
}
public static Bitmap getBitmapViewByMeasure(View view, int width, int height) {
//打开图像缓存
view.setDrawingCacheEnabled(true);
//必须调用measure和layout方法才能成功保存可视组件的截图到png图像文件
//测量View大小
if (height <= 0) {
view.measure(View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
} else if (height > 0) {
view.measure(View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY));
}
//发送位置和尺寸到View及其所有的子View
view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
Bitmap bitmap = null;
try {
//获得可视组件的截图
bitmap = view.getDrawingCache();
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}
private static class MyImageSpanText extends ImageSpan {
public MyImageSpanText(Context context, Bitmap b) {
super(context, b);
}
}
private static class MyImageSpanImage extends ImageSpan {
public MyImageSpanImage(Context context, Bitmap b) {
super(context, b);
}
}
public void setSoftInputVis(View view, boolean vis) {
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
if (vis) {
imm.showSoftInput(view, InputMethodManager.SHOW_FORCED);
} else {
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
}
public int getPx(int dp) {
return (int) (getResources().getDisplayMetrics().density * dp);
}
public int calculateLength(CharSequence c) {
if (!isChinese2English) {
return c.length();
}
double len = 0;
for (int i = 0; i < c.length(); i++) {
char cc = c.charAt(i);
if ((cc & 0xffff) <= 0xff) {
len += 0.5;
} else {
len++;
}
}
len = len * 2;
return (int) Math.round(len);
}
}
layout的代码
标签的背景图片
各位雅正!