Android实战场景 - 输入手机号、银行卡号、身份证号时动态格式化

news2024/11/16 21:38:40

在日常项目开发中,如果稍微严谨点的话,其中关于手机号、银行卡号、身份证号的输入格式有做了限制格式化操作,主要是为了给用户带来更好的体验感;
最近同事正好问到了我这个问题,虽然以前做过这类型功能,但是并未记录,所以我就去网上扒了扒,特此记录一下~

可能对你有所帮助的Blog

  • Android入门之路 - 监听EditText的文本变化
  • Android实战场景 - EditText仅支持输入数字、英文、汉字,禁止输入表情符号

功能来源需求,体验来自用户,提升来自自我 Striving ...

      • 基础了解
      • 功能实现
        • 使用方式
        • 实现过程

基础了解

首先我需要做的功能,针对EditText控件要满足以下几点

  • 当用户输入手机号、银行卡号、身份证号时动态加入空格
  • 当用户删除数据时逐步清理空格
  • 当传递数据时数据中不含空格

关于在Andoird中想要实时监听文本变化,一般都需要加 addTextChangedListener 监听者,不了解这部分的可以去看看 监听EditText的文本变化

在这里插入图片描述


功能实现

使用方式

MainActivity

package com.example.edittextdemo

import android.os.Bundle
import android.widget.Button
import android.widget.EditText
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        var etPhone = findViewById<EditText>(R.id.et_phone)
        var etBank = findViewById<EditText>(R.id.et_bank)
        var etCard = findViewById<EditText>(R.id.et_card)

        //手机号一般是11位
        var phoneTextWatcher = FormatTextWatcher(etPhone, 11 + 2)
        phoneTextWatcher.setSpaceType(FormatTextWatcher.SpaceType.mobilePhoneNumberType)

        //银行卡储蓄卡卡号有三种格式,19位、17位和16位的; 信用卡卡号则统一为16位号码~
        var bankTextWatcher = FormatTextWatcher(etBank, 19 + 4)
        bankTextWatcher.setSpaceType(FormatTextWatcher.SpaceType.bankCardNumberType)

        //身份证号一般是18位
        var cardTextWatcher = FormatTextWatcher(etCard, 18 + 2)
        cardTextWatcher.setSpaceType(FormatTextWatcher.SpaceType.IDCardNumberType)

        //获取输入数据
        var mBtn = findViewById<Button>(R.id.btn)
        mBtn.setOnClickListener {
            var phoneContent = phoneTextWatcher.textNotSpace
            var bankContent = bankTextWatcher.textNotSpace
            var cardContent = cardTextWatcher.textNotSpace
            Toast.makeText(this, "手机号:$phoneContent & 银行卡号:$bankContent & 身份证号:$cardContent", Toast.LENGTH_SHORT).show()
        }
    }
}

activity_main

<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat 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"
    tools:context=".MainActivity">

    <EditText
        android:id="@+id/et_phone"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:digits="0123456789 X"
        android:hint="输入您的手机号"
        android:inputType="numberSigned"
        android:textSize="20dp"
        tools:ignore="MissingConstraints" />

    <EditText
        android:id="@+id/et_bank"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:digits="0123456789 X"
        android:hint="输入您的银行卡"
        android:inputType="numberSigned"
        android:textSize="20dp"
        tools:ignore="MissingConstraints" />

    <EditText
        android:id="@+id/et_card"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:digits="0123456789 X"
        android:hint="输入您的身份证号"
        android:inputType="numberSigned"
        android:textSize="20dp"
        tools:ignore="MissingConstraints" />

    <Button
        android:id="@+id/btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="获取输入数据" />
</androidx.appcompat.widget.LinearLayoutCompat>

实现过程

我翻了下网上资源,发现基本都是以下这份自定义的 TextWatcher监听 处理 ,所以我直接就拿过来跑Demo了

因为我需要的身份证格式 1442202 19980201 1277 ,而其原始格式为 1442202 1998 0201 1277 ,所以我稍作了下修改

FormatTextWatcher

package com.example.edittextdemo;

import android.text.Editable;
import android.text.InputFilter;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.widget.EditText;

/**
 * 输入银行卡、手机、身份证格式化过滤器
 */
public class FormatTextWatcher implements TextWatcher {
    /**
     * text改变之前的长度
     */
    private int beforeTextLength = 0;
    private int onTextLength = 0;
    private boolean isChanged = false;
    private StringBuffer buffer = new StringBuffer();
    /**
     * 改变之前text空格数量
     */
    int spaceNumberA = 0;
    private EditText editText;
    /**
     * text最大长度限制
     */
    private int maxLength;
    private SpaceType spaceType;
    /**
     * 记录光标的位置
     */
    private int location = 0;
    /**
     * 是否是主动设置text
     */
    private boolean isSetText = false;

    public FormatTextWatcher(EditText editText, int maxLength) {
        this.editText = editText;
        this.maxLength = maxLength;
        if (editText == null) {
            new NullPointerException("editText is null");
        }
        spaceType = SpaceType.defaultType;
        editText.setFilters(new InputFilter[]{new InputFilter.LengthFilter(
                maxLength)});
        editText.addTextChangedListener(this);
    }

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count,int after) {
        beforeTextLength = s.length();
        if (buffer.length() > 0) {
            buffer.delete(0, buffer.length());
        }
        spaceNumberA = 0;
        for (int i = 0; i < s.length(); i++) {
            if (s.charAt(i) == ' ') {
                spaceNumberA++;
            }
        }
    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
        onTextLength = s.length();
        buffer.append(s.toString());
        if (onTextLength == beforeTextLength || onTextLength > maxLength
                || isChanged) {
            isChanged = false;
            return;
        }
        isChanged = true;
    }

    @Override
    public void afterTextChanged(Editable s) {
        if (isChanged) {
            location = editText.getSelectionEnd();
            int index = 0;
            while (index < buffer.length()) { // 删掉所有空格
                if (buffer.charAt(index) == ' ') {
                    buffer.deleteCharAt(index);
                } else {
                    index++;
                }
            }

            index = 0;
            int spaceNumberB = 0;
            while (index < buffer.length()) { // 插入所有空格
                spaceNumberB = insertSpace(index, spaceNumberB);
                index++;
            }

            String str = buffer.toString();

            // 下面是计算光位置的
            if (spaceNumberB > spaceNumberA) {
                location += (spaceNumberB - spaceNumberA);
                spaceNumberA = spaceNumberB;
            }
            if (isSetText) {
                location = str.length();
                isSetText = false;
            } else if (location > str.length()) {
                location = str.length();
            } else if (location < 0) {
                location = 0;
            }
            updateContext(s, str);
            isChanged = false;
        }
    }

    /**
     * 更新编辑框中的内容
     *
     * @param editable
     * @param values
     */
    private void updateContext(Editable editable, String values) {
        if (spaceType == SpaceType.IDCardNumberType) {
            editable.replace(0, editable.length(), values);
        } else {
            editText.setText(values);
            try {
                editText.setSelection(location);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 根据类型插入空格
     *
     * @param index
     * @param spaceNumberAfter
     * @return
     * @see [类、类#方法、类#成员]
     */
    private int insertSpace(int index, int spaceNumberAfter) {
        switch (spaceType) {
            // 相隔四位空格
            case mobilePhoneNumberType:
                if (index == 3 || ((index > 7) && ((index - 3) % (4 * spaceNumberAfter) == spaceNumberAfter))) {
                    buffer.insert(index, ' ');
                    spaceNumberAfter++;
                }
                break;
            case IDCardNumberType:
             /*   if (index == 6
                        || ((index > 10) && ((index - 6) % (4 * spaceNumberAfter) == spaceNumberAfter))) {
                    buffer.insert(index, ' ');
                    spaceNumberAfter++;
                }*/
                if (index == 6
                        || ((index > 14) && ((index - 6) % (4 * spaceNumberAfter) == spaceNumberAfter))) {
                    buffer.insert(index, ' ');
                    spaceNumberAfter++;
                }
                break;
            default:
                if (index > 3
                        && (index % (4 * (spaceNumberAfter + 1)) == spaceNumberAfter)) {
                    buffer.insert(index, ' ');
                    spaceNumberAfter++;
                }
                break;
        }
        return spaceNumberAfter;
    }

    /***
     * 计算需要的空格数
     *
     * @return 返回添加空格后的字符串长度
     * @see [类、类#方法、类#成员]
     */
    private int computeSpaceCount(CharSequence charSequence) {
        buffer.delete(0, buffer.length());
        buffer.append(charSequence.toString());
        int index = 0;
        int spaceNumberB = 0;
        while (index < buffer.length()) { // 插入所有空格
            spaceNumberB = insertSpace(index, spaceNumberB);
            index++;
        }
        buffer.delete(0, buffer.length());
        return index;
    }

    /**
     * 设置空格类型
     *
     * @param spaceType
     * @see [类、类#方法、类#成员]
     */
    public void setSpaceType(SpaceType spaceType) {
        this.spaceType = spaceType;
    }

    /**
     * 设置输入字符
     *
     * @param charSequence
     * @return 返回设置成功失败
     * @see [类、类#方法、类#成员]
     */
    public boolean setText(CharSequence charSequence) {
        if (editText != null && !TextUtils.isEmpty(charSequence) && computeSpaceCount(charSequence) <= maxLength) {
            isSetText = true;
            editText.removeTextChangedListener(this);
            editText.setText(charSequence);
            editText.addTextChangedListener(this);
            return true;
        }
        return false;
    }

    /**
     * 得到输入的字符串去空格后的字符串
     *
     * @return
     * @see [类、类#方法、类#成员]
     */
    public String getTextNotSpace() {
        if (editText != null) {
            return delSpace(editText.getText().toString());
        }
        return null;
    }

    /**
     * 得到输入的字符串去空格后的长度
     *
     * @return
     * @see [类、类#方法、类#成员]
     */
    public int getLengthNotSpace() {
        if (editText != null) {
            return getTextNotSpace().length();
        }
        return 0;
    }

    /**
     * 得到空格数量
     *
     * @return
     * @see [类、类#方法、类#成员]
     */
    public int getSpaceCount() {
        return spaceNumberA;
    }

    /**
     * 去掉字符空格,换行符等
     *
     * @param str
     * @return
     * @see [类、类#方法、类#成员]
     */
    private String delSpace(String str) {
        if (str != null) {
            str = str.replaceAll("\r", "");
            str = str.replaceAll("\n", "");
            str = str.replace(" ", "");
        }
        return str;
    }

    /**
     * 空格类型
     *
     * @author 江钰锋 0152
     * @version [版本号, 2015年4月21日]
     * @see [相关类/方法]
     * @since [产品/模块版本]
     */
    public enum SpaceType {
        /**
         * 默认类型
         */
        defaultType,
        /**
         * 银行卡类型
         */
        bankCardNumberType,
        /**
         * 手机号类型
         */
        mobilePhoneNumberType,
        /**
         * 身份证类型
         */
        IDCardNumberType
    }

}

如果需要修改添加空格的位置,可以自行更改内部规则

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/160624.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

你了解RTK技术吗?—— 揭秘GNSS中的定位技术

上期文章中我们一起探讨了GNSS仿真及其对测试验证的重要意义&#xff0c;今天我们将一起走进GNSS中的定位技术—RTK技术。什么是RTK技术&#xff1f;传统RTK技术与网络RTK技术又有什么区别呢&#xff1f;随着GNSS系统的迅速发展&#xff0c;RTK技术由于可以在作业区域内提供实时…

OpenMLDB v0.7.0 发布

2023 新年伊始&#xff0c;OpenMLDB v0.7.0 正式发布。本次版本更新重点增强了易用性和稳定性&#xff0c;下文将详细介绍主要改进和更新内容。更多 0.7.0 版本内容详见链接&#xff1a;Release v0.7.0 4paradigm/OpenMLDB 系统性改进消息和错误码&#xff0c;提升易用性 在…

【数据库概论】第二章 关系数据库

第二章 关系数据库 目录第二章 关系数据库2.1 关系数据结构2.1.1关系2.1.2关系模式2.1.3关系数据库2.2 关系操作2.2.1 基本的关系操作2.2.2关系数据语言的分类2.3 关系的完整性2.3.1 实体完整性2.3.2 参照完整性2.3.3 用户定义的完整性2.4 关系代数2.4.1 传统集合运算2.4.2 专门…

multimodal remote sensing survey 遥感多模态综述阅读

遥感多模态 参考&#xff1a;From Single- to Multi-modal Remote Sensing Imagery Interpretation: A Survey and Taxonomy Keywords&#xff1a;multimodal remote sensing 文章目录遥感多模态AbstractIntroductionTaxonomy1. Multi-source Alignment1.1 Spatial Alignment1…

《MySQL系列-InnoDB引擎15》慢查询日志拓展-如何开启MySQL慢查询日志?

慢查询日志拓展-如何开启MySQL慢查询日志&#xff1f; 1.查看MySQL慢查询日志是否开启&#xff1f; show variables like %query%; 查询出的结果中&#xff0c;主要观察如下三条&#xff1a; long_query_time 通过long_query_time设置阈值&#xff0c;设置阈值后&#xff0c…

Linux学习笔记 超详细 0基础(中)

Vi/Vim编辑器在Linux下一切皆文件&#xff0c;Vi编辑器和Vim编辑器是可以直接对文本文件进行编辑和操作&#xff0c;没什么大区别&#xff0c;vim有颜色区分更美观&#xff0c;vim 文件路径文件名即可进入一般模式&#xff0c;一般模式就是只读文件&#xff0c;不可进行操作。V…

K8s: Windows 下安装 K8s 开源桌面面板工具 OpenLens 查看集群信息

写在前面 分享一个桌面端的 k8s 面板工具 OpenLens博文内容为 OpenLens 简单介绍和 下载安装教程。安装非常简单,感兴趣的小伙伴快去尝试吧理解不足小伙伴帮忙指正 我所渴求的&#xff0c;無非是將心中脫穎語出的本性付諸生活&#xff0c;為何竟如此艱難呢 ------赫尔曼黑塞《德…

《c++ primer》第三章 字符串、vector、数组

前言 本章内容相比第二章要简单不少&#xff0c;里面比较重要的内容主要是vector和迭代器&#xff0c;这里只是很简单的介绍了一下&#xff0c;在后续的章节会有更详细、复杂的说明。以下记录的都是比较重要或者易混淆的知识点&#xff0c;对于像string、vector只列举了部分方法…

Sentienl一:下载,启动

Hystrix &#xff1a;1需要自己搭建监控平台 2 没有一套web界面可以给我们进行更加细粒度化的配置流控&#xff0c;速率控制 服务熔断&#xff0c;服务降级 Sentinel: 1 单独一个组件&#xff0c;可以独立出来 2 直接界面化的细粒度统一配置 一&#xff1a;丰富的应用场景&…

【Linux】分布式版本控制工具Git的学习 | 在Linux上使用git

&#x1f451;作者主页&#xff1a;安 度 因 &#x1f3e0;学习社区&#xff1a;安度因的学习社区 &#x1f4d6;专栏链接&#xff1a;Linux 文章目录一、前言二、历史背景三、版本控制1、何为版本控制2、版本控制工具① 集中式版本控制工具② 分布式版本控制工具四、代码托管平…

React 学习笔记总结(八)

react-router6版本的学习笔记。 文章目录一、React Router 6二、router6版本的 安装 和 一级路由1. 安装router6版本2. Routes组件 和 Route的 element属性三、router6 之 重定向四、router6的 NavLink高亮五、router6 的 useRoutes路由表(重要)六、router6 的 嵌套路由七、rou…

编写自己的OPTEE CA/TA demo

前言 &#xff08;默认你对optee有一点点点点了解&#xff09; 一、hello_world分析 在\optee_examples\hello_world目录下&#xff0c;optee给出了一个简单的CA/TA示例。 hello_world的结构如下&#xff1a; 1、CA端 A.main.c main.c文件有效代码如下&#xff1a; #incl…

vue2组件之间的数据传递(组件之间使用mitt第三方模块创建事件中心进行订阅与发布)

目录 一、组件之间的通信 1、组件之间的关系&#xff1a;父子关系、兄弟关系、跨级关系 2、父子组件之间的通信(数据传递)&#xff1a; 3、兄弟组件之间的通信&#xff08;数据传输&#xff09;&#xff1a; 4、跨级组件之间的通信&#xff1a;provide / inject 类似于消息…

正则表达式 - 匹配开头、结尾、中间 - 某天气网站网页源代码分析

背景 爬取某天气网站数据&#xff0c;使用 Selenium 能够得到渲染数据后的页面源代码。特定日期的真实数据肯定只有1份&#xff0c;展示在页面表格中&#xff0c;但是源代码中提供了3个都有数据的 Table&#xff0c;而其中2个Table 的数据是通过 math.random 生成后填充&#…

ASP.NET Core 3.1系列(25)——Autofac中的泛型注册和程序集注册

1、前言 在实际开发业务中&#xff0c;泛型的应用非常广泛&#xff0c;而这也就产生了一个问题&#xff1a;泛型类和泛型接口该怎么注册&#xff1f;难道要开发者一行一行去写泛型构造参数吗&#xff1f;同时&#xff0c;实际业务中往往也会对项目进行分层设计&#xff0c;例如…

【十】Netty WebSocket协议栈开发

Netty WebSocket协议栈开发背景介绍HTTP 协议的弊端WebSocket 介绍WebSocket 特点WebSocket 连接建立Socket生命周期WebSocket关闭开发WebSocket 服务端功能介绍流程图代码实现jar 依赖WebSocket 服务端启动类 WebSocketServer服务端业务处理类 WebSocketServerHandlerWebSocke…

nacos的部署以及nacos启动报错“Unable to start embedded Tomcat”(部分解决)

这几天有一个基于yshop改编的SpringBoot的项目开发需求&#xff0c;本地需要下载使用nacos-server进行部署和开发&#xff0c;于是下载了nacos-server-2.0.3,并将其解压在一个没有中文路径的地方。 接下来根据nacos需求&#xff0c;设置了JAVA_HOME的环境变量&#xff1a; 修…

防火墙NAT综合实验

实验要求 1.内网网段配置动态pat将内网192.168.1.0网段映射到防火墙外网接口上 2.dmz区域服务器做静态pat将两台服务器对应到一个外网接口的不同端口 3.开启nat 控制 4.对内网网段192.168.2.0或豁免能够发访问外网 5.并通过远程进行验证 实验命令 ciscoasa# conf t cisco…

如何快速做好SEO优化?怎样综合查询seo?

本篇接着讲新手优化网站的技巧及应该注意什么方面&#xff0c;一起来看看吧&#xff01; 6.优化你的图片 从可读性的角度来看&#xff0c;图像非常重要。他们帮助可视化你的内容&#xff0c;帮助读者更容易理解。但它们对SEO也很重要&#xff0c;因为它们可以帮助你的网站被抓…

MySQL——SQL逻辑语句相同但是性能相差巨大?

在 MySQL 中&#xff0c;有很多看上去逻辑相同&#xff0c;但性能却差异巨大的 SQL 语句。对这些语句使用不当的话&#xff0c;就会不经意间导致整个数据库的压力变大。 下面通过三个案例对SQL语句进行分析&#xff1a; 案例一&#xff1a;条件字段函数操作 假设你现在维护了…