最近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中添加。