kotlin AdMob,Recycler View中的广告项目隐藏了一些项目并在广告后创建了空间

pw136qt2  于 2023-01-13  发布在  Kotlin
关注(0)|答案(2)|浏览(110)

今天我尝试在水平方向的回收者视图中实现定制广告。
一切都很好,直到我运行应用程序,并注意到我的MutableList中的一些项目没有显示(或被显示为空白,不知道肯定),并在每一个广告后(只有在广告后)有一个巨大的空白。

我不知道该怎么解决这个问题,我不熟悉适配器内部的多个布局。
适配器声明:

class CardAdapter (val context2: Context, private val Cards:MutableList<Card>) : RecyclerView.Adapter<RecyclerView.ViewHolder>()

这是我的广告持有人内的适配器:

inner class HolderNativeAd(itemView: View): RecyclerView.ViewHolder(itemView){

    val app_ad_background : ImageView = itemView.findViewById(R.id.ad_icon)
    val ad_headline : TextView = itemView.findViewById(R.id.ad_headline)
    val ad_description : TextView = itemView.findViewById(R.id.ad_description)
    val ad_price : TextView = itemView.findViewById(R.id.ad_price)
    val ad_store : TextView = itemView.findViewById(R.id.ad_store)
    val call_to_action : CardView = itemView.findViewById(R.id.ad_call_to_action)
    val ad_advertiser : TextView = itemView.findViewById(R.id.ad_advertiser)
    val nativeAdView : NativeAdView = itemView.findViewById(R.id.nativeAdView)

    fun createAD(context : Context){
        val adLoader  = AdLoader.Builder(context, context.getString(R.string.native_ad_id_test))
            .forNativeAd { nativeAd ->
                Log.d(TAG, "onNativeAdLoaded: ")
                displayNativeAd(this@HolderNativeAd, nativeAd)
            }.withNativeAdOptions(NativeAdOptions.Builder().build()).build()
        adLoader.loadAd(AdRequest.Builder().build())
    }

}

在创建视图持有者时

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
    val view: View
    if(viewType == VIEW_TYPE_CONTENT){
        view = LayoutInflater.from(context2).inflate(R.layout.item_card, parent, false)
        return HolderCards(view)
    }else{
        view = LayoutInflater.from(context2).inflate(R.layout.native_ad_card, parent, false)
        return HolderNativeAd(view)
    }
}

在绑定视图持有者上
覆盖BindViewHolder上的功能(持有者:循环器视图,取景器,位置:整数){

if (getItemViewType(position) == VIEW_TYPE_CONTENT) {
        val model: Card = Cards[position]
        (holder as HolderCards).setCard(model, context2)
    } else if (getItemViewType(position) == VIEW_TYPE_AD) {
        (holder as HolderNativeAd).createAD(context2)

    }
}

获取项目视图类型

override fun getItemViewType(position: Int): Int {
    //logic to display Native Ad between content
    if(position != 0) {
        return if (position % 2 == 0) {
            //after 2 items, show native ad
            VIEW_TYPE_AD
        } else {
            VIEW_TYPE_CONTENT
        }
    }
    return VIEW_TYPE_CONTENT
}

getitemCount()返回卡片大小
卡可变总数:目前我有一个SingleValueEventListener,它抓取卡片并将它们放入一个mutableList中,为每个项目调用adapter. NotifyItemInserted()。
displayNativeAd(广告载体中使用的自定义方法)

private fun displayNativeAd(holderNativeAd: CardAdapter.HolderNativeAd, nativeAd: NativeAd) {
    /* Get Ad assets from the NativeAd Object  */
    val headline = nativeAd.headline
    val body = nativeAd.body
    val background = nativeAd.icon
    val callToAction = nativeAd.callToAction
    val price = nativeAd.price
    val store = nativeAd.store
    val advertiser = nativeAd.advertiser
    ...
    ... (checks to see if a val is null or not)
    holderNativeAd.nativeAdView.setNativeAd(nativeAd)
 }
xmakbtuz

xmakbtuz1#

好了,系好安全带,因为这是一个很长的问题!实际上是“添加广告”部分使事情变得复杂,而不是额外的ViewHolder类型。
您丢失了一些项目,因为您要用广告 * 替换 * 其中的一些项目。Adapter中的项目总数(itemCount)应为 * 卡片*的数量加上您要显示的 * 广告 * 的数量。
因为您没有处理它,所以使用以下代码实际上跳过了cards中的项:

override fun getItemViewType(position: Int): Int {
    //logic to display Native Ad between content
    if(position != 0) {
        return if (position % 2 == 0) {
            //after 2 items, show native ad
            VIEW_TYPE_AD
        } else {
            VIEW_TYPE_CONTENT
        }
    }
    return VIEW_TYPE_CONTENT
}

你有cards.size数量的项目,而不是显示cards[2],而是显示一个广告,和cards[2]从来没有得到显示。(同样,该代码每隔两个项目显示一个广告,position % 2产生 01,所以它每隔一个数就循环一次--你需要position % 3,所以它是3的每一个倍数,但它还有更多的内容,我们会讲到的!)
因此,您需要逻辑来处理 * 数据 *(cards)和 * 内容 *(cards+广告)不同的事实:

  • itemCount需要包含适当数量的广告
  • getItemViewType需要知道position是否持有 * 广告 * 或 * 卡 *
  • 显示 * 卡片 * 时,onBindViewHolder需要能够将position转换为cards中的相应索引

让我们先制定规则-假设您希望每三个项目显示一个广告,在前两个项目之后开始,并且您很乐意以广告结束,以使事情简单化。
因此,number of ads 就是有多少组 2 广告-整数除法可以做到这一点:
val adCount = cards.size / 2
项目总数等于项目数加上卡片数:
override fun getItemCount() = cards.size + (cards.size / 2)
判断position是一张卡片还是一个广告非常简单,基本上就是你已经做过的事情了!除了我们需要将每三个条目中的每一个都当作广告来处理之外,我们还需要考虑从零开始的索引:

|     |     |
0 1 2 3 4 5 6 7 8 9

我们在 2、5和8 上做广告。我们关心的是找到 3 的倍数(其中模运算返回零),这样我们就可以在每个位置上加上 1。这也消除了检查是否position == 0的需要(这种特殊的边缘情况表明你的逻辑不一致--别担心,我在写这篇文章的时候才意识到!)
fun isCard(position: Int) = (position + 1) % 3 != 0
注意,我们在这里使用3是因为我们处理的是列表 * 中的 * 位置,该位置每隔 2 个位置就填充了一个广告。cards中的每 2 个项目就变成了适配器内容中的 2+1 个项目。
实际上,我们应该使用一个常量val ITEMS_PER_AD = 2,并从它派生出另一个值val AD_FREQUENCY = ITEMS_PER_AD + 1。避免了难以阅读和使用,并且容易混淆的 * 幻数 *。这更清晰(也许有更好的名称!),您可以更改ITEMS_PER_AD来更改数量,其他所有内容都会沿着调整
positioncard的转换是最后一位。当position * 不是 * 一个有效的卡时,你必须考虑,例如isCard是false。在这种情况下,最容易在这里返回 null
看看翻译应该如何进行可能会有所帮助:

position:   0 1 2 3 4 5 6 7 8 9
card index: 0 1 x 2 3 x 4 5 x 6

是的,这是一个逻辑难题,这个过程的模式是什么?
偏移发生在每 3 个项的倍数处,所以如果我们用position除以 3,然后减去它,消除这些偏移会怎样?

position:   0 1 2 3 4 5 6 7 8 9
pos / 3:    0 0 0 1 1 1 2 2 2 3
card index: 0 1 x 2 3 x 4 5 x 6

很好,看起来不错!现在,如果不是卡片,我们需要返回 null,否则从数据集中获取合适的卡片:

fun getCardForPosition(position: Int): Card? {
    val offset = position / 3
    return if (isCard(position)) cards[position - offset] else null
}

这些都是正确调整列表大小、确定特定position是卡片还是广告以及从数据中获取适当卡片所需的部分。希望您可以了解如何将这些工作应用到Adapter方法中,以确定您需要哪个itemViewType,等等。
实际上,你可以尝试在onBindViewHolder中输入getCardForPosition,如果结果为 null,则显示一个广告(并将传递给你的ViewHolder转换为 ad,因为这是你应该得到的,因为它们都使用相同的函数来确定什么是什么)。
至于空格,当你所有东西都显示正确时,看看它是否能正常工作。它可能会自行解决,也可能是你的 ad 项目的布局问题。确保它们的宽度不是match_parent或任何东西。你可以随时使用Layout Inspector和一个正在运行的应用程序来查看屏幕上的布局到底发生了什么,可能会给予你一些线索
我想检查一下我是否遗漏了什么,所以我写了一个基本的实现,如果它有帮助的话:

data class Card(val info: String)

class Adapter(private val cards: List<Card>) : RecyclerView.Adapter<Adapter.MyViewHolder>() {

    private fun isCard(position: Int) = (position + 1) % AD_FREQUENCY != 0

    private fun getCardForPosition(position: Int): Card? {
        val offset = position / AD_FREQUENCY
        return if (isCard(position)) cards[position - offset] else null
    }

    override fun getItemViewType(position: Int) =
        if (isCard(position)) CARD_VIEWTYPE else AD_VIEWTYPE
    

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        val inflater = LayoutInflater.from(parent.context)
        val binding = ItemViewBinding.inflate(inflater, parent, false)
        return if (viewType == AD_VIEWTYPE) MyViewHolder.AdViewHolder(binding)
        else MyViewHolder.CardViewHolder(binding)
    }

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        val card = getCardForPosition(position)
        if (card == null) (holder as MyViewHolder.AdViewHolder).binding.textView.text = "AD"
        else (holder as MyViewHolder.CardViewHolder).binding.textView.text = card.info
    }

    override fun getItemCount() = cards.size + (cards.size / ITEMS_PER_AD)

    sealed class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        class AdViewHolder(val binding: ItemViewBinding) : MyViewHolder(binding.root)
        class CardViewHolder(val binding: ItemViewBinding) : MyViewHolder(binding.root)
    }

    companion object {
        const val ITEMS_PER_AD = 3
        const val AD_FREQUENCY = ITEMS_PER_AD + 1
        const val AD_VIEWTYPE = 0
        const val CARD_VIEWTYPE = 1
    }
}

// set up with
recycler.layoutManager =
    LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false)
recycler.adapter = Adapter(List(32) { Card("Content $it") })

非常简单,只需要在ViewHolderTextView中使用相同的布局。固定了布局的大小,没有空格弹出:

希望能有所帮助!

ozxc1zmp

ozxc1zmp2#

是的,这个很好用,我也有类似的东西可以帮助。

class MyVideoAdapter() :
    RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    private val TAG = "AdsCalled"

    companion object {
        const val AD_DISPLAY_FREQUENCY = 3
        const val ITEM_TYPE = 1
        const val AD_TYPE = 0
    }

    private val adItems: MutableList<NativeAd>

    init {
        adItems = ArrayList()
    }

    private var myResult: MyResult? = null
        set(value) {
            field = value
            notifyDataSetChanged()
        }

    private val itemList get() = myResult?.myVideos?.list?: emptyList()

    class ItemHolder(val binding: ItemSingleVideoBinding) : RecyclerView.ViewHolder(binding.root)
    
    class ItemAdHolder(val binding: ItemSingleVideoAdBinding) :
        RecyclerView.ViewHolder(binding.root) {
        init {
            with(binding) {
                nativeAdView.iconView = adAppIcon
                nativeAdView.headlineView = adHeadline
                nativeAdView.advertiserView = adAdvertiser
                nativeAdView.priceView = adPrice
                nativeAdView.storeView = adStore
            }
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
        if (viewType == AD_TYPE) 
          ItemAdHolder(ItemSingleVideoAdBinding.inflate(
            LayoutInflater
            .from(parent.context), parent, false)) 
        else 
          ItemHolder(ItemSingleVideoBinding.inflate(
            LayoutInflater
            .from(parent.context),parent,false))

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
            if (holder.itemViewType == AD_TYPE) {
                val adHolder = holder as ItemAdHolder
                var ad: NativeAd? = null
                if (adItems.size > position / AD_DISPLAY_FREQUENCY) {
                    ad = adItems[position / AD_DISPLAY_FREQUENCY]
                } else {
                    val nativeAdOptions =
                        NativeAdOptions.Builder().setMediaAspectRatio(MediaAspectRatio.LANDSCAPE)
                            .build()
                    val builder = AdLoader.Builder(adHolder.binding.root.context,
                        "ca-app-pub-3940256099942544/2247696110")
                    val adLoader: AdLoader = builder.forNativeAd { nativeAd ->
                        ad = nativeAd
                        adItems.add(nativeAd)
                    }.withNativeAdOptions(nativeAdOptions)
                        .withAdListener(object : AdListener() {
                            override fun onAdFailedToLoad(p0: LoadAdError) {
                                Log.d(TAG, "onAdFailedToLoad: Failed : ${p0.message}")
                            }
                        })
                        .build()
                    adLoader.loadAd(AdRequest.Builder().build())
                }
                ad?.let { nativeAd ->
                    adHolder.binding.run {
                        adHeadline.text = nativeAd.headline
                        adPrice.text = nativeAd.price
                        adStore.text = nativeAd.store
                        adAdvertiser.text = nativeAd.advertiser
                        adAppIcon.setImageDrawable(nativeAd.icon?.drawable)
                        nativeAdView.setNativeAd(nativeAd)
                    }
                }
            } else {
                val index = position - position / AD_DISPLAY_FREQUENCY - 1
                val item= itemList[index]
                val itemHolder = holder as ItemHolder
             
                }
            }
        } 
    }

    override fun getItemCount() = (itemList.size + adItems.size)

    override fun getItemViewType(position: Int): Int {
            if (position % AD_DISPLAY_FREQUENCY == 0)
                AD_TYPE
            else ITEM_TYPE
    }

    fun clearResult() {
        myResult = null
        notifyDataSetChanged()
    }

    fun setResult(myResult : MyResult) {
        this.myResult = myResult 
        notifyDataSetChanged()
    }
}

但这里的主要问题是,如果Admob未能加载广告呢?
如果存在此时无法从服务器加载广告的情况:

    • 广告项目大小= 0**
    • 项目列表大小= 20(假设)**
    • AD显示频率= 3**

因此,每发布2个广告后,就会显示一个广告,在getItemViewType方法中,我们有模块函数**(位置%AD_FREQ.)**
因此,默认情况下,它将返回AD_TYPEAD将不会加载,导致空的ItemAdHolder布局膨胀。此外,我们将跳过帖子项目,因为adslist的大小为0,我们正在更新帖子项目的索引,所以如何解决这个问题?我尝试在获取viewType之前检查adItems的大小,但没有帮助
我现在所尝试的是
x一个一个一个一个x一个一个二个x
在ITEM_TYPE案例的bindViewHolder()中

val index = if (adItems.isNotEmpty()) position - position / AD_DISPLAY_FREQUENCY - 1 else position

相关问题