簡體   English   中英

具有多種視圖類型的 RecyclerView 和具有邊界回調的分頁庫

[英]RecyclerView with multiple view types and paging library with boundary callback

我有一個項目,我想在其中向用戶顯示多個項目(圖像、文本、視頻等)。 我知道多種視圖類型如何為 RecyclerView 工作。 在我的項目中,我包含了大部分推薦的Android Architecture ComponentsRoomPagingLiveData等。

為了總結項目設計,我遵循了這個對我幫助很大的代碼實驗室 帶有BoundaryCallbackRoom庫 + Paging庫提供了一種實現離線緩存的好方法。 將數據庫視為事實來源並讓Paging庫在Recyclerview需要時通過DataSource.Factory請求更多數據,在我看來是一個非常好的方法。

但該項目的缺點是它們展示了整個架構組件(帶有BoundaryCallback RoomPaging )如何僅適用於一種項目類型。 但是我有多種視圖類型,我無法處理。

在下面,我將向您展示我的項目中的代碼片段,以說明我遇到的問題。

讓我們從模型開始。 假設,我們有兩種項目類型:圖像和文本。

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類視為Item 適配器類看起來像這樣:

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屬性。 我能在這里做什么?

現在,我要去存儲庫類。 您可能知道,存儲庫類負責首先從數據庫中檢索數據,因為在我的項目中,數據庫是事實的來源。 如果沒有數據或需要更多數據,Paging 庫使用 BoundaryCallback 類從服務請求更多數據並將它們存儲到數據庫中。 這是我的項目存儲庫類:

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>)
}

那么,如何處理呢?

有人可以幫忙嗎?

為什么需要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 數據類型轉換為數據庫的正確數據類型,無論如何我都建議這樣做。 在不知道邊界檢查/服務的細節的情況下,根據當前提供的代碼和信息,這似乎是一種更好的方法。

暫無
暫無

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

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