鸿蒙HarmonyOS开发实战案例:子窗口实现app应用内悬浮窗

随笔2周前发布 祥祥
49 0 0

应用经常会遇到如下的业务诉求:

场景一:通过事件添加和移除悬浮窗,悬浮窗样式可定制(暂定两种,无白边圆球形和小视频播放窗口类型),可代码修改位置和布局。

场景二:创建悬浮窗后,主窗口的系统侧滑返回事件可正常使用。

场景三:可响应正常点击事件,可通过触发拖动使悬浮窗的移动,根据最后手势停留位置,做动画靠屏幕左或靠右显示,跳转和返回上级页面后悬浮窗依然存在,且相对手机屏幕位置不变。

场景四:悬浮窗内组件事件触发主窗口的页面跳转(Router和Navigation两种都要有)。

场景五:悬浮窗的窗口大小自适应组件,子窗口中页面设置了宽高,需要让子窗口自适应页面组件大小。

场景六:支持控制悬浮窗隐藏和销毁。

场景七:视频类应用主动调用画中画完成后台播放,以及返回桌面时自动启动画中画。

方案描述

场景一:

通过事件添加和移除悬浮窗,悬浮窗样式可定制(暂定两种,无白边圆球形和小视频播放窗口类型),可代码修改位置和布局。

效果图

鸿蒙HarmonyOS开发实战案例:子窗口实现app应用内悬浮窗

方案

通过子窗口创建windowStage.createSubWindow(‘mySubWindow’),和windowClass.setWindowLayoutFullScreen去除白边。

核心代码




在EntryAbility中获取WindowStage。


 


onWindowStageCreate(windowStage: window.WindowStage): void {


 


  // Main window is created, set main page for this ability


 


  hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');


 


 


 


  windowStage.loadContent('pages/Page', (err, data) => {


 


  if (err.code) {


 


  hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');


 


  return;


 


}


 


// 保存窗口管理器


 


AppStorage.setOrCreate("windowStage", windowStage);


 


hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? '');


 


});


 


}

创建子窗口,子窗口样式由子窗口加载的页面组件样式决定。




this.windowStage.createSubWindow("mySubWindow", (err, windowClass) => {


 


  if (err.code > 0) {


 


    console.error("failed to create subWindow Cause:" + err.message)


 


    return;


 


  }


 


  try {


 


    // 设置子窗口加载页


 


    windowClass.setUIContent("pages/MySubWindow", () => {


 


      windowClass.setWindowBackgroundColor("#00000000")


 


    });


 


    // 设置子窗口左上角坐标


 


    windowClass.moveWindowTo(0, 200)


 


    // 设置子窗口大小


 


    windowClass.resize(vp2px(75), vp2px(75))


 


    // 展示子窗口


 


    windowClass.showWindow();


 


    // 设置子窗口全屏化布局不避让安全区


 


    windowClass.setWindowLayoutFullScreen(true);


 


  } catch (err) {


 


    console.error("failed to create subWindow Cause:" + err)


 


  }


 


})

场景二:

创建悬浮窗后,主窗口的系统侧滑返回事件可正常使用。

效果图

鸿蒙HarmonyOS开发实战案例:子窗口实现app应用内悬浮窗

方案

通过window.shiftAppWindowFocus转移窗口焦点实现创建子窗口后,主窗口依然可以响应事件。核心代码

在子窗口中将焦点转移到主窗口。




onPageShow(): void {


 


  setTimeout(() => {


 


  // 获取子窗口ID


 


  let subWindowID: number = window.findWindow("mySubWindow").getWindowProperties().id


 


  // 获取主窗口ID


 


  let mainWindowID: number = this.windowStage.getMainWindowSync().getWindowProperties().id


 


  // 将焦点从子窗口转移到主窗口


 


  window.shiftAppWindowFocus(subWindowID, mainWindowID)


 


}, 500)


 


}

场景三:

可响应正常点击事件,可通过拖动触发悬浮窗的拖拽移动,根据最后手势停留位置,做动画靠屏幕左或靠右显示,跳转和返回上级页面后悬浮窗依然存在,且相对手机屏幕位置不变。

效果图

鸿蒙HarmonyOS开发实战案例:子窗口实现app应用内悬浮窗

方案

通过设置手势顺序模式识别PanGesture,实现拖拽悬浮窗。

核心代码

创建Position。




interface Position {


 


  x: number,


 


  y: number


 


}

设置拖拽选项。

private panOption: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.All });

通过在子窗口父组件绑定拖拽动作完成悬浮窗坐标移动。




.gesture(


 


  // 声明该组合手势的类型为Sequence类型


 


  PanGesture(this.panOption)


 


    .onActionStart((event: GestureEvent) => {


 


      console.info('Pan start');


 


    })// 发生拖拽时,获取到触摸点的位置,并将位置信息传递给windowPosition


 


    .onActionUpdate((event: GestureEvent) => {


 


      this.windowPosition.x += event.offsetX;


 


      this.windowPosition.y += event.offsetY;


 


 


 


      this.subWindow.moveWindowTo(this.windowPosition.x, this.windowPosition.y)


 


    })


 


    .onActionEnd((event: GestureEvent) => {


 


      // 贴边判断


 


      if (event.offsetX > 0) {


 


        this.windowPosition.x = display.getDefaultDisplaySync().width - this.subWindow.getWindowProperties()


 


          .windowRect


 


          .width;


 


      } else if (event.offsetX < 0) {


 


        this.windowPosition.x = 0;


 


      }


 


      this.subWindow.moveWindowTo(this.windowPosition.x, this.windowPosition.y)


 


      console.info('Pan end');


 


    })


 


)

场景四:

悬浮窗内组件事件触发主窗口的页面跳转(Router和Navigation两种都要有)。

鸿蒙HarmonyOS开发实战案例:子窗口实现app应用内悬浮窗

鸿蒙HarmonyOS开发实战案例:子窗口实现app应用内悬浮窗

方案

通过获取窗口上下文,实现在悬浮窗点击后,实现主窗口Router跳转。

通过配置NavPathStack全局变量,实现主窗口navigation跳转 。

核心代码

通过windowStage获取主窗口的Router,实现主窗口的Router跳转。




.onClick((event: ClickEvent) => {


 


  this.windowStage.getMainWindowSync()


 


    .getUIContext()


 


    .getRouter()


 


    .back()


 


})

通过AppStorage获取NavPathStack,实现主窗口navigation跳转。




.onClick((event: ClickEvent) => {


 


  let navPath = AppStorage.get("pageInfos") as NavPathStack;


 


  navPath.pushPath({ name: 'pageOne' })


 


})

场景五:

悬浮窗的窗口大小自适应组件,子窗口中页面设置了宽高,需要让子窗口自适应页面组件大小。

效果图

鸿蒙HarmonyOS开发实战案例:子窗口实现app应用内悬浮窗

方案

通过监听通用事件ComponentObserver,设置window的resize调整窗口大小。

核心代码

查找子窗口。

@State subWindow: window.Window = window.findWindow("mySubWindow");

注册监听事件。




//监听id为COMPONENT_ID的组件回调事件listener: inspector.ComponentObserver = inspector.createComponentObserver('COMPONENT_ID');


 


通过onClick()事件,实现对组件变化的监听。


 


if (this.flag) {


 


  Image($r("app.media.voice2"))


 


    .id("COMPONENT_ID")


 


    .borderRadius(5)


 


    .width(75)


 


    .height(75)


 


    .onClick(() => {


 


      // 设置图标切换标识


 


      this.flag = !this.flag


 


      this.listener.on('layout', () => {


 


        // 监听布局变更后调整子窗大小


 


        this.subWindow.resize(componentUtils.getRectangleById("COMPONENT_ID").size.width,


 


          componentUtils.getRectangleById("COMPONENT_ID").size.height)


 


      })


 


    })


 


} else {


 


  Image($r("app.media.voice"))


 


    .id("COMPONENT_ID")


 


    .borderRadius(50)


 


    .width(100)


 


    .height(100)


 


    .onClick(() => {


 


      this.flag = !this.flag


 


      this.listener.on('layout', () => {


 


        this.subWindow.resize(componentUtils.getRectangleById("COMPONENT_ID").size.width,


 


          componentUtils.getRectangleById("COMPONENT_ID").size.height)


 


      })


 

场景六:

支持控制悬浮窗隐藏和销毁。

效果图

鸿蒙HarmonyOS开发实战案例:子窗口实现app应用内悬浮窗

方案

通过设置窗口windowClass.minimize和windowClass.destroyWindow,实现悬浮窗的隐藏和销毁。

核心代码

通过调用minimize,实现子窗口最小化。




.onClick((event: ClickEvent) => {


 


  this.subWindow.minimize()


 


})

通过实现destroyWindow,实现子窗口的资源销毁。




// 通过查找子窗口名称对子窗口进行销毁


 


window.findWindow("mySubWindow").destroyWindow()

场景七:

视频类应用主动调用画中画完成后台播放,以及返回桌面时自动启动画中画。

效果图

鸿蒙HarmonyOS开发实战案例:子窗口实现app应用内悬浮窗

方案

1.通过pipController.startPiP()完成主动调用画中画功能。

2.通过pipController.setAutoStartEnabled(true)在返回桌面时完成全局画中画播放。

核心代码

创建XComponent组件。




XComponent({ id: 'pipDemo', type: 'surface', controller: this.mXComponentController })


 


  .onLoad(() => {


 


    this.surfaceId = this.mXComponentController.getXComponentSurfaceId();


 


    // 需要设置AVPlayer的surfaceId为XComponentController的surfaceId


 


    this.player = new AVPlayerDemo(this.surfaceId);


 


    this.player.avPlayerFdSrcDemo();


 


  })


 


  .onDestroy(() => {


 


    console.info(`[${TAG}] XComponent onDestroy`);


 


  })


 


  .size({ width: '100%', height: '800px' })


 


创建pipWindowController和startPip方法。


 


startPip() {


 


  if (!pipWindow.isPiPEnabled()) {


 


    console.error(`picture in picture disabled for current OS`);


 


    return;


 


  }


 


  let config: pipWindow.PiPConfiguration = {


 


    context: getContext(this),


 


    componentController: this.mXComponentController,


 


    // 当前page导航id


 


    navigationId: this.navId,


 


    // 对于视频通话、视频会议等场景,需要设置相应的模板类型


 


    templateType: pipWindow.PiPTemplateType.VIDEO_PLAY,


 


    // 可选,创建画中画控制器时系统可通过XComponent组件大小设置画中画窗口比例


 


    contentWidth: 800,


 


    // 可选,创建画中画控制器时系统可通过XComponent组件大小设置画中画窗口比例


 


    contentHeight: 600,


 


  };


 


  // 步骤1:创建画中画控制器,通过create接口创建画中画控制器实例


 


  let promise: Promise<pipWindow.PiPController> = pipWindow.create(config);


 


  promise.then((controller: pipWindow.PiPController) => {


 


    this.pipController = controller;


 


    // 步骤1:初始化画中画控制器


 


    this.initPipController();


 


    // 步骤2:通过startPiP接口启动画中画


 


    this.pipController.startPiP().then(() => {


 


      console.info(`Succeeded in starting pip.`);


 


    }).catch((err: BusinessError) => {


 


      console.error(`Failed to start pip. Cause:${err.code}, message:${err.message}`);


 


    });


 


  }).catch((err: BusinessError) => {


 


    console.error(`Failed to create pip controller. Cause:${err.code}, message:${err.message}`);


 


  });


 


}

初始化pipWindowController。




initPipController() {


 


  if (!this.pipController) {


 


    return;


 


  }


 


  // 通过setAutoStartEnabled接口设置是否需要在应用返回桌面时自动启动画中画,注册stateChange和controlPanelActionEvent回调


 


  this.pipController.setAutoStartEnabled(true/*or true if necessary*/); // 默认为false


 


  this.pipController.on('stateChange', (state: pipWindow.PiPState, reason: string) => {


 


    this.onStateChange(state, reason);


 


  });


 


  this.pipController.on('controlPanelActionEvent', (event: pipWindow.PiPActionEventType) => {


 


    this.onActionEvent(event);


 


  });


 


}

完成画中画播放使用stopPip方法停止。




stopPip() {


 


  if (this.pipController) {


 


    let promise: Promise<void> = this.pipController.stopPiP();


 


    promise.then(() => {


 


      console.info(`Succeeded in stopping pip.`);


 


      this.pipController?.off('stateChange'); // 如果已注册stateChange回调,停止画中画时取消注册该回调


 


      this.pipController?.off('controlPanelActionEvent'); // 如果已注册controlPanelActionEvent回调,停止画中画时取消注册该回调


 


    }).catch((err: BusinessError) => {


 


      console.error(`Failed to stop pip. Cause:${err.code}, message:${err.message}`);


 


    });


 


  }


 


}

其他常见问题

Q:windowStage怎么获取?

A:WindowStage需要在EntryAbility中的onWindowStageCreate中用AppStorage.setOrCreate()获取。

Q:子窗口可以用于应用外么?

A:子窗口只能在应用内使用。

Q:子窗口的默认大小是多大?

A:子窗口默认不设置大小的话是除安全区外的屏幕区域。

Q:UIExtension可以用子窗口么?

A:UIExtension不是窗口对象,没有办法调用窗口接口。

Q:Har和Hsp中可以使用子窗口么?

A:只要能获取到windowStage就能创建并使用子窗口。

最后

小编在之前的鸿蒙系统扫盲中,有很多朋友给我留言,不同的角度的问了一些问题,我明显感觉到一点,那就是许多人参与鸿蒙开发,但是又不知道从哪里下手,因为资料太多,太杂,教授的人也多,无从选择。有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以有一份实用的鸿蒙(HarmonyOS NEXT)文档用来跟着学习是非常有必要的。 

为了确保高效学习,建议规划清晰的学习路线,涵盖以下关键阶段:

希望这一份鸿蒙学习文档能够给大家带来帮助~


 鸿蒙(HarmonyOS NEXT)最新学习路线

鸿蒙HarmonyOS开发实战案例:子窗口实现app应用内悬浮窗

该路线图包含基础技能、就业必备技能、多媒体技术、六大电商APP、进阶高级技能、实战就业级设备开发,不仅补充了华为官网未涉及的解决方案

路线图适合人群:

IT开发人员:想要拓展职业边界
零基础小白:鸿蒙爱好者,希望从0到1学习,增加一项技能。
技术提升/进阶跳槽:发展瓶颈期,提升职场竞争力,快速掌握鸿蒙技术

2.视频教程+学习PDF文档

(鸿蒙语法ArkTS、TypeScript、ArkUI教程……)

鸿蒙HarmonyOS开发实战案例:子窗口实现app应用内悬浮窗

 纯血版鸿蒙全套学习文档(面试、文档、全套视频等)

                   鸿蒙HarmonyOS开发实战案例:子窗口实现app应用内悬浮窗

鸿蒙APP开发必备

鸿蒙HarmonyOS开发实战案例:子窗口实现app应用内悬浮窗​​

总结

参与鸿蒙开发,你要先认清适合你的方向,如果是想从事鸿蒙应用开发方向的话,可以参考本文的学习路径,简单来说就是:为了确保高效学习,建议规划清晰的学习路线鸿蒙HarmonyOS开发实战案例:子窗口实现app应用内悬浮窗

© 版权声明

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...