一种可以实时检测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

 

 


 

posted @ 2019-08-13 10:32  大窟窿  阅读(1088)  评论(0编辑  收藏  举报