前言
好久没有写博客了,现在来写一篇关于compose的实现数字滚动选择器吧,这是因为我公司项目里面的一个组件有用到类似的功能,在刚开始的时候我也去网上查找借鉴了很多的大佬写的实现类似的功能,后面我根据大佬写的去研究了一下重新改了一下比较适合我的一个写法,也认为会比较通用的一个方法,话不多说先上成品给大家看看。
借鉴的博客
Jetpack Compose : 超简单实现滚轮控件(WheelPicker)
Jetpack Compose 实现的时间选择组件
正文来啦
首先在做这个滚动的列表的时候,想到的是Column,虽然要使这个滑动起来还需要加.verticalScroll(rememberScrollState())这个属性就能够使其滑动起来,不过后来我还是选择了compose中的列表组件LazyColumn;这样我们就自然而然的写好了其大概的框架。
val listState = rememberLazyListState()
LazyColumn(
modifier = Modifier,
state = listState
) {}
然后为了让其能够复用,在里面的Item直接以数据的大小来生成
items(size) { index ->
Box(
modifier = Modifier
.fillMaxWidth()
.height(itemHeight),
contentAlignment = Alignment.Center,
) {
//这里将要展示的数据给回调到外面在外面进行自定义展示
content(data[index])
}
}
同时我们去查看LazyColunm的源码可以发现 :flingBehavior接口来指定投掷行为。当拖动以scrollable中的velocity结束时,将调用performFling以通过ScrollScope.scrollBy执行fling动画和更新状态。因此我们可以将LazyColumn更改一下:
LazyColumn(
modifier = Modifier,
state = listState,
flingBehavior = rememberSnapFlingBehavior(listState),
)
如果要设置初始值的话可以根据rememberLazyListState里面的属性initialFirstVisibleItemIndex来进行设置因此完整的LazyColum的配置如下:
val listState = rememberLazyListState(
initialFirstVisibleItemIndex = selectIndex
)
LazyColumn(
modifier = Modifier,
state = listState,
flingBehavior = rememberSnapFlingBehavior(listState),
){}
整个组件的源代码为:
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun <T> ListNumberPicker(
data: List<T>,
selectIndex: Int,
visibleCount: Int,
modifier: Modifier = Modifier,
onSelect: (index: Int, item: T) -> Unit,
content: @Composable (item: T) -> Unit,
) {
BoxWithConstraints(modifier = modifier, propagateMinConstraints = true) {
val pickerHeight = maxHeight
val size = data.size
val itemHeight = pickerHeight / visibleCount
val listState = rememberLazyListState(
initialFirstVisibleItemIndex = selectIndex
)
val firstVisibleItemIndex by remember { derivedStateOf { listState.firstVisibleItemIndex } }
LazyColumn(
modifier = Modifier,
state = listState,
flingBehavior = rememberSnapFlingBehavior(listState),
) {
//占据相应的高度比如显示5个那么中间那个是选中的其他的就是非选中,但也要占据一定的空间。
for (i in 1..visibleCount / 2) {
item {
Surface(modifier = Modifier.height(itemHeight)) {}
}
}
items(size) { index ->
//预防滑动的时候出现数组越界
if (firstVisibleItemIndex >= size) {
onSelect(size - 1, data[size - 1])
} else {
onSelect(firstVisibleItemIndex, data[firstVisibleItemIndex])
}
Box(
modifier = Modifier
.fillMaxWidth()
.height(itemHeight),
contentAlignment = Alignment.Center,
) {
content(data[index])
}
}
for (i in 1..visibleCount / 2) {
item {
Surface(modifier = Modifier.height(itemHeight)) {}
}
}
}
}
}
使用过程
不多说其他的,现在就是直接用就行了
Row(
Modifier
.wrapContentHeight()
.fillMaxWidth(),
Arrangement.Center,
Alignment.CenterVertically
) {
//年
var selectYear by remember {
mutableIntStateOf(selectDate.getYearr())
}
val yearData = LinkedList<Int>().apply {
for (i in 1970..nowDate.getYearr()) {
add(i)
}
}
ListNumberPicker(
data = yearData,
selectIndex = yearData.indexOf(selectYear),
visibleCount = 3,
modifier = Modifier
.height(150.dp)
.width(89.dp)
.background(
color = colorResource(id = R.color.home_background).copy(alpha = 0.3f),
shape = RoundedCornerShape(10.dp)
),
onSelect = { _, item ->
selectYear = item
yearCall(selectYear)
}
) {
Column(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
//判断是否是选中的状态,选中要展示的样式和非选中的样式
if (it == selectYear) {
Divider(
modifier = Modifier
.width(89.dp)
.padding(horizontal = 17.5.dp),
color = colorResource(id = R.color.color_btn_line).copy(alpha = 0.5f)
)
Text(
text = "$it", color = Color.White,
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center,
fontSize = 29.sp
)
Divider(
modifier = Modifier
.width(89.dp)
.padding(horizontal = 17.5.dp),
color = colorResource(id = R.color.color_btn_line).copy(alpha = 0.5f)
)
} else {
Text(
text = "$it",
color = Color.White.copy(alpha = 0.3f),
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center,
fontSize = 18.sp
)
}
}
}
结语
写到现在感觉自己写的很粗糙,但还有很多不足之处,这篇文章也是根据前人的基础来进行完成的。再次感谢分享出自己经验的大佬。如果文章阅读当中有不同的想法欢迎提出来,让我们一起进步。