Android掩码格式化程序,只允许整数但在掩码中使用文本

时间:2018-05-25 16:52:14

标签: java android masking maskformatter

在Android上我需要创建一个编辑输入,它将具有不会更改的静态文本元素以及当用户键入"#"的值时需要用数字替换的其他值。使用符号。替换应该只是0-9之间的整数。例如,掩码可能是" SERIAL NO #####",用户在数字中键入"#"值将被替换,最终给出字符串结果"序列号12309"。

我们现有的代码使用了MaskFormatter,但它正在抛出解析其中包含任何字符的掩码的异常,如上所述(尽管它只适用于"#")。

此外,这种面具可以有很大的不同。从像" ####"这样的简单面具到更复杂的面具,如" ### A - ## WHATEVER"到" A#A $#RRT#",其中只有"#"键入时应该允许数值。

有一种简单的方法可以做到这一点还是我需要编写自己的解析代码? MaskFormatter是正确的方法还是有更优雅的机制?我很确定我可以编写自定义代码来执行此操作,但我更喜欢标准解决方案。

以下是该字段的可视化:

enter image description here

这是现有的代码(我没有写它,永远存在):

    public class MaskedWatcher implements TextWatcher {

    private String mMask;
    String mResult = "";    
    String mPrevResult = "";

    public MaskedWatcher(String mask){
        mMask = mask;
    }

    public void afterTextChanged(Editable s) {

        String mask = mMask;
        String value = s.toString();

        if(value.equals(mResult)) {
            return;
        }

        try {

            // prepare the formatter
            MaskedFormatter formatter = new MaskedFormatter(mask);
            formatter.setValueContainsLiteralCharacters(true);
            formatter.setPlaceholderCharacter((char)1);

            // get a string with applied mask and placeholder chars
            value = formatter.valueToString(value);

            try{
                // find first placeholder
                if ( value.indexOf((char)1) != -1) {
                    value = value.substring(0, value.indexOf((char)1));

                    //process a mask char
                    if(value.charAt(value.length()-1) == mask.charAt(value.length()-1) && ((value.length()-1) >= 0)){
                        value = value.substring(0, value.length() - 1);
                    }
                }
            }
            catch(Exception e){
                Utilities.logException(e);
            }

            // if we are deleting characters reset value and start over
            if(mPrevResult.trim().length() > value.trim().length()) {
                value = "";
            }

            setFieldValue(value);
            mResult = value;
            mPrevResult = value;
            s.replace(0, s.length(), value);
        } 
        catch (ParseException e) {
            //the entered value does not match a mask
            if(mResult.length() >= mMask.length()) {
                if(value.length() > mMask.length()) {
                    value = value.substring(0, mMask.length());
                }
                else {
                    value = "";
                    setFieldValue(value);
                    mPrevResult = value;
                    mResult = value;
                }
            }
            else {
                int offset = e.getErrorOffset();
                value = removeCharAt(value, offset);
            }
            s.replace(0, s.length(), value);
        }
    }

1 个答案:

答案 0 :(得分:0)

好的,现在我知道为什么没有人回答这个问题 - 这是令人讨厌的。我做了很多研究,甚至找不到任何类似的东西 - 也许我不擅长搜索。首先是我的研究历史。我原本以为我可以在现场观看按键并对那些做出反应。并不是的。您可以使用硬键盘,但不是软键盘。我尝试了针对三星设备的各种方法但没有成功。也许有人知道一个技巧,但我找不到它。 所以我选择了唯一可用的选项 - TextWatcher。唯一真正的问题是你无法真正看到按下了什么键(是添加了数字还是删除了键?),所以你必须用当前更改的字符串检查前一个字符串并尽力确定改变了什么,怎么做。

只是为了帮助,我实现的行为基本上是所有用户输入数字(0-9)并且不更改掩码的其他元素。此外,我还需要在输入或删除项目时将光标移动到正确的位置。另外,如果我们删除它们,我们需要移除适当的元素并放回面具。

例如,如果掩码为“ADX - ### - R”,则在键入时会发生以下情况:

Given : "ADX-###-R" Typing: "4" Results: "ADX-4##-R" Cursor at "4"
Given : "ADX-4##-R" Typing: "3" Results: "ADX-43#-R" Cursor at "3"
Given : "ADX-43#-R" Typing: "1" Results: "ADX-431-R" Cursor at end of string
Given : "ADX-431-R" Typing: "Del" Results: "ADX-43#-R" Cursor at "3"

这就是它的要点。我们还有Hint / Placeholder和Default值的要求,我留下了所有这些。现在是代码。

以下是屏幕截图:

enter image description here

首先是XML:

<LinearLayout 
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:focusable="true"
    android:focusableInTouchMode="true">

    <TextView
        android:id="@+id/name"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:textSize="18sp"
        android:textStyle="normal"
        android:paddingLeft="5dp"
        android:paddingRight="5dp"
        android:text="" />

    <EditText android:id="@+id/entry"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:gravity="right"
        android:singleLine="true"
        android:maxLines="1"
        android:ellipsize="end" /> 

    <View
        android:layout_marginTop="8dp"
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="@android:color/darker_gray"
        android:visibility="gone" />

</LinearLayout>

主要字段代码:

public class FormattedInput extends LinearLayout {

    private Context mContext;
    private Field mField;
    private TextView mName;
    private EditText mEntry;
    private Boolean mEnableEvents = true;
    private String mPlaceholderText = "";
    private final static String REPLACE_CHAR = " "; // Replace missing data with blank

    public FormattedInput(Context context, Field field) {
        super(context);

        mContext = context;
        mField = field;

        initialize();
        render(mField);
    }

    private void initialize() {

        LayoutInflater inflater = (LayoutInflater)mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        inflater.inflate(R.layout.field_formatted_input, this);

        // setup fields
        mName = (TextView)findViewById(R.id.name);
        mEntry = (EditText)findViewById(R.id.entry);
        mEntry.setFocusable(true);
        mEntry.setRawInputType(Configuration.KEYBOARD_QWERTY);
        mEntry.addTextChangedListener(
                new MaskedWatcher(mField.getDisplayMask())
        );
    }

    public void render(Field field) {
        mName.setText(mField.getFieldName());

        mPlaceholderText = mField.getPlaceholderText();
        if(Utilities.stringIsBlank(mPlaceholderText)) {
            mPlaceholderText = mField.getDisplayMask();
        }
        mEntry.setHint(mPlaceholderText);
        mEntry.setHintTextColor(Color.GRAY);

        if(!Utilities.stringIsBlank(mField.getValue())) {
            mEnableEvents = false;
            String value =  String.valueOf(mField.getValue());
            if (value.equalsIgnoreCase(mField.getDisplayMask()))
                mEntry.setText(mField.getDisplayMask());
            else {
                String val = fillValueWithMask(value, mField.getDisplayMask());
                mEntry.setText(val);
            }
            mEnableEvents = true;
        }
        else if (!Utilities.stringIsBlank(mField.getDefaultValue())) {
            mEnableEvents = false;
            String val = fillValueWithMask(mField.getDefaultValue(), mField.getDisplayMask());
            mEntry.setText(val);
            mEnableEvents = true;
        }
        else {
            mEnableEvents = false;
            mEntry.setText(null);
            mEnableEvents = true;
        }
    }

    public static String fillValueWithMask(String value, String mask) {
        StringBuffer result = new StringBuffer(mask);
        for (int i = 0; i < value.length() && i <= mask.length()-1 ; i++){
            if (mask.charAt(i) == '#' && value.charAt(i) != ' ' && Character.isDigit(value.charAt(i)))
                result.setCharAt(i,value.charAt(i));
        }
        return result.toString();
    }

    public class MaskedWatcher implements TextWatcher {

        private String mMask;
        String mResult = "";    
        String mPrevResult = "";
        int deletePosition = 0;

        public MaskedWatcher(String mask){
            mMask = mask;
        }

        public void afterTextChanged(Editable s) {

            String value = s.toString();

            // No Change, return - or reset of field
            if (value.equals(mPrevResult) && (!Utilities.stringIsBlank(value) && !Utilities.stringIsBlank(mPrevResult))) {
                return;
            }

            String diff = value;
            // First time in and no value, set value to mask
            if (Utilities.stringIsBlank(mPrevResult) && Utilities.stringIsBlank(value)) {
                mPrevResult = mMask;
                mEntry.setText(mPrevResult);
            }
            // If time, but have value
            else if (Utilities.stringIsBlank(mPrevResult) && !Utilities.stringIsBlank(value)) {
                mPrevResult = value;
                mEntry.setText(mPrevResult);
            }
            // Handle other cases of delete and new value, or no more typing allowed
            else {
                // If the new value is larger or equal than the previous value, we have a new value
                if (value.length() >= mPrevResult.length())
                    diff = Utilities.difference(mPrevResult, value);

                // See if new string is smaller, if so it was a delete.
                if (value.length() < mPrevResult.length()) {
                    mPrevResult = removeCharAt(mPrevResult, deletePosition);
                    // Deleted back to mask, reset
                    if (mPrevResult.equalsIgnoreCase(mMask)) {
                        mPrevResult = "";
                        setFieldValue("");
                        mEntry.setText("");
                        mEntry.setHint(mPlaceholderText);
                        return;
                    }
                    // Otherwise set value
                    else
                        setFieldValue(mPrevResult);
                    mEntry.setText(mPrevResult);
                }
                // A new value was added, add to end
                else if (mPrevResult.indexOf('#') != -1) {
                    mPrevResult = mPrevResult.replaceFirst("#", diff);
                    mEntry.setText(mPrevResult);
                    setFieldValue(mPrevResult);
                }
                // Unallowed change, reset the value back
                else {
                    mEntry.setText(mPrevResult);
                }
            }

            // Move cursor to next spot
            int i = mPrevResult.indexOf('#');
            if (i != -1)
                mEntry.setSelection(i);
            else
                mEntry.setSelection(mPrevResult.length());
        }

        private void setFieldValue(String value) {
            //mEnableEvents = false;
            if(mEnableEvents == false) {
                return;
            }
            // Set the value or do whatever you want to do to save or react to the change
        }

        private String replaceMask(String str) {
            return str.replaceAll("#",REPLACE_CHAR);
        }

        private String removeCharAt(String str, int pos) {
            StringBuilder info = new StringBuilder(str);
            // If the position is a mask character, change it, else ignore the change
            if (mMask.charAt(pos) == '#') {
                info.setCharAt(pos, '#');
                return info.toString();
            }
            else {
                Toast.makeText(mContext, "The mask value can't be deleted, only modifiable portion", Toast.LENGTH_SHORT);
                return str;
            }
        }

        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        }

        public void onTextChanged(CharSequence s, int start, int before, int count) {
            deletePosition = start;
        }

    }
}

实用程序代码:

public static boolean stringIsBlank(String stringValue) {
    if (stringValue != null) {
        return stringValue.trim().length() <= 0;
    } else {
        return true;
    }
}

public static String difference(String str1, String str2) {
    int at = indexOfDifference(str1, str2);
    if (at == -1) {
        return "";
    }
    return str2.substring(at,at+1);
}

Field类......你需要添加getter和setter:

public class Field {
    private String defaultValue;
    private Object value;
    private String displayMask;
    private String placeholderText;
}

一些最后的想法。基本机制是将前一个字符串与当前字符串进行比较。如果新字符串较小,那么我们将删除并使用deletePosition,只要位置与掩码中的“#”匹配,因为其他字符是不可修改的。还存在先前值的问题 - 并且假设如果该值出现在“#”值中,则缺失将被替换为“”(空白)。 Field不是必需的,但是在我们的案例中是一个帮助类,有很多其他功能。希望这有助于某人!