Composeのオーバースクロールエフェクト

Kenji Abe
7 min readNov 5, 2023
Photo by Richard Horvath on Unsplash

Composeでスクロール処理するときに、オーバースクロール時のエフェクトでいくつか注意することがあるので、まとめておきます。

Android 12以降はオーバースクロールすると、ストレッチして戻るような挙動になっています。それより前のバージョンは端のほうにグレーの効果が表示されるようになっています。

左: Android 12以降、右: Android 12より前

どちらかというとオーバースクロールエフェクトはAndroid 12より前のほうがどこが端なのかが分かりやすくなっています。

※ここから先は見た目がわかりやすいAndroid 12より前で挙動を確認していきます。

Column/RowのModifierの順番

Column でスクロールするときは以下のような感じの実装になると思います。

Column(
modifier = Modifier
.fillMaxWidth()
.verticalScroll(rememberScrollState())
) {
(1..30).forEach { /* */ }
}

これに全体のPaddingを設定する際に Modifier の順番に注意する必要があります。

Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp) // verticalScroll の前に padding を設定
.verticalScroll(rememberScrollState())
) {
// ...
}

verticalScroll の前に padding を設定すると、以下のようになります。

見てもらえれば分かるようにPaddingの中にオーバースクロールエフェクトが表示されてしまいます。ちょっとイマイチな感じになっちゃってます。

Paddingを設定してからのScrollという感じの設定になっているので、順番を変えてあげます。

Column(
modifier = Modifier
.fillMaxWidth()
.verticalScroll(rememberScrollState())
.padding(16.dp) // verticalScroll の後に padding を設定
) {
// ...
}

これを実行すると以下のように全体にオーバースクロールエフェクトが表示されるようになります。

LazyColumn/LazyRowのPadding

LazyColumn の場合も先程と同じように注意しないとPaddingの中にオーバースクロールエフェクトが表示されることになります。

// NG
LazyColumn(
verticalArrangement = Arrangement.spacedBy(16.dp),
modifier = Modifier.padding(16.dp)
) {
// ...
}

LazyColumn では Modifier.padding を使うとPaddingの中にオーバースクロールエフェクトが表示されてしまいます。

LazyColumn の場合は contentPadding で設定すると全体にオーバースクロールエフェクトが表示されるようになります。

// OK
LazyColumn(
verticalArrangement = Arrangement.spacedBy(16.dp),
contentPadding = PaddingValues(16.dp)
) {
// ...
}

Edge-to-edgeの対応

設定アプリのオーバースクロールエフェクトを確認すると、以下のようにOSのナビゲーションの上にエフェクトが表示されてるのが分かると思います。

Edge-to-edgeが有効にしてComposeで下にInsetのスペースを入れた実装してみます。

enableEdgeToEdge() // Activity側でEdge-to-edgeを有効にする

LazyColumn(
verticalArrangement = Arrangement.spacedBy(16.dp),
contentPadding = PaddingValues(16.dp)
) {
// ...

// https://developer.android.com/jetpack/compose/layouts/insets#inset-size
item {
Spacer(
Modifier.windowInsetsBottomHeight(
WindowInsets.systemBars
)
)
}
}

これを実行すると、以下のようになります。

ちょっと分かりにくいかもですが、オーバースクロールエフェクトがOSナビゲーションバーの下に表示されてしまっています。

これに対応するには、 OverscrollConfiguration を使ってオーバースクロールエフェクトをカスタマイズしていきます。
カスタマイズするには、 CompositionLocal を使っていきます。

CompositionLocalProvider(
// 下位階層のオーバースクロールエフェクトをカスタマイズ
LocalOverscrollConfiguration provides OverscrollConfiguration(
                // OSナビゲーションバーのPaddingを設定する
drawPadding = WindowInsets.systemBars.only(
WindowInsetsSides.Bottom
).asPaddingValues()
)
) {
LazyColumn {}
}

これを実行すると、OSナビゲーションバーの上にオーバースクロールエフェクトが表示されるようになります。

実はこれでは完璧ではないケースがあります。他にもIMEが表示されてる場合も色々考える必要がありますので、 WindowInsets.ime も組み合わせていく必要もあります。

説明は省略しますが、IMEも対応する場合は以下のような感じの実装になります。

CompositionLocalProvider(
LocalOverscrollConfiguration provides OverscrollConfiguration(
drawPadding = WindowInsets.safeDrawing
.exclude(WindowInsets.ime)
.only(
WindowInsetsSides.Bottom
).asPaddingValues()
)
) {
LazyColumn {}
}

このあたりは WindowInsets についての知識が必要になってきます。

--

--

Kenji Abe

Programmer / Gamer / Google Developers Expert for Android, Kotlin / @STAR_ZERO