Jetpack Compose : 超簡單實現(xiàn)滾輪控件(WheelPicker)

前言

滾輪應(yīng)該是我們很經(jīng)常用到一個控件了样勃,比如日期選擇吠勘,時間選擇,地區(qū)選擇等都習(xí)慣用滾輪來展示峡眶。

滾輪控件的識點

05581a530c5545f58b14b26e5b91c8e2~tplv-k3u1fbpfcp-zoom-in-crop-mark_1512_0_0_0.jpg

上圖是由三個滾輪控件組成的日期選擇器剧防,以此我們分析所需要的知識點:

  1. 手勢(滑動,慣性滾動)
  2. 內(nèi)容循環(huán)滾動
  3. 實現(xiàn)滾輪樣式

手勢(滑動幌陕,慣性滾動)

我首先想到的是 Compose滾動修飾符 效果如下:

gestures-simplescroll.gif

接下來就是解決慣性問題了诵姜,我很自然想到列表組件 LazyColumn 就準(zhǔn)備去看它的源碼是怎么實現(xiàn)時。

aee36980ee524ef6b1781134786f0959~tplv-k3u1fbpfcp-zoom-in-crop-mark_1512_0_0_0.jpg

好吧搏熄,就決定是你了 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)滾輪效果呢暇赤,這里放一張我畫的草圖:

0837484cadf34fdeaebda7b17dd82d3c~tplv-k3u1fbpfcp-zoom-in-crop-mark_1512_0_0_0.jpg

由上面的草圖我們發(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)的動力。
謝謝~~

源代碼地址

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末匪补,一起剝皮案震驚了整個濱河市伞辛,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌夯缺,老刑警劉巖蚤氏,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異踊兜,居然都是意外死亡竿滨,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進(jìn)店門捏境,熙熙樓的掌柜王于貴愁眉苦臉地迎上來于游,“玉大人,你說我怎么就攤上這事典蝌∈锷埃” “怎么了?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵骏掀,是天一觀的道長鸠澈。 經(jīng)常有香客問我,道長截驮,這世上最難降的妖魔是什么笑陈? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮葵袭,結(jié)果婚禮上涵妥,老公的妹妹穿的比我還像新娘。我一直安慰自己坡锡,他們只是感情好奠伪,可當(dāng)我...
    茶點故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布晚岭。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪炊甲。 梳的紋絲不亂的頭發(fā)上藕夫,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天克伊,我揣著相機(jī)與錄音谆膳,去河邊找鬼皮官。 笑死,一個胖子當(dāng)著我的面吹牛实辑,可吹牛的內(nèi)容都是我干的捺氢。 我是一名探鬼主播,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼剪撬,長吁一口氣:“原來是場噩夢啊……” “哼摄乒!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起婿奔,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤缺狠,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后萍摊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體挤茄,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年冰木,在試婚紗的時候發(fā)現(xiàn)自己被綠了穷劈。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡踊沸,死狀恐怖歇终,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情逼龟,我是刑警寧澤评凝,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站腺律,受9級特大地震影響奕短,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜匀钧,卻給世界環(huán)境...
    茶點故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一翎碑、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧之斯,春花似錦日杈、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至瘫絮,卻和暖如春啰劲,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背檀何。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人频鉴。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓栓辜,卻偏偏與公主長得像,于是被迫代替她去往敵國和親垛孔。 傳聞我的和親對象是個殘疾皇子藕甩,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,629評論 2 354

推薦閱讀更多精彩內(nèi)容