Razl-Dazl

eyecatch
Copyright © Google

双方向データバインディング時にEditTextのカーソル位置を調整したい

Posted at — 2023-11-30

レイアウトに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行は消えるので・・・まあ・・・そのうち直します・・・

Author@zakuro

Mastodon: 396@vivaldi.net