声明
本文来自博客园,作者:观心静 ,转载请注明原文链接:https://www.cnblogs.com/guanxinjing/p/19327053
本文版权归作者和博客园共有,欢迎转载,但必须给出原文链接,并保留此段声明,否则保留追究法律责任的权利。
效果图

代码
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import kotlin.math.abs
/**
* 刻度选择器
*
* @param count 刻度总数,默认为100
* @param defaultSelectedIndex 默认选中的刻度索引,默认为0
* @param lineWidth 每个刻度线的宽度,默认为1dp
* @param spacing 刻度线之间的间距,默认为5dp
* @param lineHeight 普通刻度线的高度,默认为20dp
* @param largeLineHeight 大刻度线的高度,默认为30dp
* @param selectedLineHeight 选中刻度线的高度,默认为40dp
* @param color 刻度线的颜色,默认为白色
* @param modifier 修饰符,用于设置组件的大小、边距等属性
* @param onSelectedIndex 当选中的刻度发生变化时的回调函数,返回当前选中的索引
*/
@Composable
fun ScalePicker(
count: Int = 100,
defaultSelectedIndex: Int = 0,
lineWidth: Dp = 1.dp,
spacing: Dp = 5.dp,
lineHeight : Dp = 20.dp,
largeLineHeight : Dp = 30.dp,
selectedLineHeight : Dp = 40.dp,
color: Color = Color.White,
modifier: Modifier = Modifier,
onSelectedIndex: (Int) -> Unit
) {
var listWidth by remember { mutableStateOf(0) } // 修改为宽度状态
val density = LocalDensity.current
val visibleItemsCount by remember(listWidth, lineWidth, spacing, density) {
derivedStateOf {
if (listWidth > 0) {
val itemWidthPx = with(density) {
//边距是左右的,所以这里需要乘以2,在计算单个item所占据的宽度
(lineWidth + (spacing * 2)).toPx()
}
val count = (listWidth / itemWidthPx).toInt().coerceAtLeast(1)
// 确保是奇数
if (count % 2 == 0) count + 1 else count
} else {
10
}
}
}
// 计算需要在列表两端添加的空白项数量,使选中项能够居中显示
val paddingCount by remember(visibleItemsCount) {
derivedStateOf {
(visibleItemsCount - 1) / 2
}
}
// 计算实际选中项在扩展后列表中的索引位置
val selectedIndex = defaultSelectedIndex + paddingCount + 1 // 调整索引计算
// 创建带有前后填充空项的数据列表
val dataList = remember(count, paddingCount) { // 注意依赖项也变了
// 起始索引:0 - paddingCount (扩展左侧)
// 结束索引:(count - 1) + paddingCount (扩展右侧)
// 总项数:count + 2 * paddingCount = count + (visibleItemsCount - 1)
val startIndex = -paddingCount
val endIndex = (count - 1) + paddingCount
(startIndex..endIndex).toList()
}
val listState = rememberLazyListState(
initialFirstVisibleItemIndex = (selectedIndex - visibleItemsCount / 2)
.coerceIn(0, dataList.size - visibleItemsCount)
)
// 计算当前居中选中的index
val centerIndex = remember {
derivedStateOf {
val visibleItems = listState.layoutInfo.visibleItemsInfo
if (visibleItems.isEmpty()) return@derivedStateOf 0
val centerPosition = listWidth / 2 // 修改为中心位置计算
val closestItem = visibleItems.minByOrNull { item ->
val itemCenter = item.offset + item.size / 2
abs(itemCenter - centerPosition)
}
onSelectedIndex((closestItem?.index ?: 0 ) - paddingCount)
closestItem?.index ?: 0
}
}
val maxHeight = listOf(lineHeight, largeLineHeight, selectedLineHeight).maxOrNull() ?: 40.dp
Box(
modifier = modifier
.height(maxHeight)
.fillMaxWidth()
) {
LazyRow(
state = listState,
flingBehavior = rememberSnapFlingBehavior(listState),
verticalAlignment = Alignment.Bottom, // 关键修改:改为底部对齐
modifier = Modifier
.fillMaxSize()
.onSizeChanged {
listWidth = it.width // 更新为获取宽度
}
) {
itemsIndexed(dataList) { index, item ->
//判断是否为选中项
val isSelected = index == centerIndex.value
val height = if (index < paddingCount || index >= dataList.size - paddingCount) {
0.dp
}else if (index == centerIndex.value) {
selectedLineHeight
} else if ((index - paddingCount) % 5 == 0) {
largeLineHeight
} else {
lineHeight
}
//计算选中项需要超出的额外高度
val extraOffset = if (isSelected) {
// 假设您希望选中项向下超出 10.dp
with(LocalDensity.current) { selectedLineHeight.toPx() / 6 }
} else {
0f // 非选中项不超出
}
Surface(
modifier = Modifier
.padding(horizontal = spacing)
.width(
when {
index == centerIndex.value -> 2.dp
(index - paddingCount) % 5 == 0 -> lineWidth // 每五格加粗
else -> lineWidth
}
)
.height(height)
.offset(y = if (isSelected) with(LocalDensity.current) { extraOffset.toDp() } else 0.dp)
.background(
color = if (isSelected) color else color.copy(0.2f),
shape = RectangleShape
)
.border(
width = when {
index == centerIndex.value -> 2.dp
(index - paddingCount) % 5 == 0 -> 1.dp // 每五格加粗
else -> 1.dp
},
color = if (isSelected) color else color.copy(0.2f),
shape = RectangleShape
)
){
}
}
}
}
}
end
本文来自博客园,作者:观心静 ,转载请注明原文链接:https://www.cnblogs.com/guanxinjing/p/19327053
本文版权归作者和博客园共有,欢迎转载,但必须给出原文链接,并保留此段声明,否则保留追究法律责任的权利。
浙公网安备 33010602011771号