You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jetpack Compose滑动按钮菜单实现:中间按钮放大高亮需求

Jetpack Compose 实现中间高亮缩放的滑动按钮菜单

以下是实现需求的完整方案,核心思路是通过跟踪滚动状态计算每个按钮与屏幕中心的距离,动态调整缩放比例和颜色亮度,实现平滑的视觉过渡效果:

完整代码示例

import androidx.compose.animation.core.lerp
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.icons.filled.Home
import androidx.compose.material.icons.filled.Person
import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.filled.ShoppingCart
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlinx.coroutines.launch

// 菜单数据类
data class MenuItem(val icon: androidx.compose.ui.graphics.vector.ImageVector, val label: String)

@Composable
fun CenteredHighlightScrollMenu() {
    // 模拟菜单数据
    val menuItems = listOf(
        MenuItem(Icons.Filled.Home, "首页"),
        MenuItem(Icons.Filled.Search, "搜索"),
        MenuItem(Icons.Filled.Favorite, "收藏"),
        MenuItem(Icons.Filled.ShoppingCart, "购物车"),
        MenuItem(Icons.Filled.Person, "我的")
    )

    val configuration = LocalConfiguration.current
    val screenWidth = configuration.screenWidthDp.dp
    val screenCenter = screenWidth / 2
    val density = LocalDensity.current
    val scrollState = rememberLazyListState()
    val coroutineScope = rememberCoroutineScope()

    LazyRow(
        state = scrollState,
        horizontalArrangement = Arrangement.spacedBy(16.dp),
        contentPadding = PaddingValues(horizontal = 32.dp),
        modifier = Modifier.fillMaxWidth(),
        verticalAlignment = Alignment.CenterVertically
    ) {
        items(menuItems) { item ->
            val index = menuItems.indexOf(item)
            var itemCenter by remember { mutableStateOf(0.dp) }

            // 计算当前按钮与屏幕中心的距离
            val distance = with(density) {
                kotlin.math.abs(itemCenter - screenCenter)
            }

            // 控制过渡效果的参数
            val maxTransitionDistance = 100.dp // 超过此距离后不再变化
            val normalizedDistance = (distance / maxTransitionDistance).coerceAtMost(1f)

            // 动态计算缩放比例:中心按钮1.2倍,边缘按钮0.8倍
            val scale = lerp(1.2f, 0.8f, normalizedDistance)
            // 动态计算颜色:中心按钮原色,边缘按钮灰色
            val textColor = lerp(Color.Black, Color.Gray, normalizedDistance)
            val iconColorFilter = ColorFilter.tint(lerp(Color.Black, Color.Gray, normalizedDistance), BlendMode.SrcIn)
            // 动态计算字体大小
            val textSize = lerp(14.sp, 12.sp, normalizedDistance)

            Column(
                horizontalAlignment = Alignment.CenterHorizontally,
                modifier = Modifier
                    // 获取按钮在屏幕中的位置
                    .onGloballyPositioned { coordinates ->
                        itemCenter = with(density) {
                            coordinates.localToWindow(coordinates.size.center).x.toDp()
                        }
                    }
                    .scale(scale)
                    .clickable {
                        // 点击按钮时滚动到屏幕中心
                        coroutineScope.launch {
                            val targetItem = scrollState.layoutInfo.visibleItemsInfo.firstOrNull { it.index == index }
                            targetItem?.let {
                                val offset = scrollState.layoutInfo.viewportSize.width / 2 - it.size.width / 2
                                scrollState.animateScrollToItem(index, offset)
                            }
                        }
                    }
                    .padding(vertical = 12.dp)
            ) {
                Image(
                    painter = rememberVectorPainter(image = item.icon),
                    contentDescription = item.label,
                    modifier = Modifier.size(28.dp),
                    colorFilter = iconColorFilter
                )
                Text(
                    text = item.label,
                    fontSize = textSize,
                    color = textColor,
                    modifier = Modifier.padding(top = 4.dp)
                )
            }
        }
    }
}

关键实现细节

  • 滚动状态跟踪:使用rememberLazyListState监听滚动位置,配合onGloballyPositioned获取每个按钮的屏幕坐标,计算与屏幕中心的距离。
  • 平滑过渡效果:通过lerp函数实现缩放比例、颜色、字体大小的线性插值,让按钮从边缘到中心的视觉变化更自然。
  • 点击交互:点击按钮时调用animateScrollToItem将目标按钮平滑滚动到屏幕中心,同时触发视觉变化。
  • 参数可调:可以通过修改maxTransitionDistance、缩放比例范围、颜色值等参数,适配不同的UI设计需求。

内容的提问来源于stack exchange,提问作者bovietvidai

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 08:50:31