现在许多App的主页都是以列表的方式显现数据,同时会在其中某些方位插入原生广告。本文简略介绍如安在RecyclerView中运用原生广告。

前置条件以及如何测验请移步Android Admob接入,本文不多赘述。

实现在RecyclerView中运用原生广告

尽管广告能够增加收入,但是必定不能影响用户体验。进入页面时应领先显现正常的数据,广告在加载完结之后再显现即可。

在RecyclerView中同时显现正常的布局和广告布局

经过RecyclerView.AdaptergetItemViewType方法来区别Item项的类型,依据类型创立不同的布局,示例代码如下:

  • 实体类
const val LAYOUT_TYPE_NORMAL = 0
const val LAYOUT_TYPE_AD = 1
data class ExampleEntity(
    val layoutType: Int,
    val exampleContent: String
) {
    override fun toString(): String {
        return "ExampleEntity(layoutType=$layoutType, exampleContent='$exampleContent')"
    }
}
  • 正常Item的布局文件
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="50dp">
    <androidx.appcompat.widget.AppCompatTextView
        android:id="@+id/tv_content"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center_vertical"
        android:paddingStart="20dp"
        android:paddingEnd="0dp"
        tools:text="this is content example" />
    <View
        android:layout_width="match_parent"
        android:layout_height="0.5dp"
        android:background="@color/color_c2c7cc"
        app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
  • 广告Item的布局文件
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/fl_native_ad_container"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:paddingStart="16dp"
    android:paddingEnd="16dp" />
  • 适配器
class NativeAdExampleAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
    private val containerData = ArrayList<ExampleEntity>()
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        // 依据viewType创立不同类型的ViewHolder
        return if (viewType == LAYOUT_TYPE_AD) {
            AdvertiseItemViewHolder(LayoutAdvertiseItemBinding.inflate(LayoutInflater.from(parent.context), parent, false))
        } else {
            NormalItemViewHolder(LayoutTextContentItemBinding.inflate(LayoutInflater.from(parent.context), parent, false))
        }
    }
    override fun getItemCount(): Int {
        return containerData.size
    }
    override fun getItemViewType(position: Int): Int {
        return containerData[position].layoutType
    }
    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when (holder) {
            is NormalItemViewHolder -> holder.itemViewBiding.tvContent.text = containerData[position].exampleContent
            is AdvertiseItemViewHolder -> {
                val nativeAdView = (holder.itemView.context as? NativeAdInListExampleActivity)?.getNativeAdView(position)
                if (nativeAdView != null) {
                    holder.itemViewBinding.flNativeAdContainer.removeAllViews()
                    holder.itemViewBinding.flNativeAdContainer.addView(nativeAdView)
                    holder.itemViewBinding.flNativeAdContainer.visibility = View.VISIBLE
                } else {
                    holder.itemViewBinding.flNativeAdContainer.visibility = View.GONE
                    holder.itemViewBinding.flNativeAdContainer.removeAllViews()
                }
            }
        }
    }
    fun setNewData(newData: List<ExampleEntity>?) {
        val currentItemCount = itemCount
        if (currentItemCount != 0) {
            containerData.clear()
            notifyItemRangeRemoved(0, currentItemCount)
        }
        if (!newData.isNullOrEmpty()) {
            containerData.addAll(newData)
            notifyItemRangeChanged(0, itemCount)
        }
    }
    fun release() {
        containerData.clear()
    }
    class AdvertiseItemViewHolder(val itemViewBinding: LayoutAdvertiseItemBinding) : RecyclerView.ViewHolder(itemViewBinding.root)
    class NormalItemViewHolder(val itemViewBiding: LayoutTextContentItemBinding) : RecyclerView.ViewHolder(itemViewBiding.root)
}

加载并显现原生广告

经过循环来创立正常元素和广告元素的调集,然后依据广告元素的数量加载原生广告并增加到页面中,示例代码如下:

class NativeAdInListExampleActivity : AppCompatActivity() {
    private lateinit var layoutNativeAdInListExampleActivityBinding: LayoutNativeAdInListExampleActivityBinding
    private val exampleDataList = ArrayList<ExampleEntity>()
    private val nativeAdLoader by lazy {
        // 运用广告测验广告位id
        AdLoader.Builder(this, "ca-app-pub-3940256099942544/2247696110")
            .forNativeAd { nativeAd ->
                // 如果在页面销毁后触发此回调,需求销毁NativeAd防止内存泄漏
                if (isDestroyed || isFinishing || isChangingConfigurations) {
                    nativeAd.destroy()
                    return@forNativeAd
                }
                populateNativeAdView(nativeAd)
            }
            .withNativeAdOptions(NativeAdOptions.Builder()
                // 设置视频是否静音播映
                .setVideoOptions(VideoOptions.Builder().setStartMuted(false).build())
                .build())
            .withAdListener(object : AdListener() {
                override fun onAdLoaded() {
                    super.onAdLoaded()
                    // 广告加载成功
                    nativeAdIndex.getOrNull(nativeAdViewList.lastIndex)?.let {
                        lifecycleScope.launch(Dispatchers.Main) {
                            nativeAdExampleAdapter.notifyItemChanged(it)
                        }
                    }
                    // 需求的原生广告数量小于广告item数量,继续加载广告
                    if (nativeAdViewList.size < nativeAdIndex.size) {
                        loadNativeAd()
                    }
                }
                override fun onAdFailedToLoad(loadAdError: LoadAdError) {
                    super.onAdFailedToLoad(loadAdError)
                    // 广告加载失利
                }
                override fun onAdOpened() {
                    super.onAdOpened()
                    // 广告页翻开
                }
                override fun onAdClicked() {
                    super.onAdClicked()
                    // 广告被点击
                }
                override fun onAdClosed() {
                    super.onAdClosed()
                    // 广告页封闭
                }
            })
            .build()
    }
    private val nativeAdIndex = ArrayList<Int>()
    private val nativeAdList = ArrayList<NativeAd>()
    private val nativeAdViewList = ArrayList<NativeAdView>()
    private val nativeAdExampleAdapter = NativeAdExampleAdapter()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        layoutNativeAdInListExampleActivityBinding = LayoutNativeAdInListExampleActivityBinding.inflate(layoutInflater).also {
            setContentView(it.root)
            it.rvExampleDataContainer.adapter = nativeAdExampleAdapter
        }
        for (index in 1..30) {
            val layoutType: Int
            val content: String
            if (index % 4 == 0) {
                layoutType = LAYOUT_TYPE_AD
                content = ""
                // 记录广告元素在全体调集中的方位
                // 广告加载完结后依据此进行改写,能够削减不必要的布局重绘。
                nativeAdIndex.add(index - 1)
            } else {
                layoutType = LAYOUT_TYPE_NORMAL
                content = "example data $index"
            }
            exampleDataList.add(ExampleEntity(layoutType, content))
        }
        nativeAdExampleAdapter.setNewData(exampleDataList)
        // 初始化SDK并加载原生广告
        MobileAds.initialize(this) { loadNativeAd() }
    }
    private fun loadNativeAd() {
        lifecycleScope.launch(Dispatchers.IO) { nativeAdLoader.loadAd(AdRequest.Builder().build()) }
    }
    @SuppressLint("InflateParams")
    private fun populateNativeAdView(nativeAd: NativeAd) {
        (LayoutInflater.from(this).inflate(R.layout.layout_admob_native_ad, null) as? NativeAdView)?.run {
            iconView = findViewById<AppCompatImageView>(R.id.iv_ad_app_icon).apply {
                nativeAd.icon?.let { setImageDrawable(it.drawable) }
                visibility = if (nativeAd.icon != null) View.VISIBLE else View.GONE
            }
            headlineView = findViewById<AppCompatTextView>(R.id.tv_ad_headline).apply {
                text = nativeAd.headline
            }
            advertiserView = findViewById<AppCompatTextView>(R.id.tv_advertiser).apply {
                text = nativeAd.advertiser
                visibility = if (nativeAd.advertiser != null) View.VISIBLE else View.INVISIBLE
            }
            starRatingView = findViewById<AppCompatRatingBar>(R.id.rb_ad_stars).apply {
                nativeAd.starRating?.let { rating = it.toFloat() }
                visibility = if (nativeAd.starRating != null) View.VISIBLE else View.INVISIBLE
            }
            bodyView = findViewById<AppCompatTextView>(R.id.tv_ad_body).apply {
                text = nativeAd.body
                visibility = if (nativeAd.body != null) View.VISIBLE else View.INVISIBLE
            }
            mediaView = findViewById<MediaView>(R.id.mv_ad_media).apply {
                nativeAd.mediaContent?.let {
                    mediaContent = it
                    it.videoController.videoLifecycleCallbacks = object : VideoController.VideoLifecycleCallbacks() {
                        override fun onVideoStart() {
                            super.onVideoStart()
                            // 视频开端
                        }
                        override fun onVideoEnd() {
                            super.onVideoEnd()
                            // 视频完毕,完毕后能够改写广告
                        }
                        override fun onVideoPlay() {
                            super.onVideoPlay()
                            // 视频播映
                        }
                        override fun onVideoPause() {
                            super.onVideoPause()
                            // 视频暂停
                        }
                        override fun onVideoMute(mute: Boolean) {
                            super.onVideoMute(mute)
                            // 视频是否静音
                            // mute true 静音 false 非静音
                        }
                    }
                }
            }
            callToActionView = findViewById<AppCompatButton>(R.id.btn_ad_call_to_action).apply {
                text = nativeAd.callToAction
                visibility = if (nativeAd.callToAction != null) View.VISIBLE else View.INVISIBLE
            }
            priceView = findViewById<AppCompatTextView>(R.id.tv_ad_price).apply {
                text = nativeAd.price
                visibility = if (nativeAd.price != null) View.VISIBLE else View.INVISIBLE
            }
            storeView = findViewById<AppCompatTextView>(R.id.tv_ad_store).apply {
                text = nativeAd.store
                visibility = if (nativeAd.store != null) View.VISIBLE else View.INVISIBLE
            }
            layoutParams = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT).apply {
                gravity = Gravity.BOTTOM
            }
            setNativeAd(nativeAd)
            nativeAdList.add(nativeAd)
            nativeAdViewList.add(this)
        }
    }
    fun getNativeAdView(layoutIndex: Int): NativeAdView? {
        return nativeAdViewList.getOrNull(nativeAdIndex.indexOf(layoutIndex))?.also { removeInParent(it) }
    }
    private fun removeInParent(view: View) {
        try {
            (view.parent as? ViewGroup)?.removeView(view)
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }
    private fun releaseNativeAd() {
        nativeAdIndex.clear()
        for (nativeAd in nativeAdList) {
            nativeAd.destroy()
        }
        nativeAdList.clear()
        for (nativeAdView in nativeAdViewList) {
            removeInParent(nativeAdView)
            nativeAdView.destroy()
        }
        nativeAdViewList.clear()
    }
    override fun onDestroy() {
        super.onDestroy()
        exampleDataList.clear()
        nativeAdExampleAdapter.release()
        releaseNativeAd()
    }
}

作用演示与示例代码

最终作用如下图:

Android AdMob(四)— 在RecyclerView中运用原生广告

演示代码已在示例Demo中增加。

ExampleDemo github

ExampleDemo gitee