android中卡号输入框控件(每四位用空格分隔)(解决输入法跳转的问题)

2023-11-01

由于项目的需求,需要在卡号输入时,每四位用空间分隔,于是就写了个控件。

该控件支持中间删除,中间增加,粘贴,末尾输入等,光标的位置显示正确。

主要的思想就是:对于添加TextWatcher监听Text的改变,text改变后,拿到该text,将text中的所有空格去掉。然后重新排列。记下来是对光标的位置处理。

1.在末尾删除或者增加的时候,光标一直处于末端。
2.在中间删除的时候,检测光标是否在空格的后面,如果在后面,就让光标前进(往左)一位,如果不是,则光标的位置不变(根据start,count,before这几个参数来算)。
3.在中间插入的时候,检测光标是否在空格的前面,如果在前面,则让光标前进(往右)一位,如果不是,则光标的位置不变(根据start,count,before这几个参数来算)。

读者要先懂得start,count,before的意义。

直接上代码

/**
 * Created by hzlinxuanxuan on 2015/12/10.
 */
public class CardInputEditText extends CleanUpEditText {

    public CardInputEditText(Context ctx) {
        this(ctx, null);
    }

    public CardInputEditText(Context context, AttributeSet attrs) {
        super(context, attrs);
        addTextChangedListener(watcher);
    }

    public CardInputEditText(Context context, AttributeSet attrs, int defStyleAttr){
        super(context, attrs,defStyleAttr);
        addTextChangedListener(watcher);
    }

    private TextWatcher watcher = new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        }

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
            LogUtil.d("s:" + s + ",start:" + start + ",before:" + before + ",count:" + count);
            if (s == null) {
                return;
            }
            //判断是否是在中间输入,需要重新计算
            boolean isMiddle = (start + count) < (s.length());
            //在末尾输入时,是否需要加入空格
            boolean isNeedSpace = false;
            if (!isMiddle && s.length() > 0 && s.length() % 5 == 0) {
                isNeedSpace = true;
            }
            if (isMiddle || isNeedSpace) {
                String newStr = s.toString();
                newStr = newStr.replace(" ", "");
                StringBuilder sb = new StringBuilder();
                for (int i = 0; i < newStr.length(); i += 4) {
                    if (i > 0) {
                        sb.append(" ");
                    }
                    if (i + 4 <= newStr.length()) {
                        sb.append(newStr.substring(i, i + 4));
                    } else {
                        sb.append(newStr.substring(i, newStr.length()));
                    }
                }
                removeTextChangedListener(watcher);
                setText(sb);
                //如果是在末尾的话,或者加入的字符个数大于零的话(输入或者粘贴)
                if(!isMiddle || count > 1){
                    setSelection(sb.length());
                } else if (isMiddle) {
                    //如果是删除
                    if (count == 0) {
                        //如果删除时,光标停留在空格的前面,光标则要往前移一位
                        if ((start - before + 1) % 5 == 0) {
                            setSelection((start - before) > 0 ? start - before : 0);
                        } else {
                            setSelection((start - before + 1) > sb.length() ? sb.length() : (start - before + 1));
                        }
                    }
                    //如果是增加
                    else {
                        if ((start - before + count) % 5 == 0) {
                            setSelection((start + count - before + 1) < sb.length() ? (start + count - before + 1) : sb.length());
                        } else {
                            setSelection(start + count - before);
                        }
                    }
                }
                addTextChangedListener(watcher);
            }
        }

        @Override
        public void afterTextChanged(Editable s) {
        }
    };

    public String getTextWithoutSpace(){
        return super.getText().toString().replace(" ","");
    }
}


 这是效果 

看了该效果,如果现在让你再写个电话号码输入框,应该会写了吧?

/**
 * Created by hzlinxuanxuan on 2015/12/22.
 */
public class PhoneInputEditText extends EditText implements ITextWithoutSpaceInter{
    public PhoneInputEditText(Context context, AttributeSet attrs) {
        super(context, attrs);
        addTextChangedListener(watcher);
        setFilters(new InputFilter[]{new InputFilter.LengthFilter(13)});
    }

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

    public PhoneInputEditText(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        addTextChangedListener(watcher);
        setFilters(new InputFilter[]{new InputFilter.LengthFilter(13)});
    }

    private TextWatcher watcher = new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        }

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
            if (s == null) {
                return;
            }
            //判断是否是在中间输入,需要重新计算
            boolean isMiddle = (start + count) < (s.length());
            //在末尾输入时,是否需要加入空格
            boolean isNeedSpace = false;
            if (!isMiddle && isNeedSpace(s.length())) {
                isNeedSpace = true;
            }
            if (isMiddle || isNeedSpace) {
                String newStr = s.toString();
                newStr = newStr.replace(" ", "");
                StringBuilder sb = new StringBuilder();
                for (int i = 0; i < newStr.length(); ) {
                    if (i == 0) {
                        sb.append(newStr.length() > 3 ? newStr.substring(0, 3) : newStr);
                        i += 3;
                        continue;
                    } else if (i > 0) {
                        sb.append(" ");
                        if (i + 4 <= newStr.length()) {
                            sb.append(newStr.substring(i, i + 4));
                        } else {
                            sb.append(newStr.substring(i, newStr.length()));
                        }
                        i += 4;
                    }
                }
                removeTextChangedListener(watcher);
                setText(sb);
                //如果是在末尾的话,或者加入的字符个数大于零的话(输入或者粘贴)
                if (!isMiddle || count > 1) {
                    setSelection(sb.length());
                } else if (isMiddle) {
                    //如果是删除
                    if (count == 0) {
                        //如果删除时,光标停留在空格的前面,光标则要往前移一位
                        if (isNeedSpace(start - before + 1)) {
                            setSelection((start - before) > 0 ? start - before : 0);
                        } else {
                            setSelection((start - before + 1) > sb.length() ? sb.length() : (start - before + 1));
                        }
                    }
                    //如果是增加
                    else {
                        if (isNeedSpace(start - before + count)) {
                            setSelection((start + count - before + 1) < sb.length() ? (start + count - before + 1) : sb.length());
                        } else {
                            setSelection(start + count - before);
                        }
                    }
                }
                addTextChangedListener(watcher);
            }
        }

        @Override
        public void afterTextChanged(Editable s) {
        }
    };

    @Override
    public String getTextWithoutSpace() {
        return super.getText().toString().replace(" ", "");
    }

    private boolean isNeedSpace(int length) {
        if (length < 4) {
            return false;
        } else if (length == 4) {
            return true;
        } else return (length + 1) % 5 == 0;
    }
}


这是效果


现在新需求来了,又来了一个身份证号码输入框,格式是:330304 1995 0406 1456

如果按照上面的方法再写个控件的话是可以解决问题,但是比较麻烦,所以将上述三种的输入框合并成一种,然后自定义属性,可以在xml中指定是哪一种类型,或者使用代码指定。直接上代码:

/**
 * Created by hzlinxuanxuan on 2015/12/22.
 * 该控件是支持输入时自带控件的,目前支持xml属性指定,也支持代码指定该输入卡所属的类型
 * 现在支持:电话、卡号、身份证号,或者无类型(正常输入)
 */
public class ContentWithSpaceEditText extends EditText{

    private int contentType;
    public static final int TYPE_PHONE = 0;
    public static final int TYPE_CARD = 1;
    public static final int TYPE_IDCARD = 2;

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

    public ContentWithSpaceEditText(Context context, AttributeSet attrs) {
        super(context, attrs);
        parseAttributeSet(context, attrs);
    }

    public ContentWithSpaceEditText(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        parseAttributeSet(context, attrs);
    }

    private void parseAttributeSet(Context context, AttributeSet attrs) {
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ContentWithSpaceEditText, 0, 0);
        contentType = ta.getInt(R.styleable.ContentWithSpaceEditText_epaysdk_type, 0);
        ta.recycle();
        initType();
        setSingleLine();
        addTextChangedListener(watcher);
    }

    private void initType(){
        if (contentType == TYPE_PHONE) {
            setInputType(InputType.TYPE_CLASS_NUMBER);
            setFilters(new InputFilter[]{new InputFilter.LengthFilter(13)});
        } else if (contentType == TYPE_CARD) {
            setInputType(InputType.TYPE_CLASS_NUMBER);
            setFilters(new InputFilter[]{new InputFilter.LengthFilter(31)});
        } else if (contentType == TYPE_IDCARD) {
            setFilters(new InputFilter[]{new InputFilter.LengthFilter(21)});
        }
    }

    public void setContentType(int contentType) {
        this.contentType = contentType;
        initType();
    }

    private TextWatcher watcher = new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        }

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
            if (s == null) {
                return;
            }
            //判断是否是在中间输入,需要重新计算
            boolean isMiddle = (start + count) < (s.length());
            //在末尾输入时,是否需要加入空格
            boolean isNeedSpace = false;
            if (!isMiddle && isSpace(s.length())) {
                isNeedSpace = true;
            }
            if (isMiddle || isNeedSpace) {
                String newStr = s.toString();
                newStr = newStr.replace(" ", "");
                StringBuilder sb = new StringBuilder();
                int spaceCount = 0;
                for (int i = 0; i < newStr.length(); i++) {
                    sb.append(newStr.substring(i, i+1));
                    //如果当前输入的字符下一位为空格(i+1+1+spaceCount),因为i是从0开始计算的,所以一开始的时候需要先加1
                    if(isSpace(i + 2 + spaceCount)){
                        sb.append(" ");
                        spaceCount += 1;
                    }
                }
                removeTextChangedListener(watcher);
                setText(sb);
                //如果是在末尾的话,或者加入的字符个数大于零的话(输入或者粘贴)
                if (!isMiddle || count > 1) {
                    setSelection(sb.length());
                } else if (isMiddle) {
                    //如果是删除
                    if (count == 0) {
                        //如果删除时,光标停留在空格的前面,光标则要往前移一位
                        if (isSpace(start - before + 1)) {
                            setSelection((start - before) > 0 ? start - before : 0);
                        } else {
                            setSelection((start - before + 1) > sb.length() ? sb.length() : (start - before + 1));
                        }
                    }
                    //如果是增加
                    else {
                        if (isSpace(start - before + count)) {
                            setSelection((start + count - before + 1) < sb.length() ? (start + count - before + 1) : sb.length());
                        } else {
                            setSelection(start + count - before);
                        }
                    }
                }
                addTextChangedListener(watcher);
            }
        }

        @Override
        public void afterTextChanged(Editable s) {
        }
    };

    public String getTextWithoutSpace() {
        return super.getText().toString().replace(" ", "");
    }

    public boolean checkTextRight(){
        String text = getTextWithoutSpace();
        //这里做个简单的内容判断
        if (contentType == TYPE_PHONE) {
            if (TextUtils.isEmpty(text)) {
                ToastUtil.show(getContext(), "手机号不能为空,请输入正确的手机号");
            } else if (text.length() < 11) {
                ToastUtil.show(getContext(), "手机号不足11位,请输入正确的手机号");
            } else {
                return true;
            }
        } else if (contentType == TYPE_CARD) {
            if (TextUtils.isEmpty(text)) {
                ToastUtil.show(getContext(), "银行卡号不能为空,请输入正确的银行卡号");
            } else if (text.length() < 14) {
                ToastUtil.show(getContext(), "银行卡号位数不正确,请输入正确的银行卡号");
            } else {
                return true;
            }
        } else if (contentType == TYPE_IDCARD) {
            if (TextUtils.isEmpty(text)) {
                ToastUtil.show(getContext(), "身份证号不能为空,请输入正确的身份证号");
            } else if (text.length() < 18) {
                ToastUtil.show(getContext(), "身份证号不正确,请输入正确的身份证号");
            } else {
                return true;
            }
        }
        return false;
    }

    private boolean isSpace(int length) {
        if (contentType == TYPE_PHONE) {
            return isSpacePhone(length);
        } else if (contentType == TYPE_CARD) {
            return isSpaceCard(length);
        } else if (contentType == TYPE_IDCARD) {
            return isSpaceIDCard(length);
        }
        return false;
    }

    private boolean isSpacePhone(int length) {
        if (length < 4) {
            return false;
        } else if (length == 4) {
            return true;
        } else return (length + 1) % 5 == 0;
    }

    private boolean isSpaceCard(int length) {
        return length % 5 == 0;
    }

    private boolean isSpaceIDCard(int length) {
        if (length <= 6) {
            return false;
        } else if (length == 7) {
            return true;
        } else return (length - 2) % 5 == 0;
    }

}

style.xml:

    <declare-styleable name="ContentWithSpaceEditText">
        <attr name="epaysdk_type" format="enum">
            <enum name="phone" value="0" />
            <enum name="card" value="1" />
            <enum name="IDCard" value="2" />
        </attr>
    </declare-styleable>

代码不详述,比较下上述的代码就懂了。


具体的用法:

<com.think.example.ContentWithSpaceEditText
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:hint="请输入银行卡号"
            android:textSize="14dp"
            app:epaysdk_type="card"/>


QA测了几天,发现在身份证情况下,输入一会儿会导致输入法的跳转。bug重现步骤:

1.在身份证情况下,因为允许输入数字和字母,所以未对InputType进行设置。

2.身份证的格式为****** **** **** ****,如果我先使用数字键盘输入,输入123456,此时再输入的时候(此时输入框会自动加一个空格),输入法会自动跳转到英文字母键盘,这会让用户很奇怪,为什么输入法突然跳转了。事实上在每次重排输入框内容的时候(例如添加空格,或者中间插入),就会出现该问题。


后来定位到setText该方法会导致重新唤起输入法,具体解决方法请看另外一篇文章。Android中EditText.setText(String)方法导致输入法跳转

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

android中卡号输入框控件(每四位用空格分隔)(解决输入法跳转的问题) 的相关文章

随机推荐

  • python add argument list_python链表:TypeError: add() missing 1 required positional argument: 'item'。这个...

    展开全部 python 2 6用add很正常啊 add看起来没啥问题 到是别的函数有些小问题 1 remove前判断下这个item是不是存在 2 if curNode is head 应该是 if curNode is self head
  • Docker容器时间与宿主机差8小时

    近日测试提了个bug说是登录时间比北京时间晚了8个小时 发现是docker容器的问题 Linux下用date查看的时间与在docker容器里面用date查看的时间相差8小时 docker容器里默认是 UTC 时间 本人用一下两种方式尝试了均
  • 字符串哈希

    方法 字符串前缀哈希法 用 h 存储前缀的哈希值 将字符串转成对应的哈希值 看成p进制的数 例 ABCD 1 2 3 4 gt 1 p 3 2 p 2 3 p 1 4 p 0 mod q 技巧 p 131或1331 q 2 64 用usin
  • 深拷贝与浅拷贝的简单理解

    首先说下什么是简单数据类型 什么是复杂数据类型 简单数据类型有 number 数字型 null undfande booler 布尔值 string 字符串 复杂数据类型 有array 数组 object 对象 function 函数 等等
  • Python探索性数据分析畅销书

    探索性数据分析 探索性数据分析 EDA 是一种分析和调查数据集以了解数据特征的方法 数据集 查看数据集示例 有许多与 2009 年至 2019 年在销售的畅销书的标题和作者相关的信息 除了标题和作者之外 数据中还有其他元素 例如用户评分 评
  • 以太坊Web3.js开发基础

    简介 web3 js是一个通过RPC调用和本地以太坊节点进行通信的js库 web3 js可以与任何暴露了RPC接口的以太坊节点连接 web3中提供了eth对象 web3 eth来与以太坊区块链进行交互 在github上上获得代码 安装Tes
  • Lua--字符串操作

    str1 luaC Java str2 SQLServerOracle 一 输出字符串的长度 print str1 print string len str1 二 字符串的大小写转换 print 全大写 string upper str1
  • 超简单 STM32 RTC闹钟 时钟配置

    基于正点原子的RTC时钟 实验效果 LCD屏幕显示 年月日时分秒 设置任意时间 到时间蜂鸣器启动 直接上代码 主函数 简单说 就是初始化各个部件 然后让LED1 闪烁来提示系统的正常运行 显示屏显示实时时间 include led h in
  • winform 中 Devexpress Charts动态添加数据

    参考 Devexpress Charts动态添加数据 https www cnblogs com zhangruisoldier p 4226950 html DevExpress 图表控件 ChartControl 动态绑定数据 http
  • 打开ftp服务器显示成文件,打开ftp文件时出现与"服务器连接被重置"是怎么回事?拜托各位大神...

    满意答案 追问 我才注册的啊 回答 哦 看一下这份资料 希望能解决你的问题 分析解决首先可以排除物理连接上的问题 因为其他网络应用都是正常的 由于出现不能访问ftp服务器现象的电脑不止一台 所有的办公电脑均存在这个问题 因此只能从软件设置上
  • 自动化测试-Selenium

    一 selenium环境搭建 1 检查python环境 2 在cmd命令窗口 输入pip3 install selenium 3 浏览器驱动安装 由于执行的脚本需要浏览器驱动来驱动浏览器 所以需要安装形影的浏览器驱动 WebDriver浏览
  • 论三网融合对数据中心的影响

    近日国务院办公厅印发 三网融合推广方案 方案明确要加快在全国推进三网融合 推动信息网络基础设施互联互通和资源共享 将广电 电信业务双向进入扩大到全国范围 并实质性展开工作 三网融合其实国家政府提了好多年 是指电信网 广播电视网和互联网三网的
  • styled-components 基本用法

    styled components 基本用法 安装 npm install save styled components 或 yarn add styled components 注 如使用tsx语法请同时安装相应的 types声明文件 n
  • qt 程序中执行额外程序和脚本

    1 最简单的 我们可以通过system直接启动一个应用程序或者脚本 system helloworld system hello sh 操作简单 但是我们可以很清晰的看到弊端 虽然很顺利的匹出一个进程去执行另外一个应用 但是我们拿不到这个新
  • 新冠造成的经济崩溃对女性影响最大

    Yui Koizumi 化名 曾经过的挺不错的 大学毕业后她进入了一家广告公司 人生逐渐走上正轨 今年3月的时候 她收到了公司发来的邮件 公司暂时要关闭 不过她无须担心 因为收到了一些补偿金 一旦COVID 19疫情缓解了 公司就又会开张营
  • 23 KVM管理虚拟机-使用VNC密码登录虚拟机

    文章目录 23 KVM管理虚拟机 使用VNC密码登录虚拟机 23 1 概述 23 2 前提条件 23 3 操作步骤 23 KVM管理虚拟机 使用VNC密码登录虚拟机 本章介绍使用VNC密码登录虚拟机的方法 23 1 概述 当虚拟机操作系统安
  • IDEA安装MybatisX插件及使用

    打开idea File gt Setting gt Plugins gt Marketplace gt 搜索 mybatis 出现MybatisX选择点击Install gt Apply gt OK 提示重启即可 图示如下 在IDEA中使用
  • 机械硬盘无法弹出的问题:进程 ID 为 4 的应用程序 System 已停止删除或弹出设备

    一般的解决方法 此电脑单机右键选管理 1 计算机管理 gt 系统工具 gt 事件查看器 gt 自定义视图 gt 管理事件 2 在日期与事件进行排序找到最新的事件 3 合理的关掉这个程序 直接结束进程 保存相关文档后关闭 Word 等程序 另
  • android sdk 64bit,Android SDK不安裝在win 7 64位上。

    I am trying to install Android SDK on windows 7 64 bit but it doesn t work I keep getting this screen 我正在嘗試在windows 7 64
  • android中卡号输入框控件(每四位用空格分隔)(解决输入法跳转的问题)

    由于项目的需求 需要在卡号输入时 每四位用空间分隔 于是就写了个控件 该控件支持中间删除 中间增加 粘贴 末尾输入等 光标的位置显示正确 主要的思想就是 对于添加TextWatcher监听Text的改变 text改变后 拿到该text 将t