基于子窗口实现应用内悬浮窗

基于子窗口实现应用内悬浮窗

HarmonyOS
2024-06-11 23:12:01
浏览
收藏 0
回答 1
待解决
回答 1
按赞同
/
按时间
richard_li_li

场景一:

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

方案

通过子窗口创建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) 
  } 
})

场景二:

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

方案

通过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) 
}

场景三:

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

方案

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

核心代码

创建Position。

interface Position { 
  x: number, 
  y: number 
}

设置拖拽选项。

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两种都要有)。

方案

通过获取窗口上下文,实现在悬浮窗点击后,实现主窗口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' }) 
})

场景五:

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

方案

通过监听通用事件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) 
      }) 
    }) 
}

场景六:

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

方案

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

核心代码

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

.onClick((event: ClickEvent) => { 
  this.subWindow.minimize() 
})

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

// 通过查找子窗口名称对子窗口进行销毁 
window.findWindow("mySubWindow").destroyWindow()

场景七:

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

方案

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}`); 
    }); 
  } 
}
分享
微博
QQ
微信
回复
2024-06-12 23:15:51
相关问题
使用悬浮和端want唤醒APP
819浏览 • 1回复 待解决
使用dialog窗口实现红包雨窗口创建
694浏览 • 1回复 待解决
HarmonyOS桌面小窗口实现方案
358浏览 • 1回复 待解决
如何实现悬浮桌面穿透
729浏览 • 1回复 待解决
横屏应用如何适配华为悬浮?
2515浏览 • 1回复 待解决
HarmonyOS有没有悬浮组件或者库
323浏览 • 1回复 待解决
HarmonyOS 悬浮拖拽功能怎么处理?
210浏览 • 1回复 待解决
应用窗口的开发流程是什么
1820浏览 • 1回复 待解决
如何实现一个页面显示窗口
541浏览 • 1回复 待解决
HarmonyOS 悬浮的文档在哪儿呢?
167浏览 • 1回复 待解决
在hsp子模块中如何加载悬浮页面
1787浏览 • 1回复 待解决
HarmonyOS 多模块下悬浮显示不出来
279浏览 • 1回复 待解决