背景
数字入力に限定したEditText(android:inputType="number")に対して、3桁ごとのセパレーターとsuffixとして単位をつけたい
理由
・ユーザーに極力数字以外のものを受け付けたくない
・桁数と単位でより入力内容をわかりやすくしたい
環境
MVVM-DataBinding
※導入方法は省略
実装
今回は長さ(m)を例に実装します
やりたい事は、もしユーザーが1222と入力したとすると、1,222mとEditTextに表示させたいです
XML
<data>
<import type="your.package.name.Converter" />
<variable
name="viewModel"
type="your.package.name.SnippetViewModel" />
</data>
(略)
<EditText
android:id="@+id/length_m"
android:layout_width="match_parent"
android:gravity="end|center_vertical"
android:hint="0m"
android:inputType="number"
android:maxLines="1"
android:paddingStart="0dp"
android:paddingEnd="8dp"
android:text="@={Converter.addNumberSeparatorAndUnitMeter(viewModel.length)}"
android:textSize="18sp" />
・ViewModelをbindingのvariableとして登録
・EditTextの内容を後述するMutableLiveDataと対応させたいので双方向データバインディングしています
・Converterのimportと使用、こちらも後述
ViewModel
class SnippetViewModel() : ViewModel() {
val length = MutableLiveData<Int>()
(略)
ここでは対象のMutableLiveDataを定義しています
Converter
ここではEditText上でユーザーが数字が入力するものの、EditTextには数字+セパレーター+単位を表示したいわけです
ただし、今回は双方向データバインディングのため、またユーザーが数字を入力する際に、EditTextに表示されている数字+セパレーター+単位をそのままgetText等で使い数字のまま使えるわけではないので不整合が生じやすいです
そのため、
・EditTextへの入力内容(Int) -> EditTextの表示内容(String)
・EditTextの表示内容(String) -> EditTextの入力内容(Int)
これらをうまく変換するためのメソッドが必要になります
ここで役立つのが、@InverseMethodです
@InverseMethodでは上記の変換、逆変換をスマートにこなしてくれます
今回の例でいうとこのようになります
object Converter {
@InverseMethod("inverseToInt")
@JvmStatic
fun addNumberSeparatorAndUnitMeter(value: Int): String {
val intValueWithFormat = String.format("%,d", value)
return "${intValueWithFormat}m"
}
@JvmStatic
fun inverseToInt(value: String?): Int {
return kotlin.runCatching { value?.replace("m", "")?.replace(",", "")?.toInt() }
.getOrNull() ?: 0
}
}
ひとつめのaddNumberSeparatorAndUnitMeterではユーザーが入力した内容=LiveDataの値に対してセパレーターとmの単位を付与しています
@InverseMethod("inverseToInt")
のinverseToIntは逆変換するのに必要なメソッド名になります
これにより再度ユーザーが数字を入力した際、逆変換のメソッドが呼ばれIntとなり、そのIntの値を使ってまたaddNumberSeparatorAndUnitMeterが呼ばれるということになります
inverseToIntメソッドでは数字+セパレーター+単位をIntに戻しています
これでユーザーが数字を入力するだけでEditTextにセパレーターと単位を補助入力できるようになりました🎉
InverseMethodを使うと何が嬉しいの?
個人的な意見です
・パターンに応じた複雑なロジックを自前で定義する手間が減る
・変換、逆変換の処理を無限ループしにくい状態で定義できる
・もとのLiveDataの値を変換していないままの型(今回はInt)として使うことができるので、その値を使ったバリデーションや値変換(Transformations.map)がしやすい
おわり
以上です 参考になれば幸いです
よりよい方法がありましたら是非ご教授ください🙏