阅读 253

paging3 如何删除列表项

paging3 是 Google 推出的用户加载分页数据的库。但令人意外的是——他没有提供删除相关的接口。在某些场合里,我们就是需要删除数据。我们可以怎么办呢?

本文主要提供了一种我认为的比较合理、高效的方式用于删除 paging3 列表数据项的方法,不涉及 paging3 的其他用法。

搭个简单的测试 APP

PagingSource

data class User(val uid: Long, val name: String) class NamePagingSource(     private val max: Long ) : PagingSource<Long, User>() {     override fun getRefreshKey(state: PagingState<Long, User>): Long {         return 0     }     override suspend fun load(params: LoadParams<Long>): LoadResult<Long, User> {         val begin = params.key ?: 0         val end = (begin + params.loadSize).coerceAtMost(max)         val data = (begin until end).map {             User(it, "Name@$it")         }         val prevCursor = if (begin == 0L) null else begin         val nextCursor = if (end < max) end else null         return LoadResult.Page(data, prevCursor, nextCursor)     } } 复制代码

ViewModel

class UserViewModel : ViewModel() {     companion object {         private const val LIMIT = 100L         private const val PAGE_SIZE = 10     }     private var dataFlow: Flow<PagingData<User>>? = null     fun getDataFlow(): Flow<PagingData<User>> {         return dataFlow ?: Pager(             config = PagingConfig(PAGE_SIZE),             initialKey = 0,             pagingSourceFactory = {                 NamePagingSource(LIMIT)             }         ).flow.cachedIn(viewModelScope).also {             dataFlow = it         }     } } 复制代码

Adapter

class UserListAdapter(     private val deletionCallback: (Int, User) -> Unit ) : PagingDataAdapter<User, UserListAdapter.NameHolder>(COMPARATOR) {     override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NameHolder {         val inflater = LayoutInflater.from(parent.context)         val binding = UserListItemBinding.inflate(inflater, parent, false)         return NameHolder(binding)     }     override fun onBindViewHolder(holder: NameHolder, position: Int) {         holder.bind(position, getItem(position)!!)     }     inner class NameHolder(         private val binding: UserListItemBinding     ) : RecyclerView.ViewHolder(binding.root), View.OnLongClickListener {         private var index = -1         private var user: User? = null         init {             binding.root.setOnLongClickListener(this)         }         fun bind(index: Int, user: User) {             this.index = index             this.user = user             binding.name.text = user.name         }         override fun onLongClick(v: View): Boolean {             val user = user ?: return false             deletionCallback(index, user)             return true         }     }     companion object {         private val COMPARATOR = object : DiffUtil.ItemCallback<User>() {             override fun areItemsTheSame(oldItem: User, newItem: User): Boolean {                 return oldItem.uid == newItem.uid             }             override fun areContentsTheSame(oldItem: User, newItem: User): Boolean {                 return oldItem == newItem             }         }     } } 复制代码

layout 很简单,就是一个 TextView

<?xml version="1.0" encoding="utf-8"?> <TextView     xmlns:android="http://schemas.android.com/apk/res/android"     xmlns:tools="http://schemas.android.com/tools"     android:id="@+id/name"     android:layout_width="match_parent"     android:layout_height="wrap_content"     android:textColor="#222222"     android:textSize="24sp"     android:paddingTop="10dp"     android:paddingBottom="10dp"     android:paddingStart="16dp"     android:paddingEnd="16dp"     tools:text="ffff"     /> 复制代码

RecyclerView 的初始化

class MainActivity : AppCompatActivity() {     private lateinit var binding: ActivityMainBinding     private lateinit var viewModel: UserViewModel     override fun onCreate(savedInstanceState: Bundle?) {         super.onCreate(savedInstanceState)         binding = ActivityMainBinding.inflate(layoutInflater)         setContentView(binding.root)         viewModel = ViewModelProvider(this).get(UserViewModel::class.java)         initUserList()     }     private fun initUserList() {         binding.userList.layoutManager = LinearLayoutManager(this)         val adapter = UserListAdapter { position, user ->             // TODO delete it         }         binding.userList.adapter = adapter         lifecycleScope.launch {             viewModel.getDataFlow().collectLatest {                 adapter.submitData(it)             }         }     } } 复制代码

这样,一个基于 paging3 的简单应用就搭建完成了:

如何删除列表项?

为了删除列表项,首先我们需要拿到当前列表对应的数据。有两种方法可以实现:

  1. adapter.snapshot() 返回一个不可修改的列表。我们拷贝一份后并修改后,构造一个新的 PagingData 然后提交(submitData)给 adapter

  2. 缓存一个 PagingData 的实例,经由它得到删除后的列表数据

这里我选择的是方法二,这样我们可以不用太关心 PagingData 的实现细节。删除列表项的方法如下:

class MainActivity : AppCompatActivity() {     private lateinit var adapter: UserListAdapter     private lateinit var pagingData: PagingData<User>     // ...     private fun initUserList() {         binding.userList.layoutManager = LinearLayoutManager(this)         binding.userList.adapter = UserListAdapter(::deleteItem).also {             adapter = it         }         lifecycleScope.launch {             viewModel.getDataFlow().collectLatest {                 pagingData = it                 adapter.submitData(it)             }         }     }     private fun deleteItem(position: Int, user: User) {         lifecycleScope.launch {             pagingData = pagingData.filter { it !== user }             adapter.submitData(pagingData)         }     } } 复制代码

效果如下:

性能怎么样?

可能有些同学会觉得,这里直接 filter 效率上太差了但其实不然。filter 的耗时为 Θ(n),ArrayList 的删除耗时为 Ο(n)。最差情况下,两者是相同的;平均而言,filter 会多一倍,但在数量级上是一致的;加之列表数据的项数通常不会非常大,filter 是可接受的。

正确性如何?

正确性包括以下两点:

  1. 我们删除了某个列表项后,如果重新刷新列表数据,这个被删除的项不应该再出现;

  2. 我们能够确确实实删除目标。

我们所使用的删除方式的正确是,分别依赖于以下两个事实:

  1. 客户端展示的列表数据实际上是后端数据的一份拷贝。在需要删除本地这个拷贝的情况下,肯定是由于用户执行了某些操作,导致该列表项无效了。再次从后端拉数据的话,这个列表项也就不会再出现了;

  2. PagingData 包含了列表的所有数据(而不是某一页数据)。只有这样,filter 才能起到删除的效果。


作者:Jekton
链接:https://juejin.cn/post/7048520991193956389


文章分类
代码人生
文章标签
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐