Jetpack ComposeのLazyColumnでスクロールに合わせて処理を行う

はじめに
Jetpack ComposeのLazyColumn
を使用していると、「スクロールのタイミングで特定の処理を実行したい」と思うことがあります。例えば、キーボードを閉じたり、特定のアイテムが表示されたことを検知したりする場合です。本記事では、そうした場面で役立つ実装方法を紹介します。
スクロールを検知して処理を行う実装例
では早速本題にいきましょう!Jetpack ComposeでLazyColumn
のスクロール状態を監視するにはrememberScrollableState
を使用します。これを利用することで、リストの長さが画面縦幅より短くリストがスクロールしない場合でもスクロールを検知できます。

以下の例では上記ピクチャのようなリストでスクロールを検知したらテキストフィールドに当たっているフォーカスを外して、キーボードを閉じる処理を行っています。
@Composablefun MyApp(modifier: Modifier = Modifier) { var text by remember { mutableStateOf("") } val initialList = (1..20).map { "表示テスト${it}" } var itemsList by remember { mutableStateOf(initialList) }
val focusRequester = remember { FocusRequester() } val focusManager = LocalFocusManager.current
val listState = rememberLazyListState() val coroutinScope = rememberCoroutineScope() val scrollableState = rememberScrollableState { focusManager.clearFocus() coroutinScope.launch { listState.scrollBy(-it) } it }
LaunchedEffect(Unit) { focusRequester.requestFocus() }
Column( modifier = modifier .fillMaxSize() .padding(16.dp) ) { OutlinedTextField( value = text, onValueChange = { text = it }, label = { Text("Enter text") }, keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Done), keyboardActions = KeyboardActions(onDone = { if (text.isNotBlank()) { itemsList = itemsList + text text = "" } }), modifier = Modifier .fillMaxWidth() .focusRequester(focusRequester) ) Spacer(modifier = Modifier.height(16.dp)) LazyColumn( modifier = Modifier .fillMaxSize() .scrollable(state = scrollableState, orientation = Orientation.Vertical), state = listState, userScrollEnabled = false ) { items(itemsList) { item -> Column { Text( text = item, style = TextStyle(fontSize = 18.sp, fontWeight = FontWeight.Normal), modifier = Modifier.padding(8.dp) )
HorizontalDivider() } } } }}
Column
を使ってスクロールする画面を作る場合も同様でLazyColumn
の場合はstate
に渡していたリストの状態をModifier
のverticalScroll
から渡してあげるようにするだけです。
val scrollState = rememberScrollState()val coroutinScope = rememberCoroutineScope()val scrollableState = rememberScrollableState { focusManager.clearFocus() coroutinScope.launch { scrollState.scrollBy(-it) } it}Column( modifier = Modifier .fillMaxSize() .verticalScroll(state = scrollState, enabled = false) .scrollable(state = scrollableState, orientation = Orientation.Vertical),)
やりがちな実装だがうまくいかないパターン
pointerInputを利用してスクロールを検知しようとすると、LazyColumnのスクロール挙動と競合してうまく動作しない可能性があります。 以下のような実装では、指でドラッグした際にスクロール量を取得できますが、LazyColumnのネイティブなスクロール挙動と干渉するため、意図した動作にならないことが多いです。そのため、スクロールイベントの検知にはrememberScrollableStateを使用することを推奨します。
Column(modifier = Modifier.fillMaxSize().pointerInput(Unit) { detectVerticalDragGestures { _, dragAmount -> println("ドラッグされた: $dragAmount") }}) { LazyColumn { items(itemsList) { item -> Text(text = item, modifier = Modifier.padding(8.dp)) } }}