Jetpack Compose:实现LazyColumn滚动时文本动态缩放并固定
Jetpack Compose LazyColumn滚动时动态调整文本大小实现方案
需求是在Jetpack Compose的LazyColumn中实现滚动时文本动态调整大小,停止滚动后保持当前尺寸。具体规则为:中间选中的数字(如「4」)字号为20.sp,相邻的「3」「5」为18.sp,再外层的「2」「6」为16.sp,以此类推。尝试通过Modifier.scale()实现,但未找到具体方法,现有示例代码及依赖如下:
@OptIn(ExperimentalSnapperApi::class) @Composable fun Example() { val listStateHour = rememberLazyListState() val hourTexts: List<String> = (1..23).map { it.toString().padStart(2, '0') } Box( modifier = Modifier.background(color = Color.LightGray).fillMaxWidth(), contentAlignment = Alignment.Center ) { Box( modifier = Modifier .height(32.dp) .fillMaxWidth() .background(color = Color.Black.copy(0.2f)), ) {} LazyColumn( modifier = Modifier.height(200.dp), state = listStateHour, horizontalAlignment = Alignment.Start, contentPadding = PaddingValues(vertical = 116.dp), flingBehavior = rememberSnapperFlingBehavior(lazyListState = listStateHour) ) { itemsIndexed(hourTexts) { index, item -> Box( modifier = Modifier.height(32.dp), contentAlignment = Alignment.Center ) { Text( text = item, color = Color.Black, fontSize = 20.sp ) } } } } }
外部依赖:
implementation 'dev.chrisbanes.snapper:snapper:0.3.0'
解决方案
核心思路是利用LazyListState的滚动状态,实时计算每个列表项与中间参考区域的位置偏移量,根据偏移距离动态调整文本的缩放比例,从而实现字号变化的效果。具体实现如下:
@OptIn(ExperimentalSnapperApi::class) @Composable fun ScalingLazyColumnExample() { val listStateHour = rememberLazyListState() val hourTexts: List<String> = (1..23).map { it.toString().padStart(2, '0') } val itemHeight = 32.dp val itemHeightPx = with(LocalDensity.current) { itemHeight.toPx() } // LazyColumn自身的中心Y坐标 val columnCenterY = 200.dp / 2 // 实时计算每个可见item的缩放比例 val itemScales by remember { derivedStateOf { val layoutInfo = listStateHour.layoutInfo val visibleItems = layoutInfo.visibleItemsInfo visibleItems.associate { item -> // 计算当前item的中心Y坐标(相对于LazyColumn的布局) val itemCenterY = item.offset + itemHeightPx / 2 val columnCenterYPx = with(LocalDensity.current) { columnCenterY.toPx() } // 计算与中间位置的偏移item数量 val offsetItems = abs((itemCenterY - columnCenterYPx) / itemHeightPx).toInt() // 根据偏移量匹配缩放比例,超出范围后保持最小缩放值 val scale = when (offsetItems) { 0 -> 1f // 中间项:20.sp * 1 = 20.sp 1 -> 0.9f // 相邻项:20.sp * 0.9 = 18.sp 2 -> 0.8f // 外层项:20.sp * 0.8 = 16.sp else -> max(0.6f, 1f - offsetItems * 0.1f) } item.index to scale } } } Box( modifier = Modifier.background(color = Color.LightGray).fillMaxWidth(), contentAlignment = Alignment.Center ) { Box( modifier = Modifier .height(itemHeight) .fillMaxWidth() .background(color = Color.Black.copy(0.2f)), ) {} LazyColumn( modifier = Modifier.height(200.dp), state = listStateHour, horizontalAlignment = Alignment.CenterHorizontally, contentPadding = PaddingValues(vertical = 116.dp), flingBehavior = rememberSnapperFlingBehavior(lazyListState = listStateHour) ) { itemsIndexed(hourTexts) { index, item -> Box( modifier = Modifier.height(itemHeight), contentAlignment = Alignment.Center ) { Text( text = item, color = Color.Black, fontSize = 20.sp, // 应用计算好的缩放比例,不可见item使用默认最小缩放 modifier = Modifier.scale(itemScales.getOrDefault(index, 0.6f)) ) } } } } }
关键说明
- 使用
derivedStateOf监听布局变化,仅当可见item位置改变时重新计算缩放比例,避免不必要的重组,优化性能; - 通过item中心与LazyColumn中心的偏移量,转换为相对的item数量差,精准匹配需求中的字号规则;
- 缩放比例与字号直接关联,基础字号20.sp通过scale系数动态调整,完全符合需求中的尺寸梯度;
- 针对不可见item设置默认最小缩放值,避免首次加载或滚动到边缘时出现显示异常。
内容的提问来源于stack exchange,提问作者Svilen Rusev
相关产品推荐
相关产品推荐

