更新的部分实作上并没有太困难的地方,主要是处理冲突比较麻烦。
更新的部分我是使用Handler和Runnable来处理,传送的指令是"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的获取是调用LinearLayoutManager的findLastVisibleItemPosition,如果当前RecyclerView的position不在最後一项我就会当作滑动中而停止置底画面。
在Day19的内容中可以看到,在读取更多推文时我是传送了^B
指令,而更新推文需要传送qrG
,这两个指令要是一直交错传肯定是会有问题的,因此在读取更多推文时我会将updateRunnable从updateHandler中移除,并且设置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())
}
}
}
若isUpdating为true
我也不会进行读取更多推文的程序。此外可以看到我有用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的过程中需把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()
// ...
}
>>: Kotlin Android 第30天,从 0 到 ML - 总结
大家好,我是韦恩,今天是第二十八天,让我们会练习获取extension的api,为专案的重点功能做准...
承上一篇,模型训练完成之後的那些Vertex列出评估函数,除了R^2也一并介绍剩下的名词。 MAE ...
Group Managed ServiceAccounts (gMSA) 用途 AD网域的环境下,要...
终於到星期五啦 明天就是周末六日了 今天也是我课最多的一天 从早八到五点连八堂 我遇到做图障碍的挫折...
自制 Progress Bar 继前一篇,来补充自制一个小功能,让这个程序会好用一些些。 爬虫在爬的...