作成日 2025/06/23
最終更新日 2025/07/22

ボタンでスクロール

背景

アプリを作っているときに、タップ以外の操作でスクロールを呼びたかった。

内容

  1. スクロールリストを作成
  2. ボタンでスクロール
  3. まとめ

スクロールリストを作成

LazyColumnを利用して作りました。

要素ごとにわかりやすいように枠線をつけています。

ここは別段説明することはないと思います。差分は こちら です。

ボタンでスクロール

ボタンでスクロールできるようにしていきます。

ボタンでスクロールするためにはボタンが必要なので配置してあげます。差分は こちら です。Composeでボタンを置いただけです。clickableにしているのは、触っていることがわかりやすいようにするためです。

で、ボタンにスクロール機能を付けていきます。差分は こちら です。

タップでも長押しでもスクロールできるようにしてあげます。

ModifierExt.kt
fun Modifier.isPressed(
    buttonState: ButtonState,
    update: (ButtonState) -> Unit,
): Modifier {
    return this.pointerInput(Unit) {
        awaitEachGesture {
            awaitPointerEvent()
            update(buttonState)

            // 押されている間はtrue
            while (
                awaitPointerEvent().changes.any {
                    it.pressed
                }
            ) {
               // なにもしない
            }

            // 放されたらfalse
            update(ButtonState.None)
        }
    }
}
            

ボタンの長押し検知用に作ったpointerInputです。

buttonStateがどのボタンが押しているかを指定し、updateで親で保持しているstateを更新します。

タップされたときに引数で指定されたボタンを押下状態にして、タップをやめたときにボタンの押下状態を解除します。

長押しを検知するModifierのcombinedClickableや 、PointerInput内で呼べるdetectTapGesturestがありますが [1] 、長押しになったことを検知するだけなので今回は使えません。

前に作ったのがpointerInputだったのでponiterInputを利用しましたが、pointerIntoropFilterというメソッドでも同様のことが出来そうです [2]

App.kt
@Composable
@Preview
fun App() {
    val scrollState = rememberLazyListState()

    var isButtonPressed by remember {
        mutableStateOf(ButtonState.None)
    }

    LaunchedEffect(isButtonPressed) {
        val delayTime = 100L
        var dif = 1
        while (true) {
            when (isButtonPressed) {
                ButtonState.Up -> scrollState.animateScrollToItem(
                    max(
                        scrollState.firstVisibleItemIndex - dif,
                        0,
                    ),
                )

                ButtonState.Down -> scrollState.animateScrollToItem(
                    scrollState.firstVisibleItemIndex + dif,
                )

                ButtonState.None -> return@LaunchedEffect
            }
            delay(delayTime)
            dif++
        }
    }


    ~省略~
    
} 
    

ボタンの状態が変わったときに非同期処理が呼ばれ、押されている間は上下にスクロールし、放された場合なにもしません。

difは移動量です。上に移動するときは、先頭のアイテム番号を小さくし、下に移動するときは先頭のアイテムを大きくします。itemIndexが0になると困るので0にならないようにします。大きい方は問題ないです。

まとめ

以上、スクロールを外部からいじる方法について調べ、リストのタップ操作以外でスクロールできるような実装をしました。

スクロールバーも別記事で作成している(こちら)ので、興味がある方はそちらもみてくださいませ。

ではまた。

参考

  1. 『タップして押す』 Developers (2025/06/23)
    https://developer.android.com/develop/ui/compose/touch-input/pointer-input/tap-and-press?hl=ja
  2. 『Jetpack Composeで長押し中インクリメントするボタンを作る』 Zenn (2025/06/23)
    https://zenn.dev/tada_k/articles/352aea81d6d74d