Harmony OS搭建广告展示页
有些app需要广告页,有的不需要,我们需要搞个配置。
1. 通过首选项配置存储我们的常用配置,比如是否需要广告页、广告页的路由地址、点击广告页跳转的链接以及广告页倒计时。
1.1 新建一个关于广告页的数据模型。
export class AdvertClass {
showAd: boolean = false // 是否展示广告
isFull: boolean = true // 是否全屏
adTime: number = 5 // 倒计时数据
adUrl?: string = "" // 要跳转的连接
adImg?: ResourceStr = "" // 图片连接
}
1.2 新建一个关于读取首选项的类,用于读取和设置首选项的广告设置
import { preferences } from '@kit.ArkData'
import { USER_SETTING, USER_SETTING_AD } from '../constants'
import { AdvertClass } from '../viewmodels'
export const defaultAd: AdvertClass = {
showAd: true,
isFull: true,
adTime: 3,
adImg: $r("app.media.start")
}
// 负责首选项的读取
export class UserSetting {
context?: Context
// 获取仓库
getStore () {
return preferences.getPreferencesSync(this.context || getContext(), {
name: USER_SETTING
})
}
// 设置用户广告
async setUserAd(ad: AdvertClass) {
const store = this.getStore()
store.putSync(USER_SETTING_AD, ad)
await store.flush() // 让外界能够控制自己的流程
}
// 获取用户广告
getUserAd() {
const store = this.getStore()
return store.getSync(USER_SETTING_AD, defaultAd) as AdvertClass
}
}
export const userSetting = new UserSetting() // 导出一个单例
上面还用到了两个常量,我们需要在constants目录下定义一个文件专门用来记录setting
export const USER_SETTING = 'fast_driver_setting' // 用来存储用户设置的首选项的key
export const USER_SETTING_AD = 'fast_driver_setting_ad' // 用来存储用户设置广告首选项的key
1.3 在ability中判断
async onWindowStageCreate(windowStage: window.WindowStage): Promise<void> {
// Main window is created, set main page for this ability
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
userSetting.context = this.context // 首选项赋值上下文
// 发起一个向云端获取广告数据的请求
const ad = await new Promise<AdvertClass>((resolve, reject) => {
setTimeout(() => {
resolve(cloudAd)
}, 500)
})
await userSetting.setUserAd(ad) // 将广告设置到首选项
// 此时应该判断是不是展示广告?
if (ad.showAd) {
windowStage.loadContent('pages/Start/Start');
} else {
windowStage.loadContent('pages/Index');
}
}
1.4 模拟一个请求,给一个默认广告,写入首选项-正常加载主页
实现start页的页面结构及倒计时逻辑
import { AdvertClass, userSetting } from 'basic'
import { router } from '@kit.ArkUI'
@Entry
@Component
struct Start {
// 需要广告对象
@State
ad: AdvertClass = new AdvertClass()
timer: number = -1 // 用来记录定时器的标记
// @State
// ad: Partial<AdvertClass> = {}
aboutToAppear(): void {
// 获取首选项的广告数据给到ad
this.getAdInfo()
}
getAdInfo() {
// 首选项的读取
this.ad = userSetting.getUserAd()
// 开启倒计时了
if (this.ad.showAd) {
// 如果真的展示广告要开始倒计时
this.timer = setInterval(() => {
if (this.ad.adTime === 0) {
clearInterval(this.timer)
this.toMain()
return // return一定要写
}
this.ad.adTime--
}, 1000)
}
}
// 去主页的方法
toMain() {
router.replaceUrl({
url: 'pages/Index'
})
}
aboutToDisappear(): void {
clearInterval(this.timer)
}
build() {
RelativeContainer() {
if (this.ad.showAd) {
Image(this.ad.adImg)
.width("100%")
.height("100%")
.objectFit(ImageFit.Cover)
Text(`${this.ad.adTime}秒跳过`)
.padding({
left: 10,
right: 10
})
.alignRules({
right: {
anchor: '__container__',
align: HorizontalAlign.End
},
top: {
anchor: '__container__',
align: VerticalAlign.Top
}
})
.borderRadius(15)
.height(30)
.fontSize(14)
.backgroundColor($r("app.color.background_page"))
.margin({
right: 20,
top: 20
})
.onClick(() => {
// 此时跳过广告
// 跳转到主页
this.toMain()
})
}
}
.height('100%')
.width('100%')
}
}
1.5 使用子窗口模式加载广告
我们可以使用windowStage的createSubWindow来实现当前页面上创建一个窗口
if (result.showAd) {
const win = await windowStage.createSubWindow("ad_window")
await win.showWindow()
win.setUIContent("pages/Start/Start")
}
2. window窗口广告模式
封装window窗口广告模式
import { display, window } from '@kit.ArkUI'
import { util } from '@kit.ArkTS'
export class AdManager {
context?: Context // 是给ability使用的
private winNames: string [] = []
// 展示广告 采用windows窗口的创建和销毁的方式
async showAd(url: string, width?: number, height?: number) {
if (url) {
let name = `win_${util.generateRandomUUID()}`
const win = await window.createWindow({
name,
windowType: window.WindowType.TYPE_DIALOG,
ctx: this.context || getContext()
})
if (width && width >= 320 && height && height >= 240) {
const screen = display.getDefaultDisplaySync()
let mainWidth = vp2px(width)
let mainHeight = vp2px(height)
win.resizeAsync(mainWidth, mainHeight)
win.moveWindowToAsync((screen.width - mainWidth) / 2, (screen.height - mainHeight) / 2)
}
await win.showWindow() // 展示窗口
win.setUIContent(url) // 设置地址
this.winNames.push(name)
return name
}
return ""
}
// 关闭广告
async closeAd(name?: string) {
if (name) {
window.findWindow(name).destroyWindow()
this.winNames = this.winNames.filter(item => item !== name) //清空数组内容
} else {
// 不传就认为 想关闭所有
let index = 0
while (index < this.winNames.length) {
await window.findWindow(this.winNames[index]).destroyWindow()
index++
}
this.winNames = [] // 清空数组
}
}
}
export const adManger = new AdManager()
Start页面代码
import { adManger, AdvertClass, userSetting } from 'basic'
import { router, window } from '@kit.ArkUI'
@Entry
@Component
struct Start {
// 需要广告对象
@State
ad: AdvertClass = new AdvertClass()
timer: number = -1 // 用来记录定时器的标记
// @State
// ad: Partial<AdvertClass> = {}
aboutToAppear(): void {
// 获取首选项的广告数据给到ad
this.getAdInfo()
}
getAdInfo() {
// 首选项的读取
this.ad = userSetting.getUserAd()
// 开启倒计时了
if (this.ad.showAd) {
// 如果真的展示广告要开始倒计时
this.timer = setInterval(() => {
if (this.ad.adTime === 0) {
clearInterval(this.timer)
this.toMain()
return // return一定要写
}
this.ad.adTime--
}, 1000)
}
}
// 去主页的方法
toMain() {
// router.replaceUrl({
// url: 'pages/Index'
// })
// 销毁当前的窗口
clearInterval(this.timer) // 先清理一下定时器
adManger.closeAd()
}
aboutToDisappear(): void {
clearInterval(this.timer)
}
build() {
RelativeContainer() {
if (this.ad.showAd) {
Image(this.ad.adImg)
.width("100%")
.height("100%")
.objectFit(ImageFit.Cover)
Text(`${this.ad.adTime}秒跳过`)
.padding({
left: 10,
right: 10
})
.alignRules({
right: {
anchor: '__container__',
align: HorizontalAlign.End
},
top: {
anchor: '__container__',
align: VerticalAlign.Top
}
})
.borderRadius(15)
.height(30)
.fontSize(14)
.backgroundColor($r("app.color.background_page"))
.margin({
right: 20,
top: 20
})
.onClick(() => {
// 此时跳过广告
// 跳转到主页
this.toMain()
})
}
}
.height('100%')
.width('100%')
}
}
EntryAbility完整代码
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';
import { adManagerFinal, adManger, AdvertClass, userSetting } from 'basic';
// 云端广告
const cloudAd: AdvertClass = {
showAd: true,
isFull: false,
adTime: 100,
adImg: $r("app.media.start")
}
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
}
onDestroy(): void {
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy');
}
async onWindowStageCreate(windowStage: window.WindowStage): Promise<void> {
// Main window is created, set main page for this ability
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
userSetting.context = this.context // 首选项赋值上下文
// 发起一个向云端获取广告数据的请求
const ad = await new Promise<AdvertClass>((resolve, reject) => {
setTimeout(() => {
resolve(cloudAd)
}, 500)
})
await userSetting.setUserAd(ad) // 将广告设置到首选项
// 此时应该判断是不是展示广告?
// 1. 第一种页面方式
// if (ad.showAd) {
// windowStage.loadContent('pages/Start/Start');
// } else {
// windowStage.loadContent('pages/Index');
// }
// 2. 第二种window窗口模式
// if (ad.showAd) {
// const win = await windowStage.createSubWindow("ad_win") // 二级窗口的实际对象
// await win.showWindow() // 展示二级窗口
// win.setUIContent("pages/Start/Start")
// }
await windowStage.loadContent('pages/Index'); // 必须等到有了UIContext才可以使用
if (ad.showAd) {
adManagerFinal.context = this.context
// adManger.context = this.context
// await adManger.showAd("pages/Start/Start", 330, 440)
adManagerFinal.showAd(ad) // 展示广告
}
}
onWindowStageDestroy(): void {
// Main window is destroyed, release UI related resources
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
}
onForeground(): void {
// Ability has brought to foreground
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground');
}
onBackground(): void {
// Ability has back to background
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground');
}
}
3. 还可采用与页面解耦的方式实现广告
import { AdvertClass } from '../viewmodels'
import { ComponentContent, promptAction, window } from '@kit.ArkUI'
import { util } from '@kit.ArkTS'
// 展示广告的结构最重要写的代码
@Builder
function AdBuilder(ad: AdvertClass) {
Column() {
Image(ad.adImg)
.width("100%")
.height("100%")
.objectFit(ImageFit.Cover)
.borderRadius(10)
Row() {
Image($r("app.media.ic_btn_close"))
.width(14)
.aspectRatio(1)
.fillColor("#ccc")
}
.width(30)
.aspectRatio(1)
.justifyContent(FlexAlign.Center)
.borderRadius(15)
.border({
color: '#ff343232',
width: 2
})
.margin({
top: 40
})
.onClick(() => {
if (ad.dialogName) {
adManagerFinal.closeAd(ad.dialogName) // ? name从哪里进来
}
})
}
.width(ad.isFull ? "100%" : "80%")
.height(ad.isFull ? "100%" : "50%")
}
export class AdManagerFinal {
context?: Context
// 所有的弹窗都放到这个map中 通过name来标识
private map: Map<string, ComponentContent<AdvertClass>> = new Map()
// 实际上需要广告
async showAd(ad: AdvertClass) {
// 按照文档实现
// UIContext上下文必须得等到页面初始化之后才可以进行获取
// 生成一个name
let name = `dialog_${util.generateRandomUUID()}`
// 通过当前的主窗口来获取
const mainWin = await window.getLastWindow(this.context || getContext())
let uiContext = mainWin.getUIContext() // 拿到UIContext
let promptAction = uiContext.getPromptAction();
ad.dialogName = name // 目的是将dialog的弹窗名称传递到builder中
let contentNode = new ComponentContent(uiContext, wrapBuilder(AdBuilder), ad);
let options: promptAction.BaseDialogOptions = {
alignment: DialogAlignment.Center,
autoCancel: false
};
this.map.set(name, contentNode) // 将key/value写入到map中
promptAction.openCustomDialog(contentNode, options);
// 一般半屏广告 是得用户手动点击才能关闭的 一般不会自动关闭
// setTimeout(() => {
// promptAction.closeCustomDialog(contentNode)
// }, 2000)
}
async closeAd(name: string) {
if (name) {
const mainWin = await window.getLastWindow(this.context || getContext())
let uiContext = mainWin.getUIContext() // 拿到UIContext
let promptAction = uiContext.getPromptAction();
promptAction.closeCustomDialog(this.map.get(name))
// 清理map
this.map.delete(name) // 删除已经关闭的弹窗
}
}
}
export const adManagerFinal = new AdManagerFinal()