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

相关推荐
无极程序员30 分钟前
PHP常量
android·ide·android studio
萌面小侠Plus2 小时前
Android笔记(三十三):封装设备性能级别判断工具——低端机还是高端机
android·性能优化·kotlin·工具类·低端机
慢慢成长的码农2 小时前
Android Profiler 内存分析
android
大风起兮云飞扬丶2 小时前
Android——多线程、线程通信、handler机制
android
L72562 小时前
Android的Handler
android
清风徐来辽2 小时前
Android HandlerThread 基础
android
HerayChen3 小时前
HbuildderX运行到手机或模拟器的Android App基座识别不到设备 mac
android·macos·智能手机
顾北川_野3 小时前
Android 手机设备的OEM-unlock解锁 和 adb push文件
android·java
hairenjing11233 小时前
在 Android 手机上从SD 卡恢复数据的 6 个有效应用程序
android·人工智能·windows·macos·智能手机
小黄人软件3 小时前
android浏览器源码 可输入地址或关键词搜索 android studio 2024 可开发可改地址
android·ide·android studio