疑問
次のコードは、表示されている要素のインデックスの中で最も小さいものをログに出力するコードです。出力されるタイミングは値が変わったとき、つまり一方向にゆっくりと 64dp スクロールしたときです。私の環境では 1dp = 2.75px であるため、176px 毎に出力されることになります。
data class Data(val id: Int)
val data = (1..1000).map { Data(it) }
@Composable
fun MainScreen() {
val state = rememberLazyListState()
Log.e("TAG", state.firstVisibleItemIndex.toString())
LazyColumn(Modifier.fillMaxSize(), state = state) {
items(data, key = { it.id }) {
Box(Modifier.border(1.dp, Color.Black).fillMaxWidth().height(64.dp))
}
}
}
それに対して次のコードは、1px スクロールするたびに出力されます。
data class Data(val id: Int)
val data = (1..1000).map { Data(it) }
@Composable
fun MainScreen() {
val state = rememberLazyListState()
// ここだけを書き換えている
Log.e("TAG", state.firstVisibleItemScrollOffset.toString())
LazyColumn(Modifier.fillMaxSize(), state = state) {
items(data, key = { it.id }) {
Box(Modifier.border(1.dp, Color.Black).fillMaxWidth().height(64.dp))
}
}
}
私の感覚では、state 変数のプロパティの一部だけを参照していようとも、1px スクロールする毎に firstVisibleItemScrollOffset の値が変わるのだから state 自身が変更されると思ってしまいます。しかし実際はもっと賢くて、firstVisibleItemIndex だけを参照しているときは 176px 毎に再描画が走ってくれます。firstVisibleItemScrollOffset が変更されているにも関わらず。
答え
rememberLazyListState の実装を読むと答えは単純でした。この関数は State#value を返すのではなく、State#value をプロパティに持つ State#value でないオブジェクトを返していました。つまり、state.firstVisibleItemIndex や state.firstVisibleItemScrollOffset は State#value ですが、state は State#value ではありませんでした。
終わりに
参照しているプロパティが変更されたときだけ再描画が行われるので、「スクロールするたびに再描画が走るので rememberLazyListState を使うことは避けよう」と思わなくていいとわかり、安心しました。