📱
[Android]手軽にスクロールに合わせたAppBarを作りたい
Collapsing App Bar
Jetpack Composeではマテリアル3のコンポーネントで
- TopAppBar
- CenterAlignedTopAppBar
- MediumTopAppBar
- LargeTopAppBar
上記4種類が用意されていて、決められた挙動でCollapsing App Barを実装できる
しかし、欲しい動きはそうじゃないというのがあると思う。
それをできるだけお手軽に実装してみたい
1.ジャンプ+方式
デフォルトだとNavigation Icon以外は透明にしておく、itemのindexが1以上かつちょうど良い感じのoffsetを設定する
それ対応したalpha値のStateを用意する
スクロールによって徐々にalpha値が変化する
val scrollState = rememberLazyListState()
val appBarAlpha = remember(scrollState) {
derivedStateOf {
val scrollOffset = when {
scrollState.firstVisibleItemIndex > 0 -> 1f // 1つ目のアイテムが見えなくなったら完全に表示
else -> {
// スクロールオフセットに基づいて0〜1の値を計算
// 例: 200pxスクロールしたら完全に表示する場合
val maxOffset = 220f
(scrollState.firstVisibleItemScrollOffset / maxOffset).coerceIn(0f, 1f)
}
}
scrollOffset
}
}
Box {
CenterAlignedTopAppBar(
modifier = Modifier.zIndex(1f),
title = {
Text("テキスト", modifier = Modifier
.graphicsLayer {
alpha = appBarAlpha.value
}
)
},
navigationIcon = {
IconButton(
onClick = { /* Handle back navigation */ },
) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "戻る"
)
}
},
colors = TopAppBarDefaults.centerAlignedTopAppBarColors(
containerColor = Color.White.copy(alpha = appBarAlpha.value)
)
)
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(innerPadding),
state = scrollState
) {
item {
Image(
painter = painterResource(id = R.drawable.img),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier
.fillMaxWidth()
.height(220.dp)
)
}
items(100) {
Text("Item $it", modifier = Modifier.fillMaxWidth())
}
}
}
2.ジャンプTOON方式
LargeTopAppBarを利用してこの動きを利用しつつ画像を重ねてcollapsedFractionに合わせて画像サイズを変えていく
Scaffold(
modifier = Modifier
.fillMaxSize()
.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
Box {
Image(
painter = painterResource(id = R.drawable.img),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier
.fillMaxWidth()
.height(220.dp * (1f - scrollBehavior.state.collapsedFraction))
.graphicsLayer {
alpha = 1f - scrollBehavior.state.collapsedFraction
}
)
Box(
modifier = Modifier
.padding(top = statusBarHeight)
.height(64.dp)
.fillMaxWidth(),
contentAlignment = Alignment.Center
) {
Text(
"テキスト",
modifier = Modifier
.graphicsLayer {
alpha = (scrollBehavior.state.collapsedFraction - 0.5f) * 2
}
)
}
LargeTopAppBar(
scrollBehavior = scrollBehavior,
expandedHeight = 220.dp,
title = {},
navigationIcon = {
IconButton(
onClick = { /* Handle back navigation */ },
) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "戻る"
)
}
},
colors = TopAppBarDefaults.largeTopAppBarColors(
containerColor = Color.Transparent,
scrolledContainerColor = Color.Transparent
)
)
}
}
) { innerPadding ->
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(innerPadding),
state = scrollState
) {
items(100) {
Text("Item $it", modifier = Modifier.fillMaxWidth())
}
}
}
Discussion