android 编辑标签,在一个文本框输入标签回车添加退格删除
来源:互联网 发布:python中字符串的 编辑:程序博客网 时间:2024/05/16 11:12
先上图:
最近产品需要一个编辑标签的页面如图所示。
需要实现可以输入标签,按回车键或逗号生成标签。并且可以移动光标删除标签,和插入标签。
并且单个标签有字符限制(英文算一个中文算两个字符)
直接上代码
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<String> 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<MyImageSpanImage>() { @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的代码
<?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" android:id="@+id/ll"> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:text="完成" /> <EditText android:id="@+id/et_tags" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="10dp" android:background="#0000" android:inputType="textMultiLine" android:textSize="14sp" /></LinearLayout>
标签的背景图片
<?xml version="1.0" encoding="utf-8"?><shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <corners android:radius="3dp" /> <solid android:color="#ffd800" /></shape>
各位雅正!
阅读全文
0 0
- android 编辑标签,在一个文本框输入标签回车添加退格删除
- Jquery,添加/删除一行输入标签
- 窗体上有两个文本框:一个文本框中最多输入字符6个;一个文本框中输入任何内容都显示*号。再添加一个按钮、2个单选按钮。实现单击按钮后,根据单选按钮,将对应文本框中内容显示在标签
- android开发之添加标签与删除标签
- Android删除添加标签(FlowLayout案例)
- Angular添加一个文本框的回车响应
- 文本框编辑多按几次删除(退格)键返回登录页面异常修改
- 在ASP.NET(C#)中实现在一个文本框输入值后按回车时将光标移到下一个文本框
- 用户在文本框中输入需要转换的十进制数,通过点击3个按钮在标签中显示结果。(点击一个实现一个)
- 一个简单的输入关键字添加标签效果
- jquery怎么在点击li标签之后添加一个在class,点击下一个li时删除上一个class?
- 动态添加删除tabs标签
- Android 添加个人标签
- 在select标签中添加a标签
- VS编辑代码时不能输入回车,删除情况处理
- VC 编辑框 退格删除
- 文本框实现输入回车换行
- 文本框输入内容回车事件
- 使用内嵌TOMCAT开发spring mvc 项目
- Netty学习心得 netty服务端和客户端的连接
- C 位域
- 算法:从键盘输入能够构成三角形的三条边长,编程计算该三角形的面积
- 文件COPY
- android 编辑标签,在一个文本框输入标签回车添加退格删除
- [bzoj3012][Usaco2015 Dec][字典树][Top序]First!
- 1102. Invert a Binary Tree (25)
- Java中Servlet的使用(二)
- S5PV210开发 -- Nand和e-MMC区别以及系统更新
- 如何安装单节点的hadoop
- java8新特性
- ubifs文件系统的移植
- 关于Android安卓APP保活