簡體   English   中英

如何在 Jetpack Compose 中以可移植的方式實現計時器?

[英]How can I implement a timer in a portable way in Jetpack Compose?

我想編寫一些應用程序,其中我希望某些事情按計划發生。

每隔幾分鍾輪詢一次 URL 更新似乎是一個相當常見的用例。 不過,在這種特殊情況下,我只是想實現一個時鍾。

這有效:

@Composable
fun App() {
    var ticks by remember { mutableStateOf(0) }

    // Not 100% happy about this unused variable either
    val timer = remember {
        Timer().apply {
            val task = object : TimerTask() {
                override fun run() {
                    ticks++
                }
            }
            scheduleAtFixedRate(task, 1000L, 1000L)
        }
    }

    MaterialTheme {
        Text(
            // A real application would format this number properly,
            // but that's a different question
            text = "$ticks"
        )
    }
}

但我必須導入java.util.Timer ,所以它不會是可移植的。

Jetpack Compose 可以做動畫,所以它肯定在某處有自己的計時器,這意味着也應該有一些可移植的方法來做到這一點,但我似乎無法弄清楚。

有沒有一種跨平台的方法可以為此目的獲取計時器?

在 Compose 中,您可以使用LaunchedEffect - 它是在協程作用域上運行的副作用,因此您可以在內部使用delay ,如下所示:

var ticks by remember { mutableStateOf(0) }
LaunchedEffect(Unit) {
    while(true) {
        delay(1.seconds)
        ticks++
    }
}

我只是想分享一個我嘗試過的替代方案,以防其他人想到它並遇到我遇到的相同問題。 這是天真的實現:

@Composable
fun Countdown(targetTime: Long, content: @Composable (remainingTime: Long) -> Unit) {
    var remainingTime by remember(targetTime) {
        mutableStateOf(targetTime - System.currentTimeMillis())
    }

    content.invoke(remainingTime)

    LaunchedEffect(remainingTime) {
        delay(1_000L)
        remainingTime = targetTime - System.currentTimeMillis()
    }
}

假設您想要精確到一秒,此代碼段將導致LaunchedEffect在更新remainingTime后更新一秒,更新與當前時間相關的remainingTime (以毫秒為單位)。 這基本上創建了一個循環。 將此邏輯包裝在@Composable中很好,因為它可以防止在大型組件樹中嵌入LaunchedEffect時可能導致的過度重新組合。

這行得通,但有一個問題:您最終會注意到您的計時器正在跳秒。 發生這種情況是因為在將新值分配給remainingTime變量和重新執行LaunchedEffect之間會有一些額外的延遲,這實際上意味着更新之間有超過一秒的時間。

這是上述內容的改進實現:

@Composable
fun Countdown(targetTime: Long, content: @Composable (remainingTime: Long) -> Unit) {
    var remainingTime by remember(targetTime) {
        mutableStateOf(targetTime - System.currentTimeMillis())
    }

    content.invoke(remainingTime)

    LaunchedEffect(remainingTime) {
        val diff = remainingTime - (targetTime - System.currentTimeMillis())
        delay(1_000L - diff)
        remainingTime = targetTime - System.currentTimeMillis()
    }
}

我們只需從預期的延遲時間中減去LaunchedEffect重新執行所花費的時間。 這將避免您的計時器跳過秒數。

額外的延遲不應該成為已接受答案中實施的問題。 我注意到這種方法的唯一優點是,一旦我們離開屏幕,循環就會停止運行。 在我的測試中,當導航到另一個 Activity 時,具有true條件的 while 循環繼續運行。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM