EditText
フォーマットで入力を受け入れる方法:
4digit 4digit 4digit 4digit
クレジットカード番号を受け入れるためにカスタムフォーマット編集テキスト入力アンドロイドを試しましたが、残念ながらスペースを削除できませんでした。スペースがあるときはいつでも削除できませんでした。問題を見つけるのを手伝ってください。
EditText
フォーマットで入力を受け入れる方法:
4digit 4digit 4digit 4digit
クレジットカード番号を受け入れるためにカスタムフォーマット編集テキスト入力アンドロイドを試しましたが、残念ながらスペースを削除できませんでした。スペースがあるときはいつでも削除できませんでした。問題を見つけるのを手伝ってください。
'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"
ます。これにより他の文字が有効になりますが、カスタムinputfilterを使用するだけで、問題が解決します。
遅い答えですが、誰かに役立つかもしれません:
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;
}
});
これは、開始文字列/終了文字列/中間文字列の編集で完全に機能し、貼り付けも完全に機能します。
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;
}
}
});
これは、正規表現を使用したよりクリーンなソリューションです。正規表現は非効率的ですが、この場合、キーを押すたびに処理が行われる場合でも、最大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);
}
}
});
これが私がクレジットカード番号に使用するクラスです。以下の使用例。
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(/ 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>
使用例
<?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>
私は自分のソリューションをリストに追加しています。私の知る限り、欠点はありません。途中で編集したり、間隔の文字を削除したり、コピーして貼り付けたりすることができます。
文字列内の任意の場所で編集を実行できるようにし、カーソル位置を維持するために、編集可能文字がトラバースされ、すべての空白(存在する場合)が1つずつ削除されます。次に、新しい空白が適切な位置に追加されます。これにより、コンテンツに加えられた変更に合わせてカーソルが移動します。
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;
}
}
TextWatcherを使用するのが正しいかどうかわからない-InputFilterを使用する必要があります
Androidのドキュメントによると、外部での使用例としてTextWatcherを使用する必要があります。パスワード入力用の1つの[EditView] + 「弱い」、「強い」などを表示する1つの[TextView]ビュー...
クレジットカード形式の場合、 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クラスで実行する必要があります
それが役に立てば幸い !
次の実装を行ったばかりで、の任意の位置に新しいテキストを貼り付けて入力しても、うまく機能しますEditText
。
/**
* 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);
}
}
}
使用法: editText.addTextChangedListener(new CreditCardFormattingTextWatcher());
この実装により、ユーザーが文字列の途中で編集した場合でも、間隔文字が正しく配置されます。ソフトキーボードに表示される他の文字(ダッシュなど)もサポートされています。つまり、ユーザーはそれらを入力できません。行われる可能性のある1つの改善:この実装では、文字列の途中にあるスペーシング文字を削除できません。
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ディジットをマスクするための適切な実装でうまく機能します。
Kotlinを使用している場合、これは役立つ場合があります。
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) {
}
}
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" />
そしてそれを使用する方法:
etCardNumber.addTextChangedListener(CreditCardTextFormatter())
私のソリューションは、ミドルテキスト操作やコピーアンドペースト操作でうまく機能すると思います。
以下のコードをご覧ください。
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の数値にします。
それがあなたのお役に立てば幸いです。
このプロジェクトをご覧ください。Androidフォーム編集テキストはEditTextの拡張であり、データ検証機能を編集テキストにもたらします
たくさん検索して、自分のニーズを満たす満足のいく答えが得られなかった後、私は自分の関数を書くことになりました。
入力するカードの種類に基づいて、入力したクレジットカードの詳細をフォーマットする例を次に示します。現在、フォーマットの目的でVisa、MasterCard、AmericanExpressを処理しています。
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;
それが役に立てば幸い!
あなたはすでにそれを理解しているかもしれませんが、これが私がしたことです。私がオーバーライドしなければならなかった唯一のメソッドはAfterTextChangedでした。
クレジットカードのフォームがすでに有効であるかどうかを確認します。無限の再帰を防ぐための基本ケースです。
フォームが無効な場合は、すべての空白を削除し、別の文字列にコピーして、必要に応じて空白を挿入します。
次に、編集可能な文字列を新しい文字列に置き換えるだけです。
特定のステップのコードが必要な場合は、お気軽にお問い合わせください。
また、Preethi、スペースを削除できない理由は、onTextChangedコールバックでテキストを変更できないためです。開発者サイトから:
public abstract void onTextChanged(CharSequence s、int start、int before、int count)APIレベル1に追加されました
このメソッドは、s内で、開始から始まるカウント文字が、以前の長さの古いテキストを置き換えたことを通知するために呼び出されます。このコールバックからsに変更を加えようとするとエラーになります。
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) {
}
});
}
これは、すべての関数を適切に使用して決定を下す例です。コードは少し長くなる可能性がありますが、主に関数で指定された値(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.
}
}
}
これが私の解決策です。私のコメントは、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;
}
}
}
TextWatcher
これは、クラスを使用したシンプルで簡単にカスタマイズ可能なソリューションです。メソッドEditText
を使用して割り当てることができます。addTextChangedListener()
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の実装に基づいたはるかにクリーンな実装があります。
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;
}
}
上記の答えはどれも私にとって完璧ではありません。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
}
このソリューションは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();
}
}
まだ答えを探している人がいたら、
1行のコードでテキストを自動フォーマットするには、format-edit-textライブラリを試してください。このライブラリは、ダッシュを使用して入力の形式を定義します。
editText.setFormat("any (dash) format");
app/build.gradleにformat-edit-textライブラリの依存関係を追加します
implementation 'com.androidwidgets:formatedittext:0.2.0'
activity_main.xmlにFormatEditTextビューを追加します
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:focusableInTouchMode="true"
android:focusable="true">
<com.androidwidgets.formatedittext.widgets.FormatEditText
android:id="@+id/edit_text_1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:imeOptions="actionSend"
android:inputType="number"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity.javaでクレジットカードの形式をFormatEditTextビューに設定します
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final FormatEditText editText1 = findViewById(R.id.edit_text_1);
editText1.setFormat("---- ---- ---- ----");
}
}
これにより、以下の出力が生成されます
PS:パラメータinputTypeがレイアウトファイルのFormatEditTextビューに追加されていることを確認してください。
android:inputType="number"
私はこの質問が少し古いことを知っていますが、私はIBANのためにこれを実装する必要があり、与えられた答えに満足していません。だから私はこれのためにいくつかのコードを書いた。ただし、「パターン」と「ディバイダー」をパラメーターとして使用するため、クレジットカード番号にも使用できます。
これは拡張テキストウォッチャークラスです。
import android.text.Editable;
import android.text.TextWatcher;
import android.widget.EditText;
public class IbanTextWatcher implements TextWatcher {
private int[] pattern;
private String divider;
private String before;
private EditText field;
private boolean dividerDeleted;
public IbanTextWatcher(int[] pattern, String divider, EditText field) {
this.divider = divider;
this.pattern = pattern;
this.field = field;
}
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
before = charSequence.toString();
if (!String.valueOf(charSequence).equals("") && charSequence.length() > i) {
if (String.valueOf(before.charAt(i)).equals(getDivider())) {
dividerDeleted = true;
} else {
dividerDeleted = false;
}
}
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void afterTextChanged(Editable editable) {
String input = editable.toString().replaceAll("\\s", "");
StringBuilder output = new StringBuilder();
boolean error = false;
int currentIndex = 0;
int cursorPosition = getField().getSelectionStart();
int lengthBefore;
int currentPatternMember = 0;
//prevent user to delete the divider
if (dividerDeleted && cursorPosition != getField().getText().length()) {
getField().setText(getBefore());
getField().setSelection(cursorPosition + 1);
return;
} else if (input.equals(getBefore().replaceAll("\\s", ""))) {
return;
}
for (int i = 0; i < getPattern().length; i++) {
error = false;
currentPatternMember = getPattern()[i];
try {
output.append(input.substring(currentIndex, currentIndex + currentPatternMember));
} catch (StringIndexOutOfBoundsException e) {
error = true;
}
if (!error) {
if (i != getPattern().length - 1) {
output.append(getDivider());
}
currentIndex += currentPatternMember;
} else {
break;
}
}
if (error) {
output.append(input.substring(currentIndex, input.length()));
}
cursorPosition = getField().getSelectionStart();
lengthBefore = getBefore().length();
getField().setText(output.toString());
if (cursorPosition != lengthBefore && cursorPosition != lengthBefore + 1) {
getField().setSelection(cursorPosition);
} else {
getField().setSelection(getField().getText().length());
}
}
public int[] getPattern() {
return pattern;
}
public String getDivider() {
return divider;
}
public String getBefore() {
return before;
}
public EditText getField() {
return field;
}
}
そして、これは私がそれを使用する方法です:
int[] pattern = {2,4,4,4,4,4,2}; //
iban.addTextChangedListener(new IbanTextWatcher(pattern, " ", iban)); //here iban is my edittext field
ちなみに、フィールドの最大長はxmlで設定しました。
レイアウト内:
<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)
}
}
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());
class XYZ : TextWatcher {
private val formatSymbols = DecimalFormatSymbols(Locale.getDefault())
private lateinit var formatter: DecimalFormat
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
.
.
formatSymbols.groupingSeparator = ' '
formatter = DecimalFormat("####,####", formatSymbols)
.
.
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
editText.addTextChangedListener(this)
}
override fun afterTextChanged(s: Editable?) {
if (editText.error != null) {
editText.error = null
}
editText.removeTextChangedListener(this)
try {
var originalString = s.toString()
if (originalString.contains(" ")) {
originalString = originalString.replace(" ", "", true)
}
val longVal: Long? = originalString.toLong()
val formattedString = formatter.format(longVal)
editText.setText(formattedString)
editText.setSelection(editText.text.length)
} catch (error: NumberFormatException) {
// Print Error Or Do Whatever you want.
}
editText.addTextChangedListener(this)
}
}
これは、 Igor Tyulkanovのアイデアに基づく私の実装ベースであり、カーソル位置の問題を修正する小さな改善があります。
class CardNumbersInputWatcher(private val editText: EditText) : TextWatcher {
companion object {
private const val TOTAL_SYMBOLS = 19
private const val DIVIDER_DISTANCE = 4
private const val DIVIDER = ' '
}
override fun afterTextChanged(s: Editable) {
if (!isInputCorrect(s, TOTAL_SYMBOLS, DIVIDER_DISTANCE, DIVIDER)) {
val beforeCurPos = editText.selectionStart
val beforeLength = s.length
s.replace(0, s.length, buildCorrectString(s, TOTAL_SYMBOLS, DIVIDER_DISTANCE, DIVIDER))
if (beforeLength > TOTAL_SYMBOLS && beforeCurPos <= s.length && editText.selectionStart < beforeCurPos) {
editText.setSelection(beforeCurPos)
}
}
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) = Unit
}
private fun isInputCorrect(s: Editable, totalSymbols: Int, dividerDistance: Int, divider: Char): Boolean {
if (s.length > totalSymbols) {
return false
}
return s.withIndex().all { (index, c) ->
if (index != 0 && ((index + 1) % (dividerDistance + 1) == 0)) {
// it should be divider
c == divider
} else {
c.isDigit()
}
}
}
private fun buildCorrectString(s: Editable, totalSymbols: Int, dividerDistance: Int, divider: Char): String {
return buildString {
for (c in s) {
if (length >= totalSymbols) break
if (!c.isDigit()) continue
if (length > 0 && ((length + 1) % (dividerDistance + 1)) == 0) append(divider)
append(c)
}
}
}
1.このクラスをコピーして貼り付けます
class EditTextForCards @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = androidx.appcompat.R.attr.editTextStyle
) : AppCompatEditText(context, attrs, defStyleAttr) {
private var mCCPatterns = SparseArray<Pattern>()
private var mSeparator: Separator = Separator.NONE
private var mDrawableGravity: Gravity? = null/*Gravity.END*/
private var isValidCard: Boolean = false
private var mCurrentDrawableResId = Card.UNKNOWN.drawableRes
val textWithoutSeparator
get() = if (mSeparator == Separator.NONE) {
text.toString()
} else {
text.toString().replace(mSeparator.toRegex(), "")
}
val isCardValid: Boolean
get() = textWithoutSeparator.length > 12 && isValidCard
val cardType: Card
get() = Card.from(mCurrentDrawableResId)
enum class Separator(private val stringValue: String) {
NONE(""), SPACES(" "), DASHES("-");
override fun toString() = stringValue
internal fun toRegex() = stringValue.toRegex()
internal val length
get() = stringValue.length
}
enum class Gravity {
START, END, LEFT, RIGHT
}
enum class Card(internal val value: Int, @field:DrawableRes internal val drawableRes: Int) {
VISA(1, R.drawable.ic_visa),
MASTERCARD(2, R.drawable.ic_mastercard),
AMEX(4, R.drawable.amex),
DISCOVER(8, R.drawable.discover),
UNKNOWN(-1, R.drawable.ic_visa);
companion object {
internal fun from(@DrawableRes drawableRes: Int): Card {
for (card in values()) {
if (card.drawableRes == drawableRes) {
return card
}
}
return UNKNOWN
}
}
}
private val textWatcher = object : TextWatcher {
override fun afterTextChanged(s: Editable?) {}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(
text: CharSequence,
start: Int,
lengthBefore: Int,
lengthAfter: Int
) {
val textWithoutSeparator = textWithoutSeparator
var mDrawableResId = 0
for (i in 0 until mCCPatterns.size()) {
val key = mCCPatterns.keyAt(i)
val p = mCCPatterns.get(key)
val m = p.matcher(textWithoutSeparator)
isValidCard = m.find()
if (isValidCard) {
mDrawableResId = key
break
}
}
// if (mDrawableResId != 0 && mDrawableResId != mCurrentDrawableResId) {
// mCurrentDrawableResId = mDrawableResId
// } else if (mDrawableResId == 0) {
// mCurrentDrawableResId = Card.UNKNOWN.drawableRes
// }
// addDrawable()
addSeparators()
}
}
init {
setDisabledCards()
inputType = InputType.TYPE_CLASS_PHONE
setSeparator(Separator.NONE)
// setDrawableGravity(Gravity.END)
attrs?.let { applyAttributes(it) }
addTextChangedListener(textWatcher)
}
private fun applyAttributes(attrs: AttributeSet) {
val a = context.theme.obtainStyledAttributes(
attrs,
R.styleable.EditTextForCards,
0, 0
)
try {
setSeparator(
Separator.values()[a.getInt(
R.styleable.EditTextForCards_separator,
Separator.NONE.ordinal
)]
)
setDisabledCardsInternal(a.getInt(R.styleable.EditTextForCards_disabledCards, 0))
setDrawableGravity(
Gravity.values()[a.getInt(
R.styleable.EditTextForCards_drawableGravity,
Gravity.END.ordinal
)]
)
} finally {
a.recycle()
}
}
private fun addDrawable() {
var currentDrawable = ContextCompat.getDrawable(context, mCurrentDrawableResId)
if (currentDrawable != null && error.isNullOrEmpty()) {
currentDrawable = resize(currentDrawable)
when (mDrawableGravity) {
Gravity.START -> setDrawablesRelative(start = currentDrawable)
Gravity.RIGHT -> setDrawables(right = currentDrawable)
Gravity.LEFT -> setDrawables(left = currentDrawable)
else -> setDrawablesRelative(end = currentDrawable)
}
}
}
private fun addSeparators() {
val text = text.toString()
if (mSeparator != Separator.NONE) {
if (text.length > 4 && !text.matches("(?:[0-9]{4}$mSeparator)+[0-9]{1,4}".toRegex())) {
val sp = StringBuilder()
val caretPosition = selectionEnd
val segments = splitString(text.replace(mSeparator.toRegex(), ""))
for (segment in segments) {
sp.append(segment).append(mSeparator)
}
setText("")
append(sp.delete(sp.length - mSeparator.length, sp.length).toString())
if (caretPosition < text.length)
setSelection(caretPosition)
}
}
}
private fun removeSeparators() {
var text = text.toString()
text = text.replace(" ".toRegex(), "").replace("-".toRegex(), "")
setText("")
append(text)
}
private fun splitString(s: String): Array<String?> {
val arrayLength = ceil(s.length / 4.toDouble()).toInt()
val result = arrayOfNulls<String>(arrayLength)
var j = 0
val lastIndex = result.size - 1
for (i in 0 until lastIndex) {
result[i] = s.substring(j, j + 4)
j += 4
}
result[lastIndex] = s.substring(j)
return result
}
/*@Deprecated("Please use the method that accepts a Separator enum instead.", ReplaceWith("this.setSeparator(Separator.)"))
fun setSeparator(@IntRange(from = 0, to = 2) separator: Int) {
require(!(separator > 2 || separator < 0)) {
"The separator has to be one of the following:" +
"NO_SEPARATOR." +
"SPACES_SEPARATOR." +
"DASHES_SEPARATOR."
}
setSeparator(Separator.values()[separator])
}*/
/**
* Use this method to set the separator style.
* The default separator is [Separator.NONE].
*
* @param separator the style of the separator.
*/
fun setSeparator(separator: Separator) {
mSeparator = separator
if (mSeparator != Separator.NONE) {
filters = arrayOf<InputFilter>(InputFilter.LengthFilter(23))
keyListener = DigitsKeyListener.getInstance("0123456789$mSeparator")
addSeparators()
} else {
filters = arrayOf<InputFilter>(InputFilter.LengthFilter(19))
keyListener = DigitsKeyListener.getInstance("0123456789")
removeSeparators()
}
}
/**
* Use this method to set the location of the card drawable.
* The default gravity is [Gravity.END].
*
* @param gravity the drawable location.
*/
fun setDrawableGravity(gravity: Gravity) {
mDrawableGravity = gravity
addDrawable()
}
private fun setDisabledCardsInternal(disabledCards: Int) {
val cards = ArrayList<Card>()
if (containsFlag(disabledCards, Card.VISA.value)) {
cards.add(Card.VISA)
}
if (containsFlag(disabledCards, Card.MASTERCARD.value)) {
cards.add(Card.MASTERCARD)
}
/*if (containsFlag(disabledCards, Card.AMEX.value)) {
cards.add(Card.AMEX)
}
if (containsFlag(disabledCards, Card.DISCOVER.value)) {
cards.add(Card.DISCOVER)
}*/
setDisabledCards(*cards.toTypedArray())
}
@Deprecated(
"Please use the method that accepts an array of Cards instead.",
ReplaceWith("this.setDisabledCards(cards)")
)
fun setDisabledCards(disabledCards: Int) {
setDisabledCardsInternal(disabledCards)
}
/**
* Use this method to set which cards are disabled.
* By default all supported cards are enabled.
*
* @param cards the cards to be disabled.
*/
fun setDisabledCards(vararg cards: Card) {
var disabledCards = 0
for (card in cards) {
disabledCards = disabledCards or card.value
}
mCCPatterns.clear()
if (!containsFlag(disabledCards, Card.VISA.value)) {
mCCPatterns.put(Card.VISA.drawableRes, Pattern.compile("^4[0-9]{1,12}(?:[0-9]{6})?$"))
}
if (!containsFlag(disabledCards, Card.MASTERCARD.value)) {
mCCPatterns.put(Card.MASTERCARD.drawableRes, Pattern.compile("^5[1-5][0-9]{0,14}$"))
}
/*if (!containsFlag(disabledCards, Card.AMEX.value)) {
mCCPatterns.put(Card.AMEX.drawableRes, Pattern.compile("^3[47][0-9]{0,13}$"))
}
if (!containsFlag(disabledCards, Card.DISCOVER.value)) {
mCCPatterns.put(Card.DISCOVER.drawableRes, Pattern.compile("^6(?:011|5[0-9]{1,2})[0-9]{0,12}$"))
}*/
textWatcher.onTextChanged("", 0, 0, 0)
}
private fun containsFlag(flagSet: Int, flag: Int): Boolean {
return flagSet or flag == flagSet
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
var noDrawablesVisible = true
for (drawable in compoundDrawables) {
if (drawable != null) {
noDrawablesVisible = false
break
}
}
if (noDrawablesVisible) {
addDrawable()
}
}
private fun resize(image: Drawable) =
when (val height = measuredHeight - (paddingTop + paddingBottom)) {
in 1 until image.intrinsicHeight -> {
val bitmap = (image as BitmapDrawable).bitmap
val ratio = image.getIntrinsicWidth().toFloat() / image.intrinsicHeight.toFloat()
val resizedBitmap =
Bitmap.createScaledBitmap(bitmap, (height * ratio).toInt(), height, false)
resizedBitmap.density = Bitmap.DENSITY_NONE
BitmapDrawable(resources, resizedBitmap)
}
in Int.MIN_VALUE..0 -> null
else -> image
}
private fun setDrawablesRelative(
start: Drawable? = null,
top: Drawable? = null,
end: Drawable? = null,
bottom: Drawable? = null
) =
/*TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(this, start, top, end, bottom)*/
TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(this, null, null, null, null)
private fun setDrawables(
left: Drawable? = null,
top: Drawable? = null,
right: Drawable? = null,
bottom: Drawable? = null
) =
/*setCompoundDrawablesWithIntrinsicBounds(left, top, right, bottom)*/
setCompoundDrawablesWithIntrinsicBounds(null, null, null, null)
companion object {
@Deprecated("This constant has been replace with an enum.", ReplaceWith("Separator.NONE"))
const val NO_SEPARATOR = 0
@Deprecated("This constant has been replace with an enum.", ReplaceWith("Separator.SPACES"))
const val SPACES_SEPARATOR = 1
@Deprecated("This constant has been replace with an enum.", ReplaceWith("Separator.DASHES"))
const val DASHES_SEPARATOR = 2
@Deprecated("This constant has been replace with an enum.", ReplaceWith("null"))
const val NONE = 0
@Deprecated("This constant has been replace with an enum.", ReplaceWith("Card.VISA"))
const val VISA = 1
@Deprecated("This constant has been replace with an enum.", ReplaceWith("Card.MASTERCARD"))
const val MASTERCARD = 2
@Deprecated("This constant has been replace with an enum.", ReplaceWith("Card.AMEX"))
const val AMEX = 4
@Deprecated("This constant has been replace with an enum.", ReplaceWith("Card.DISCOVER"))
const val DISCOVER = 8
}
}
2.このスタイルを貼り付けます
<declare-styleable name="EditTextForCards">
<attr name="separator" format="enum">
<enum name="no_separator" value="0" />
<enum name="spaces" value="1" />
<enum name="dashes" value="2" />
</attr>
<attr name="disabledCards">
<flag name="none" value="0" />
<flag name="visa" value="1" />
<flag name="mastercard" value="2" />
<flag name="amex" value="4" />
<flag name="discover" value="8" />
</attr>
<attr name="drawableGravity">
<enum name="start" value="0" />
<enum name="end" value="1" />
<enum name="left" value="2" />
<enum name="right" value="3" />
</attr>
</declare-styleable>
3.レイアウトファイルで、
<EditTextForCards
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/dp_5"
android:digits="0123456789 "
android:hint="@string/card_number"
android:padding="@dimen/dp_20"
android:textColor="@android:color/white"
android:textColorHint="@android:color/white"
android:textSize="@dimen/sp_16"
app:separator="spaces" />
import android.text.Editable;
import android.text.TextWatcher;
import android.widget.EditText;`
public class CreditCard implements TextWatcher
{
EditText editText;
public CreditCard(EditText editText)
{
this.editText = editText;
}
@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) {
try
{
editText.removeTextChangedListener(this);
String str = editText.getText().toString().replaceAll("-", "");
editText.setText(setDash(str));
editText.setSelection(editText.getText().toString().length());
editText.addTextChangedListener(this);
return;
}
catch (Exception ex)
{
ex.printStackTrace();
editText.addTextChangedListener(this);
}
}
public static String setDash(String value)
{
String str = "";
int j = 0;
for (int i = 0;i<value.length(); i++)
{
j++;
if (j == 5)
{
str = str+"-";
j = 1;
}
str = str + value.charAt(i);
}
return str;
}
public static String trimDashOfString(String string)
{
if (string.contains("-")) {
return string.replace("-", "");
} else {
return string;
}
}
}