前言
在Android中 用CollapsingToolbarLayout来实现折叠工具栏效果;
在Jetpack Compose 中 可以用compose-collapsing-toolbar这个第三方库来实现。
implementation "me.onebone:toolbar-compose:2.3.3"
这是最终效果:
1、CollapsingToolbar的脚手架
val pic = "https://example.com/image.jpg"
val state = rememberCollapsingToolbarScaffoldState()
//高斯模糊背景
ToolBarBg(state, pic,48f, 220f, 10f)
CollapsingToolbarScaffold(
modifier = Modifier
.statusBarsPadding()
.navigationBarsPadding(),
state = state,
scrollStrategy = ScrollStrategy.ExitUntilCollapsed,
toolbar = {
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colors.surface) {
MyToolBar(state, "瓢盆大泼泼,瓢泼大盆飘,盆飘,下大雨了", pic, 48.dp, 220.dp)
}
}
) {
Surface(shape = RoundedCornerShape(topStart = 10.dp, topEnd = 10.dp),
color = MaterialTheme.colors.background) {
LazyColumn(
contentPadding = PaddingValues(5.dp),
verticalArrangement = Arrangement.spacedBy(5.dp)) {
itemsIndexed(items = lazyPagingItems,
key = { _, item -> item.id }) { _, item ->
item?.let {
RepositorCard(item)
}
}
if (lazyPagingItems.loadState.append is LoadState.Loading) {
//list 底部loading
item {
Box(modifier = Modifier
.fillMaxWidth()
.height(50.dp)) {
CircularProgressIndicator(modifier = Modifier.align(alignment = Alignment.Center))
}
}
}
}
}
}
lazyPagingItems是用的我上一篇的paging3的分页,大家可以看看【Jetpack Compose】LazyColumn 使用Paging3分页+SwipeRefresh下拉刷新 - 掘金 (juejin.cn)
2、toolbar
/**
* @param collapseHeight toolbar收缩的高度
* @param expandHeight toolbar展开的高度
*/
@Composable
fun CollapsingToolbarScope.MyToolBar(
state: CollapsingToolbarScaffoldState,
title: String,
pic: String,
collapseHeight: Dp,
expandHeight: Dp,
) {
Box(
modifier = Modifier
.fillMaxWidth()
.height(expandHeight)
.pin()//表示固定那里不跟随toolbar变化
)
Box(
modifier = Modifier
.fillMaxWidth()
.height(collapseHeight)
.pin()
) {
IconButton(
onClick = { /*navController.popBackStack() */ },
modifier = Modifier.align(Alignment.CenterStart).padding(horizontal = 5.dp)
) {
Icon(imageVector = Icons.Default.ArrowBackIos, contentDescription = null)
}
}
//收缩state.toolbarState.progress为0 ,展开为1
val progress = state.toolbarState.progress
//38 :Toolbar收缩后image的大小,110是Toolbar展开后image的大小
val imageSize = (38 + (110 - 38) * progress).dp
//40 :Toolbar收缩后image x轴的偏移量,10:Toolbar展开后image x轴的偏移量
val imageOffsetX = (40 + (10 - 40) * progress).dp
//0 :Toolbar收缩后image y轴的偏移量,64:Toolbar展开后image y轴的偏移量
val imageOffsetY = (0 + (64 - 0) * progress).dp
Surface(elevation = 2.dp, color = Color.Transparent, shape = RoundedCornerShape(6.dp),
modifier = Modifier
.padding(5.dp)
.size(imageSize)
.offset(x = imageOffsetX, y = imageOffsetY)) {
AsyncImage(
model = pic,
contentDescription = null,
)
}
//90 :Toolbar收缩后Text X轴的偏移量,135:Toolbar展开后Text X轴的偏移量
val offsetTextX = (90 + (135 - 90) * progress).dp
Text(
text = title,
modifier = Modifier
.padding(start = offsetTextX)
.fillMaxWidth()
.height(collapseHeight)
.wrapContentHeight(if (progress < 0.2f) Alignment.CenterVertically else Alignment.Top)
.offset(y = imageOffsetY),
maxLines = if (progress < 0.2f) 1 else 2,
overflow = TextOverflow.Ellipsis,
textAlign = TextAlign.Start,
fontSize = 18.sp
)
}
toolbar完全折叠时state.toolbarState.progress = 0,完全展开时state.toolbarState.progress = 1
例如这个图片大小折叠时是38dp,展开大小是110dp图片大小就跟着progress动态去改变
imageSize = (38 + (110 - 38) * state.toolbarState.progress).dp
这里通过lerp方法去实现更好,例如:
val fontSize : TextUnit = androidx.compose.ui.unit.lerp(collapseFontSize, expandSize, progress)
val fontWeight : FontWeight =
androidx.compose.ui.text.font.lerp(collapseFontWeight, expandFontWeight, progress)
val color :Color=
androidx.compose.ui.graphics.lerp(collapseColor, expandColor, progress)
val imageSize:Dp = androidx.compose.ui.unit.lerp(collapseSize,expandSize,progress)
compose的单位包含color都有lerp方法
CollapsingToolbarScope 的Modifier扩展方法
-
fun Modifier.pin() :固定在那里不会移动。
-
fun Modifier.parallax(ratio: Float = 0.2f)会随着整体的滑动的偏移量*ratio去滑动,ratio为1就是和整体的滑动保持一致。
-
fun Modifier.road(whenCollapsed: Alignment, whenExpanded: Alignment):直接看它这里官方描述吧
-
fun Modifier.progress(listener: ProgressListener) 就不用再说了吧。
另外注意的一点是,如果scrollStrategy = ScrollStrategy.ExitUntilCollapsed,折叠时toolbar的高度是toolbar里所有的组合项最小的一个组合的高度,展开是最高的一个组合的高度
3、状态栏 toolbar 背景高斯模糊
/**
* @param collapseHeight toolbar收缩的高度
* @param expandHeight toolbar展开的高度
* @param radiu body顶部圆角度数,如果没有则为0
*/
@Composable
fun ToolBarBg(
state: CollapsingToolbarScaffoldState,
pic: String,
collapseHeight: Float,
expandHeight: Float,
radiu: Float,
) {
/**
* 如果没有圆角可以把背景放到toolbar里面,可以直接用parallax属性
* Image(painter = painter,
* modifier = Modifier
* .fillMaxWidth()
* .height(expandHeight)
* .parallax(1f)//1f 表示和整体的滑动保持一致
*/
val offsetBgY =
((collapseHeight - expandHeight) + (0 - (collapseHeight - expandHeight)) * state.toolbarState.progress).dp
Box(modifier = Modifier
.fillMaxWidth()
//状态栏的高度+toolbar展开的高度+圆角的度数
.statusBarsHeight((expandHeight + radiu).dp)
.offset(y = offsetBgY)
) {
val painter = rememberAsyncImagePainter(
model = ImageRequest.Builder(LocalContext.current)
.data(pic)
.transformations(CoilBlurTransformation(LocalContext.current, 25f, 2f))
.build())
Image(painter = painter,
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier
.fillMaxSize())
//蒙板 防止图片全白,导致看不到toolbar的内容
Box(modifier = Modifier.fillMaxSize().background(color = MaterialTheme.colors.onSurface.copy(alpha = 0.55f)))
}
}
关于图片的高斯模糊可以看我另一篇文章Jetpack Compose Coil2.0 高斯模糊 - 掘金 (juejin.cn)
大家觉得这个奇怪我这为什么不把这个高斯模糊的背景放到toolbar里面去,因为toolbar和body之间是圆角的,如果放到toolbar里面去,这body圆角以外的这部分背景就不是这个高斯模糊的背景了,又有童靴说可以把顶部的圆角也放toolbar里面去,这样的话展开没问题收起来就会有问题了。
当然如果设计不是圆角的,可以把高斯模糊背景放到toolbar里面去:
val painter = rememberAsyncImagePainter(
model = ImageRequest.Builder(LocalContext.current)
.data(pic)
.transformations(CoilBlurTransformation(LocalContext.current, 25f, 2f))
.build())
Image(model = painter,
contentDescription = null,
modifier = Modifier
.fillMaxWidth()
.height(expandHeight)
.parallax(1f))//1f 表示和整体的滑动保持一致
4.最后是activity
class CollapsingToolbarActivity :ComponentActivity(){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
setContent {
ProvideWindowInsets {
MyAppTheme {
val systemUiController: SystemUiController = rememberSystemUiController()
//状态栏背景设为透明,icon为白色
systemUiController.setStatusBarColor(Color.Transparent, false)
CollapsingToolbarExample()
}
}
}
}
}
5. 拖动toolbar,无法折叠/展开的问题
拖动content可以折叠/展开,但是拖动toolbar,无法折叠/展开,但是在Android的CollapsingToolbarLayout拖动toolbar可以展开和折叠,这个库就不行有bug;然后我也在compose-collapsing-toolbar也找到了这个issues,作者说会在3.0版本解决这个问题,但问题是目前是compose-collapsing-toolbar才2.3.4版本,不知道啥时候更新到3.0版本。
如果toolbar的高度比较高这就是个严重的问题了,用户想要折叠查看content的内容只能向上拖动toolbar下面的content。
解决方案
toolbarModifier = Modifier.verticalScroll(rememberScrollState())
我也在这个issues提交了评论:
我写这一篇文章Jetpack compose 仿QQ音乐实现下拉刷新上拉加载更多 - 掘金 (juejin.cn)的时候研究过NestedScrollConnection,然后fork了compose-collapsing-toolbar,在ScrollStrategy的NestedScrollConnection里面加了打印,发现拖动工具栏,这个NestedScrollConnection里面没有任何打印,滑动下面的LazyColumn就有打印。
这是由于父compose的NestedScrollConnection的那些方法都是由子compose可组合项把events传递过去的。而toolbar没有设置verticalScroll也不是LazyColumn,它就没有滑动,没有滑动何来把events传递给父compose的NestedScrollConnection
最后效果(拖动toolbar可以折叠、展开工具栏了):