• 一种可以实时检测IP地址合法性的EditText输入框


    2019-08-13

    关键字:EditText、自定义View、实时检测IP地址


    在做 APK 开发的时候,有时会遇到要输入IP地址的情况,但IP地址是有一定规则的,而 Android 官方又没有提供专用于输入IP地址的输入框。所以通常最简单节省时间的做法就是直接提供一个 EditText 供用户输入,然后等用户提交以后再去检查规则。还有些同学不愿意这么随便,他们可能自己开发或者到网上去寻找相关的开源代码。笔者昨天出于无聊,也做了一个IP地址输入框控件,现在将它开源出来,希望能给后来者带来些许帮助。

    笔者的这个IP地址输入框控件非常简单,它是直接继承自官方的 EditText 控件并加入相关处理逻辑实现的。它具有如下几个特点:

    1、与 EditText 控件在外观与使用方式上均一致;

    2、伴随着输入依据 IPv4 规则实时检测输入字符;

    3、根据 IPv4 规则,不同状态的数据以不同形态区分显示;

    4、源码极其轻量。

    使用展示

     

     

    笔者的这个输入框并没有对输入字符做很严格的限定,事实上它允许输入任何字符,但是对于那些非IP地址字符,它就会以灰色显示。之所以这么做,是考虑到可能真的有些场景会有这种显示非IP地址字符的需求,不想限制的太死了。

    输入的字符中,符合IP地址规范的,会以绿色显示;可能符合的文本,则以橙色显示;不符合规范的,则以红色显示。

    在使用上也很简单,直接在需要用到的 layout 上按以下格式引入即可:

    <com.my.pkg.IPEditText
        android:id="@+id/iv01"
        android:layout_centerInParent="true"
        android:inputType="text|number"
        android:layout_width="300dp"
        android:layout_height="50dp" />

    可以说和官方的 EditText 完全没有区别了。

    源码浅析

    笔者的这个IP地址输入框控件命名为 IPEditText。它仅一份三百多行的 Java 代码,没有额外的文件。它大致的流程如下图所示

    关于构造器

    构造器虽然有两个,但实际上笔者只实现了在 XML 中使用的构造方法

        public IPEditText(Context context) {
            super(context);
            log("IPEditText(context)");
        }
    
        public IPEditText(Context context, AttributeSet attrs) {
            super(context, attrs);
            log("IPEditText(context,attrs)");
            super.addTextChangedListener(this);
    
            textInitialization();
        }

    在构造中注册了文本改变的监听器,并且拦截了外部监听注册请求

        @Override
        public void addTextChangedListener(TextWatcher watcher) {
            log("we ignore your call");
        }

    关于IP规则检查

    IPv4 规则的检查功能全部封装在内部类 IPChecker 中。检查会将IP地址分成 4 个字段逐一检查。每个IP字段又被抽象成 IPs 类,IPs 类就包括了当前字段的状态,是正确还是未知还是错误。同时类里面还有两个 StringBuilder 对象,它们在默认情况下是 null 的。只有在检查到当前IP字段的值有错时,才会去实例化对应的 StringBuilder 类对象,这些错误不同类型的信息会被记录到这些 StringBuilder 对象中。

    完整的 IPEditText 源码如下所示

    package com.my.pkg;
    
    import android.content.Context;
    import android.text.Editable;
    import android.text.Html;
    import android.text.TextWatcher;
    import android.util.AttributeSet;
    import android.util.Log;
    import android.view.View;
    import android.widget.EditText;
    
    public class IPEditText extends EditText implements TextWatcher {
    
        private static final String TAG = "IPEditText";
    
    //    private static final String COLOR_CORRECT = "";
    //    private static final String COLOR_WARN = "";
    //    private static final String COLOR_ERROR = "";
    
        private boolean isNeed2Update;
        /**
         * 编辑前的光标。
         * */
        private int oldPos;
        /**
         * 编辑前的字符长度。
         * */
        private int oldLength;
    
        private View bg;
        private IPChecker ipChecker;
    
        public IPEditText(Context context) {
            super(context);
            log("IPEditText(context)");
        }
    
        public IPEditText(Context context, AttributeSet attrs) {
            super(context, attrs);
            log("IPEditText(context,attrs)");
            super.addTextChangedListener(this);
    
            textInitialization();
        }
    
        private void textInitialization(){
            ipChecker = new IPChecker();
    
            //get text.
            String txt = getText().toString();
            log("txt null?" + (txt==null) + ",txt:" + txt);
    
            //check whether ip string.
            if (txt != null && !"".equals(txt)) {
                ipChecker.check(txt);
                setText(Html.fromHtml(ipChecker.toString()));
                setSelection(getText().length());
            }
            log("getNormalText:" + ipChecker.getNormalTxt());
        }
    
        private void notifyUpdate(String txt){
            isNeed2Update = false;
            if (txt != null && !"".equals(txt)) {
                ipChecker.check(txt);
                setText(Html.fromHtml(ipChecker.toString()));
                if(getText().length() > oldLength){
                    setSelection(oldPos + 1);
                }else{
                    setSelection(oldPos);
                }
            }
        }
    
        @Override
        public void addTextChangedListener(TextWatcher watcher) {
            log("we ignore your call");
        }
    
        private void log(String msg){
            Log.d(TAG, msg);
        }
    
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            if(isNeed2Update){
                oldPos = start;
                oldLength = s.length();
            }
    //        log("isNeed:" + isNeed2Update + ",start:" + start + ",length:" + s.length() + ",after:" + after);
        }
    
        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
    
        }
    
        @Override
        public void afterTextChanged(Editable s) {
            if(isNeed2Update){
                notifyUpdate(s.toString());
            }else{
                isNeed2Update = true;
                log("after " + s.toString());
            }
        }
    
        private class IPChecker {
    
            /**ipv4*/
            IPs[] ips = new IPs[4];
            /**记录当前检查到哪一IP段落了*/
            int checkingIdx;
            /**记录小数点的数量。*/
            int pointCounter;
    
            void check(String txt){
                IPEditText.this.log("checking:" + txt);
    
                // reset environment.
                checkingIdx = 0;
                pointCounter = 0;
                ips[0] = null;
                ips[1] = null;
                ips[2] = null;
                ips[3] = null;
    
                if (txt == null || "".equals(txt)) {
                    return;
                }
    
                char a[] = txt.toCharArray();
                int idx = 0;
                for(int i = 0; i < a.length; i++){
                    if(isNumber(a[i])){
                        if (ips[checkingIdx] == null) {
                            ips[checkingIdx] = new IPs();
                        }
    
                        //over length.
                        if(ips[checkingIdx].insertIdx >= 3){
                            ips[checkingIdx].initVariableArray();
                            ips[checkingIdx].sb.append(a[i]);
                            ips[checkingIdx].status = ips[checkingIdx].STATUS_ERROR;
    
                            continue;
                        }
    
                        ips[checkingIdx].number[ips[checkingIdx].insertIdx++] = a[i];
                        int c = Integer.valueOf(new String(ips[checkingIdx].number, 0, ips[checkingIdx].insertIdx));
                        if(c < 0 || c > 255){
                            ips[checkingIdx].status = ips[checkingIdx].STATUS_ERROR;
                        }else{
                            if(checkingIdx == 3){
                                ips[checkingIdx].status = ips[checkingIdx].STATUS_CORRECT;
                            }else if(checkingIdx > 3){
    
                            }else{
                                ips[checkingIdx].status = ips[checkingIdx].STATUS_WARN;
                            }
                        }
                    }else{
                        if('.' != a[i]){
                            // Error string...
                            IPEditText.this.log("character is not number.");
                            if(ips[checkingIdx] == null){
                                ips[checkingIdx] = new IPs();
                            }
    
                            ips[checkingIdx].initInvalidCharacterArray();
                            ips[checkingIdx].sbInvalid.append(new String(a, i, a.length - i));
    
                            break;
                        }
    
                        if(ips[checkingIdx] == null){
                            // '.' as header.
    
                            ips[checkingIdx] = new IPs();
                            ips[checkingIdx].initInvalidCharacterArray();
                            ips[checkingIdx].sbInvalid.append(new String(a, i, a.length - i));
    
                            break;
                        }
    
                        // check current ip part.
                        if(ips[checkingIdx].status == 1){
                            ips[checkingIdx].status = ips[checkingIdx].STATUS_CORRECT;
                        }
    
                        // record it.
                        pointCounter++;
    
                        // upgrade~
                        checkingIdx++;
                    }
                }// for -- end.
    
                //traversal ips
                for(IPs i:ips){
                    if (i != null) {
                        String jkl = new String(i.number, 0, i.insertIdx);
                        if("---".equals(jkl)){
                            jkl = i.sb.toString();
                        }
                        IPEditText.this.log(jkl + ",status:" + i.status);
                    }
                }
            }
    
            boolean isNumber(char c){
                IPEditText.this.log("checking character:" + c);
                if('0' <= c && c <= '9') {
                    return true;
                }
                return false;
            }
    
            CharSequence getRichTxt(){
                StringBuilder sb = new StringBuilder();
                int point = pointCounter;
                for(IPs i:ips){
                    if (i == null) {
                        break;
                    }
    
                    if(i.isCharacterInvalid){
                        sb.append("<font color='#A8A8A8'>");
                        sb.append(i.sbInvalid.toString());
                        sb.append("</font>");
    
                        continue;
                    }
    
                    String tmp = new String(i.number, 0, i.insertIdx);
                    if("---".equals(tmp)){
                        tmp = i.sb.toString();
                    }
    
                    if(i.status == i.STATUS_CORRECT){
                        sb.append("<font color='#008b00'>");
                    }else if(i.status == i.STATUS_WARN){
                        sb.append("<font color='#FFA500'>");
                    }else if(i.status == i.STATUS_ERROR){
                        sb.append("<font color='#ff0000'>");
                    }else{
                        //...
                    }
    
                    sb.append(tmp);
                    sb.append("</font>");
    
                    if(point > 0){
                        sb.append('.');
                        point--;
                    }
                }
    
                return sb.toString();
            }
    
            CharSequence getNormalTxt(){
                StringBuilder sb = new StringBuilder();
                int point = pointCounter;
                for(IPs i:ips){
                    if (i == null) {
                        break;
                    }
    
                    if(i.isCharacterInvalid){
                        sb.append(i.sbInvalid.toString());
    
                        continue;
                    }
    
                    String tmp = new String(i.number, 0, i.insertIdx);
                    if("---".equals(tmp)){
                        tmp = i.sb.toString();
                    }
                    sb.append(tmp);
                    if(point > 0){
                        sb.append('.');
                        point--;
                    }
                }
    
                return sb.toString();
            }
    
            @Override
            public String toString() {
                return getRichTxt().toString();
            }
    
            private class IPs {
    
                final int STATUS_CORRECT = 0;
                final int STATUS_WARN = 1;
                final int STATUS_ERROR = 2;
    
                char[] number = new char[3];
                int insertIdx;
                /**
                 * 0 -- correct
                 * 1 -- warn
                 * 2 -- error
                 * */
                int status;
                /**
                 * 当这个值为 true,所有内容都记录到 sbInvalid 中,
                 * number[], sb 都没有内容。
                 * */
                boolean isCharacterInvalid;
    
                StringBuilder sb;
                StringBuilder sbInvalid;
    
                IPs(){
                    number[0] = 0;
                    number[1] = 0;
                    number[2] = 0;
                }
    
                void initVariableArray(){
                    if (sb != null) {
                        return;
                    }
    
                    sb = new StringBuilder();
    
                    // append old data.
                    for(int i = 0; i < insertIdx; i++){
                        sb.append(number[i]);
                    }
    
                    // indicator
                    number[0] = '-';
                    number[1] = '-';
                    number[2] = '-';
                    insertIdx = 3;
                }
    
                void initInvalidCharacterArray(){
                    if (sbInvalid != null) {
                        return;
                    }
    
                    sbInvalid = new StringBuilder();
                    String tmp = new String(number, 0, insertIdx);
                    if("---".equals(tmp)){
                        tmp = sb.toString();
                    }
                    number[0] = 0;
                    number[1] = 0;
                    number[2] = 0;
                    insertIdx = 0;
    
                    sb = null;
    
                    status = STATUS_ERROR;
    
                    sbInvalid.append(tmp);
    
                    isCharacterInvalid = true;
                }
            }
        }
    }
    View Code

  • 相关阅读:
    react 【ref转发forwardRef】【useImperativeHandle】
    node 【redis 使用】
    vue vuerouter 实现原理
    webpack基础 【loader使用】
    【编程范式】范式新感悟
    【领域驱动设计】最佳实践之:单据集合根、命令、状态机
    【读书笔记】架构整洁之道
    Java中的Unsafe在安全领域的一些应用总结和复现
    CVE202222947 Spring Cloud Gateway SPEL RCE复现
    java高版本下各种JNDI Bypass方法复现
  • 原文地址:https://www.cnblogs.com/chorm590/p/11308125.html
Copyright © 2020-2023  润新知