HarmonyOS 鸿蒙Next中画中画开发求助

HarmonyOS 鸿蒙Next中画中画开发求助 目前正利用画中画开发语音通话的小窗模式。但是画中画无法拦截双击窗口放大能力,画中画中组件的事件也无法响应。

sdk版本是5.0.5(17)

运行在手机、平板和PC上,主要为手机和平板。

需求是希望开启小窗之后能够点击上面的按钮,并且双击小窗不会将窗口放大(希望能够固定宽高)。

目前用画中画实现,代码如下:

private pipController?:PiPWindow.PiPController
private nodeController = new XCNodeController()
initPiP(ctx: Context) {
  if (this.pipController !== null && this.pipController != undefined) {
    return;
  }
  console.info(TAG, 'onPageShow');
  if (!PiPWindow.isPiPEnabled()) {
    console.error(TAG, `picture in picture disabled for current OS`);
    return;
  }

  let config: PiPWindow.PiPConfiguration = {
    context: ctx,
    componentController: new XComponentController(),
    customUIController:this.nodeController,
    templateType: PiPWindow.PiPTemplateType.VIDEO_CALL,
    contentWidth: 1, // 使用typeNode启动画中画时,contentWidth需设置为大于0的值,否则将设置为16:9默认比例
    contentHeight: 2, // 使用typeNode启动画中画时,contentHeight需设置为大于0的值,否则将设置为16:9默认比例

  };
  // 通过create接口创建画中画控制器实例

  PiPWindow.create(config).then((controller: PiPWindow.PiPController) => {
    this.pipController = controller;
    // 通过画中画控制器实例的setAutoStartEnabled接口设置是否需要在应用返回桌面时自动启动画中画
    this.pipController.setAutoStartEnabled(true);
    // 通过画中画控制器实例的on('stateChange')接口注册生命周期事件回调
    this.pipController.on('stateChange', (state: PiPWindow.PiPState, reason: string) => {
      let curState: string = '';
      switch (state) {
        case PiPWindow.PiPState.ABOUT_TO_START:
          curState = 'ABOUT_TO_START';
          break;
        case PiPWindow.PiPState.STARTED:
          curState = 'STARTED';
          break;
        case PiPWindow.PiPState.ABOUT_TO_STOP:
          curState = 'ABOUT_TO_STOP';
          break;
        case PiPWindow.PiPState.STOPPED:
          curState = 'STOPPED';
          break;
        case PiPWindow.PiPState.ABOUT_TO_RESTORE:
          curState = 'ABOUT_TO_RESTORE';
          break;
        case PiPWindow.PiPState.ERROR:
          curState = 'ERROR';
          break;
        default:
          break;
      }
      console.info(`[${TAG}] onStateChange: ${curState}, reason: ${reason}`);
    });
    this.pipController.on("pipWindowSizeChange", (size)=>{
      console.info(`[${TAG}] pipWindowSizeChange: width:${size.width}, height: ${size.height}, scale: ${size.scale}`);
    })
  }).catch((err: ESObject) => {
    console.error(TAG, `Failed to create pip controller. Cause:${err.code}, message:${err.message}`);
  });
}
startPip() {
  this.pipController?.startPiP().then(() => {
    console.info(TAG, `Succeeded in starting pip.`);
  }).catch((err:ESObject) => {
    console.error(TAG, `Failed to start pip. Cause:${err.code}, message:${err.message}`);
  });
}

updateContentSize(width: number, height: number) {
  if (this.pipController) {
    this.pipController.updateContentSize(width, height);
  }
}

// 步骤4:关闭画中画
stopPip() {
  if (this.pipController === null || this.pipController === undefined) {
    return;
  }
  let promise: Promise<void> = this.pipController.stopPiP();
  promise.then(() => {
    console.info(TAG, `Succeeded in stopping pip.`);
  }).catch((err: BusinessError) => {
    console.error(TAG, `Failed to stop pip. Cause:${err.code}, message:${err.message}`);
  });
}

setAutoStart(autoStart: boolean): void {
  this.pipController?.setAutoStartEnabled(autoStart);
}

XNodeController.ets

import { BuilderNode, FrameNode, NodeController, UIContext } from '@kit.ArkUI';

const TAG = 'XCNodeController';

// 创建自定义NodeController
export class XCNodeController extends NodeController {
  private textNode: BuilderNode<[VoiceCallPipParams]> | null = null;

  makeNode(uiContext: UIContext): FrameNode | null {
    this.textNode = new BuilderNode(uiContext)
    this.textNode.build(wrapBuilder(textBuilder), {
      img:''
    } as VoiceCallPipParams)
    return this.textNode.getFrameNode()
  }
  update(img: ResourceStr) {
    this.textNode?.update({ img } as VoiceCallPipParams)
  }
}
export interface VoiceCallPipParams {
  img:ResourceStr
}

@Builder
function textBuilder(param:VoiceCallPipParams){
  Column(){
    Image(param.img)
      .height(95).aspectRatio(1)
      .borderRadius(50)
      .alt($r('app.media.default_head_img'))
    Text("挂断")
  }
  .backgroundColor('#8F69')
  .width('100%')
  .height('100%')
  .gesture(TapGesture({count:2}).onAction(()=>{}))
}

更多关于HarmonyOS 鸿蒙Next中画中画开发求助的实战教程也可以访问 https://www.itying.com/category-93-b0.html

6 回复

开发者您好,双击放缩是目前画中画的规格。

系统根据设定比例自动适配画中画的窗口大小。updateContentSize接口只能更新比例,无法固定宽高。画中画尺寸详细信息您可参考官网文档:画中画窗口尺寸

自定义显示的UI无法响应交互事件。具体可参考官网文档:在画中画内容上方展示自定义UI

如果您期望双击小窗不会将窗口放大(希望能够固定宽高),您方便的话,麻烦您提供下以下信息:

请问您是在语音通话的小窗模式业务场景中使用该能力,点击自定义UI无法响应,双击窗口会将窗口放大,无法固定宽高,方便说明能力不满足可能带来的影响:什么时间用到?是否高频?有无三方库可以做到?若提供该能力,是否会造成大工作量返工?请您注意提供的内容不要包含您或第三方的非公开信息,如给您带来不便,敬请谅解。

更多关于HarmonyOS 鸿蒙Next中画中画开发求助的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


您好,目前放弃使用画中画实现。采用悬浮窗(应用内)加实况窗(应用外)的方案去实现。

好的开发者,后续有任何问题欢迎您随时提问~

遇到同样的问题,楼主有解决吗

鸿蒙Next画中画开发使用UIAbility和WindowStage实现。通过UIAbility的onWindowStageCreate生命周期创建子窗口,调用Window的setWindowMode接口设置窗口模式为WINDOW_MODE_FLOATING。需在module.json5配置abilities的continuable为true并声明ohos.permission.KEEP_BACKGROUND_RUNNING权限。

在HarmonyOS Next中,画中画(PiP)窗口的交互行为主要由系统管理,目前存在以下限制:

  1. 双击放大行为:画中画窗口的双击放大是系统默认行为,无法通过应用层API直接禁用或拦截。这是为了保持跨应用画中画体验的一致性。

  2. 组件事件响应:画中画窗口内的组件事件(如点击、手势)默认可能无法正常响应。这是因为画中画窗口运行在独立的渲染上下文中,与主应用窗口的事件传递机制不同。

针对你的需求,可以尝试以下方案:

方案一:使用自定义手势替代双击textBuilder中,避免依赖双击手势。可以改为使用长按或其他自定义手势组合来触发你的业务逻辑,从而绕过系统的双击放大。

.gesture(
  GestureGroup(
    GestureMode.Exclusive,
    LongPressGesture().onAction(() => {
      // 你的业务逻辑
    })
  )
)

方案二:调整窗口尺寸策略 虽然无法禁用双击放大,但可以通过updateContentSize方法在窗口尺寸变化后尝试恢复预设尺寸。在pipWindowSizeChange事件回调中处理:

this.pipController?.on("pipWindowSizeChange", (size) => {
  console.info(`窗口尺寸变化: width:${size.width}, height: ${size.height}`);
  // 如果检测到非预期的尺寸变化,可以尝试恢复
  if (size.width !== yourTargetWidth || size.height !== yourTargetHeight) {
    this.updateContentSize(yourTargetWidth, yourTargetHeight);
  }
});

方案三:组件事件处理优化 确保画中画内的组件使用明确的事件绑定方式。对于按钮点击,建议:

@Builder
function textBuilder(param: VoiceCallPipParams) {
  Column() {
    Image(param.img)
      // ... 其他属性
      .onClick(() => {
        // 处理图片点击
      })
    
    Text("挂断")
      .onClick(() => {
        // 处理挂断按钮点击
        // 注意:这里可能需要通过Emitter或其他方式将事件传递回主应用
      })
  }
  // ... 其他属性
}

重要注意事项:

  • 画中画窗口中的事件处理可能需要通过EmitterCall机制与主应用通信
  • VIDEO_CALL模板可能有特定的交互限制,可以尝试使用GENERIC模板获得更灵活的控制
  • 确保contentWidthcontentHeight设置为实际需要的比例值,而不是示例中的1:2

目前画中画的能力还在持续演进中,对于更精细的交互控制需求,建议关注后续SDK版本的更新。

回到顶部