Day20 - 更新推文及冲突

今天来做更新推文的部分。

更新的部分实作上并没有太困难的地方,主要是处理冲突比较麻烦。

更新推文

更新的部分我是使用HandlerRunnable来处理,传送的指令是"qrG"分别代表离开文章、阅读文章、跳到文章页尾,接着就是使用Day17的解析推文方法了。

private val updateHandler = Handler(Looper.getMainLooper())
private var isUpdating = false
private var isLoadingMore = false

private val updateRunnable = object : Runnable {
    override fun run() {
        if (isLoadingMore) return
        viewLifecycleOwner.lifecycleScope.launch(Dispatchers.IO)
        {
            isUpdating = true
            PttClient.getInstance().send("qrG")
            delay(200L)
            commentList.clear()
            parseComments(PttClient.getInstance().getScreen())
            isUpdating = false
        }

        updateHandler.postDelayed(this, 2000)
    }
}

Runnable的结尾有再次呼叫updateHandler.postDelayed(this, 2000),如此持续更新推文,目前更新间隔是先设2秒(2000毫秒)。

而初次注册updateRunnable的地方则是在Day17一进入PreviewFragment页面时解析完推文後。

viewLifecycleOwner.lifecycleScope.launch(Dispatchers.IO) {
    PttClient.getInstance().send("G")
    delay(100L)
    parseComments(PttClient.getInstance().getScreen())

    updateHandler.postDelayed(updateRunnable, 2000)
}

最後为了让更新後的RecyclerView持续在置底显示最新推文parseComments方法中有加入以下:

private suspend fun parseComments(screen: String) {
    // ...
    val currentPosition =
        (binding.recyclerView.layoutManager as LinearLayoutManager)
            .findLastVisibleItemPosition()
    if (isUpdating && currentPosition == commentList.size - 1) {
        withContext(Dispatchers.Main) {
            binding.recyclerView.scrollToPosition(commentList.size - 1)
        }
    }
}

再来就是要处理自动更新与其他操作的冲突了。

冲突一:滑动画面与推文更新

这项冲突主要是在当我滑动手机画面来看上方旧推文时,我不希望因为自动更新又把我拉回置底画面。
主要的解法就是上方parseComments程序码区块的currentPosition == commentList.size - 1条件。

currentPosition的获取是调用LinearLayoutManagerfindLastVisibleItemPosition,如果当前RecyclerViewposition不在最後一项我就会当作滑动中而停止置底画面。

冲突二:读取更多推文与推文更新

Day19的内容中可以看到,在读取更多推文时我是传送了^B指令,而更新推文需要传送qrG,这两个指令要是一直交错传肯定是会有问题的,因此在读取更多推文时我会将updateRunnableupdateHandler中移除,并且设置isLoadingMore的Flag来避免移除不及。

修改後的moreCommentCallback

adapter.moreCommentCallback = {

    if (!isLoadingMore) {
        val animator = ObjectAnimator.ofFloat(
            binding.resumeUpdate,
            "translationY",
            200f.dpToPx(requireContext()).toFloat(),
            0f
        )
        animator.interpolator = AccelerateDecelerateInterpolator()
        animator.duration = 300
        animator.start()
        isLoadingMore = true
    }

    if (hasMore && !isUpdating) {
        updateHandler.removeCallbacks(updateRunnable)
        viewLifecycleOwner.lifecycleScope.launch(Dispatchers.IO) {
            PttClient.getInstance().send(Char(2).toString())
            delay(100L)
            parseComments(PttClient.getInstance().getScreen())
        }
    }
}

isUpdatingtrue我也不会进行读取更多推文的程序。此外可以看到我有用ObjectAnimator,这个是我在画面中下加入的一个自动更新按钮(待会画面中能看到,就不放layout了)。按钮的点击事件就是用来恢复自动更新状态的。

binding.resumeUpdate.setOnClickListener {
    isLoadingMore = false
    isUpdating = true
    adapter.setData(listOf())
    updateHandler.post(updateRunnable)
    val animator = ObjectAnimator.ofFloat(
        binding.resumeUpdate,
        "translationY",
        0f,
        200f.dpToPx(requireContext()).toFloat()
    )
    animator.interpolator = AccelerateDecelerateInterpolator()
    animator.duration = 300
    animator.start()
}

调用updateHandler.post(updateRunnable)来重新更新推文画面,同时用adapter.setData(listOf())将目前的内容清掉,避免恢复後没有自动置底的状态。

冲突三:离开PreviewFragment

在离开PreviewFragment的过程中需把updateRunnable取消,否则也会有冲突导致画面卡住或crash。

onBackPressedDispatcher

requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner,
    object : OnBackPressedCallback(true) {
        override fun handleOnBackPressed() {
            updateHandler.removeCallbacks(updateRunnable)
            // ...
        }
    })

onDestroyView

override fun onDestroyView() {
    updateHandler.removeCallbacks(updateRunnable)
    super.onDestroyView()
    // ...
}

目前画面

https://imgur.com/vExOmjH.gif


<<:  Day 21. Zabbix 自动化通知介绍

>>:  Kotlin Android 第30天,从 0 到 ML - 总结

Day28 | 获取安装的extension进行操作

大家好,我是韦恩,今天是第二十八天,让我们会练习获取extension的api,为专案的重点功能做准...

其他名词解释 | ML#Day23

承上一篇,模型训练完成之後的那些Vertex列出评估函数,除了R^2也一并介绍剩下的名词。 MAE ...

gMSA 设定无密码的工作排程 (上 )

Group Managed ServiceAccounts (gMSA) 用途 AD网域的环境下,要...

[Day 16] Reverse 小疲累

终於到星期五啦 明天就是周末六日了 今天也是我课最多的一天 从早八到五点连八堂 我遇到做图障碍的挫折...

[Python 爬虫这样学,一定是大拇指拉!] DAY29 - 实战演练:自制进度条 Progress Bar

自制 Progress Bar 继前一篇,来补充自制一个小功能,让这个程序会好用一些些。 爬虫在爬的...