前言
滾輪應(yīng)該是我們很經(jīng)常用到一個控件了样勃,比如日期選擇吠勘,時間選擇,地區(qū)選擇等都習(xí)慣用滾輪來展示峡眶。
滾輪控件的識點
上圖是由三個滾輪控件組成的日期選擇器剧防,以此我們分析所需要的知識點:
- 手勢(滑動,慣性滾動)
- 內(nèi)容循環(huán)滾動
- 實現(xiàn)滾輪樣式
手勢(滑動幌陕,慣性滾動)
我首先想到的是 Compose 的 滾動修飾符 效果如下:
接下來就是解決慣性問題了诵姜,我很自然想到列表組件 LazyColumn
就準(zhǔn)備去看它的源碼是怎么實現(xiàn)時。
好吧搏熄,就決定是你了 LazyColumn
內(nèi)容循環(huán)滾動
既然決定使用 LazyColumn
那內(nèi)容循環(huán)也變的簡單,這里直接貼代碼:
val size = data.size
val count = Int.MAX_VALUE
val startIndex = count / 2
val listState = rememberPagerState(initialPage = startIndex - startIndex % size)
LazyColumn(
modifier = Modifier,
state = listState,
flingBehavior = rememberSnapFlingBehavior(listState),
) {
items(count) { index ->
......
}
}
實現(xiàn)滾輪樣式
如何通過調(diào)整 LazyColumn
的 Item 項樣式實現(xiàn)滾輪效果呢暇赤,這里放一張我畫的草圖:
由上面的草圖我們發(fā)現(xiàn)關(guān)鍵點在 Item 項的旋轉(zhuǎn)角度和平移距離心例。
原理竟然比草圖還簡單。
既然如此我們先拿到 Item 項的滑動時的偏移距離鞋囊,直接貼代碼:
val listState = rememberPagerState(initialPage = startIndex - startIndex % size)
val layoutInfo by remember { derivedStateOf { listState.layoutInfo } }
LazyColumn(
modifier = Modifier,
state = listState,
flingBehavior = rememberSnapFlingBehavior(listState),
) {
items(count) { index ->
val item = layoutInfo.visibleItemsInfo.find { it.index == index }
if (item != null) {
val itemCenterY = item.offset + item.size / 2 //獲取Item項的偏移距離
}
}
}
通過偏離距離計算調(diào)整系數(shù)止后。
/**
* pickerCenterLinePx 滾輪控件中線
* itemCenterY < pickerCenterLinePx 說明Item項在上半部,逐漸縮小,反之則逐漸放大
**/
currentsAdjust = 0.75f + 0.25f * if (itemCenterY < pickerCenterLinePx) {
itemCenterY / pickerCenterLinePx
} else {
1 - (itemCenterY - pickerCenterLinePx) / pickerCenterLinePx
}
最后按照慣例貼上完整代碼译株。
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun <T> WheelPicker(
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 density = LocalDensity.current
val size = data.size
val count = size * 10000
val pickerHeight = maxHeight
val pickerHeightPx = density.run { pickerHeight.toPx() }
val pickerCenterLinePx = pickerHeightPx / 2
val itemHeight = pickerHeight / visibleCount
val itemHeightPx = pickerHeightPx / visibleCount
val startIndex = count / 2
val listState = rememberLazyListState(
initialFirstVisibleItemIndex = startIndex - startIndex.floorMod(size) + selectIndex,
initialFirstVisibleItemScrollOffset = ((itemHeightPx - pickerHeightPx) / 2).roundToInt(),
)
val layoutInfo by remember { derivedStateOf { listState.layoutInfo } }
LazyColumn(
modifier = Modifier,
state = listState,
flingBehavior = rememberSnapFlingBehavior(listState),
) {
items(count) { index ->
val currIndex = (index - startIndex).floorMod(size)
val item = layoutInfo.visibleItemsInfo.find { it.index == index }
var currentsAdjust = 1f
if (item != null) {
val itemCenterY = item.offset + item.size / 2
currentsAdjust = 0.75f + 0.25f * if (itemCenterY < pickerCenterLinePx) {
itemCenterY / pickerCenterLinePx
} else {
1 - (itemCenterY - pickerCenterLinePx) / pickerCenterLinePx
}
if (!listState.isScrollInProgress
&& item.offset < pickerCenterLinePx
&& item.offset + item.size > pickerCenterLinePx
) {
onSelect(currIndex, data[currIndex])
}
}
Box(
modifier = Modifier
.fillMaxWidth()
.height(itemHeight)
.graphicsLayer {
alpha = currentsAdjust
scaleX = currentsAdjust
scaleY = currentsAdjust
rotationX = (1 + currentsAdjust) * 180
},
contentAlignment = Alignment.Center,
) {
content(data[currIndex])
}
}
}
}
}
private fun Int.floorMod(other: Int): Int = when (other) {
0 -> this
else -> this - floorDiv(other) * other
}
Thanks
以上就是本篇文章的全部內(nèi)容瓜喇,如有問題歡迎指出,我們一起進(jìn)步歉糜。
如果覺得本篇文章對您有幫助的話請點個贊讓更多人看到吧乘寒,您的鼓勵是我前進(jìn)的動力。
謝謝~~