欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Jetpack?Compose實(shí)現(xiàn)對(duì)角線(xiàn)滾動(dòng)效果

 更新時(shí)間:2023年02月09日 09:08:58   作者:ALuoBo  
這篇文章主要為大家詳細(xì)介紹了如何利用Jetpack?Compose實(shí)現(xiàn)一個(gè)簡(jiǎn)單的對(duì)角線(xiàn)滾動(dòng)效果,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下

緣起

不久前刷到 newki 前輩的文章,用自定義 viewGroup的方式實(shí)現(xiàn)了如圖效果: Android自定義ViewGroup嵌套與交互實(shí)戰(zhàn),幕布全屏滾動(dòng)效果

我當(dāng)時(shí)的反應(yīng): new bee ! new bee ! 這效果不錯(cuò)

初試

大佬用 Android View 出來(lái)了,那能否用 Google 新一代 UI Compose 來(lái)整一個(gè)呢?

正好手上有本 fun 神寫(xiě)得書(shū) 《Jetpack Compose 從入門(mén)到實(shí)戰(zhàn)》。這不就好辦了么!

正當(dāng)我 啪的一下,很快啊,吭! 開(kāi)始行動(dòng)之后,

拿著書(shū)翻到了手勢(shì)處理這一章節(jié),找到了這個(gè):

Scrollable,當(dāng)視圖組件的寬度或長(zhǎng)度超出屏幕邊界時(shí),我們希望能滑動(dòng)查看更多的內(nèi)容... 這不就完事了么,隨便寫(xiě)個(gè) composable 加一個(gè) Modifier.scrollable即可實(shí)現(xiàn)滑動(dòng)效果

但是,緊接著一句話(huà) “Orientation 僅有 Horizontal 與 Vertical 可供選擇,這說(shuō)明我們只能監(jiān)聽(tīng)水平或垂直方向的滾動(dòng)。”

那我們?nèi)绻o一個(gè)組合同時(shí)添加兩個(gè)方向的scrollable呢? 比如這樣:

private fun TwoOrientaionScrollView(modifier: Modifier = Modifier) {
    val horizontalScrollState = rememberScrollState()
    val verticalScrollState = rememberScrollState()
    Column(modifier = modifier
        .horizontalScroll(horizontalScrollState)
        .verticalScroll(verticalScrollState)
    ) {
        ...
    }
}

經(jīng)過(guò)測(cè)試,這種方法只能實(shí)現(xiàn)在兩個(gè)方向滑動(dòng)(垂直,水平)且每次手勢(shì)只有一個(gè)方向在滑動(dòng),我們要達(dá)到目標(biāo)效果,那必須是要支持斜著滑動(dòng)的。

大意了,沒(méi)有閃,被 Android 官方擺了一道。

探索

既然官方提供的開(kāi)箱即用的 API 無(wú)法滿(mǎn)足我們的要求,那我們就需要?jiǎng)邮秩ザㄖ埔粋€(gè)特殊的手勢(shì)處理規(guī)則去實(shí)現(xiàn)。

那萬(wàn)能的互聯(lián)網(wǎng)中有沒(méi)有大佬已經(jīng)用compose自定義手勢(shì)實(shí)現(xiàn)了呢?

可是找遍了 google 百度 chatGPT 也沒(méi)有找到什么有價(jià)值的文章值得去參考,倒是在Stack Overflow上一番翻箱倒柜之后,找到了一個(gè)線(xiàn)索————這種需求叫做 對(duì)角線(xiàn)滾動(dòng) / diagonal scroll ,并且外國(guó)同行已經(jīng)提了 issuegoogle 質(zhì)問(wèn)他們?yōu)楹螞](méi)有對(duì)角線(xiàn)滾動(dòng)。但截止到今天 2023/2/7 仍舊google沒(méi)有提供新的api也沒(méi)有關(guān)閉這個(gè)問(wèn)題。

插一句,不知道為何隔壁鴻蒙原本是支持自由方向滾動(dòng)的,鴻蒙稱(chēng)之為 Orientation.free , 但是在 api v9 時(shí)卻把這個(gè)方向給廢棄了

當(dāng)我愈發(fā)苦惱時(shí),我把 diagonal scroll鍵入交友網(wǎng)站github時(shí),一道閃光出現(xiàn)了

chihsuanwu/compose-free-scroll:提供可讓組合自由滾動(dòng)的 modifier

這是來(lái)自臺(tái)灣省的開(kāi)發(fā)者的開(kāi)源項(xiàng)目,作者也已經(jīng)發(fā)布到遠(yuǎn)程倉(cāng),可以讓大家一鍵導(dǎo)入并極速使用

測(cè)試效果:

完美!

學(xué)習(xí)

接下來(lái)一起學(xué)習(xí)一下大佬的代碼吧 ,核心代碼:

  • FreeScrollState.kt 用來(lái)表示滑動(dòng)狀態(tài),并提供了滑動(dòng)到指定位置的方法
  • FreeScroll.kt實(shí)現(xiàn)允許對(duì)角線(xiàn)滾動(dòng)的 modifier

FreeScrollState

內(nèi)部使用兩個(gè) ScrollState 分別控制水平和垂直滾動(dòng)的 state

class FreeScrollState(
    val horizontalScrollState: ScrollState,
    val verticalScrollState: ScrollState,
) { 
        ...
}
// 用rememberScrollState 分別創(chuàng)建兩個(gè)方向的 scrollState
@Composable
fun rememberFreeScrollState(initialX: Int = 0, initialY: Int = 0): FreeScrollState {
    val horizontalScrollState = rememberScrollState(initialX)
    val verticalScrollState = rememberScrollState(initialY)
    return FreeScrollState(
        horizontalScrollState = horizontalScrollState,
        verticalScrollState = verticalScrollState,
    )
}

值得一提的是,可以學(xué)習(xí)到作者使用協(xié)程來(lái)處理 scrollBy, scrollTo 以及 animateScrollBy animateScrollTo , 例如:

suspend fun scrollTo(
    x: Int,
    y: Int,
): Offset = coroutineScope {
    val xOffset = async {
        horizontalScrollState.scrollTo(x)
    }
    val yOffset = async {
        verticalScrollState.scrollTo(y)
    }
    // 使用 async.awawit() 來(lái)同時(shí)獲取兩個(gè)結(jié)果
    Offset(xOffset.await(), yOffset.await()) 
}

freeScroll

這是一個(gè)Modifier的拓展方法,在這個(gè)方法中,實(shí)現(xiàn)了自定義手勢(shì)邏輯。

fun Modifier.freeScroll(
    state: FreeScrollState,
    enabled: Boolean = true
): Modifier = composed {
    val velocityTracker = remember { VelocityTracker() }
    val flingSpec = rememberSplineBasedDecay<Float>()
    this.verticalScroll(state = state.verticalScrollState, enabled = false)
        .horizontalScroll(state = state.horizontalScrollState, enabled = false)
        .pointerInput(enabled) {
            if (!enabled) return@pointerInput
            coroutineScope {
                detectDragGestures(
                    onDragStart = { },
                    onDrag = { change, dragAmount ->
                        change.consume()
                        //1 拖拽中
                        onDrag(change, dragAmount, state, velocityTracker, this) 
                      
                    },
                    onDragEnd = {
                        //2 拖拽結(jié)束時(shí)
                        onEnd(velocityTracker, state, flingSpec, this)
                        
                    }
                )
            }
        }
}

可以看到,核心就是PointerInput中采用detectDraGestures 拖拽監(jiān)聽(tīng),并聲明了一個(gè)速度追蹤 器velocityTracker,和一個(gè)衰減動(dòng)畫(huà) rememberSplineBasedDecay 來(lái)使拖拽結(jié)束有一段慣性運(yùn)動(dòng)也就是fling

@OptIn(ExperimentalComposeUiApi::class)
private fun onDrag(
    change: PointerInputChange,
    dragAmount: Offset,
    state: FreeScrollState,
    velocityTracker: VelocityTracker,
    coroutineScope: CoroutineScope
) {
    // Add historical position to velocity tracker to increase accuracy
    val changeList = change.historical.map {
        it.uptimeMillis to it.position
    } + (change.uptimeMillis to change.position)

    changeList.forEach { (time, pos) ->
        val position = Offset(
            pos.x - state.horizontalScrollState.value,
            pos.y - state.verticalScrollState.value
        )
        velocityTracker.addPosition(time, position)
    }

    coroutineScope.launch {
        state.horizontalScrollState.scrollBy(-dragAmount.x)
        state.verticalScrollState.scrollBy(-dragAmount.y)
    }
}

onDrag抽出一個(gè)方法,方法中,我們將拖拽的過(guò)程中的手勢(shì)點(diǎn)位添加到速度追蹤 器velocityTracker中不斷精確我們得滾動(dòng)速度。并將位置點(diǎn)位更新到兩個(gè)scrollState

private fun onEnd(
    velocityTracker: VelocityTracker,
    state: FreeScrollState,
    flingSpec: DecayAnimationSpec<Float>,
    coroutineScope: CoroutineScope
) {
    val velocity = velocityTracker.calculateVelocity()
    velocityTracker.resetTracking()

    // Launch two animation separately to make sure they work simultaneously.
    coroutineScope.launch {
        state.horizontalScrollState.fling(-velocity.x, flingSpec)
    }
    coroutineScope.launch {
        state.verticalScrollState.fling(-velocity.y, flingSpec)
    }
}
private suspend fun ScrollState.fling(initialVelocity: Float, flingDecay: DecayAnimationSpec<Float>) {
    if (abs(initialVelocity) < 0.1f) return // Ignore flings with very low velocity

    scroll {
        var lastValue = 0f
        AnimationState(
            initialValue = 0f,
            initialVelocity = initialVelocity,
        ).animateDecay(flingDecay) {
            val delta = value - lastValue
            val consumed = scrollBy(delta)
            lastValue = value
            // avoid rounding errors and stop if anything is unconsumed
            if (abs(delta - consumed) > 0.5f) this.cancelAnimation()
        }
    }
}

在拖拽結(jié)束后,從velocityTracker拿出估算的速度值,用來(lái)給設(shè)置fling的衰減滾動(dòng)動(dòng)畫(huà)。 也就是說(shuō)實(shí)際上滾動(dòng)效果== 拖拽移動(dòng) + fling。

總結(jié)

JetPack Compose 是一個(gè)很強(qiáng)大很現(xiàn)代的 UI 工具,與使用自定義 View 來(lái)實(shí)現(xiàn)復(fù)雜手勢(shì)以及動(dòng)畫(huà)效果時(shí),代碼量大大減少,更加靈活。但是現(xiàn)在由于一方面 Android 原生開(kāi)發(fā)者不斷減少,以及官方文檔相對(duì)簡(jiǎn)陋,社區(qū)資料也比較匱乏,在出現(xiàn)不能覆蓋需求的問(wèn)題時(shí),比較耗費(fèi)時(shí)間去找到問(wèn)題的答案,好在官方目前更新速度還是非常的快,目前也已經(jīng)是達(dá)到可用甚至是易用的程度了,相信距離好用也不遙遠(yuǎn)。

到此這篇關(guān)于Jetpack Compose實(shí)現(xiàn)對(duì)角線(xiàn)滾動(dòng)效果的文章就介紹到這了,更多相關(guān)Jetpack Compose對(duì)角線(xiàn)滾動(dòng)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論