Android EditText过滤换行符,回车符和空白符 您所在的位置:网站首页 去掉所有回车标识码怎么弄 Android EditText过滤换行符,回车符和空白符

Android EditText过滤换行符,回车符和空白符

2023-07-25 05:21| 来源: 网络整理| 查看: 265

写在前面:本文是实际开发中遇到的EditText坑点,记为笔记

过滤换行符,回车符,空白符过滤Emoji

1. 背景

项目有个需求,所有与“标题”有关的输入,都不允许有换行。 第一次拿到这个需求的时候觉得很简单,直接设置一个InputFilter

 

public class NewlineFilter implements InputFilter { /** * @param source 输入的文字 * @param start 输入-0,删除-0 * @param end 输入-文字的长度,删除-0 * @param dest 原先显示的内容 * @param dstart 输入-原光标位置,删除-光标删除结束位置 * @param dend 输入-原光标位置,删除-光标删除开始位置 * @return null表示原始输入,""表示不接受输入,其他字符串表示变化值 */ @Override public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) { if (source.toString().contains("\n")) { return source.toString().replace("\n", ""); } return null; } }

然后拿起手机测试,发现没毛病,开开心心的提测了,打算回家过个好年。

2. 问题

第二天打开jira一看,有个bug:

“魅蓝Note5输入字符的时候字符成倍出现,删除的时候还会输入字符”

我拿来测试机试了一下:

 

魅族Note5

华为Mate10

很明显,魅族的输入法会把当前“待输入字符”放入到EditText输入框里,而华为的讯飞输入法不会。 再回看上面的代码就会发现一个问题:return source.toString().replace("\n", "");会把当前魅族Note5输入框中的“待输入字符”转化为输入字符,但是,推荐词区域的字符并没有丢失,所以下次输入字符的时候会把推荐词内容一并倒入到输入框里,这就是测试同学说的现象。完美复现!

3. 方案

好,现在问题明了了,说白就是适配问题。 解决适配问题有个准则:

尽量少些特有平台代码覆盖测试

所以我的思考方向是:看看官方怎么实现的

查阅官方文档,想起了TextView的singleLine,先跑了一遍,发现不论内部输入和外部粘贴,它都直接转化成了空格。这立马勾起了我的兴趣,查看源码,发现有一个很有趣的类TransformationMethod。这个类有点类似于MovementMethod。前者处理字符串变换,后者处理span之类的变换。

TransformationMethod有个子类:

 

/** * This transformation method causes the characters in the {@link #getOriginal} * array to be replaced by the corresponding characters in the * {@link #getReplacement} array. */ public abstract class ReplacementTransformationMethod implements TransformationMethod { /** * Returns the list of characters that are to be replaced by other * characters when displayed. */ protected abstract char[] getOriginal(); /** * Returns a parallel array of replacement characters for the ones * that are to be replaced. */ protected abstract char[] getReplacement(); ... }

它有个子类:SingleLineTransformationMethod,TextView的singleLine就是靠这个东西实现的。 所以我使用了一下,发现效果不错,没有适配问题。不过有个小问题,其实我的需求里是想要把换行直接pass的,看了一下这几个类,没法实现我的需求。ReplacementTransformationMethod有另外一个子类:HideReturnsTransformationMethod

 

/** * This transformation method causes any carriage return characters (\r) * to be hidden by displaying them as zero-width non-breaking space * characters (\uFEFF). */ public class HideReturnsTransformationMethod extends ReplacementTransformationMethod { private static char[] ORIGINAL = new char[] { '\r' }; private static char[] REPLACEMENT = new char[] { '\uFEFF' }; /** * The character to be replaced is \r. */ protected char[] getOriginal() { return ORIGINAL; } /** * The character that \r is replaced with is \uFEFF. */ protected char[] getReplacement() { return REPLACEMENT; } }

他把回车符(回车符是\r,换行符是\n)换成了'\uFEFF',我测了一下这个字符是一个不可见字符,我立马把\n也替换成这个字符,高兴之余,发现这个字符虽然不可见,但是还是占用一个字符位。

所以我只能找别的方案。 再回去查看EditText的源码,对于输入内容Editable,它的实现类是SpannableStringBuilder,所以在仔细回想魅族输入法的时候,发现输入的过程中有个小细节:“待输入字符”有下划线,经测试,这些字符是一个span,它标识着自己是“待输入字符”。回想起之前最早的实现,实际上是破坏了这个span,通过查看系统里的InputFilter实现,发现这些实现都是new了一个新的SpannableStringBuilder,同时没有破坏原先的字符串。我照葫芦画瓢,写了一个InputFilter:

 

public class CharFilter implements InputFilter { private final char[] filterChars; public static CharFilter newlineCharFilter() { return new CharFilter(new char[]{'\n'}); } public static CharFilter whitespaceCharFilter() { return new CharFilter(new char[]{' '}); } public static CharFilter returnCharFilter() { return new CharFilter(new char[]{'\r'}); } public static CharFilter wnrCharFilter() { return new CharFilter(new char[]{' ', '\n', '\r'}); } private CharFilter(char[] filterChars) { this.filterChars = filterChars == null ? new char[0] : filterChars; } /** * @param source 输入的文字 * @param start 输入-0,删除-0 * @param end 输入-文字的长度,删除-0 * @param dest 原先显示的内容 * @param dstart 输入-原光标位置,删除-光标删除结束位置 * @param dend 输入-原光标位置,删除-光标删除开始位置 * @return null表示原始输入,""表示不接受输入,其他字符串表示变化值 */ @Override public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) { if (needFilter(source)) { SpannableStringBuilder builder = new SpannableStringBuilder(); int abStart = start; for (int i = start; i < end; i++) { if (isFilterChar(source.charAt(i))) { if (i != abStart) { builder.append(source.subSequence(abStart, i)); } abStart = i + 1; } } if (abStart < end) { builder.append(source.subSequence(abStart, end)); } return builder; } return null; } private boolean needFilter(CharSequence source) { String s = source.toString(); for (char filterChar : filterChars) { if (s.indexOf(filterChar) >= 0) { return true; } } return false; } private boolean isFilterChar(char c) { for (char filterChar : filterChars) { if (filterChar == c) { return true; } } return false; } }

实现非常简单,把之前原字符串里的\n \r 和空格都过滤掉了,剩下的子串按顺序组成新的SpannableStringBuilder。

我覆盖测试后,这个完美的解决了问题。 这个类有些局限,假如我想过滤所有中文,在魅族Note5上还是会有同样的问题。这个问题有别的解决方案,不在这里阐述。

4. 总结

这个问题暴露的原因主要还是早期覆盖测试不够,但是好在测试同学发现,不然这将是一个线上事故了。虽然是一个小小的字符问题,但是不管是从技术角度考虑还是客户角度考虑,都要引起足够的重视。解决过程还是学习到不少东西,比如TransformationMethod可以用来提前做字符变换。我相信这个问题也能用它解决。出于代码效率和设计考虑,并没有使用TextWatcher和自定义EditText。优先考虑解耦的实现方式。

5. 持续更新

有一天产品出了一个需求:部分标题类输入不能有特殊字符,比如 各种显示特殊字符和Emoji。

我立马想到用上述的方法实现:

 

package com.icourt.alpha.widget.filter; import android.text.InputFilter; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.TextUtils; import java.util.regex.Pattern; /** * Description emoji过滤器 * Company Beijing iCourt.cc */ public class EmojiFilter implements InputFilter { public static final Pattern EMOJI_PATTERN = Pattern.compile("(?:[\uD83C\uDF00-\uD83D\uDDFF]|[\uD83E\uDD00-\uD83E\uDDFF]|[\uD83D\uDE00-\uD83D\uDE4F]|[\uD83D\uDE80-\uD83D\uDEFF]|[\u2600-\u26FF]\uFE0F?|[\u2700-\u27BF]\uFE0F?|\u24C2\uFE0F?|[\uD83C\uDDE6-\uD83C\uDDFF]{1,2}|[\uD83C\uDD70\uD83C\uDD71\uD83C\uDD7E\uD83C\uDD7F\uD83C\uDD8E\uD83C\uDD91-\uD83C\uDD9A]\uFE0F?|[\u0023\u002A\u0030-\u0039]\uFE0F?\u20E3|[\u2194-\u2199\u21A9-\u21AA]\uFE0F?|[\u2B05-\u2B07\u2B1B\u2B1C\u2B50\u2B55]\uFE0F?|[\u2934\u2935]\uFE0F?|[\u3030\u303D]\uFE0F?|[\u3297\u3299]\uFE0F?|[\uD83C\uDE01\uD83C\uDE02\uD83C\uDE1A\uD83C\uDE2F\uD83C\uDE32-\uD83C\uDE3A\uD83C\uDE50\uD83C\uDE51]\uFE0F?|[\u203C\u2049]\uFE0F?|[\u25AA\u25AB\u25B6\u25C0\u25FB-\u25FE]\uFE0F?|[\u00A9\u00AE]\uFE0F?|[\u2122\u2139]\uFE0F?|\uD83C\uDC04\uFE0F?|\uD83C\uDCCF\uFE0F?|[\u231A\u231B\u2328\u23CF\u23E9-\u23F3\u23F8-\u23FA]\uFE0F?)", Pattern.UNICODE_CASE | Pattern.CASE_INSENSITIVE); @Override public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) { if (needFilter(source)) { SpannableStringBuilder builder = new SpannableStringBuilder(); int abStart = start; for (int i = start; i < end; i++) { if (isEmoji(String.valueOf(source.charAt(i)))) { if (i != abStart) { builder.append(source.subSequence(abStart, i)); } abStart = i + 1; } else { // 所有的emoji不是一个字符就是两个字符,所以单独处理 if (i + 1


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有