【问题标题】:RecyclerView with multiple view types and paging library with boundary callback具有多种视图类型的 RecyclerView 和具有边界回调的分页库
【发布时间】:2020-04-20 14:23:33
【问题描述】:

我有一个项目,我想向用户显示多个项目(图像、文本、视频等)。我知道 RecyclerView 的多种视图类型是如何工作的。 在我的项目中,我已经包含了大部分推荐的Android Architecture Components 比如RoomPagingLiveData等。

为了总结项目设计,我关注了这个codelab,这对我帮助很大。 Room 库 + Paging 库和 BoundaryCallback 提供了一种实现离线缓存的好方法。将数据库视为事实来源,并让Paging 库在Recyclerview 需要时通过DataSource.Factory 请求更多数据,在我看来这是一个非常好的方法。

但是project 的缺点是它们显示了整个架构组件(RoomPagingBoundaryCallback)仅适用于一种项目类型。 但我有多种视图类型,我无法处理。

在下面,我将向您展示我项目中的代码 sn-ps 以说明我卡在哪里。

让我们从模型开始。假设,我们有两种项目类型:图像和文本。

sealed class Item {

    @JsonClass(generateAdapter = true)
    @Entity(tableName = "image_table")
    data class Image(
        @PrimaryKey
        @Json(name = "id")
        val imageId: Long,
        val image: String
    ): Item()


    @JsonClass(generateAdapter = true)
    @Entity(tableName = "text_table")
    data class Text(
        @PrimaryKey
        @Json(name = "id")
        val textId: Long,
        val text:String
    ):Item()
}

如您所见,我的模型类正在扩展 Item 密封类。所以,我可以将ImageText 类视为Items。 适配器类看起来像这样:

private val ITEM_VIEW_TYPE_IMAGE = 0
private val ITEM_VIEW_TYPE_TEXT = 1

class ItemAdapter():
        PagedListAdapter<Item, RecyclerView.ViewHolder>(ItemDiffCallback()) {

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when (holder) {
            is TextItemViewHolder -> {
                val textItem = getItem(position) as Item.Text
                holder.bind(textItem)
            }
            is ImageItemViewHolder -> {
                val imageItem = getItem(position) as Item.Image
                holder.bind(imageItem)
            }
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return when (viewType) {
            ITEM_VIEW_TYPE_IMAGE -> ImageItemViewHolder.from(parent)
            ITEM_VIEW_TYPE_TEXT -> TextItemViewHolder.from(parent)
            else -> throw ClassCastException("Unknown viewType ${viewType}")
        }
    }

    override fun getItemViewType(position: Int): Int {
        return when (getItem(position)) {
            is Item.Image -> ITEM_VIEW_TYPE_IMAGE
            is Item.Text -> ITEM_VIEW_TYPE_TEXT
        }
    }

    class TextItemViewHolder private constructor(val binding: ListItemTextBinding): RecyclerView.ViewHolder(binding.root) {

        fun bind(item: Text) {
            binding.text = item
            binding.executePendingBindings()
        }
        companion object {
            fun from(parent: ViewGroup): TextItemViewHolder {
                val layoutInflater = LayoutInflater.from(parent.context)
                val binding = ListItemTextBinding.inflate(layoutInflater, parent, false)
                return TextItemViewHolder(binding)
            }
        }
    }


    class ImageItemViewHolder private constructor(val binding: ListItemImageBinding) : RecyclerView.ViewHolder(binding.root){

        fun bind(item: Image) {
            binding.image = item
            binding.executePendingBindings()
        }

        companion object {
            fun from(parent: ViewGroup): ImageItemViewHolder {
                val layoutInflater = LayoutInflater.from(parent.context)
                val binding = ListItemImageBinding.inflate(layoutInflater, parent, false)
                return ImageItemViewHolder(binding)
            }
        }
    }
}

class ItemDiffCallback : DiffUtil.ItemCallback<Item>() {

    // HERE, I have the problem that I can not access the id attribute
    override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean {
        return oldItem.id == newItem.id
    }

    override fun areContentsTheSame(oldItem: Item, newItem: Item): Boolean {
        return oldItem == newItem
    }
}

}

在这里,我面临的问题是ItemDiffCallback 类。在areItemsTheSame 方法中,我无法使用超类Item 访问id 属性。我可以在这里做什么?

现在,我要去存储库类。您可能知道,存储库类首先负责从数据库中检索数据,因为在我的项目中,数据库是事实的来源。如果没有数据或需要更多数据,则分页库使用 BoundaryCallback 类从服务请求更多数据并将它们存储到数据库中。这是我的 Items 存储库类:

class ItemRepository(private val database: MyLimDatabase, private val service: ApiService) {

    fun getItems(): LiveData<PagedList<Item>> {
        val dataSourceFactory = ???????????? FROM WHICH TABLE I HAVE TO CHOOSE ???????? database.textDao.getTexts() or database.imageDao.getImages()
        val config = PagedList.Config.Builder()
            .setPageSize(30)            // defines the number of items loaded at once from the DataSource
            .setInitialLoadSizeHint(50) // defines how many items to load when first load occurs
            .setPrefetchDistance(10)    // defines how far from the edge of loaded content an access must be to trigger further loading
            .build()

        val itemBoundaryCallback = ItemBoundaryCallback(service, database)

        return LivePagedListBuilder(dataSourceFactory, config)
            .setBoundaryCallback(itemBoundaryCallback)
            .build()
    }
}

在这种情况下,我的问题是我不知道如何初始化 dataSourceFactory 变量,因为我有两个 DAO 类。它们是:

@Dao
interface ImageDao{
    @Query("SELECT * from image_table")
    fun getImages(): DataSource.Factory<Int, Image>

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insertAll(images: List<Image>)
}

@Dao
interface TextDao{
    @Query("SELECT * from text_table")
    fun getTexts(): DataSource.Factory<Int, Text>

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insertAll(texts: List<Text>)
}

那么,如何处理呢?

有人可以帮忙吗?

【问题讨论】:

    标签: android android-recyclerview android-room multiple-views android-paging-library


    【解决方案1】:

    为什么需要 2 张桌子?两者的列是相同的。对保存另一个字段的两种类型使用通用数据类来区分行文本值表示的内容、文本、图像 uri、图像路径。这可以使用IntDefenum 轻松完成。然后,这允许您返回一组可以根据该新列进行相应处理的数据。

    例如:

    @IntDef(IMAGE, TEXT)
    annotation class ItemType {
        companion object {
            const val IMAGE = 1
            const val TEXT = 2
        }
    }
    
    @Entity(tableName = "items_table")
    data class Item(
        @PrimaryKey
        val id: Long,
        val itemData: String,
        @ItemType
        val type : Int)
    
    @Dao
    interface ItemsDao {
        @Query("SELECT * from items_table")
        fun getItems(): DataSource.Factory<Int, Item>
    
        @Insert(onConflict = OnConflictStrategy.REPLACE)
        fun insertAll(itemss: List<Item>)
    }
    
    class ItemAdapter():
        PagedListAdapter<Item, RecyclerView.ViewHolder>(ItemDiffCallback()) {
    
        override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
            with(getItem(position)) {
                when (holder) {
                     // this could be simplified if using a common super view holder with a bind method that these subtypes inherit from
                     is TextItemViewHolder -> holder.bind(this)
                     is ImageItemViewHolder -> holder.bind(this)
                }
            }
        }
    
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
            return when (viewType) {
                ITEM_VIEW_TYPE_IMAGE -> ImageItemViewHolder.from(parent)
                ITEM_VIEW_TYPE_TEXT -> TextItemViewHolder.from(parent)
                else -> throw ClassCastException("Unknown viewType ${viewType}")
            }
        }
    
        override fun getItemViewType(position: Int): Int {
            return when (getItem(position)?.type) {
                IMAGE -> ITEM_VIEW_TYPE_IMAGE
                TEXT -> ITEM_VIEW_TYPE_TEXT
                else -> -1
            }
        }
    }
    

    使用单个表,您当前面临的问题将不存在。如果您从远程数据源/api 中提取信息,您可以在插入之前轻松地将 api 数据类型转换为数据库的正确数据类型,无论如何我都建议您这样做。在不了解边界检查/服务的细节的情况下,根据当前提供的代码和信息,这似乎是一种更好的方法。

    【讨论】:

    • 确实,一张表就足够我的用例了。感谢您的提示/解决方案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-12-02
    • 1970-01-01
    相关资源
    最近更新 更多