TopAppBar の ScrollBehavior の NestedScrollConnection と PullToRefreshBox の NestedScrollConnection は Modifier の渡し方やネストの関係で競合して意図しない動きになります。
問題のコードと原因
TopAppBar の ScrollBehavior をルートの Scaffold の Modifier に NestedScrollConnection を設定するのはよくあるパターンだと思います。
その状態でコンテンツ側に PullToRefreshBox -> スクロールするコンテンツ、とネストさせるとします。
@Composable
fun Sample() {
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
Scaffold(
topBar = {
MediumFlexibleTopAppBar(
title = {
Text(text = "Title")
},
scrollBehavior = scrollBehavior
)
},
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection) // ここでScrollBehaviorのNestedScrollを設定
) {
PullToRefreshBox(
isRefreshing = isRefreshing,
onRefresh = { ... },
modifier = Modifier
.padding(it),
) {
LazyColumn { ... }
}
}
}
この実装の場合に Pull To Refresh が動くまで TopAppBar が展開されない挙動になってしまいます。
原因は PullToRefreshBox の NestedScrollConnection の消費処理で TopAppBar の ScrollBehavior が意図した動きをしないためです。
回避策
NestedScrollConnection の処理順序が重要なので、一つ目の回避策は PullToRefreshBox の子の Composable の Modifier に TopAppBar の ScrollBehavior を設定することで回避できます。
@Composable
fun Sample() {
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
Scaffold(
topBar = {
MediumFlexibleTopAppBar(
title = {
Text(text = "Title")
},
scrollBehavior = scrollBehavior
)
},
) {
PullToRefreshBox(
isRefreshing = isRefreshing,
onRefresh = { ... },
modifier = Modifier
.padding(it),
) {
LazyColumn(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection) // PullToRefreshBoxよりも子のModifierにScrollBehaviorのNestedScrollを設定
) { ... }
}
}
}
派生した回避策としては、PullToRefreshBox を使わずに Modifier#pullToRefresh の後に TopAppBar の ScrollBehavior を設定することでも回避できます。PullToRefreshBox は内部的に Box に Modifier#pullToRefresh を適用しているだけなので、挙動を細かく制御したいのであれば分解して使うこともできます。
Modifier#pullToRefresh と Modifier#nestedScroll の順序が逆だと問題が起きるので順序が重要になります。
@Composable
fun Sample() {
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
Scaffold(
topBar = {
MediumFlexibleTopAppBar(
title = {
Text(text = "Title")
},
scrollBehavior = scrollBehavior
)
},
modifier = Modifier
) {
Box(
modifier = Modifier
.pullToRefresh(
state = rememberPullToRefreshState(),
isRefreshing = isRefreshing,
onRefresh = { ... },
)
.nestedScroll(scrollBehavior.nestedScrollConnection), // pullToRefreshの後でScrollBehaviorのNestedScrollを設定
) {
LazyColumn { ... }
}
}
}
NestedScrollConnection 周りは処理をネストしているときに消費の処理の関係でおかしな挙動になり得るので、もし遭遇した場合は処理の順序を疑ってみることをお勧めします。