双方向データバインディング時にEditTextのカーソル位置を調整したい
レイアウトにEditTextを含む画面にて双方向データバインディングを導入したところ、EditTextに対するsetText()やsetSelection()が正しく動作しなくなってしまいました
元々は以下のようなコードを記述していました(一例)
val text = intent.getStringExtra("text")
editText.setText(text)
editText.setSelection(editText.text.length)
しかし、双方向データバインディングを用いてEditTextの中身をViewModelのプロパティと紐付けるようにした場合、setText()を使用した値の入力は反映されなくなってしまいます
で、いろいろ試行錯誤してみたのでそれぞれの結果を残しておきます
1. ViewModelのプロパティに値をセットするように書き換える
最初のコードの2行目を置き換えただけです
VIewModelに定義したプロパティへ値をセットするように変更
val text = intent.getStringExtra("text")
viewModel.text.value = text
editText.setSelection(editText.text.length)
結果、正しく動作せずIndexOutOfBoundsException()が出る・・・
Logcatを見てみると、空の EditTextに対してsetSelection(20)のような操作を行い例外が発生していました
ViewModelにセットされた値がEditTextに反映される前にsetSelection()が実行されたと推測
2. Observerで値の変更を検知するようにして、ViewModelのプロパティに値をセット
viewModel.text.observe(this, Observer{
editText.setSelection(it.length)
})
val text = intent.getStringExtra("text")
viewModel.text.value = text
1と同様例外が発生してクラッシュ・・・
ここで注意したいのが、あくまでもObserverはViewModelのプロパティの変化を検知しているだけである事
よって最初の場合と同じように、EditTextの中身に値が反映されるまではラグが発生するのでダメ
3. ViewModelの値ではなくEditTextの値に対して変更を検知する
val watcher = object: TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
}
override fun afterTextChanged(s: Editable?) {
if(isFirst) {
isFirst = false
binding.content.setSelection(binding.content.text.length)
}
}
}
binding.editText.addTextChangedListener(watcher)
val text = intent.getStringExtra("text")
viewModel.text.value = text
これは問題なく動いた。
EditText自体にリスナをセットする手法です
TextWatcherを継承したクラスの中にフラグを置いていますが、カーソルを動かすのを値をセットした直後だけにするためです
本当はremoveTextChangedListener()
でリスナを無効化したかったのですが、自分の試行錯誤した範囲では例外が発生してしまったり、そもそもリスナ自体がうまく動作しなくなったりと使いこなす事が出来なかったので諦めました
双方向データバインディングを使えばActivityクラスがスッキリするはずなのに、今回ばかりは余計に記述量が増えてしまいました うーん・・・
あと、Activity内からViewModelのプロパティに値をセットしているのもあまり良くないですね、ここはそのうち直すことにします
ViewModelを生成する前にIntentから文字列を受け取って、ViewModel生成時に値をぶち込んでしまえば最後の1行は消えるので・・・まあ・・・そのうち直します・・・
完