Jetpackにおける数入力用のセパレータを追加する
導入
ほとんどの金融アプリでは、数値テキストフィールドが重要な役割を果たしている.人々は正確にこれらの入力ボックスで彼らの命令を登録したいです、そして、一貫した経験は重要です.これらのフィールドの基本的な必需品のうちの1つは、ユーザーが入力している間、数の間で挿入される数千のセパレータです.主な課題は、通常、その正しい位置にカーソルを維持し、迷惑カーソルカーソルを防ぐことです.
Jetpack構成の
VisualTransformation
Sに加えられた新しい TextField
オプションのおかげで、この仕事はEditText
を使用している従来のアプローチより簡単になりました.このポストでは、私のカスタム242479142は、CryptoCurrencyトレーダーアプリで通貨のために意味されているJetpack Composeで提供される新しい施設に移行する結果を説明しました.一言で言えば、新しい
EditText
で何が起こるかは、それがユーザーによって値入力を維持するということです.その結果、TextField
は2つの部分から構成されなければなりません.VisualTransformation
メソッドで行われるべきであり、戻り値は変換されたテキストとオフセットマッピングからなるタイプfilter
である.例のセットはofficial documentで見つかります、そして、クレジットカード変換の大きいセットはここにあります.
Formatting credit card number input in Jetpack compose Android
Benyam ・ Aug 31 ・ 5 min read
#android
#kotlin
#compose
#text
AntiSandSeparorVisualTransformation変換
千の区切り記号を実装する方法を見ましょう.初めに、実装が金融アプリケーションで働くことになっていたので、私はそれを言いたいです.
セパレータの追加
正規化
些細なアプローチはテキストを逆の順序で横断して、3つのキャラクタの後にコンマを加えることです.しかし、私はあまりにも多くのregexのように、私はあまりにも何千ものセパレータに適した位置を選択するこのパターンを使用します.
\B(?=(?:\d{3})+(?!\d))
regex101の助けを借りてこの簡単なパターンを説明しましょう.TransformedText
は3桁を意味します.私はそれらをキャプチャすることに興味がありません(理由を見つけるでしょう).したがって、\d{3}
は3の多数である数で数字で作られた文字のグループと一致します.(?:\d{3})
は負の先読みです.これは、テキストやドットの末尾のような数ではないアンカーポイントに一致します.前のグループとともに、それは数の多数に等しい長さで数のストリングを意味して、数が終わるところで終わります.それで、それは数の中央であらゆるトリプルを捕えません.(?:\d{3})+
は、最後の場所が含まれていないことを保証します.番号の開始時にセパレータを必要としません.整数部分の変換
パターンで、残りの仕事は全く簡単です.コンマを入れるために、文字列に
(?!\d)
メソッドを使用します.override fun filter(text: AnnotatedString): TransformedText {
val commaReplacementPattern = Regex("""\B(?=(?:\d{3})+(?!\d))""")
val transformed =
text.text.replace(commaReplacementPattern, ",")
// ...
}
しかし、さらなる考慮が必要である.ローカライズはあなたのアプリケーションの重要な部分の一つである場合、それは多言語になるだろう.したがって、ハードコード化コンマを直接置く代わりに、\B
を使用します.val symbols = DecimalFormat().decimalFormatSymbols
val comma = symbols.groupingSeparator
なぜ
replace
を使用しない?このクラスが書式化の前に構文解析を必要とするという事実に加えて、うまく動作しない場合もあります.例えば、ユーザーはいくつかのゼロを加えて、それから左に番号を入れたいかもしれません.その場合、フォーマッタはすべての意味のないゼロをクリアします便利.
小数点以下
前述のように、多くの通貨では、分数の部分が利用可能です.また、この部分が小数点以下の場所を持つことを知っています.それで、我々のトランスは2つのパラメタ、
DecimalFormatSymbols
とDecimalFormat
(強制的な小数点のために)をとります.それはコードのこの部分で終わります.val zero = symbols.zeroDigit
val minFractionDigits = min(this.maxFractionDigits, this.minFractionDigits)
fracPart = fracPart.take(maxFractionDigits).padEnd(minFractionDigits, zero)
また、入力が端数部分のみである場合、エッジケースが存在するので、ユーザはドットで開始する.この場合、私たちの戦略は読みやすくするためにテキストの開始時にゼロを置くことです.//Ensure there is at least one zero for integer places
val normalizedIntPart =
if (intPart.isEmpty() && fracPart != null) zero.toString() else intPart
一緒に
次のコードを終了します.
/* As android uses icu in the recent versions we just let DecimalFormat to
* take care of the selection
*/
private val symbols = DecimalFormat().decimalFormatSymbols
private val commaReplacementPattern = Regex("""\B(?=(?:\d{3})+(?!\d))""")
override fun filter(text: AnnotatedString): TransformedText {
val comma = symbols.groupingSeparator
val dot = symbols.decimalSeparator
val zero = symbols.zeroDigit
var (intPart, fracPart) = text.text.split(dot)
.let { Pair(it[0], it.getOrNull(1)) }
//Ensure there is at least one zero for integer places
val normalizedIntPart =
if (intPart.isEmpty() && fracPart != null) zero.toString() else intPart
val integersWithComma = normalizedIntPart.replace(commaReplacementPattern, comma.toString())
val minFractionDigits = min(this.maxFractionDigits, this.minFractionDigits)
if (minFractionDigits > 0 || !fracPart.isNullOrEmpty()) {
if (fracPart == null)
fracPart = ""
fracPart = fracPart.take(maxFractionDigits).padEnd(minFractionDigits, zero)
}
val newText = AnnotatedString(
integersWithComma + if (fracPart == null) "" else ".$fracPart",
text.spanStyles,
text.paragraphStyles
)
// ...
}
オフセットの翻訳
変換が行われた後、上で説明した2つのオフセットマッピング機能を実装する必要があります.ここでの主なアイデアは正しく位置の前に追加された文字を数えることです.私は、彼らが非常に率直であるので、詳細に進んでいません、そして、私は下記に与えられるコードに十分です.
private inner class ThousandSeparatorOffsetMapping(
val originalIntegerLength: Int,
val transformedIntegersLength: Int,
val transformedLength: Int,
val commaIndices: Sequence<Int>,
addedLeadingZero: Boolean
) : OffsetMapping {
val commaCount = calcCommaCount(originalIntegerLength)
val leadingZeroOffset = if (addedLeadingZero) 1 else 0
override fun originalToTransformed(offset: Int): Int =
// Adding number of commas behind the character
if (offset >= originalIntegerLength)
if (offset - originalIntegerLength > maxFractionDigits)
transformedLength
else
offset + commaCount + leadingZeroOffset
else
offset + (commaCount - calcCommaCount(originalIntegerLength - offset))
override fun transformedToOriginal(offset: Int): Int =
// Subtracting number of commas behind the character
if (offset >= transformedIntegersLength)
min(offset - commaCount, transformedLength) - leadingZeroOffset
else
offset - commaIndices.takeWhile { it <= offset }.count()
private fun calcCommaCount(intDigitCount: Int) =
max((intDigitCount - 1) / 3, 0)
}
最終結果
また、完全な実装をチェックアウトすることができます.
< div >
Reference
この問題について(Jetpackにおける数入力用のセパレータを追加する), 我々は、より多くの情報をここで見つけました https://dev.to/momt99/add-thousand-separators-for-number-inputs-in-jetpack-compose-2650テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol