Format credit card in edit text in android

后端 未结 29 1962
耶瑟儿~
耶瑟儿~ 2020-11-30 19:18

How to make EditText accept input in format:

4digit 4digit 4digit 4digit 

I tried Custom format edit text input android to acc

相关标签:
29条回答
  • 2020-11-30 19:55

    Here's the class that I use for credit card numbers. Usage examples below.

    FormattedNumberEditText.kt

    import android.content.Context
    import android.text.Editable
    import android.text.InputType
    import android.text.TextWatcher
    import android.text.method.DigitsKeyListener
    import android.util.AttributeSet
    import android.widget.EditText
    
    open class FormattedNumberEditText : AppCompatEditText {
    
        var prefix = ""
            private set
    
        var groupSeparator = ' '
            private set
    
        var numberOfGroups = 4
            private set
    
        var groupLength = 4
            private set
    
        var inputLength = numberOfGroups * (groupLength + 1) - 1
            private set
    
        private val digitsKeyListener = DigitsKeyListener.getInstance("0123456789")
    
        private lateinit var separatorAndDigitsKeyListener: DigitsKeyListener
    
        private var initCompleted = false
    
        constructor(context: Context) : super(context) {
            init(null)
        }
    
        constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
            init(attrs)
        }
    
        constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
            init(attrs)
        }
    
        private fun init(attrs: AttributeSet?) {
            if (attrs != null) {
                val a = context.theme.obtainStyledAttributes(attrs, R.styleable.FormattedNumberEditText, 0, 0)
                prefix = a.getString(R.styleable.FormattedNumberEditText_prefix) ?: prefix
                val separatorStr = a.getString(R.styleable.FormattedNumberEditText_groupSeparator)
                if (!separatorStr.isNullOrEmpty()) {
                    groupSeparator = separatorStr[0]
                }
                numberOfGroups = a.getInteger(R.styleable.FormattedNumberEditText_numberOfGroups, numberOfGroups)
                groupLength = a.getInteger(R.styleable.FormattedNumberEditText_groupLength, groupLength)
            }
    
            inputLength = numberOfGroups * (groupLength + 1) - 1
            separatorAndDigitsKeyListener = DigitsKeyListener.getInstance("0123456789$groupSeparator")
    
            setText(prefix)
            setSelection(text!!.length)
            inputType = InputType.TYPE_CLASS_NUMBER
            keyListener = digitsKeyListener
            addTextChangedListener(TextChangeListener())
    
            initCompleted = true
        }
    
        override fun onSelectionChanged(start: Int, end: Int) {
            if (!initCompleted) {
                return
            }
    
            // make sure input always starts with the prefix
            if (!text!!.startsWith(prefix)) {
                setText(prefix)
                setSelection(text!!.length, text!!.length)
                return
            }
    
            // make sure cursor is always at the end of the string
            if (start != text!!.length || end != text!!.length) {
                setSelection(text!!.length)
            } else {
                super.onSelectionChanged(start, end)
            }
        }
    
        private inner class TextChangeListener : TextWatcher {
    
            var textBefore = ""
            var enteredText = ""
            var deletedChars = 0
    
            var listenerEnabled = true
    
            override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
                if (!listenerEnabled) return
    
                textBefore = text.toString()
                enteredText = ""
                deletedChars = 0
            }
    
            override fun onTextChanged(text: CharSequence?, start: Int, lengthBefore: Int, lengthAfter: Int) {
                if (!listenerEnabled) return
    
                if (text == null) {
                    deletedChars = textBefore.length
                    return
                }
    
                if (text.length < textBefore.length) {
                    deletedChars = textBefore.length - text.length
                    return
                }
    
                enteredText = text.toString().substring(textBefore.length, text.length)
            }
    
            override fun afterTextChanged(s: Editable?) {
                if (!listenerEnabled) return
    
                if (s == null) {
                    return
                }
    
                listenerEnabled = false
    
                if (deletedChars > 0) {
                    handleTextChange(s)
                } else {
                    if (enteredText.length > 1) {
                        s.replace(s.length - enteredText.length, s.length, "")
    
                        // Append one char at a time
                        enteredText.forEach {
                            s.append("$it")
                            handleTextChange(s)
                        }
                    } else {
                        handleTextChange(s)
                    }
                }
    
                listenerEnabled = true
            }
    
            fun handleTextChange(s: Editable) {
                if (s.length > inputLength) {
                    while (s.length > inputLength) {
                        s.delete(s.length - 1, s.length)
                    }
                } else if (s.isNotEmpty() && s.length % (groupLength + 1) == 0) {
                    if (s.last() == groupSeparator) {
                        s.delete(s.length - 1, s.length)
                    } else if (s.last().isDigit() && s.length < inputLength) {
                        keyListener = separatorAndDigitsKeyListener
                        s.insert(s.length - 1, groupSeparator.toString())
                        keyListener = digitsKeyListener
                    }
                }
            }
    
        }
    
    }
    

    attrs.xml (belongs in /res/values)

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <declare-styleable name="FormattedNumberEditText">
            <attr name="prefix" format="string" />
            <attr name="numberOfGroups" format="integer" />
            <attr name="groupLength" format="integer" />
            <attr name="groupSeparator" format="string" />
        </declare-styleable>
    </resources>
    

    Usage examples

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="16dp">
    
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Credit card number" />
    
        <com.example.myapplication.FormattedNumberEditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="16dp"
            android:text="Credit card number (different separator)" />
    
        <com.example.myapplication.FormattedNumberEditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:groupSeparator="-" />
    
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="16dp"
            android:text="Phone number starting with +370" />
    
        <com.example.myapplication.FormattedNumberEditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:groupLength="13"
            app:groupSeparator=" "
            app:numberOfGroups="1"
            app:prefix="+370\u0020" />
    
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="16dp"
            android:text="IBAN number starting with LT" />
    
        <com.example.myapplication.FormattedNumberEditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:groupLength="4"
            app:groupSeparator=" "
            app:numberOfGroups="5"
            app:prefix="LT" />
    
    </LinearLayout>
    
    0 讨论(0)
  • 2020-11-30 19:57

    I think that my solution can work well whatever middle text operation or copy-paste operation.

    Please see code as below,

      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();
            }
      }
    

    Makes inputType to number for EditText to avoid other characters in the layout file.

    Hope that be helpful for you.

    0 讨论(0)
  • 2020-11-30 19:58

    Please look at this project . Android form edit text is an extension of EditText that brings data validation facilities to the edittext

    0 讨论(0)
  • 2020-11-30 19:58

    Here's my solution. My comments should suffice enough information for an Android developer to understand what's happening but if you have any questions then please feel free to ask and I'll answer to the best of my knowledge.

    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;
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-30 19:59

    If you are using Kotlin, this can be helpful:

    class CreditCardTextFormatter(
        private var separator: String = " - ",
        private var divider: Int = 5
    ) : TextWatcher {
    
        override fun afterTextChanged(s: Editable?) {
            if (s == null) {
                return
            }
            val oldString = s.toString()
            val newString = getNewString(oldString)
            if (newString != oldString) {
                s.replace(0, oldString.length, getNewString(oldString))
            }
        }
    
        private fun getNewString(value: String): String {
    
            var newString = value.replace(separator, "")
    
            var divider = this.divider
            while (newString.length >= divider) {
                newString = newString.substring(0, divider - 1) + this.separator + newString.substring(divider - 1)
                divider += this.divider + separator.length - 1
            }
            return newString
        }
    
        override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
        }
    
        override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
        }
    }
    

    The XML:

    <EditText
            android:id="@+id/etCardNumber"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:digits="0123456789- "
            android:inputType="number"
            android:hint="____ - ____ - ____ - ____"
            android:maxLength="25" />
    

    And how to use it:

    etCardNumber.addTextChangedListener(CreditCardTextFormatter())
    
    0 讨论(0)
提交回复
热议问题