【问题标题】:How to insert different item e.g. native ads on each 10 items of recyclerView using diffutil "AsyncListDiffer"如何插入不同的项目,例如使用 diffutil \"AsyncListDiffer\" 在每 10 项 recyclerView 上的原生广告
【发布时间】:2022-08-21 06:02:26
【问题描述】:

由于我决定从设置项目和 notifyDataSetChanged() 迁移到使用 AsyncListDiffer SubmitList 方法,因此我在当前的适配器类中将原生广告实现到 RecyclerView 时遇到问题

我用了this method在旧问题中,它与我的应用程序的旧结构完美配合,但是当我尝试 AsyncListDiffer 时它不起作用

**问题

当我在getItemViewType 中使用这个逻辑时

if (shouldLoadNativeAds && position != 0 && position % 10 == 0) VIEW_TYPE_AD_CARD_LAYOUT else VIEW_TYPE_CONTENT

它将内容项替换为 AD 项 例如如果项目为 20,则 recyclerView 显示 18 和 2 用于广告 至于我使用以下代码 (滚动时它会重复项目)**

video showing the duplicating problem

的旧结构后适配器

class PostAdapter(
    private val titleAndGridLayout: TitleAndGridLayout
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
    private var items = arrayListOf<Item>()

    private var context: Context? = null

    val VIEW_TYPE_CONTENT = 1
    val VIEW_TYPE_AD_CARD_LAYOUT = 2
    val VIEW_TYPE_AD_GRID_LAYOUT = 3

    var isDestroyed = false

    private var adsCnt = 3


    var viewType = 0
        set(value) {
            field = value
            notifyDataSetChanged()
        }

    fun submitList(items: List<Item>) {
        this.items.addAll(items)
        notifyDataSetChanged()
    }

    fun clearList() {
        this.items.clear()
        notifyDataSetChanged()
    }


    override fun getItemViewType(position: Int): Int {

        val androidVersionCode: Int = Build.VERSION.SDK_INT

        val shouldLoadNativeAds: Boolean = (context?.let { Utils.hasInternetConnection(it) } == true
                && androidVersionCode >= Build.VERSION_CODES.N)


        return when (viewType) {
            CARD, CARD_MAGAZINE -> {
                if (shouldLoadNativeAds && position > 0 && position % LIST_AD_DELTA == 0)
                    VIEW_TYPE_AD_CARD_LAYOUT else VIEW_TYPE_CONTENT
            }
            TITLE -> {
                if (shouldLoadNativeAds && position > 0 && position % LIST_AD_DELTA == 0) VIEW_TYPE_AD_GRID_LAYOUT else VIEW_TYPE_CONTENT
            }
            GRID -> {
                if (shouldLoadNativeAds && (position + 1) % 10 == 0 && (position + 1) != 1) VIEW_TYPE_AD_GRID_LAYOUT else VIEW_TYPE_CONTENT
            }
            else -> VIEW_TYPE_CONTENT
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val inflater = LayoutInflater.from(parent.context)

        this@PostAdapter.context = parent.context


        when (viewType) {
            VIEW_TYPE_CONTENT -> {
                when (this.viewType) {
                    CARD -> {
                        val cardLayoutBinding: CardLayoutBinding =
                            CardLayoutBinding.inflate(inflater, parent, false)
                        return CardViewHolder(cardLayoutBinding)
                    }
                    CARD_MAGAZINE -> {
                        val cardMagazineBinding: CardMagazineBinding =
                            CardMagazineBinding.inflate(
                                LayoutInflater.from(parent.context),
                                parent,
                                false
                            )
                        return CardMagazineViewHolder(cardMagazineBinding)
                    }
                    TITLE -> {
                        val titleLayoutBinding: TitleLayoutBinding =
                            TitleLayoutBinding.inflate(inflater, parent, false)
                        return TitleViewHolder(titleLayoutBinding)
                    }
                    else -> {
                        val gridLayoutBinding: GridLayoutBinding =
                            GridLayoutBinding.inflate(inflater, parent, false)
                        return GridViewHolder(gridLayoutBinding)
                    }
                }
            }
            VIEW_TYPE_AD_CARD_LAYOUT -> {

                val nativeAdRowBinding = AdUnifiedBinding.inflate(inflater, parent, false)
                return AdViewHolder(nativeAdRowBinding)

            }
            else -> {

                val nativeAdRowTitleGridBinding =
                    NativeAdRowTitleGridBinding.inflate(inflater, parent, false)
                return AdViewHolderGrid(nativeAdRowTitleGridBinding)
            }
        }
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        val item: Item = items[getRealPosition(position)]

        val intent = Intent(holder.itemView.context, DetailsActivity::class.java)

        if (getItemViewType(position) == VIEW_TYPE_CONTENT) {
            when (this.viewType) {
                CARD -> if (holder is CardViewHolder) {

                    holder.bind(item)
                    holder.itemView.setOnClickListener { view: View ->
                        intent.putExtra(\"postItem\", item)
                        view.context.startActivity(intent)
                    }
                }
                CARD_MAGAZINE -> if (holder is CardMagazineViewHolder) {
                    holder.bind(item)
                    holder.itemView.setOnClickListener { view: View ->
                        intent.putExtra(\"postItem\", item)
                        view.context.startActivity(intent)
                    }
                }
                TITLE -> if (holder is TitleViewHolder) {
                    holder.bind(item)

                    if (position == itemCount - 1)
                        titleAndGridLayout.tellFragmentToGetItems()

                    holder.itemView.setOnClickListener { view: View ->
                        intent.putExtra(\"postItem\", item)
                        view.context.startActivity(intent)
                    }
                }
                GRID -> if (holder is GridViewHolder) {
                    holder.bind(item)

                    if (position == itemCount - 1)
                        titleAndGridLayout.tellFragmentToGetItems()

                    holder.itemView.setOnClickListener { view: View ->
                        intent.putExtra(\"postItem\", item)
                        view.context.startActivity(intent)
                    }
                }
            }
        } else if (getItemViewType(position) == VIEW_TYPE_AD_CARD_LAYOUT) {
            if (holder is AdViewHolder) {
                holder.bindAdData()
            }
        } else {
            if (holder is AdViewHolderGrid) {
                holder.bindAdData()


                if (getItemViewType(position) == VIEW_TYPE_AD_GRID_LAYOUT) {
                    if (position == itemCount - 1)
                        titleAndGridLayout.tellFragmentToGetItems()
                }


            }
        }
    }


    private fun getRealPosition(position: Int): Int {
        return if (LIST_AD_DELTA == 0) {
            position
        } else {
            position - position / LIST_AD_DELTA
        }
    }

    override fun getItemCount(): Int {
        var additionalContent: Int = 0
        if (items.size > 0 && LIST_AD_DELTA > 0 && items.size > LIST_AD_DELTA) {
            additionalContent = (items.size + (items.size / LIST_AD_DELTA)) / LIST_AD_DELTA
        }
        return items.size + additionalContent;
    }

    override fun getItemId(position: Int): Long {
        return getRealPosition(position).toLong()
    }

    }


    companion object {
        private const val CARD = 0
        private const val CARD_MAGAZINE = 1
        private const val TITLE = 2
        private const val GRID = 3
        private const val TAG = \"POST_ADAPTER\"
        private const val LIST_AD_DELTA = 10

    }

    init {
        setHasStableIds(true)
    }


   
}

新的 AsyncListDiffer

class PostAdapter(
     private val titleAndGridLayout: TitleAndGridLayout
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
    
    private var context:Context?=null

    val VIEW_TYPE_CONTENT = 1
    val VIEW_TYPE_AD_CARD_LAYOUT = 2
    val VIEW_TYPE_AD_GRID_LAYOUT = 3

    var isDestroyed = false

    private var adsCnt = 3





    var viewType = 0
        set(value) {
            field = value
            notifyDataSetChanged()
        }



    private val differCallback = object : DiffUtil.ItemCallback<Item>() {
        override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean {
            return (oldItem.id == newItem.id)
        }

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

    val differ = AsyncListDiffer(this, differCallback)



    override fun getItemViewType(position: Int): Int {

        val androidVersionCode: Int = Build.VERSION.SDK_INT

        val shouldLoadNativeAds: Boolean = (context?.let { Utils.hasInternetConnection(it) } == true
                && androidVersionCode >= Build.VERSION_CODES.N)


        return when (viewType) {
            CARD, CARD_MAGAZINE -> {
                if (shouldLoadNativeAds && position > 0 && position  % LIST_AD_DELTA == 0)
                    VIEW_TYPE_AD_CARD_LAYOUT else VIEW_TYPE_CONTENT
            }
            TITLE -> {
                if (shouldLoadNativeAds && position > 0 && position % LIST_AD_DELTA == 0) VIEW_TYPE_AD_GRID_LAYOUT else VIEW_TYPE_CONTENT
            }
            else -> VIEW_TYPE_CONTENT
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val inflater = LayoutInflater.from(parent.context)

        this@PostAdapter.context = parent.context

        val nativeAdRowBinding = AdUnifiedBinding.inflate(inflater, parent, false)
        val nativeAdRowTitleGridBinding =
            NativeAdRowTitleGridBinding.inflate(inflater, parent, false)
        when (viewType) {
            VIEW_TYPE_CONTENT -> {
                when (this.viewType) {
                    CARD -> {
                        val cardLayoutBinding: CardLayoutBinding =
                            CardLayoutBinding.inflate(inflater, parent, false)
                        return CardViewHolder(cardLayoutBinding)
                    }
                    CARD_MAGAZINE -> {
                        val cardMagazineBinding: CardMagazineBinding =
                            CardMagazineBinding.inflate(
                                LayoutInflater.from(parent.context),
                                parent,
                                false
                            )
                        return CardMagazineViewHolder(cardMagazineBinding)
                    }
                    TITLE -> {
                        val titleLayoutBinding: TitleLayoutBinding =
                            TitleLayoutBinding.inflate(inflater, parent, false)
                        return TitleViewHolder(titleLayoutBinding)
                    }
                    else -> {
                        val gridLayoutBinding: GridLayoutBinding =
                            GridLayoutBinding.inflate(inflater, parent, false)
                        return GridViewHolder(gridLayoutBinding)
                    }
                }
            }
            VIEW_TYPE_AD_CARD_LAYOUT -> {

                                return AdViewHolder(nativeAdRowBinding)

            }
            else -> {

                                return AdViewHolderGrid(nativeAdRowTitleGridBinding)
            }
        }
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        val item: Item = differ.currentList[getRealPosition(position)]

        val intent = Intent(holder.itemView.context, DetailsActivity::class.java)

        if (getItemViewType(getRealPosition(position)) == VIEW_TYPE_CONTENT) {
            when (this.viewType) {
                CARD -> if (holder is CardViewHolder) {
                    holder.bind(item)
                    holder.itemView.setOnClickListener { view: View ->
                        intent.putExtra(\"postItem\", item)
                        view.context.startActivity(intent)
                    }
                }
                CARD_MAGAZINE -> if (holder is CardMagazineViewHolder) {
                    holder.bind(item)
                    holder.itemView.setOnClickListener { view: View ->
                        intent.putExtra(\"postItem\", item)
                        view.context.startActivity(intent)
                    }
                }
                TITLE -> if (holder is TitleViewHolder) {

                    holder.bind(item)

                    if (position == itemCount - 1)
                        titleAndGridLayout.tellFragmentToGetItems(\"titleLayout\")

                    holder.itemView.setOnClickListener { view: View ->
                        intent.putExtra(\"postItem\", item)
                        view.context.startActivity(intent)
                    }
                }
                GRID -> if (holder is GridViewHolder) {

                    holder.bind(item)
                    if (position == itemCount - 1) {
                        titleAndGridLayout.tellFragmentToGetItems(\"gridLayout\")
                    }
                    holder.itemView.setOnClickListener { view: View ->
                        intent.putExtra(\"postItem\", item)
                        view.context.startActivity(intent)
                    }
                }
            }
        } else if (getItemViewType(getRealPosition(position)) == VIEW_TYPE_AD_CARD_LAYOUT) {
            if (holder is AdViewHolder) {
                holder.bindAdData()
            }
        } else {
            holder as AdViewHolderGrid
            holder.bindAdData()
            holder.setIsRecyclable(false)
            if (position == itemCount - 1) {
                titleAndGridLayout.tellFragmentToGetItems(\"gridLayout\")
            }
        }
    }




     private fun getRealPosition(position: Int): Int {
        return if (LIST_AD_DELTA == 0) {
            position
        } else {
            position - position / LIST_AD_DELTA
        }
    }

    override fun getItemCount(): Int {
        var additionalContent: Int = 0
        val itemsSize = differ.currentList.size
        
        if (itemsSize > 0 && LIST_AD_DELTA > 0 && itemsSize > LIST_AD_DELTA) {
            additionalContent = (itemsSize + (itemsSize / LIST_AD_DELTA)) / LIST_AD_DELTA
        }
        return itemsSize + additionalContent;
    }

    override fun getItemId(position: Int): Long {
        return getRealPosition(position).toLong()
    }


    companion object {
        private const val CARD = 0
        private const val CARD_MAGAZINE = 1
        private const val TITLE = 2
        private const val GRID = 3
        private const val TAG = \"POST_ADAPTER\"
        private const val LIST_AD_DELTA = 10

    }

    init {
        setHasStableIds(true)
    }


    inner class AdViewHolder(private val binding: AdUnifiedBinding) :
        RecyclerView.ViewHolder(binding.root) {

        private val videoOptions = VideoOptions.Builder()
            .setStartMuted(false)
            .build()


        private var adOptions = NativeAdOptions.Builder()
            .setVideoOptions(videoOptions)
            .build()

        fun bindAdData() {
            val builder =
                AdLoader.Builder(binding.root.context, \"ca-app-pub-3940256099942544/2247696110\")

            builder.forNativeAd { nativeAd ->
                                                               if(isDestroyed){

                   Toast.makeText(this@PostAdapter.context, \"$isDestroyed\", Toast.LENGTH_SHORT).show()

                   Log.e(TAG, \"bindAdData: $isDestroyed\", )

                   nativeAd.destroy()

                   Log.e(TAG, \"bindAdData: ${nativeAd.body.toString()}\", )
               }
                populateNativeAdView(nativeAd, binding)
            }

            builder.withNativeAdOptions(adOptions)


            val adLoader = builder
                .withAdListener(
                    object : AdListener() {
                        override fun onAdFailedToLoad(loadAdError: LoadAdError) {

                            if (adsCnt > 0) {
                                bindAdData()
                            } else {
                                adsCnt -= 1
                            }

                            val error =
                                \"\"\"
           domain: ${loadAdError.domain}, code: ${loadAdError.code}, message: ${loadAdError.message}
          \"\"\"\"
                            Toast.makeText(
                                binding.root.context,
                                \"Failed to load native ad with error $error\",
                                Toast.LENGTH_SHORT
                            )
                                .show()
                        }
                    }
                )
                .build()

            adLoader.loadAds(AdRequest.Builder().build(), 5)



        }

        private fun populateNativeAdView(nativeAd: NativeAd, unifiedAdBinding: AdUnifiedBinding) {
            val nativeAdView = unifiedAdBinding.root

                        nativeAdView.mediaView = unifiedAdBinding.adMedia

                        nativeAdView.headlineView = unifiedAdBinding.adHeadline
            nativeAdView.bodyView = unifiedAdBinding.adBody
            nativeAdView.callToActionView = unifiedAdBinding.adCallToAction
            nativeAdView.iconView = unifiedAdBinding.adAppIcon
            nativeAdView.priceView = unifiedAdBinding.adPrice
            nativeAdView.starRatingView = unifiedAdBinding.adStars
            nativeAdView.storeView = unifiedAdBinding.adStore
            nativeAdView.advertiserView = unifiedAdBinding.adAdvertiser

                        unifiedAdBinding.adHeadline.text = nativeAd.headline
            nativeAd.mediaContent?.let { unifiedAdBinding.adMedia.setMediaContent(it) }

                                    if (nativeAd.body == null) {
                unifiedAdBinding.adBody.visibility = INVISIBLE
            } else {
                unifiedAdBinding.adBody.visibility = View.VISIBLE
                unifiedAdBinding.adBody.text = nativeAd.body
            }

            if (nativeAd.callToAction == null) {
                unifiedAdBinding.adCallToAction.visibility = INVISIBLE
            } else {
                unifiedAdBinding.adCallToAction.visibility = View.VISIBLE
                unifiedAdBinding.adCallToAction.text = nativeAd.callToAction
            }

            if (nativeAd.icon == null) {
                unifiedAdBinding.adAppIcon.visibility = View.GONE
            } else {
                unifiedAdBinding.adAppIcon.setImageDrawable(nativeAd.icon?.drawable)
                unifiedAdBinding.adAppIcon.visibility = View.VISIBLE
            }

            if (nativeAd.price == null) {
                unifiedAdBinding.adPrice.visibility = INVISIBLE
            } else {
                unifiedAdBinding.adPrice.visibility = View.VISIBLE
                unifiedAdBinding.adPrice.text = nativeAd.price
            }

            if (nativeAd.store == null) {
                unifiedAdBinding.adStore.visibility = INVISIBLE
            } else {
                unifiedAdBinding.adStore.visibility = View.VISIBLE
                unifiedAdBinding.adStore.text = nativeAd.store
            }

            if (nativeAd.starRating == null) {
                unifiedAdBinding.adStars.visibility = INVISIBLE
            } else {
                unifiedAdBinding.adStars.rating = nativeAd.starRating!!.toFloat()
                unifiedAdBinding.adStars.visibility = View.VISIBLE
            }

            if (nativeAd.advertiser == null) {
                unifiedAdBinding.adAdvertiser.visibility = INVISIBLE
            } else {
                unifiedAdBinding.adAdvertiser.text = nativeAd.advertiser
                unifiedAdBinding.adAdvertiser.visibility = View.VISIBLE
            }

           nativeAdView.setNativeAd(nativeAd)


        }
    }

    inner class AdViewHolderGrid(private val binding: NativeAdRowTitleGridBinding) :
        RecyclerView.ViewHolder(binding.root) {

        fun bindAdData() {
            val builder =
                AdLoader.Builder(binding.root.context, \"ca-app-pub-3940256099942544/2247696110\")
            builder.forNativeAd { nativeAd: NativeAd ->


                if (isDestroyed) {
                    nativeAd.destroy()
                }

                val styles =
                    NativeTemplateStyle.Builder().withMainBackgroundColor(
                        ColorDrawable(
                            ContextCompat.getColor(
                                binding.root.context,
                                R.color.backgroundColor
                            )
                        )
                    ).build()

                val template: TemplateView = binding.myTemplate

                Log.d(TAG, \"bindAdData: ${nativeAd.body}\")

                template.setStyles(styles)
                template.setNativeAd(nativeAd)


            }
            val adLoader = builder
                .withAdListener(
                    object : AdListener() {
                        override fun onAdFailedToLoad(loadAdError: LoadAdError) {

                            if (adsCnt > 0) {
                                bindAdData()
                            } else {
                                adsCnt -= 1
                            }

                            val error =
                                \"\"\"
           domain: ${loadAdError.domain}, code: ${loadAdError.code}, message: ${loadAdError.message}
          \"\"\"\"
                            Toast.makeText(
                                binding.root.context,
                                \"Failed to load native ad with error $error\",
                                Toast.LENGTH_SHORT
                            )
                                .show()
                        }
                    }
                )
                .build()

            adLoader.loadAds(AdRequest.Builder().build(), 5)
        }
    }

}

    标签: android kotlin android-recyclerview android-diffutils android-asynclistdiffer


    【解决方案1】:

    当您需要在 RecyclerView 中显示不同的项目类型时,我强烈建议您开始使用AdapterDelegates,尤其是当您有如何显示它们的条件时。 AdapterDelegates 也支持 AsyncListDiffer。 您将永远忘记管理类型的麻烦,并将逻辑排除在适配器之外。因此,它将是灵活的、易于更改和支持的。

    在这种情况下,您可以像管理列表中的项目一样管理您的项目。

    例如:

    1. 您将需要为您的类型设置类,例如CardItemCardMagazineItemTitleItemAdItem 等。

    2. 然后您需要为每种类型创建适配器委托:

      fun cardItemDelegate(): AdapterDelegate<List<Any>> {
          return adapterDelegate<CardItem>(
              layout = ...,
              on = { item, _, _ -> item is CardItem }
          ) {
              bind {
                  ... // here code of binding like you have in ViewHolders
              }
          }
      }
      
      fun cardMagazineItemDelegate(): AdapterDelegate<List<Any>> {
        ...
      }
      
      1. 将您的differCallback 和委托添加到适配器:
      val postAdapter = object : AsyncListDifferDelegationAdapter<Any>(
          differCallback,
          cardItemDelegate(),
          ... // your rest delegates for other types
      )
      
      1. 现在您可以根据需要修改主要项目列表:
      val items: MutableList<Any> // your main items
      
      val iterator = items.listIterator()
      for (i in iterator.withIndex()) {
           if (i.index != 0 && i.index % 10 == 0) { // add the AddItem after every 10th item
              iterator.add(AdItem())
           }
      }
      
      postAdapter.items = items
      

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-06-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-09-30
      • 2016-01-23
      相关资源
      最近更新 更多