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

相关推荐
Estar.Lee2 小时前
查手机号归属地免费API接口教程
android·网络·后端·网络协议·tcp/ip·oneapi
温辉_xh2 小时前
uiautomator案例
android
工业甲酰苯胺3 小时前
MySQL 主从复制之多线程复制
android·mysql·adb
少说多做3434 小时前
Android 不同情况下使用 runOnUiThread
android·java
Estar.Lee5 小时前
时间操作[计算时间差]免费API接口教程
android·网络·后端·网络协议·tcp/ip
找藉口是失败者的习惯6 小时前
从传统到未来:Android XML布局 与 Jetpack Compose的全面对比
android·xml
Jinkey7 小时前
FlutterBasic - GetBuilder、Obx、GetX<Controller>、GetxController 有啥区别
android·flutter·ios
大白要努力!9 小时前
Android opencv使用Core.hconcat 进行图像拼接
android·opencv
天空中的野鸟9 小时前
Android音频采集
android·音视频
小白也想学C11 小时前
Android 功耗分析(底层篇)
android·功耗