Android — 通过页面根布局添加Banner广告

最近SDK项目接到一个新需求,需要把Banner广告改为常驻在页面中,提高变现效率。为了不遮挡主页面内容,需要把Banner广告和主页面分隔开。本文简单介绍下如何通过操作页面根布局添加Banner广告,以及如何与主页面分隔。

添加Banner广告

常见的AdMob、AppLovin、IronSource等广告聚合SDK,其Banner广告都是提供一个AdView,让使用者根据需要添加到自己的布局中。

常规做法是让接入方在布局文件中添加一个FrameLayout作为Banner的容器,然后再把容器作为参数传入SDK,但是这样有点麻烦。可以通过Activity的根布局直接添加Banner广告,这样可以直接覆盖在主页面上,代码如下:

  • 辅助类
kotlin 复制代码
class AdMobController {  
  
    private var bannerAdView: AdView? = null  
  
    private val bannerListener = object : AdListener() {  
        override fun onAdLoaded() {  
            super.onAdLoaded()  
            // 广告加载成功  
            bannerAdLoadCallback?.invoke(true)  
        }  
  
        override fun onAdFailedToLoad(loadAdError: LoadAdError) {  
            super.onAdFailedToLoad(loadAdError)  
            // 广告加载失败
            bannerAdLoadCallback?.invoke(false)  
        }  
  
        override fun onAdImpression() {  
            super.onAdImpression()  
            // 被记录为展示成功时调用  
        }  
  
        override fun onAdClicked() {  
            super.onAdClicked()  
            // 被点击时调用  
        }  
  
        override fun onAdOpened() {  
            super.onAdOpened()  
            // 广告落地页打开时调用  
        }  
  
        override fun onAdClosed() {  
            super.onAdClosed()  
            // 广告落地页关闭时调用  
        }  
    }  
  
    private var bannerAdLoadCallback: ((succeed: Boolean) -> Unit)? = null  
  
    fun initSdk(activity: Activity, bannerAdLoadCallback: ((succeed: Boolean) -> Unit)? = null) {  
        this.bannerAdLoadCallback = bannerAdLoadCallback  
        MobileAds.initialize(activity) { initializationStatus ->  
            val readyAdapter = initializationStatus.adapterStatusMap.entries.find {  
                // 判断适配器初始化的状态  
                // 准备就绪 AdapterStatus.State.READY  
                // 没准备好 AdapterStatus.State.NOT_READY  
                it.value.initializationState == AdapterStatus.State.READY  
            }  
            // 有任意一种适配器初始化成功就可以开始加载广告  
            if (readyAdapter != null) {  
                // 适配器的名称  
                val adapterName = readyAdapter.key  
                createBannerAdView(activity)  
            }  
        }  
    }  
  
    private fun createBannerAdView(activity: Activity) {  
        // 获取页面的根布局  
        val rootView = activity.findViewById<FrameLayout>(android.R.id.content)  
        bannerAdView = AdView(activity).apply {  
            // 设置Banner的尺寸  
            setAdSize(AdSize.BANNER)  
            adUnitId = "ca-app-pub-3940256099942544/6300978111"  
            // 设置广告事件回调  
            adListener = bannerListener  
            val bannerViewLayoutParams = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT)  
            // 默认显示在页面的底部中间  
            bannerViewLayoutParams.gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL  
            layoutParams = bannerViewLayoutParams  
            alpha = 0f  
            // 把 Banner Ad 添加到根布局  
            rootView.addView(this)  
            // 加载广告
            loadAd(AdRequest.Builder().build())  
        }  
    }  
  
    fun showBanner(onTop: Boolean) {  
        bannerAdView?.run {  
            context?.let {  
                if (it is Activity) {  
                    it.runOnUiThread {  
                        // 设置显示在顶部还是底部  
                        updateLayoutParams<FrameLayout.LayoutParams> {  
                            gravity = (if (onTop) Gravity.TOP else Gravity.BOTTOM) or Gravity.CENTER_HORIZONTAL  
                        }  
                        // 先从原有父布局中移除  
                        removeViewParent(this)  
                        if (alpha == 0f) {  
                            // 设置为不透明  
                            alpha = 1f  
                        }  
                        // 添加到根布局中  
                        it.findViewById<FrameLayout>(android.R.id.content).addView(this)  
                    }  
                }  
            }  
        }  
    }  
  
    fun hideBanner() {  
        bannerAdView?.run {  
            // 设置为透明  
            alpha = 0f  
            // 从父布局中移除  
            removeViewParent(this)  
        }  
    }  
  
    private fun removeViewParent(view: View) {  
        try {  
            val parent = view.parent  
            (parent as? ViewGroup)?.removeView(view)  
        } catch (e: Exception) {  
            e.printStackTrace()  
        }  
    }  
}
  • 测试页面
kotlin 复制代码
class BannerTestActivity : AppCompatActivity() {  
  
    private var bannerOnTop = false  
  
    override fun onCreate(savedInstanceState: Bundle?) {  
        super.onCreate(savedInstanceState)  
        val binding = LayoutBannerTestActivityBinding.inflate(layoutInflater).apply {  
            setContentView(root)  
        }  
  
        val controller = AdMobController().apply {  
            initSdk(this@BannerTestActivity) { succeed ->  
                binding.btnShowBanner.isEnabled = succeed  
                binding.btnHideBanner.isEnabled = succeed  
            }  
        }  
  
        binding.btnShowBanner.setOnClickListener {  
            controller.showBanner(bannerOnTop)  
        }  
        binding.btnHideBanner.setOnClickListener {  
            controller.hideBanner()  
            bannerOnTop = !bannerOnTop  
        }  
    }  
}

效果如图:

分隔主页面内容和Banner广告

还是通过操作Activity的根布局来实现,由于Activity的根布局是FrameLayout,要实现主页面内容和Banner容器分隔其实不太方便,所以换一种ViewGroup(示例代码中使用ConstraintLayout)来实现,代码如下:

  • 辅助类(与上面相同的代码省略)
ini 复制代码
class AdMobController {  
  
    ......
  
    var enableBannerResident = false  
  
    private var newRootView: ConstraintLayout? = null  
  
    ...... 
  
    private fun createNewRootView(activity: Activity) {  
        if (newRootView == null) {  
            activity.findViewById<FrameLayout>(android.R.id.content).let { rootView ->  
                // 获取主页面布局  
                rootView.getChildAt(0).let { mainPageView ->  
                    // 设置tag用于获取主页面布局  
                    mainPageView.tag = R.id.layout_main_page_container  
                    // 把主页布局从原来的根部局中移除  
                    rootView.removeView(mainPageView)  
                    // 使用ConstraintLayout创建一个新的根部局并添加到原来的根部局中  
                    rootView.addView(ConstraintLayout(activity).apply {  
                        layoutParams = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,                 FrameLayout.LayoutParams.MATCH_PARENT)  
                        // 创建一个FrameLayout作为Banner容器  
                        addView(FrameLayout(activity).apply {  
                            id = R.id.layout_banner_container  
                            setBackgroundResource(android.R.color.black)  
                        }, ConstraintLayout.LayoutParams(ConstraintLayout.LayoutParams.MATCH_PARENT,                 ConstraintLayout.LayoutParams.WRAP_CONTENT).apply {  
                            // 默认放在页面底部  
                            bottomToBottom = ConstraintLayout.LayoutParams.PARENT_ID  
                        })  
                        // 把主页布局添加到新的根布局中  
                        addView(mainPageView, ConstraintLayout.LayoutParams(ConstraintLayout.LayoutParams.MATCH_PARENT, 0).apply {  
                            topToTop = ConstraintLayout.LayoutParams.PARENT_ID  
                            bottomToTop = R.id.layout_banner_container  
                        })  
                        newRootView = this  
                    }, FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT))  
                }  
            }  
        }  
    }  
  
    ...... 
  
    fun showBanner(onTop: Boolean) {  
        bannerAdView?.let { bannerAdView ->  
            bannerAdView.context?.let {  
                if (it is Activity) {  
                    it.runOnUiThread {  
                        if (enableBannerResident) {  
                            createNewRootView(it)  
                        }  
                        // 先从原有父布局中移除  
                        removeViewParent(bannerAdView)  
                        if (bannerAdView.alpha == 0f) {  
                            // 设置为不透明  
                            bannerAdView.alpha = 1f  
                        }  
                        // 添加到根布局中  
                        if (enableBannerResident) {  
                            it.findViewById<FrameLayout>(android.R.id.content).let { rootView ->  
                                rootView.findViewWithTag<ViewGroup>(R.id.layout_main_page_container)?.run {  
                                    updateLayoutParams<ConstraintLayout.LayoutParams> {  
                                        if (onTop) {  
                                            topToBottom = R.id.layout_banner_container  
                                            bottomToBottom = ConstraintLayout.LayoutParams.PARENT_ID  
                                            topToTop = ConstraintLayout.LayoutParams.UNSET  
                                            bottomToTop = ConstraintLayout.LayoutParams.UNSET  
                                        } else {  
                                            topToTop = ConstraintLayout.LayoutParams.PARENT_ID  
                                            bottomToTop = R.id.layout_banner_container  
                                            topToBottom = ConstraintLayout.LayoutParams.UNSET  
                                            bottomToBottom = ConstraintLayout.LayoutParams.UNSET  
                                        }  
                                    }  
                                }  
                                rootView.findViewById<ViewGroup>(R.id.layout_banner_container)?.run {  
                                    updateLayoutParams<ConstraintLayout.LayoutParams> {  
                                        if (onTop) {  
                                            topToTop = ConstraintLayout.LayoutParams.PARENT_ID  
                                            bottomToBottom = ConstraintLayout.LayoutParams.UNSET  
                                        } else {  
                                            bottomToBottom = ConstraintLayout.LayoutParams.PARENT_ID  
                                            topToTop = ConstraintLayout.LayoutParams.UNSET  
                                        }  
                                    }  
                                    addView(bannerAdView)  
                                }  
                            }  
                        } else {  
                            // 设置显示在顶部还是底部  
                            bannerAdView.updateLayoutParams<FrameLayout.LayoutParams> {  
                                gravity = (if (onTop) Gravity.TOP else Gravity.BOTTOM) or Gravity.CENTER_HORIZONTAL  
                            }  
                            it.findViewById<FrameLayout>(android.R.id.content).addView(bannerAdView)  
                        }  
                    }  
                }  
            }  
        }  
    }  
  
    ......
}
  • 测试页面
kotlin 复制代码
class BannerTestActivity : AppCompatActivity() {  
  
    private var bannerOnTop = false  
  
    override fun onCreate(savedInstanceState: Bundle?) {  
        super.onCreate(savedInstanceState)  
        val binding = LayoutBannerTestActivityBinding.inflate(layoutInflater).apply {  
            setContentView(root)  
        }  
  
        val controller = AdMobController().apply {  
            initSdk(this@BannerTestActivity) { succeed ->  
                binding.btnShowBanner.isEnabled = succeed  
                binding.btnHideBanner.isEnabled = succeed  
                binding.btnChangeBannerResidentStatus.isEnabled = succeed  
            }  
        }  
  
        binding.btnShowBanner.setOnClickListener {  
            controller.showBanner(bannerOnTop)  
        }  
        binding.btnHideBanner.setOnClickListener {  
            controller.hideBanner()  
            bannerOnTop = !bannerOnTop  
        }  
        binding.btnChangeBannerResidentStatus.setOnClickListener {  
            controller.enableBannerResident = !controller.enableBannerResident  
        }  
    }  
}

效果如图:

示例

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

ExampleDemo github

ExampleDemo gitee

相关推荐
松仔log7 小时前
Java中级——组合和继承
android·java·开发语言
我命由我1234510 小时前
Jetpack Compose - MaterialExpressiveTheme 与 MaterialTheme、ColorScheme
android·java·开发语言·java-ee·kotlin·android jetpack·android runtime
lilian23311 小时前
Harmony os 技术实战|拼豆制图06:收藏 ID、生成记录与重启恢复怎么不打架
android·java·数据库·harmonyos
2601_9557596211 小时前
ClaudeAPI成本中心与业务标签设计指南
android·java·数据库
余衫马12 小时前
2026最新版!Android Studio 极速安装与配置指南
android·ide·android studio
AFinalStone12 小时前
Android 7系统异常问题排查(五)Framework层(下)—System Server崩溃
android·tombstone·系统异常
AFinalStone12 小时前
Android 7系统异常问题排查(八)系统追踪—Trace机制与性能诊断
android·系统异常
极客猴子15 小时前
Android录音转写频繁卡顿?多款APP长时间会议场景稳定性实测
android·人工智能·智能手机·飞书
Kapaseker16 小时前
Boolean 变量到底该怎么命名?
android·kotlin
AFinalStone16 小时前
Android 7系统异常问题排查(六)应用异常(上)—ANR机制全解
android·系统异常