HarmonyOS 鸿蒙Next 实况窗能力

发布于 1周前 作者 bupafengyu 最后一次编辑是 5天前 来自 鸿蒙OS

HarmonyOS 鸿蒙Next 实况窗能力

故事描述

便了么是一款通过让用户每日记录、监测、分析排便情况,继而为用户健康建议和方案的应用程序。大家可能不知道的是如果你蹲便的时间过长,身体有可能出问题,我们需要在用户打开便了么开始便便的时候,需要能有一个地方能够提醒用户现在蹲了多少时间了。

解决方案

我们使用了鸿蒙的实况窗能力,下图第一幅点开始便便,然后切换到桌面或者去玩其他的app,在左上角会出现实况窗胶囊,显示现在便便多久了。然后你点一下胶囊会弹出右图更清楚的便便计时和提醒你不要蹲太久的贴心提示,这样对用户就很友好了!

WechatIMG779.jpg

我们来看看实况窗如何实现,不过首先你要实现实况窗这个特性,需要先看一下你的应用是否符合申请实况窗的条件,华为对特定的类型应用才能申请实况窗

场景类型

EVENT取值

场景描述

适用范围

出行打车

TAXI

用户线上约车后,向用户展示司机接驾等待时间、行程中的剩余距离和时间等信息。

适用于网约车、出租车、拼车、顺风车等场景。

即时配送

DELIVERY

指配送员将餐品、商品送达到用户指定地点的业务场景,通常在较短时间内完成配送环节。

适用于外卖、生鲜配送、同城配送等场景。

航班

FLIGHT

用户主动关注某个航班时,向用户展示航班的关键变动,如航班开始登机、航班起飞、航班延误、航班取消、航班到达等关键场景。

适用于用户通过航班出行或者主动关注某个航班进展的场景。

高铁/火车

TRAIN

用户通过高铁、火车出行,向用户展示检票口、座位号、车次信息及列车运行状态等信息。

适用于高铁出行、火车出行的场景。

排队

QUEUE

需要通过排队叫号的方式,按顺序为用户提供服务的业务场景。

适用于办事大厅、医院、银行、餐饮等排队叫号能力场景。

取餐

PICK_UP

指的是用户完成餐品/商品下单后,自行取餐或者取件的场景。

适用于餐饮线下取餐提醒,包括餐品排队情况、制作进度、取餐提醒等。

赛事比分

SCORE

展示比赛双方成绩变化情况。

适用于游戏赛事、体育赛事等展示比分变化情况的场景。

共享租赁

RENT

用户使用临时租赁服务时,向用户展示实时租赁时长和费用等租赁状态信息的场景。

适用于共享单车、共享充电宝、停车场临时停车等场景。

计时

TIMER

用户在某个短时间段持续的正计时或任务前的倒计时场景。

适用于专注时刻、番茄时钟、抢票倒计时提醒场景,仅限于工具类应用申请(计时场景仅支持通过端侧创建与更新)。

运动锻炼

WORKOUT

运动过程中,向用户实时展示运动的时长和进度等信息。

适用于户外或室内的运动记录,如跑步、骑行等。

导航

NAVIGATION

用户使用导航服务时,展示将要发生的路线变化。

适用于步行导航、骑行导航、车辆导航。

如果你的应用类型在上述的列表中,就可以参考这个链接申请实况窗去申请,便了么这个功能属于计时类应用的功能,所以去申请了实况窗,目前实况窗控制还比较严格,申请即使通过也是先放给你测试的权限,等你测试通过后还需要一次正式的验收才会有正式的权限,不过审核时间周期都不长。

你首先需要构建的是一个实况窗控制器类,这里我直接把整体代码贴出来,如果你和我一样是计时器类,粘过去改改就行了</p

/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { liveViewManager } from '@kit.LiveViewKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { ContextUtil } from './ContextUtil';
import { getAvatarName, getUserNickname } from './UserFileHelper';
import { AvatarEnum } from './CommonConstant';

export class TimerController {
  private static defaultView: liveViewManager.LiveView | undefined = undefined;
  private static capsuleColor: string = '#FFFBCF2F';
  private static pickUpColor: string = '#FFFFD700';
  // private static resourceManager = ContextUtil.applicationContext.resourceManager;
  private static isCountdown: boolean = false;

  public async startLiveView(isCountdown: boolean = false): Promise<void> {
    //live view is disabled
    console.log('********startLIveView')
    if (!await TimerController.isLiveViewEnabled()) {
      console.log(`liveview disable`)
      return;
    }

    console.log('********startLiveView2')

    //update liveView parameters
    TimerController.isCountdown = isCountdown;
    TimerController.defaultView = await TimerController.buildDefaultView(TimerController.isCountdown);
    //start liveView
    try {
      console.log(`startLiveView start`)
      const result = await liveViewManager.startLiveView(TimerController.defaultView);
      console.log(`startLiveView result=${result}`)
    } catch (e) {
      console.log(`liveview=${e}`)
      const err: BusinessError = e as BusinessError;
    }
  }

  public async pauseTimer(): Promise<void> {
    try {
      //is liveView parameters initialized
      if (!TimerController.defaultView) {
        return;
      }
      //update liveView parameters
      TimerController.defaultView.timer = {
        isPaused: true
      }
      TimerController.defaultView.liveViewData.primary.title = '已暂停'
      TimerController.defaultView.liveViewData.capsule = {
        type: liveViewManager.CapsuleType.CAPSULE_TYPE_TIMER,
        status: 1,
        isPaused: true,
      }
      if (TimerController.defaultView.sequence) {
        TimerController.defaultView.sequence += 1;
      }
      //update liveView
      await liveViewManager.updateLiveView(TimerController.defaultView);
    } catch (e) {
      const err: BusinessError = e as BusinessError;
    }
  }

  public async continueTimer(): Promise<void> {
    try {
      //is liveView parameters initialized
      if (!TimerController.defaultView) {
        return;
      }
      //update liveView parameters
      TimerController.defaultView.timer = {
        isPaused: false
      }
      TimerController.defaultView.liveViewData.primary.title = '计时中'
      TimerController.defaultView.liveViewData.capsule = {
        type: liveViewManager.CapsuleType.CAPSULE_TYPE_TIMER,
        status: 1,
        isPaused: false,
      }
      if (TimerController.defaultView.sequence) {
        TimerController.defaultView.sequence += 1;
      }
      //update liveView
      await liveViewManager.updateLiveView(TimerController.defaultView);
    } catch (e) {
      const err: BusinessError = e as BusinessError;
    }
  }

  public async stopLiveView(): Promise<void> {
    try {
      //live view is disabled
      if (!await TimerController.isLiveViewEnabled() || !TimerController.defaultView) {
        return;
      }
      //update liveView parameters
      if (TimerController.defaultView.sequence) {
        TimerController.defaultView.sequence += 1;
      }
      TimerController.defaultView.liveViewData.primary.title = '已结束'
      TimerController.defaultView.liveViewData.primary.layoutData = {
        layoutType: liveViewManager.LayoutType.LAYOUT_TYPE_PICKUP,
        title: '计时时间',
        underlineColor: TimerController.pickUpColor,
        descPic: 'timer.png'
      };
      TimerController.defaultView.timer = {
        isPaused: true
      };
      TimerController.defaultView.liveViewData.capsule = {
        type: liveViewManager.CapsuleType.CAPSULE_TYPE_TEXT,
        status: 1,
        title: "便了么"
      }
      //destroy the liveView
      const result = await liveViewManager.stopLiveView(TimerController.defaultView);
    } catch (e) {
      const err: BusinessError = e as BusinessError;
    }
  }

  private static async isLiveViewEnabled(): Promise<boolean> {
    //live view is disabled
    let result: boolean = false;
    try {
      result = await liveViewManager.isLiveViewEnabled();
    } catch (e) {
    }
    return result;
  }

  private static async buildDefaultView(isCountdown: boolean = false): Promise<liveViewManager.LiveView> {
    let avatarName = getAvatarName()

    return {
      id: 8, // liveView ID, generated by the developer
      event: "TIMER", // application scenarios of liveView:TIMER。
      sequence: 1,//serial number
      isMute: true,//is ringing reminder
      timer: {
        time: isCountdown ? 5 * 60 * 1000 : 0,
        isCountdown: isCountdown,
        isPaused: false
      },
      liveViewData: {
        primary: {
          title: '便便💩中',
          content: [
            {
              text: '便便时间不要超过3分钟,避免得痔疮'
            }
          ],
          keepTime: 0,
          clickAction: await ContextUtil.buildWantAgent(),
          layoutData: {//auxiliary area
            layoutType: liveViewManager.LayoutType.LAYOUT_TYPE_PICKUP,
            title: '计时时间',
            content: '${placeholder.timer}',
            underlineColor: TimerController.pickUpColor,
            descPic: `${avatarName}.png`
          },
          extensionData: {//expansion Zone
            type: liveViewManager.ExtensionType.EXTENSION_TYPE_DEFAULT,
          }
        },
        capsule: {//capsule
          type: liveViewManager.CapsuleType.CAPSULE_TYPE_TIMER,
          status: 1,
          icon: 'capsule_logo.png',
          backgroundColor: TimerController.capsuleColor,
          time: isCountdown ? 5 * 60 * 1000 : 0,
          isCountdown: isCountdown,
          isPaused: false,
        }
      }
    }
  }
}


然后你需要在调用这个实况窗控制器的地方进行初始化,比如说我就是在点开始便便的时候进行初始化

  [@State](/user/State) liveViewController: TimerController = new TimerController();

private startLiveView = async () => { //Start timing and create liveView try { await this.liveViewController.startLiveView(); } finally { } }

然后记住你如果是需要结束计时,需要调用实况窗控制器的结束方法

  private stopLiveView = async () => {
//Start timing and create liveView
try {
await this.liveViewController.stopLiveView()
} finally {
}
}

好了,这样我们就集成了一个可以给用户带来很清晰信息提示的实况窗能力!


更多关于HarmonyOS 鸿蒙Next 实况窗能力的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html

1 回复

更多关于HarmonyOS 鸿蒙Next 实况窗能力的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


回复帖子内容

在《HarmonyOS 鸿蒙Next <便了么>APP开发技术分享四——实况窗能力》中,实况窗(Live Window)是鸿蒙系统提供的一项关键能力,旨在提升应用间的交互体验与效率。它允许一个应用在不完全占据屏幕的前提下,以窗口形式在其他应用上层展示信息或功能。

对于开发者而言,实现实况窗能力需遵循鸿蒙系统的API规范。首先,需声明应用具备显示实况窗的权限,并在代码中正确调用相关API创建并管理实况窗。这包括设置窗口的位置、大小、透明度及交互行为等。

在<便了么>APP的开发实践中,实况窗能力被用于展示订单状态、消息通知等关键信息,使用户无需切换应用即可快速获取所需内容。通过鸿蒙系统提供的动画效果与事件处理机制,进一步增强了用户体验的流畅性。

值得注意的是,开发者在实现实况窗功能时,需确保与系统的UI设计规范保持一致,避免影响用户体验。同时,需处理好应用间的资源竞争与权限管理,确保系统的稳定性与安全性。

如果问题依旧没法解决请联系官网客服,官网地址是:https://www.itying.com/category-93-b0.html

回到顶部