【问题标题】:Dynamic mask for EditText using TextWatcher (for Blood Pressue unit)使用 TextWatcher 的 EditText 的动态掩码(用于血压单位)
【发布时间】:2019-12-13 22:06:52
【问题描述】:

我想为血压单位屏蔽 EditText,例如 (###/###)。

值可以是2digits/2digits3digits/3digits3digits/2digits2digits/3digits

目前我正在使用以下代码,该代码适用于 2digits/2digits3digits/3digits3digits/2digits 不适用于 2digits/3digits

这是我的代码:

import android.text.Editable
import android.text.TextUtils
import android.text.TextWatcher
import android.widget.EditText
import com.google.android.material.textfield.TextInputLayout

object EditTextBPMask {
    private const val mask22 = "##/##"
    private const val mask32 = "###/##"
    private const val mask33 = "###/###"

    fun unmask(s: String): String {
        return s.replace("[^0-9]*".toRegex(), "")
    }

    fun insert(editText: EditText): TextWatcher {
        return object : TextWatcher {
            var isUpdating: Boolean = false
            var old = ""

            override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
                val str = unmask(s.toString())
                val mask: String
                val defaultMask = getDefaultMask(str)
                mask = when (str.length) {
                    6 -> mask33
                    5 -> mask32
                    else -> defaultMask
                }

                var mascara = ""
                if (isUpdating) {
                    old = str
                    isUpdating = false
                    return
                }
                var i = 0
                for (m in mask.toCharArray()) {
                    if (m != '#' && str.length > old.length || m != '#' && str.length < old.length && str.length != i) {
                        mascara += m
                        continue
                    }

                    try {
                        mascara += str[i]
                    } catch (e: Exception) {
                        break
                    }

                    i++
                }
                isUpdating = true
                editText.setText(mascara)
                editText.setSelection(mascara.length)
                /*if (isValid(editText)) {
                    editText.error = null
                }
                else {
                    editText.error = "Incorrect value!"
                }*/
            }

            override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
            override fun afterTextChanged(s: Editable) {}
        }
    }

    private fun getDefaultMask(str: String): String {
        var defaultMask = mask22
        if (str.length > 6) {
            defaultMask = mask33
        }
        return defaultMask
    }

    fun isValid(editText: EditText): Boolean {
        return if (!TextUtils.isEmpty(editText.text.toString())) {
            unmask(editText.text.toString()).length > 3
        }
        else {
            false
        }
    }

}

【问题讨论】:

    标签: java android kotlin masking input-mask


    【解决方案1】:

    如果执行得当,您要尝试实现的内容最终将需要您使用regular expressions 中的状态机。

    粗略一看,您的解决方案缺少一些关键点,例如处理光标不在字符串末尾的情况;因此,您的解决方案也无法正确处理从剪贴板粘贴。

    接下来,您必须使用 ::afterTextChanged 方法及其 Editable 变量,以便在用户输入的同时“即时”在您的 EditText 中进行更改。

    我宁愿鼓励您采用已建立的解决方案,而且有很多。我看到您的项目中已经有了 Kotlin 运行时,因此您将能够引入像 RedMadRobot/input-mask-android 这样的库。该库允许根据用户输入在格式之间切换,我必须提到我是它的维护者,所以请随时询问有关其使用的任何问题。

    其他荣誉奖:

    ——尽管我不确定这些中的任何一个是否会真正处理具有不同长度值的特定情况。

    【讨论】:

      【解决方案2】:

      我最终想出了另一个解决方案,而不是使用屏蔽输入。这是我在应用中实现的逻辑:

      血压的正则表达式为[0-9]{0,3}/[0-9]{0-3}。但是不要只放/,而是使用[^a-zA-Z0-9]来防止在/位置插入任何字母和数字,以便我们稍后将十进制字段中的.更改为/

      InputFilter filter = new InputFilter() {
          final int maxDigitsBeforeDecimalPoint=3;
          final int maxDigitsAfterDecimalPoint=3;
      
          @Override
          public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
              StringBuilder builder = new StringBuilder(dest);
              builder.replace(dstart, dend, source
                      .subSequence(start, end).toString());
              if (!builder.toString().matches(
                      "(([1-9]{1})([0-9]{0,"+(maxDigitsBeforeDecimalPoint-1)+"})?)?([^a-zA-z0-9][0-9]{0,"+maxDigitsAfterDecimalPoint+"})?"
      
              )) {
                  if(source.length()==0)
                      return dest.subSequence(dstart, dend);
                  return "";
              }
              return null;
          }
      };
      

      然后,也为editText 创建一个TextWatcher。第一个 if 语句是为了防止 . 显示在 editText 中,因为 editText 设置为 android:inputType="number|numberDecimal"。对于第二个和第三个 if 语句,分别验证 2 位输入和 3 位输入。您也可以在/ 之后添加验证输入的逻辑:)。

      bloodPressureAm.setFilters(new InputFilter[] {filter});
      bloodPressureAm.addTextChangedListener(new TextWatcher() {
          @Override
          public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
      
          @Override
          public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
      
          @Override
          public void afterTextChanged(Editable editable) {
              if (String.valueOf(bloodPressureAm.getText()).contains(".")){
                  bloodPressureAm.setText(String.valueOf(bloodPressureAm.getText()).replace(".","/"));
              }
              //Means it's going to insert 100+
              if (editable.length() == 2 && !isDeleting){
                  if (Integer.parseInt(String.valueOf(bloodPressureAm.getText())) >= 20){
                      bloodPressureAm.setText(bloodPressureAm.getText() + "/");
                  }
              }
              if (editable.length() == 3 && !isDeleting){
                  if (!String.valueOf(bloodPressureAm.getText()).contains("/")){
                      bloodPressureAm.setText(bloodPressureAm.getText() + "/");
                  }
              }
              //Set the cursor to the end of the editText
              bloodPressureAm.setSelection(bloodPressureAm.getText().length());
          }
      });
      

      最后,由于我们强制将 / 插入到 2 或 3 位数字之后,由于 TextWatcher,我们无法将其删除。因此,要克服它,请为 editText 创建一个 onKeyListener

      bloodPressureAm.setOnKeyListener((view, i, keyEvent) -> {
          if (i == KeyEvent.KEYCODE_DEL){
              isDeleting = true;
          }else{
              isDeleting = false;
          }
          return false;
      });
      

      希望这会有所帮助! >:>

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-05-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-10-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多