首页 文章

在android中编辑文本中的信用卡格式

提问于
浏览
48

如何使 EditText 接受输入格式:

4digit 4digit 4digit 4digit

我试过Custom format edit text input android to accept credit card number,但遗憾的是我无法删除这些空格 . 只要有空间,我就无法删除它 . 请帮我找出问题所在 .

20 回答

  • 1
    int          keyDel;
    String       a;
    String       a0;
    int          isAppent = 0;
    final String ch       = " ";
    
    private void initListner() {
    
    
        txtCreditNumber.addTextChangedListener(new TextWatcher() {
    
            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
    
                boolean flag = true;
                if (s.length() > 19) {
                    txtCreditNumber.setText(a0);
                    txtCreditNumber.setSelection(txtCreditNumber.getText().length());
                    return;
                }
                String eachBlock[] = s.toString().split(ch);
                for(int i = 0; i < eachBlock.length; i++) {
                    if (eachBlock[i].length() > 4) {
                        flag = false;
                    }
                }
                if (a0.length() > s.toString().length()) {
                    keyDel = 1;
                }
                if (flag) {
                    if (keyDel == 0) {
    
                        if (((txtCreditNumber.getText().length() + 1) % 5) == 0) {
    
                            if (s.toString().split(ch).length <= 3) {
                                isAppent = 1;
                                txtCreditNumber.setText(s + ch);
                                isAppent = 0;
                                txtCreditNumber.setSelection(txtCreditNumber.getText().length());
                                a = txtCreditNumber.getText().toString();
                                return;
                            }
                        }
                        if (isAppent == 0) {
                            String str = s.toString();
                            if (str.lastIndexOf(ch) == str.length() - 1) {
                                str = str.substring(0, str.lastIndexOf(ch));
                                keyDel = 1;
                                txtCreditNumber.setText(str);
                                keyDel = 0;
                                txtCreditNumber.setSelection(txtCreditNumber.getText().length());
                                a = txtCreditNumber.getText().toString();
                                return;
                            }
                        }
    
                    }
                    else {
                        String str = s.toString();
                        if (str.length() > 0 && str.lastIndexOf(ch) == str.length() - 1) {
                            str = str.substring(0, str.lastIndexOf(ch));
                            keyDel = 1;
                            txtCreditNumber.setText(str);
                            keyDel = 0;
                            txtCreditNumber.setSelection(txtCreditNumber.getText().length());
                            a = txtCreditNumber.getText().toString();
                            return;
                        }
                        else {
                            a = txtCreditNumber.getText().toString();
                            keyDel = 0;
                        }
                    }
    
                }
                else {
                    String str = s.toString();
                    str = str.substring(0, str.length() - 1) + ch + str.substring(str.length() - 1, str.length());
    
                    a = str;
                    txtCreditNumber.setText(a);
                    txtCreditNumber.setSelection(txtCreditNumber.getText().length());
                }
    
            }
    
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                // TODO Auto-generated method stub
                a0 = s.toString();
            }
    
            @Override
            public void afterTextChanged(Editable s) {
            }
        });
    }
    
  • 1

    找到多个'OK'的答案后 . 我走向了一个更好的TextWatcher,它可以正确地独立于 TextView 工作 .

    TextWatcher类如下:

    /**
     * Formats the watched EditText to a credit card number
     */
    public static class FourDigitCardFormatWatcher implements TextWatcher {
    
        // Change this to what you want... ' ', '-' etc..
        private static final char space = ' ';
    
        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
        }
    
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        }
    
        @Override
        public void afterTextChanged(Editable s) {
            // Remove spacing char
            if (s.length() > 0 && (s.length() % 5) == 0) {
                final char c = s.charAt(s.length() - 1);
                if (space == c) {
                    s.delete(s.length() - 1, s.length());
                }
            }
            // Insert char where needed.
            if (s.length() > 0 && (s.length() % 5) == 0) {
                char c = s.charAt(s.length() - 1);
                // Only if its a digit where there should be a space we insert a space
                if (Character.isDigit(c) && TextUtils.split(s.toString(), String.valueOf(space)).length <= 3) {
                    s.insert(s.length() - 1, String.valueOf(space));
                }
            }
        }
    }
    

    然后将其添加到TextView中,就像其他任何 TextWatcher 一样 .

    {
      //...
      mEditTextCreditCard.addTextChangedListener(new FourDigitCardFormatWatcher()); 
    }
    

    这将自动删除合理返回的空间,以便用户在编辑时实际上可以做更少的击键 .

    警告

    如果您使用 inputType="numberDigit" ,这将禁用'-'和' '字符,因此我建议使用 inputType="phone" . 这可以启用其他字符,但只需使用自定义输入过滤器并解决问题 .

  • 48

    这是我的解决方案 . 我的评论应该足以让Android开发人员了解正在发生的事情,但如果您有任何问题,请随时提出,我会尽我所能回答 .

    private KeyEvent keyEvent;
    
    final TextWatcher cardNumberWatcher = new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int start, int before, int count) {
                // NOT USING
            }
    
            @Override
            public void onTextChanged(CharSequence charSequence, int start, int before, int count) {
                // NOT USING
            }
    
            @Override
            public void afterTextChanged(Editable editable) {
                String cardNumbersOnly = editable.toString().replace("-", "");
    
                /**
                * @PARAM keyEvent
                * This gets called upon deleting a character so you must keep a 
                * flag to ensures this gets skipped during character deletion
                */
                if (cardNumbersOnly.length() >= 4 && keyEvent == null) {
                    formatCreditCardTextAndImage(this);
                }
    
                keyEvent = null;
            }
        };
    
        cardNumberEditText.addTextChangedListener(cardNumberWatcher);
    
        /**
        * @LISTENER
        * Must keep track of when the backspace event has been fired to ensure    
        * that the delimiter character and the character before it is deleted 
        * consecutively to avoid the user from having to press backspace twice 
        */
        cardNumberEditText.setOnKeyListener(new View.OnKeyListener() {
            @Override
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                if (event.getAction() != KeyEvent.ACTION_UP) {
                    // Hold reference of key event for checking within the text watcher
                    keyEvent = event;
                    String cardNumberString = cardNumberEditText.getText().toString();
    
                    if (keyCode == event.KEYCODE_DEL) {
                        if (cardNumberString.substring(cardNumberString.length() - 1).equals("-")) {
                            // Remove listener to avoid infinite looping
                            cardNumberEditText.removeTextChangedListener(cardNumberWatcher);
                            // Remove hyphen and character before it
                            cardNumberEditText.setText(cardNumberString.substring(0, cardNumberString.length() - 1));
                            // Set the cursor back to the end of the text
                            cardNumberEditText.setSelection(cardNumberEditText.getText().length());
                            // Add the listener back
                            cardNumberEditText.addTextChangedListener(cardNumberWatcher);
                        }
                        else if (cardNumberString.length() < 2) {
                            cardNumberBrandImageView.setImageDrawable(null);
                            cardNumberBrandImageView.setVisibility(View.INVISIBLE);
                        }
                    }
                }
                return false;
            }
        });
    }
    
    private void formatCreditCardTextAndImage (TextWatcher textWatcher) {
        // Remove to avoid infinite looping
        cardNumberEditText.removeTextChangedListener(textWatcher);
    
        String cardNumberString = cardNumberEditText.getText().toString();
    
        /**
        * @CONDITION
        * Append delimiter after every fourth character excluding the 16th
        */
        if ((cardNumberString.length() + 1) % 5 == 0 && !cardNumberString.substring(cardNumberString.length() - 1).equals("-")) {
                cardNumberEditText.setText(cardNumberString + "-");
        }
    
        // Set the cursor back to the end of the text
        cardNumberEditText.setSelection(cardNumberEditText.getText().length());
        cardNumberEditText.addTextChangedListener(textWatcher);
    
        /**
        * @CardBrand
        * Is an enum utility class that checks the card numbers 
        * against regular expressions to determine the brand and updates the UI
        */
        if (cardNumberString.length() == 2) {
            switch (CardBrand.detect(cardNumberEditText.getText().toString())) {
                case VISA:
                    cardNumberBrandImageView.setImageResource(R.drawable.visa);
                    cardNumberBrandImageView.setVisibility(View.VISIBLE);
                    card.setBrand(Brand.Visa);
                    break;
                case MASTERCARD:
                    cardNumberBrandImageView.setImageResource(R.drawable.mastercard);
                    cardNumberBrandImageView.setVisibility(View.VISIBLE);
                    card.setBrand(Brand.MasterCard);
                    break;
                case DISCOVER:
                    cardNumberBrandImageView.setImageResource(R.drawable.discover);
                    cardNumberBrandImageView.setVisibility(View.VISIBLE);
                    card.setBrand(Brand.Discover);
                    break;
                case AMERICAN_EXPRESS:
                    cardNumberBrandImageView.setImageResource(R.drawable.americanexpress);
                    cardNumberBrandImageView.setVisibility(View.VISIBLE);
                    card.setBrand(Brand.AmericanExpress);
                    break;
                case UNKNOWN:
                    cardNumberBrandImageView.setImageDrawable(null);
                    cardNumberBrandImageView.setVisibility(View.INVISIBLE);
                    card.setBrand(null);
                    break;
            }
        }
    }
    
  • 19
    private class TextWatcherIBAN implements TextWatcher {
    
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    
            }
    
            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
    
            }
    
            @Override
            public void afterTextChanged(Editable s) {
                textInputEditText.removeTextChangedListener(this);
                formatIBANEditText(textInputEditText);
                textInputEditText.addTextChangedListener(this);
    
            }
        }
    
    
    public void formatIBANEditText(TextInputEditText editText) {
        String decimalAmount = editText.getText().toString();
        int selection = editText.getSelectionEnd() == decimalAmount.length() ? -1 : editText.getSelectionEnd();
        decimalAmount = formatIBAN(decimalAmount);
        editText.setText(decimalAmount);
    
        if (selection != -1) {
            editText.setSelection(selection);
        } else {
            editText.setSelection(decimalAmount.length());
        }
    
    }
    
    public String formatIBAN(String text) {
        return formatterIBAN(new StringBuilder(text));
    }
    
    private String formatterIBAN(StringBuilder text) {
        int group = text.toString().length() / 5;
        int spaceCount = getSpaceCount(text);
        if (spaceCount < group) {
            return formatterIBAN(text.insert(4 + 5 * spaceCount, space));
        } else {
            return text.toString();
        }
    }
    
    private int getSpaceCount(StringBuilder text) {
        int spaceCount = 0;
        for (int index = 0; index < text.length(); index++) {
            if (text.charAt(index) == space.charAt(0)) {
                spaceCount++;
            }
        }
        return spaceCount;
    }
    
    
    textInputEditText.addTextChangedListener(new TextWatcherIBAN());
    
  • 1

    这是一个使用正则表达式的清洁解决方案虽然正则表达式效率低,但在这种情况下它们就足够了,因为它处理的字符串最多为19个字符,即使每次按键后都进行处理 .

    editTxtCardNumber.addTextChangedListener(new TextWatcher() {
    
        @Override
        public void onTextChanged(CharSequence s, int arg1, int arg2,
                int arg3) { }
    
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
    
        @Override
        public void afterTextChanged(Editable s) {
            String initial = s.toString();
            // remove all non-digits characters
            String processed = initial.replaceAll("\\D", "");
            // insert a space after all groups of 4 digits that are followed by another digit
            processed = processed.replaceAll("(\\d{4})(?=\\d)", "$1 ");
            // to avoid stackoverflow errors, check that the processed is different from what's already
            //  there before setting
            if (!initial.equals(processed)) {
                // set the value
                s.replace(0, initial.length(), processed);
            }
    
        }
    
    });
    
  • 10

    我认为我的解决方案可以很好地适用于中间文本操作或复制粘贴操作 .

    请参阅下面的代码,

    class BankNumberTextWatcher implements TextWatcher {
          private int previousCodeLen = 0;
    
          @Override
          public void beforeTextChanged(CharSequence s, int start, int count, int after) {
          }
    
          @Override
          public void onTextChanged(CharSequence s, int start, int before, int count) {
          }
    
          @Override
          public void afterTextChanged(Editable s) {
              if (s.length() > 0) {
                  String numbersOnly = s.toString().replaceAll("[^0-9]", "");
                  // current code pattern miss-match, then handle cursor position and format the code
                  handleEditInput(numbersOnly);
              } else {
                  previousCodeLen = 0;
              }
          }
    
          /**
           * Handle EditText input process for credit card including insert, delete during middle position,
           * end position or copy-paste controller
           *
           * @param numbersOnly the pure number without non-digital characters
           */
          private void handleEditInput(final String numbersOnly) {
              String code = formatNumbersAsCode(numbersOnly);
              int cursorStart = etBankCardNumber.getSelectionStart();
              etBankCardNumber.removeTextChangedListener(this);
              etBankCardNumber.setText(code);
              int codeLen = code.length();
              if (cursorStart != codeLen) {
                 // middle-string operation
                 if (cursorStart > 0 && cursorStart % 5 == 0) {
                    if (codeLen > previousCodeLen) {
                        // insert, move cursor to next
                        cursorStart++;
                    } else if (codeLen < previousCodeLen) {
                        // delete, move cursor to previous
                        cursorStart--;
                    }
                 }
                 etBankCardNumber.setSelection(cursorStart);
              } else {
                 // end-string operation
                 etBankCardNumber.setSelection(codeLen);
              }
              etBankCardNumber.addTextChangedListener(this);
              previousCodeLen = codeLen;
          }
    
          /**
           * formats credit code like 1234 1234 5123 1234
           *
           * @param s
           * @return
           */
           public String formatNumbersAsCode(CharSequence s) {
              if (TextUtils.isEmpty(s)) {
                return "";
              }
              int len = s.length();
              StringBuilder tmp = new StringBuilder();
              for (int i = 0; i < len; ++i) {
                  tmp.append(s.charAt(i));
                  if ((i + 1) % 4 == 0 && (i + 1) != len) {
                      tmp.append(" ");
                  }
              }
              return tmp.toString();
            }
      }
    

    使inputType为EditText的数字,以避免布局文件中的其他字符 .

    希望对你有所帮助 .

  • 1

    上述答案都不适合我 . 我创建了一个解决start-string / end-string / mid-string问题的方法 . 复制和粘贴也应该正常工作 . 这支持Mastercard,Visa和Amex . 您可以更改分隔符 . 如果您不需要付款方式类型,请将其删除 . 不过是Kotlin . 这个想法很简单 . 每次文本更改时我都会删除所有分隔符并根据格式重新添加它们 . 解决了start-string / mid-string问题 . 那么唯一的问题是你需要在添加分隔符后计算出正确的文本位置 .

    fun addCreditCardNumberTxtWatcher(et: EditText, separator: Char, paymentMethodType: PaymentMethodType): TextWatcher {
        val tw = object : TextWatcher {
            var mBlock = false
            override fun afterTextChanged(s: Editable) {
            }
            override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
                  Logger.d("_debug", "s: $s, start: $start, count: $count, after $after")
            }
            override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
                if (mBlock)
                    return
                var lastPos = et.selectionStart
                val oldStr = et.text.toString().replace(separator.toString(), "", false)
                var newFormattedStr = ""
                if (before > 0) {
                    if (lastPos > 0 && et.text.toString()[lastPos - 1] == separator) lastPos--
                }
                Logger.d("_debug", "lastPos: $lastPos, s: $s, start: $start, before: $before, count $count")
                mBlock = true
                oldStr.forEachIndexed { i, c ->
                    when (paymentMethodType) {
                        PaymentMethodType.MASTERCARD, PaymentMethodType.VISA -> {
                            if (i > 0 && i % 4 == 0) {
                                newFormattedStr += separator
                            }
                        }
                        PaymentMethodType.AMERICAN_EXPRESS -> {
                            if (i == 4 || i == 10 || i == 15) {
                                newFormattedStr += separator
                            }
                        }
                    }
                    newFormattedStr += c
                }
                et.setText(newFormattedStr)
                if (before == 0) {
                    if (et.text.toString()[lastPos - 1] == separator) lastPos++
                }
                et.setSelection(lastPos)
                mBlock = false
            }
        }
        et.addTextChangedListener(tw)
        return tw
    }
    
  • 2

    我修改了Chris Jenkins的答案,使其更加强大 . 这样,即使用户编辑文本的中间部分,间距字符仍然会正确插入(并在错误的位置自动删除) .

    要使其正常工作,请确保 EditText 属性设置如下(请注意 digits 上的空格):

    android:digits="01234 56789"
    android:inputType="number"
    android:maxLength="19"
    

    那么这就是你需要的 TextWatcher . 匿名类也可以是静态的,因为它独立于 EditText .

    yourTextView.addTextChangedListener(new TextWatcher() {
            private static final char space = ' ';
    
            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
            }
    
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            }
    
            @Override
            public void afterTextChanged(Editable s) {
                // Remove all spacing char
                int pos = 0;
                while (true) {
                    if (pos >= s.length()) break;
                    if (space == s.charAt(pos) && (((pos + 1) % 5) != 0 || pos + 1 == s.length())) {
                        s.delete(pos, pos + 1);
                    } else {
                        pos++;
                    }
                }
    
                // Insert char where needed.
                pos = 4;
                while (true) {
                    if (pos >= s.length()) break;
                    final char c = s.charAt(pos);
                    // Only if its a digit where there should be a space we insert a space
                    if ("0123456789".indexOf(c) >= 0) {
                        s.insert(pos, "" + space);
                    }
                    pos += 5;
                }
            }
        });
    
  • 78

    你可能已经弄明白了,但这就是我所做的 . 我必须覆盖的唯一方法是AfterTextChanged .

    检查信用卡的格式是否已经有效,基本情况以防止无限递归

    如果表单无效,请删除所有空格,然后复制到另一个字符串中,在适当的位置插入空格 .

    然后只需用新字符串替换editable即可 .

    如果您需要特定步骤的代码,请随时询问 .

    而Preethi,你无法删除空格的原因是因为你无法在onTextChanged回调中更改文本 . 来自开发者网站:

    public abstract void onTextChanged(CharSequence s,int start,int before,int count)在API级别1中添加

    调用此方法通知您,在s中,从start开始的计数字符刚刚替换了之前具有长度的旧文本 . 尝试从此回调中更改s是错误的 .

  • 1

    不确定 TextWatcher 是否正确使用 - 我们应该使用 InputFilter

    根据Android文档,TextWatcher应该用于外部使用示例:一个[EditView]用于密码输入 + 一个[TextView]视图,显示"weak","strong"等...

    对于 Credit Card Format 我正在使用 InputFilter

    public class CreditCardInputFilter implements InputFilter {
        public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
            if (dest != null & dest.toString().trim().length() > 24) return null;
            if (source.length() == 1 && (dstart == 4 || dstart == 9 || dstart == 14))
                return " " + new String(source.toString());
            return null; // keep original
        }
    }
    

    并结合长度过滤器(Android SDK):

    mEditCardNumber.setFilters(new InputFilter[]{
         new InputFilter.LengthFilter(24),
         new CreditCardInputFilter(),
    });
    

    这可以在输入和删除数字时处理这种情况 .

    (!)但是这不能处理整个字符串的复制/粘贴的情况,这个应该在不同的InputFilter类中完成

    希望能帮助到你 !

  • 0

    即使用户编辑了mid-string,此实现也可确保正确放置间距字符 . 还支持显示在软键盘上的其他字符(例如破折号);也就是说,用户无法输入它们 . 可以进行一项改进:此实现不允许删除字符串中间的字符 .

    public class CreditCardTextWatcher implements TextWatcher {
    
        public static final char SPACING_CHAR = '-'; // Using a Unicode character seems to stuff the logic up.
    
        @Override
        public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) { }
    
        @Override
        public void onTextChanged(final CharSequence s, final int start, final int before, final int count) { }
    
        @Override
        public void afterTextChanged(final Editable s) {
            if (s.length() > 0) {
    
                // Any changes we make to s in here will cause this method to be run again.  Thus we only make changes where they need to be made,
                // otherwise we'll be in an infinite loop.
    
                // Delete any spacing characters that are out of place.
                for (int i=s.length()-1; i>=0; --i) {
                    if (s.charAt(i) == SPACING_CHAR  // There is a spacing char at this position ,
                            && (i+1 == s.length()    // And it's either the last digit in the string (bad),
                            || (i+1) % 5 != 0)) {    // Or the position is not meant to contain a spacing char?
    
                        s.delete(i,i+1);
                    }
                }
    
                // Insert any spacing characters that are missing.
                for (int i=14; i>=4; i-=5) {
                    if (i < s.length() && s.charAt(i) != SPACING_CHAR) {
                        s.insert(i, String.valueOf(SPACING_CHAR));
                    }
                }
            }
        }
    }
    

    适用于适当的 PasswordTransformationMethod 实现以屏蔽CC数字 .

  • 3

    经过大量搜索而没有得到满意的答案来满足我的需求后,我最终编写了自己的功能 .

    以下是根据输入的卡类型格式化输入的信用卡详细信息的示例 . 目前,它负责格式化Visa,MasterCard和American Express .

    editTxtCardNumber.addTextChangedListener(new TextWatcher() {
    
            private boolean spaceDeleted;
    
            @Override
            public void onTextChanged(CharSequence s, int arg1, int arg2,
                    int arg3) {
    
            }
    
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                CharSequence charDeleted = s.subSequence(start, start + count);
                spaceDeleted = " ".equals(charDeleted.toString());
            }
    
            @Override
            public void afterTextChanged(Editable editable) {
    
                if(editTxtCardNumber.getText().length() > 0 && editTxtCardNumber.getText().charAt(0) == '3') {
                    editTxtCardNumber.setFilters(new InputFilter[] { new InputFilter.LengthFilter(Constants.MAX_LENGTH_CARD_NUMBER_AMEX) });
    
                    editTxtCardNumber.removeTextChangedListener(this);
                    int cursorPosition = editTxtCardNumber.getSelectionStart();
                    String withSpaces = formatTextAmEx(editable);
                    editTxtCardNumber.setText(withSpaces);
                    editTxtCardNumber.setSelection(cursorPosition + (withSpaces.length() - editable.length()));
    
                    if (spaceDeleted) {
                        editTxtCardNumber.setSelection(editTxtCardNumber.getSelectionStart() - 1);
                        spaceDeleted = false;
                    }
    
                    editTxtCardNumber.addTextChangedListener(this);
                } else if(editTxtCardNumber.getText().length() > 0 
                        && (editTxtCardNumber.getText().charAt(0) == '4' || editTxtCardNumber.getText().charAt(0) == '5')) {
                    editTxtCardNumber.setFilters(new InputFilter[] { new InputFilter.LengthFilter(Constants.MAX_LENGTH_CARD_NUMBER_VISA_MASTERCARD) });
    
                    editTxtCardNumber.removeTextChangedListener(this);
                    int cursorPosition = editTxtCardNumber.getSelectionStart();
                    String withSpaces = formatTextVisaMasterCard(editable);
                    editTxtCardNumber.setText(withSpaces);
                    editTxtCardNumber.setSelection(cursorPosition + (withSpaces.length() - editable.length()));
    
                    if (spaceDeleted) {
                        editTxtCardNumber.setSelection(editTxtCardNumber.getSelectionStart() - 1);
                        spaceDeleted = false;
                    }
    
                    editTxtCardNumber.addTextChangedListener(this);
                } else {
                    editTxtCardNumber.setFilters(new InputFilter[] { new InputFilter.LengthFilter(Constants.MAX_LENGTH_CARD_NUMBER_VISA_MASTERCARD) });
    
                    editTxtCardNumber.removeTextChangedListener(this);
                    int cursorPosition = editTxtCardNumber.getSelectionStart();
                    String withSpaces = formatTextVisaMasterCard(editable);
                    editTxtCardNumber.setText(withSpaces);
                    editTxtCardNumber.setSelection(cursorPosition + (withSpaces.length() - editable.length()));
    
                    if (spaceDeleted) {
                        editTxtCardNumber.setSelection(editTxtCardNumber.getSelectionStart() - 1);
                        spaceDeleted = false;
                    }
    
                    editTxtCardNumber.addTextChangedListener(this);
                }
            }
        });
    
        private String formatTextVisaMasterCard(CharSequence text)
        {
            StringBuilder formatted = new StringBuilder();
            int count = 0;
            for (int i = 0; i < text.length(); ++i)
            {
                if (Character.isDigit(text.charAt(i)))
                {
                    if (count % 4 == 0 && count > 0)
                        formatted.append(" ");
                    formatted.append(text.charAt(i));
                    ++count;
                }
            }
            return formatted.toString();
        }
    
        private String formatTextAmEx(CharSequence text)
        {
            StringBuilder formatted = new StringBuilder();
            int count = 0;
            for (int i = 0; i < text.length(); ++i)
            {
                if (Character.isDigit(text.charAt(i)))
                {
                    if (count > 0 && ((count == 4) || (count == 10))) {
                        formatted.append(" ");
                    }
                    formatted.append(text.charAt(i));
                    ++count;
                }
            }
            return formatted.toString();
        }
    

    除格式化空格外,我还应用检查以确保卡号不超过其最大限制,并且当达到最大限制时,通过执行字体更改,用户会收到通知他已输入所有数字的通知 . 这是执行上述操作的功能 .

    public void checkCardNoEnteredCorrectly() {
    if(editTxtCardNumber.getText().length() > 0 && editTxtCardNumber.getText().charAt(0) == '3') {
        if(editTxtCardNumber.getText().length() == Constants.MAX_LENGTH_CARD_NUMBER_AMEX) {
            editTxtCardNumber.setCompoundDrawablesWithIntrinsicBounds(getResources().getDrawable(R.drawable.amex), null, null, null);
        } else {
            editTxtCardNumber.setCompoundDrawablesWithIntrinsicBounds(getResources().getDrawable(R.drawable.amex), null, null, null);
        }
    } else if(editTxtCardNumber.getText().length() > 0 && editTxtCardNumber.getText().charAt(0) == '4') {
        if(editTxtCardNumber.getText().length() == Constants.MAX_LENGTH_CARD_NUMBER_VISA_MASTERCARD) {
            editTxtCardNumber.setCompoundDrawablesWithIntrinsicBounds(getResources().getDrawable(R.drawable.visa), null, null, null);
        } else {
            editTxtCardNumber.setCompoundDrawablesWithIntrinsicBounds(getResources().getDrawable(R.drawable.visa), null, null, null);
        }
    } else if(editTxtCardNumber.getText().length() > 0 && editTxtCardNumber.getText().charAt(0) == '5') {
        if(editTxtCardNumber.getText().length() == Constants.MAX_LENGTH_CARD_NUMBER_VISA_MASTERCARD) {
            editTxtCardNumber.setCompoundDrawablesWithIntrinsicBounds(getResources().getDrawable(R.drawable.master_card), null, null, null);
        } else {
            editTxtCardNumber.setCompoundDrawablesWithIntrinsicBounds(getResources().getDrawable(R.drawable.master_card), null, null, null);
        }
    } else {
        editTxtCardNumber.setCompoundDrawablesWithIntrinsicBounds(getResources().getDrawable(R.drawable.credit_card_number), null, null, null);
    }
    

    }

    注意:Constants.java中的声明如下:

    public static final int MAX_LENGTH_CARD_NUMBER_VISA_MASTERCARD = 19;
    public static final int MAX_LENGTH_CARD_NUMBER_AMEX = 17;
    

    希望能帮助到你!

  • 3

    在你的布局中:

    <android.support.design.widget.TextInputEditText
            android:id="@+id/et_credit_card_number"
            android:digits=" 1234567890"
            android:inputType="number"
            android:maxLength="19"/>
    

    这里 TextWachter 在16号信用卡中每4位数设置一个空格 .

    class CreditCardFormatWatcher : TextWatcherAdapter() {
    
        override fun afterTextChanged(s: Editable?) {
            if (s == null || s.isEmpty()) return
    
            s.forEachIndexed { index, c ->
                val spaceIndex = index == 4 || index == 9 || index == 14
                when {
                    !spaceIndex && !c.isDigit()     -> s.delete(index, index + 1)
                    spaceIndex && !c.isWhitespace() -> s.insert(index, " ")
                }
            }
    
            if (s.last().isWhitespace())
                s.delete(s.length - 1, s.length)
        }
    
    }
    
  • 0

    我正在将我的解决方案添加到列表中 . 据我所知,它没有任何缺点;您可以在中间编辑,删除间距字符,复制并粘贴到其中等 .

    为了允许在字符串中的任何位置进行编辑,并保持光标位置,遍历Editable并逐个取出所有空格(如果有的话) . 然后在适当的位置添加新的空格 . 这将确保光标随着对内容的更改而移动 .

    import java.util.LinkedList;
    
    
    import android.text.Editable;
    import android.text.TextWatcher;
    import android.widget.EditText;
    
    
    /**
     * Formats the watched EditText to groups of characters, with spaces between them.
     */
    public class GroupedInputFormatWatcher implements TextWatcher {
    
        private static final char SPACE_CHAR = ' ';
        private static final String SPACE_STRING = String.valueOf(SPACE_CHAR);
        private static final int GROUPSIZE = 4;
    
        /**
         * Breakdown of this regexp:
         * ^             - Start of the string
         * (\\d{4}\\s)*  - A group of four digits, followed by a whitespace, e.g. "1234 ". Zero or more times.
         * \\d{0,4}      - Up to four (optional) digits.
         * (?<!\\s)$     - End of the string, but NOT with a whitespace just before it.
         * 
         * Example of matching strings:
         *  - "2304 52"
         *  - "2304"
         *  - ""
         */
        private final String regexp = "^(\\d{4}\\s)*\\d{0,4}(?<!\\s)$";
        private boolean isUpdating = false;
    
        private final EditText editText;
    
        public GroupedInputFormatWatcher(EditText editText) {
            this.editText = editText;
        }
    
        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
    
        }
    
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    
        }
    
        @Override
        public void afterTextChanged(Editable s) {
            String originalString = s.toString();
    
            // Check if we are already updating, to avoid infinite loop.
            // Also check if the string is already in a valid format.
            if (isUpdating || originalString.matches(regexp)) {
                return;
            }
    
            // Set flag to indicate that we are updating the Editable.
            isUpdating = true;
    
            // First all whitespaces must be removed. Find the index of all whitespace.
            LinkedList<Integer> spaceIndices = new LinkedList <Integer>();
            for (int index = originalString.indexOf(SPACE_CHAR); index >= 0; index = originalString.indexOf(SPACE_CHAR, index + 1)) {
                spaceIndices.offerLast(index);
            }
    
            // Delete the whitespace, starting from the end of the string and working towards the beginning.
            Integer spaceIndex = null;
            while (!spaceIndices.isEmpty()) {
                spaceIndex = spaceIndices.removeLast();
                s.delete(spaceIndex, spaceIndex + 1);
            }
    
            // Loop through the string again and add whitespaces in the correct positions
            for(int i = 0; ((i + 1) * GROUPSIZE + i) < s.length(); i++) {
                s.insert((i + 1) * GROUPSIZE + i, SPACE_STRING);
            }
    
            // Finally check that the cursor is not placed before a whitespace.
            // This will happen if, for example, the user deleted the digit '5' in
            // the string: "1234 567".
            // If it is, move it back one step; otherwise it will be impossible to delete
            // further numbers.
            int cursorPos = editText.getSelectionStart();
            if (cursorPos > 0 && s.charAt(cursorPos - 1) == SPACE_CHAR) {
                editText.setSelection(cursorPos - 1);
            }
    
            isUpdating = false;
        }
    }
    
  • 1

    这个解决方案是针对IBAN实现的,但原理是一样的,我试着纠正上面答案中的所有主要问题,如果你发现错误随意说出来,谢谢 .

    设置EditText并限制可以使用的字符:

    private void setEditTextIBAN(View view) {
        editTextIBAN = (EditText) view.findViewById(R.id.client_iban);
        editTextIBAN.setKeyListener(
                DigitsKeyListener.getInstance("ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 "));
        editTextIBAN.addTextChangedListener(new IBANTextWatcher());
    }
    

    这是TextWatcher:

    private class IBANTextWatcher implements TextWatcher {
    
        // means divider position is every 5th symbol
        private static final int DIVIDER_MODULO = 5;
        private static final int GROUP_SIZE = DIVIDER_MODULO - 1;
        private static final char DIVIDER = ' ';
        private static final String STRING_DIVIDER = " ";
        private String previousText = "";
    
        private int deleteLength;
        private int insertLength;
        private int start;
    
        private String regexIBAN = "(\\w{" + GROUP_SIZE + "}" + DIVIDER +
                ")*\\w{1," + GROUP_SIZE + "}";
        private Pattern patternIBAN = Pattern.compile(regexIBAN);
    
        @Override
        public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) {
            this.previousText = s.toString();
            this.deleteLength = count;
            this.insertLength = after;
            this.start = start;
        }
    
        @Override
        public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
    
        }
    
        @Override
        public void afterTextChanged(final Editable s) {
            String originalString = s.toString();
    
            if (!previousText.equals(originalString) &&
                    !isInputCorrect(originalString)) {
                String newString = previousText.substring(0, start);
                int cursor = start;
    
                if (deleteLength > 0 && s.length() > 0 &&
                        (previousText.charAt(start) == DIVIDER ||
                                start == s.length())) {
                    newString = previousText.substring(0, start - 1);
                    --cursor;
                }
    
                if (insertLength > 0) {
                    newString += originalString.substring(start, start + insertLength);
                    newString = buildCorrectInput(newString);
                    cursor = newString.length();
                }
    
                newString += previousText.substring(start + deleteLength);
                s.replace(0, s.length(), buildCorrectInput(newString));
    
                editTextIBAN.setSelection(cursor);
            }
        }
    
        /**
         * Check if String has the white spaces in the correct positions, meaning
         * if we have the String "123456789" and there should exist a white space
         * every 4 characters then the correct String should be "1234 5678 9".
         *
         * @param s String to be evaluated
         * @return true if string s is written correctly
         */
        private boolean isInputCorrect(String s) {
            Matcher matcherDot = patternIBAN.matcher(s);
            return matcherDot.matches();
        }
    
        /**
         * Puts the white spaces in the correct positions,
         * see the example in {@link IBANTextWatcher#isInputCorrect(String)}
         * to understand the correct positions.
         *
         * @param s String to be corrected.
         * @return String corrected.
         */
        private String buildCorrectInput(String s) {
            StringBuilder sbs = new StringBuilder(
                    s.replaceAll(STRING_DIVIDER, ""));
    
            // Insert the divider in the correct positions
            for (int i = GROUP_SIZE; i < sbs.length(); i += DIVIDER_MODULO) {
                sbs.insert(i, DIVIDER);
            }
    
            return sbs.toString();
        }
    }
    
  • 5

    这是一个使用所有函数来做出决定的示例 . 代码可能会更长一点,但它会更快,因为它主要使用给定值的函数(start,before,count ...) . 此示例每4位数添加“ - ”,并在用户使用退格时删除它们 . 同时,确保光标在最后 .

    public class TextWatcherImplement implements TextWatcher {
    
    private EditText creditCard;
    private String beforeText, currentText;
    private boolean noAction, addStroke, dontAddChar, deleteStroke;
    
    public TextWatcherImplement(EditText creditCard) {
        // TODO Auto-generated constructor stub
        this.creditCard = creditCard;
        noAction = false;
        addStroke = false;
        dontAddChar = false;
        deleteStroke = false;
    }
    
    /* here I save the previous string if the max character had achieved */
    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        // TODO Auto-generated method stub
        Log.i("TextWatcherImplement", "beforeTextChanged start==" + String.valueOf(start) + " count==" + String.valueOf(count) + " after==" + String.valueOf(after));
        if (start >= 19)
            beforeText = s.toString();
    }
    
    
    /* here I check were we add a character, or delete one. 
    if we add character and it is time to add a stroke, then I flag it -> addStroke 
    if we delete a character and it time to delete a stroke, I flag it -> deleteStroke
    if we are in max character for the credit card, don't add char -> dontAddChar 
    */
    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
        // TODO Auto-generated method stub
        Log.i("TextWatcherImplement", "onTextChanged start==" + String.valueOf(start) + " before==" + String.valueOf(before) + " count==" + String.valueOf(count) + " noAction ==" + String.valueOf(noAction));
        if ( (before < count) && !noAction ) {
            if ( (start == 3) || (start == 8) || (start == 13) ) {
                currentText = s.toString();
                addStroke = true;
            } else if (start >= 19) {
                currentText = s.toString();
                dontAddChar = true;
            }
        } else {
            if ( (start == 4) ||  (start == 9) ||  (start == 14)  ) { //(start == 5) || (start == 10) || (start == 15)
                currentText = s.toString();
                deleteStroke = true;
            }
        }
    }
    
    /* noAction flag is when we change the text, the interface is being called again.
    the NoAction flag will prevent any action, and prevent a ongoing loop */
    
    @Override
    public void afterTextChanged(Editable stext) {
        // TODO Auto-generated method stub
        if (addStroke) {
            Log.i("TextWatcherImplement", "afterTextChanged String == " + stext + " beforeText == " + beforeText + " currentText == " + currentText);
            noAction = true;
            addStroke = false;
            creditCard.setText(currentText + "-");
        } else if (dontAddChar) {
            dontAddChar = false;
            noAction = true;
            creditCard.setText(beforeText);
        } else if (deleteStroke) {
            deleteStroke = false;
            noAction = true;
            currentText = currentText.substring(0, currentText.length() - 1);
            creditCard.setText(currentText);
        } else {
            noAction = false;
            creditCard.setSelection(creditCard.getText().length()); // set cursor at the end of the line.
        }
    }
    

    }

  • 11

    这是一个使用 TextWatcher 类的简单且易于定制的解决方案 . 可以使用 addTextChangedListener() 方法将其分配给 EditText .

    new TextWatcher() {
        /** Formats the Field to display user-friendly separation of the input values. */
        @Override public final void afterTextChanged(final Editable pEditable) {
            // Declare the separator.
            final char lSeparator      = '-';
            // Declare the length of separated text. i.e. (XXXX-XXXX-XXXX)
            final int  lSeparationSize = 4;
            // Declare the count; tracks the number of allowed characters in a row.
                  int lCount          = 0;
            // Iterate the Characters.
            for(int i = 0; i < pEditable.length(); i++) {
                // Fetch the current character.
                final char c              = pEditable.charAt(i);
                // Is it a usual character. Here, we permit alphanumerics only.
                final boolean lIsExpected = (Character.isDigit(c) || Character.isLetter(c)) && (c != lSeparator);
                // Is the character expected?
                if(lIsExpected) {
                    // Increase the count.
                    lCount++;
                }
                else {
                    // Is it a separator?
                    if(c == lSeparator) {
                        // Reset the count.
                        lCount = 0;
                        // Continue the iteration.
                        continue;
                    }
                }
                // Has the count been exceeded? Is there more text coming?
                if(lCount >= (lSeparationSize + 1) && (i < pEditable.length())) {
                    // Reset the count.
                    lCount = 0;
                    // Insert the separator.
                    pEditable.insert(i, Character.toString(lSeparator));
                    // Increase the iteration count.
                    i++;
                }
            }
        }
        /** Unused overrides. */
        @Override public final void beforeTextChanged(final CharSequence pCharSequence, final int pStart, final int pCount, final int pAfter) { }
        @Override public final void onTextChanged(final CharSequence pCharSequence, final int pStart, final int pBefore, final int pCount) { }
    }
    

    或者,这是一个基于epool's实现的更清晰的实现 .

    public final class TextGroupFormattingListener implements TextWatcher {
    
        /* Member Variables. */
        private final int    mGroupLength;
        private final String mSeparator;
        private       String mSource;
    
        /** Constructor. */
        public TextGroupFormattingListener(final String pSeparator, final int pGroupLength) {
            // Initialize Member Variables.
            this.mSeparator   = pSeparator;
            this.mGroupLength = pGroupLength;
            this.mSource      = "";
        }
    
        /** Formats the Field to display user-friendly separation of the input values. */
        @Override public final void afterTextChanged(final Editable pEditable) {
            // Fetch the Source.
            String lSource = pEditable.toString();
            // Has the text changed?
            if (!this.getSource().equals(lSource)) {
                // Remove all of the existing Separators.
                lSource = lSource.replace(this.getSeparator(), "");
                // Allocate a StringBuilder.
                StringBuilder lStringBuilder = new StringBuilder();
                // Iterate across the Source String, which contains the raw user input.
                for(int i = 0; i < lSource.length(); i++) {
                    // Have we exceeded the GroupLength?
                    if(i > 0 && i % this.getGroupLength() == 0) {
                        // Append the separator.
                        lStringBuilder.append(this.getSeparator());
                    }
                    // Append the user's character data.
                    lStringBuilder.append(lSource.charAt(i));
                }
                // Track changes to the Source.
                this.setSource(lStringBuilder.toString());
                // Replace the contents of the Editable with this new String.
                pEditable.replace(0, pEditable.length(), this.getSource());
            }
        }
    
        /** Unused overrides. */
        @Override public final void beforeTextChanged(final CharSequence pCharSequence, final int pStart, final int pCount, final int pAfter) { }
        @Override public final void onTextChanged(final CharSequence pCharSequence, final int pStart, final int pBefore, final int pCount)    { }
    
        public final int getGroupLength() {
            return this.mGroupLength;
        }
    
        public final String getSeparator() {
            return this.mSeparator;
        }
    
        private final void setSource(final String pSource) {
            this.mSource = pSource;
        }
    
        private final String getSource() {
            return this.mSource;
        }
    
    }
    
  • 5

    Demo - How this works

    Example on github.com

    迟到的答案,但我想这可能对某人有帮助:

    cardNumberEditText.addTextChangedListener(new TextWatcher() {
    
            private static final int TOTAL_SYMBOLS = 19; // size of pattern 0000-0000-0000-0000
            private static final int TOTAL_DIGITS = 16; // max numbers of digits in pattern: 0000 x 4
            private static final int DIVIDER_MODULO = 5; // means divider position is every 5th symbol beginning with 1
            private static final int DIVIDER_POSITION = DIVIDER_MODULO - 1; // means divider position is every 4th symbol beginning with 0
            private static final char DIVIDER = '-';
    
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                // noop
            }
    
            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                // noop
            }
    
            @Override
            public void afterTextChanged(Editable s) {
                if (!isInputCorrect(s, TOTAL_SYMBOLS, DIVIDER_MODULO, DIVIDER)) {
                    s.replace(0, s.length(), buildCorrectString(getDigitArray(s, TOTAL_DIGITS), DIVIDER_POSITION, DIVIDER));
                }
            }
    
            private boolean isInputCorrect(Editable s, int totalSymbols, int dividerModulo, char divider) {
                boolean isCorrect = s.length() <= totalSymbols; // check size of entered string
                for (int i = 0; i < s.length(); i++) { // check that every element is right
                    if (i > 0 && (i + 1) % dividerModulo == 0) {
                        isCorrect &= divider == s.charAt(i);
                    } else {
                        isCorrect &= Character.isDigit(s.charAt(i));
                    }
                }
                return isCorrect;
            }
    
            private String buildCorrectString(char[] digits, int dividerPosition, char divider) {
                final StringBuilder formatted = new StringBuilder();
    
                for (int i = 0; i < digits.length; i++) {
                    if (digits[i] != 0) {
                        formatted.append(digits[i]);
                        if ((i > 0) && (i < (digits.length - 1)) && (((i + 1) % dividerPosition) == 0)) {
                            formatted.append(divider);
                        }
                    }
                }
    
                return formatted.toString();
            }
    
            private char[] getDigitArray(final Editable s, final int size) {
                char[] digits = new char[size];
                int index = 0;
                for (int i = 0; i < s.length() && index < size; i++) {
                    char current = s.charAt(i);
                    if (Character.isDigit(current)) {
                        digits[index] = current;
                        index++;
                    }
                }
                return digits;
            }
        });
    

    这与start-string / end-string / mid-string编辑完美配合,也可以完美粘贴 .

  • 0

    请看project . Android表单编辑文本是EditText的扩展,它将数据验证工具带到edittext

  • 0

    我刚刚完成了下一个实现并且对我很有用,即使在 EditText 的任何位置粘贴和键入新文本也是如此 .

    Gist file

    /**
     * Text watcher for giving "#### #### #### ####" format to edit text.
     * Created by epool on 3/14/16.
     */
    public class CreditCardFormattingTextWatcher implements TextWatcher {
    
        private static final String EMPTY_STRING = "";
        private static final String WHITE_SPACE = " ";
        private String lastSource = EMPTY_STRING;
    
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        }
    
        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
        }
    
        @Override
        public void afterTextChanged(Editable s) {
            String source = s.toString();
            if (!lastSource.equals(source)) {
                source = source.replace(WHITE_SPACE, EMPTY_STRING);
                StringBuilder stringBuilder = new StringBuilder();
                for (int i = 0; i < source.length(); i++) {
                    if (i > 0 && i % 4 == 0) {
                        stringBuilder.append(WHITE_SPACE);
                    }
                    stringBuilder.append(source.charAt(i));
                }
                lastSource = stringBuilder.toString();
                s.replace(0, s.length(), lastSource);
            }
        }
    
    }
    

    Usage: editText.addTextChangedListener(new CreditCardFormattingTextWatcher());

相关问题