Wander Purrgraming

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

はじめに

Jetpack ComposeのLazyColumnを使用していると、「スクロールのタイミングで特定の処理を実行したい」と思うことがあります。例えば、キーボードを閉じたり、特定のアイテムが表示されたことを検知したりする場合です。本記事では、そうした場面で役立つ実装方法を紹介します。

スクロールを検知して処理を行う実装例

では早速本題にいきましょう!Jetpack ComposeでLazyColumnのスクロール状態を監視するにはrememberScrollableStateを使用します。これを利用することで、リストの長さが画面縦幅より短くリストがスクロールしない場合でもスクロールを検知できます。

テキストフィールドとリスト

以下の例では上記ピクチャのようなリストでスクロールを検知したらテキストフィールドに当たっているフォーカスを外して、キーボードを閉じる処理を行っています。

@Composable
fun 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に渡していたリストの状態をModifierverticalScrollから渡してあげるようにするだけです。

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))
}
}
}