现在许多App的主页都是以列表的方式显现数据,同时会在其中某些方位插入原生广告。本文简略介绍如安在RecyclerView
中运用原生广告。
前置条件以及如何测验请移步Android Admob接入,本文不多赘述。
实现在RecyclerView中运用原生广告
尽管广告能够增加收入,但是必定不能影响用户体验。进入页面时应领先显现正常的数据,广告在加载完结之后再显现即可。
在RecyclerView中同时显现正常的布局和广告布局
经过RecyclerView.Adapter
的getItemViewType
方法来区别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()
}
}
作用演示与示例代码
最终作用如下图:
演示代码已在示例Demo中增加。