“答开发者问”之HarmonyOS 鸿蒙Next技术问题解析 第16期

“答开发者问”之HarmonyOS 鸿蒙Next技术问题解析 第16期

本期问题如下(解决方案见评论区):

  1. 如何监听弱网情况事件?
  2. 如何在entryability里实现一个全局监听器?
  3. 如何实现LED效果的弹幕滚动?
  4. HarmonyOS应用未上架,如何调试检测应用更新功能是否正常?
  5. ListItem怎么设置只能一侧滑动?

向所有参与社区互助的开发者致以最诚挚的感谢!

特别感谢本期优质答复贡献者: @哈莫尼OS

社区的蓬勃发展,离不开每一位积极参与者的贡献。本期“答开发者问”栏目,精选自广大热心开发者针对提问帖所贡献的众多优质答复之中。它们不仅是智慧与经验的璀璨结晶,更是“众人拾柴火焰高”这一真理的生动体现。在此,我们由衷地感谢每一位热心参与、乐于分享的开发者,是你们的热情与智慧,让这个社区充满了生机与活力,每一次的解答都是对技术探索精神的最好诠释。同时,我们也诚挚邀请更多的开发者加入到这场智慧碰撞的盛宴中来。无论是抛出难题寻求解答,还是慷慨解囊分享经验,您的每一份参与都将为鸿蒙开发者社区注入新的活力,推动我们共同前行,在技术的海洋中扬帆远航。

答开发者问系列汇总:

“答开发者问”系列汇总(持续更新中…)

往期问题回顾:

“答开发者问”之HarmonyOS技术问题解析 第1期-华为开发者问答 | 华为开发者联盟 (huawei.com)

“答开发者问”之HarmonyOS技术问题解析 第2期-华为开发者问答 | 华为开发者联盟 (huawei.com)

“答开发者问”之HarmonyOS技术问题解析 第3期-华为开发者问答 | 华为开发者联盟 (huawei.com)

“答开发者问”之HarmonyOS技术问题解析 第4期-华为开发者问答 | 华为开发者联盟 (huawei.com)

“答开发者问”之HarmonyOS技术问题解析 第5期-华为开发者问答 | 华为开发者联盟 (huawei.com)

“答开发者问”之HarmonyOS技术问题解析 第6期-华为开发者问答 | 华为开发者联盟 (huawei.com)

“答开发者问”之HarmonyOS技术问题解析 第7期-华为开发者问答 | 华为开发者联盟 (huawei.com)

“答开发者问”之HarmonyOS技术问题解析 第8期-华为开发者问答 | 华为开发者联盟 (huawei.com)

“答开发者问”之HarmonyOS技术问题解析 第9期-华为开发者问答 | 华为开发者联盟 (huawei.com)

“答开发者问”之HarmonyOS技术问题解析 第10期-华为开发者问答 | 华为开发者联盟 (huawei.com)

“答开发者问”之HarmonyOS技术问题解析 第11期-华为开发者问答 | 华为开发者联盟 (huawei.com)

“答开发者问”之HarmonyOS技术问题解析 第12期-华为开发者问答 | 华为开发者联盟 (huawei.com)

“答开发者问”之HarmonyOS技术问题解析 第13期-华为开发者问答 | 华为开发者联盟 (huawei.com)

“答开发者问”之HarmonyOS技术问题解析 第14期-华为开发者问答 | 华为开发者联盟 (huawei.com)

“答开发者问”之HarmonyOS技术问题解析 第15期-华为开发者问答 | 华为开发者联盟 (huawei.com)

注意:

开发者小伙伴们,规范提问,高效沟通!更快得到问题答案的秘诀来啦,点击链接直达


更多关于“答开发者问”之HarmonyOS 鸿蒙Next技术问题解析 第16期的实战教程也可以访问 https://www.itying.com/category-93-b0.html

8 回复

问题五:ListItem怎么设置只能一侧滑动?

如何实现ListItem只能从右向左滑,禁止从左向右滑。示例代码如下:

// xxx.ets
@Entry
@Component
struct ListItemExample2 {
  @State arr: number[] = [0, 1, 2, 3, 4];
  @State enterEndDeleteAreaString: string = 'not enterEndDeleteArea';
  @State exitEndDeleteAreaString: string = 'not exitEndDeleteArea';
  private scroller: ListScroller = new ListScroller();

  @Builder itemEnd() {
    Row() {
      Button('Delete').margin('4vp')
      Button('Set').margin('4vp').onClick(() => {
        this.scroller.closeAllSwipeActions();
      })
    }.padding('4vp').justifyContent(FlexAlign.SpaceEvenly)
  }

  build() {
    Column() {
      List({ space: 10, scroller: this.scroller }) {
        ForEach(this.arr, (item: number) => {
          ListItem() {
            Text('item' + item)
              .width('100%')
              .height(100)
              .fontSize(16)
              .textAlign(TextAlign.Center)
              .borderRadius(10)
              .backgroundColor(0xFFFFFF)
          }
          .transition({ type: TransitionType.Delete, opacity: 0 })
          .swipeAction({
            end: {
              builder: () => { this.itemEnd() }
            }
          })
        }, (item: number) => item.toString())
      }
      Text(this.enterEndDeleteAreaString).fontSize(20)
      Text(this.exitEndDeleteAreaString).fontSize(20)
    }
    .padding(10)
    .backgroundColor(0xDCDCDC)
    .width('100%')
    .height('100%')
  }
}

解决方案:

通过SwipeActionOption限制滑动方向,将不需要滑动方向设为null,并配置edgeEffect

ListItem() {
  // 主内容区域
}
.swipeAction({
  // 只设置end参数实现右侧滑动
  end: () => {
    Button('删除').onClick(() => {})
  },
  start: null, // 禁止左侧滑动
  edgeEffect: SwipeEdgeEffect.None
})

原链接:

ListItem怎么设置只能一边滑动-华为开发者问答 | 华为开发者联盟 (huawei.com)

更多关于“答开发者问”之HarmonyOS 鸿蒙Next技术问题解析 第16期的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


问题四:HarmonyOS应用未上架 ,如何调试检测应用更新功能是否正常?

checkAppUpdate这个API在APP未上架时是否可以生效调用?

解决方案:

checkAppUpdate这个API在APP未上架时可以生效调用。 应用市场更新功能为开发者提供版本检测、显示更新提醒功能。开发者使用应用市场更新功能可以提醒用户及时更新到最新版本。

接口说明:

  • checkAppUpdate(context: common.UIAbilityContext): Promise<checkupdateresult>。检查更新接口,用于检测当前是否有新版本。
  • showUpdateDialog(context:common.UIAbilityContext): Promise<showupdateresultcode>。显示升级对话框接口,用于提示用户进行升级。
  • 参考链接:应用市场更新接口API

开发步骤:

开发者可以在应用启动或者设置内手动触发检查版本更新,并显示升级弹窗,点击升级跳转应用市场进行升级。 注意: 假设某应用在应用市场的版本是100001,开发者可以将本地调试代码中app.json5文件里的versionCode改为100000,然后可以调试版本更新检查业务。

检测新版本和升级弹窗代码如下:

import { common } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { updateManager } from '@kit.AppGalleryKit';

const TAG = 'checkAppUpdate';

@Entry
@Component
struct Index {
  @State message: string = 'check';
  @State @Watch('showUpdateDialog') updateAvailableResult: number = 0; // 默认为0,1表示存在新版本
  context: common.UIAbilityContext = this.getUIContext().getHostContext() as common.UIAbilityContext;

  checkAppUpdate() {
    try {
      updateManager.checkAppUpdate(this.context).then((checkResult: updateManager.CheckUpdateResult) => {
        this.updateAvailableResult = checkResult.updateAvailable.valueOf();
        hilog.info(0, TAG, `Succeeded in checking Result updateAvailable: ${checkResult.updateAvailable}`);
      }).catch((error: BusinessError) => {
        hilog.error(0, TAG, `checkAppUpdate onError.code is ${error.code}, message is ${error.message}`);
      });
    } catch (error) {
      hilog.error(0, TAG, `checkAppUpdate onError.code is ${error.code}, message is ${error.message}`);
    }
  }

  showUpdateDialog() {
    try {
      updateManager.showUpdateDialog(this.context).then((resultCode: updateManager.ShowUpdateResultCode) => {
        hilog.info(0, TAG, `Succeeded in showing UpdateDialog resultCode: ${resultCode}`);
      }).catch((error: BusinessError) => {
        hilog.error(0, TAG, `showUpdateDialog onError.code is ${error.code}, message is ${error.message}`);
      });
    } catch (error) {
      hilog.error(0, TAG, `showUpdateDialog onError.code is ${error.code}, message is ${error.message}`);
    }
  }

  build() {
    Column() {
      Text(this.message)
        .fontSize($r('app.float.page_text_font_size'))
        .fontWeight(FontWeight.Bold)
        .margin(50)
        .onClick(() => {
          this.checkAppUpdate();
        })
    }
    .height('100%')
    .width('100%')
  }
}

原链接:

HarmonyOS 应用未上架 ,如何调试检测应用更新功能是否正常-华为开发者问答 | 华为开发者联盟 (huawei.com)

问题三:如何实现LED效果的弹幕滚动?

如何实现下图中的效果,需要滚动?

图像

解决方案:

通过Marquee实现滚动播放的效果,Canvas组件画出一个LED效果的遮罩,具体步骤如下:

  1. 计算文本是否超过Marquee的宽度,如果没有超过,用空格补齐,以保证正常滚动,示例代码如下:

    CompleteTextByTextWidth(src: string, fontSize: number, fontWeight: number) {
      let displayInfo = display.getDefaultDisplaySync()
      let displayWidth = displayInfo.width
      let TextWidth = this.getUIContext()
        .getMeasureUtils()
        .measureText({ textContent: src, fontSize: fontSize, fontWeight: fontWeight })
      while (TextWidth <= displayWidth) {
        src += ' '
        TextWidth = this.getUIContext()
          .getMeasureUtils()
          .measureText({ textContent: src, fontSize: fontSize, fontWeight: fontWeight })
      }
      return src
    }
    
  2. 使用Marquee实现滚动显示,代码示例如下:

    Marquee({
      start: this.start,
      step: this.step,
      fromStart: this.fromStart,
      src: this.danMuSrc
    })
      .width(this.danMuScreenWidth)
      .height(this.danMuScreenHeigth)
      .fontColor('#ffc60f0f')
      .fontSize(48)
      .fontWeight(700)
      .backgroundColor('#182431')
    
  3. 通过Canvas绘制网格遮罩,实现LED点阵屏的效果,代码示例如下:

    // LED点阵屏模拟遮罩
    Canvas(this.canvasContext)
      .width(this.danMuScreenWidth)
      .height(this.danMuScreenHeigth)
      .onReady(() => {
        this.canvasContext.beginPath();
        this.canvasContext.lineWidth = 0.55;
        let lineCountW = this.danMuScreenWidth / this.spaceWidth
        let lineCountH = this.danMuScreenHeigth / this.spaceWidth
        // 相等间隔画出横线
        for (let index = 0; index <= lineCountH; index++) {
          let y = index === lineCountH ? (this.danMuScreenHeigth) : this.spaceWidth * index;
          this.canvasContext.moveTo(0, y);
          this.canvasContext.lineTo(this.danMuScreenWidth, y);
        }
        // 相等间隔画出竖线
        for (let index = 0; index <= lineCountW; index++) {
          let x = index === lineCountW ? (this.danMuScreenWidth) : this.spaceWidth * index;
          this.canvasContext.moveTo(x, 0);
          this.canvasContext.lineTo(x, this.danMuScreenHeigth);
        }
        this.canvasContext.stroke();
      })
    

完整代码示例如下:

import { display } from '@kit.ArkUI';

@Entry
@Component
struct Index {
  @State start: boolean = false
  @State danMuSrc: string = ""
  context = this.getUIContext().getHostContext() as Context
  private fromStart: boolean = true
  private step: number = 10
  private danMuScreenWidth: number = this.getUIContext().px2vp(display.getDefaultDisplaySync().width)
  private danMuScreenHeigth: number = 200
  private spaceWidth: number = 3
  private settings: RenderingContextSettings = new RenderingContextSettings(true);
  private canvasContext: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);

  aboutToAppear(): void {
    this.danMuSrc = this.CompleteTextByTextWidth(this.danMuSrc, 48, 700)
  }

  CompleteTextByTextWidth(src: string, fontSize: number, fontWeight: number) {
    let displayInfo = display.getDefaultDisplaySync()
    let displayWidth = displayInfo.width
    let TextWidth = this.getUIContext()
      .getMeasureUtils()
      .measureText({ textContent: src, fontSize: fontSize, fontWeight: fontWeight })
    while (TextWidth <= displayWidth) {
      src += ' '
      TextWidth = this.getUIContext()
        .getMeasureUtils()
        .measureText({ textContent: src, fontSize: fontSize, fontWeight: fontWeight })
    }
    return src
  }

  build() {
    Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
      Stack() {
        Marquee({
          start: this.start,
          step: this.step,
          fromStart: this.fromStart,
          src: this.danMuSrc
        })
          .width(this.danMuScreenWidth)
          .height(this.danMuScreenHeigth)
          .fontColor('#ffc60f0f')
          .fontSize(48)
          .fontWeight(700)
          .backgroundColor('#182431')
        // LED点阵屏模拟遮罩
        Canvas(this.canvasContext)
          .width(this.danMuScreenWidth)
          .height(this.danMuScreenHeigth)
          .onReady(() => {
            this.canvasContext.beginPath();
            this.canvasContext.lineWidth = 0.55;
            let lineCountW = this.danMuScreenWidth / this.spaceWidth
            let lineCountH = this.danMuScreenHeigth / this.spaceWidth
            // 相等间隔画出横线
            for (let index = 0; index <= lineCountH; index++) {
              let y = index === lineCountH ? (this.danMuScreenHeigth) : this.spaceWidth * index;
              this.canvasContext.moveTo(0, y);
              this.canvasContext.lineTo(this.danMuScreenWidth, y);
            }
            // 相等间隔画出竖线
            for (let index = 0; index <= lineCountW; index++) {
              let x = index === lineCountW ? (this.danMuScreenWidth) : this.spaceWidth * index;
              this.canvasContext.moveTo(x, 0);
              this.canvasContext.lineTo(x, this.danMuScreenHeigth);
            }
            this.canvasContext.stroke();
          })
      }

      TextInput({ placeholder: '输入弹幕' })
        .type(InputType.USER_NAME)
        .placeholderColor(0x182431)
        .showPasswordIcon(false)
        .placeholderFont({ size: 16, weight: FontWeight.Regular })
        .opacity(0.6)
        .width('90%')
        .margin({ top: 20 })
        .hitTestBehavior(HitTestMode.Default)
        .onChange((value: string) => {
          this.danMuSrc = this.CompleteTextByTextWidth(value, 48, 700);
        })
      Button('Start')
        .onClick(() => {
          this.start = true
        })
        .width(120)
        .height(40)
        .fontSize(16)
        .margin({ top: 20 })
        .fontWeight(500)
        .backgroundColor('#007DFF')
    }
    .width('100%')
    .height('100%')
  }
}

原链接:

如何实现LED效果的弹幕滚动现实-华为开发者问答 | 华为开发者联盟 (huawei.com)

如何在entryability里实现一个全局监听器?

如何在app里面自定义事件并监听?

解决方案:

定义全局事件管理器类:

import { hilog } from '@kit.PerformanceAnalysisKit';

// common/EventManager.ets
export class EventManager {
  private static instance: EventManager;
  private listeners: Map<string, Function[]> = new Map();

  static getInstance(): EventManager {
    if (!EventManager.instance) {
      EventManager.instance = new EventManager();
    }
    return EventManager.instance;
  }

  // 注册事件监听
  on(eventName: string, callback: Function): void {
    if (!this.listeners.has(eventName)) {
      this.listeners.set(eventName, []);
    }
    this.listeners.get(eventName)?.push(callback);
  }

  // 触发事件
  emit(eventName: string, ...args: string[]): void {
    const callbacks = this.listeners.get(eventName);
    callbacks?.forEach(callback => {
      try {
        callback(...args);
      } catch (error) {
        hilog.error(0x0000, 'EventManager', `Event callback error: ${error}`);
      }
    });
  }

  // 移除监听
  off(eventName: string, callback: Function): void {
    const callbacks = this.listeners.get(eventName);
    if (callbacks) {
      this.listeners.set(eventName, callbacks.filter(cb => cb !== callback));
    }
  }
}

定义GlobalContext类:

// common/GlobalContext.ets
export class GlobalContext {
  private static instance: GlobalContext;
  private _objects: Map<string, Object>;

  private constructor() {
    this._objects = new Map<string, Object | string>();
  }

  public static getContext(): GlobalContext {
    if (!GlobalContext.instance) {
      GlobalContext.instance = new GlobalContext();
    }
    return GlobalContext.instance;
  }

  getObject(key: string): Object | undefined {
    return this._objects.get(key);
  }

  setObject(key: string, objectClass: Object): void {
    this._objects.set(key, objectClass);
  }

  deleteObject(key: string): void {
    this._objects.delete(key);
  }
}

初始化并挂载到全局上下文:

// entryability/EntryAbility.ets
import { GlobalContext } from '../common/GlobalContext';
import { EventManager } from '../common/EventManager';

export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    // 初始化全局事件管理器
    GlobalContext.getContext().setObject('eventManager', EventManager.getInstance());
  }
}

使用事件:

const eventManager = GlobalContext.getContext().getObject('eventManager') as EventManager;
// 注册监听器
this.eventManager.on('customEvent', (data: string) => {
  hilog.info(0x0000, 'testTag', `Received event data: ${JSON.stringify(data)}`);
});

// 触发事件
eventManager.emit('customEvent', '改变值');

原链接

问题一:如何监听弱网情况事件?

请问on(‘netUnavailable’)是弱网情况的监听订阅事件吗?代码如下:

netConn.on('netUnavailable', () => {
  let netisuseful: SubscribedAbstractProperty<boolean> = AppStorage.link("Isusenet")
  if (netisuseful.get()) {
    AppStorage.set('Isusenet', false)
  }

  windowStage.loadContent('pages/NetWrong', (err) => {
  if (err.code) {
    hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err));
    return;
  }

  windowStage.getMainWindow((err, data) => {
    if (err.code) {
      console.error('Failed to obtain the main window.')
      return;
    }
    // 获取到窗口对象
    // GlobalThisUtil.setProperty("windowClass",data)
    this.windowFull = data;
  })
  hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.');
});
  console.info('网络不可用hhl')
});

解决方案:

netUnavailable事件属于网络状态监听中的网络不可用,而不是弱网监控。弱网监听可以通过netCapabilitiesChange事件监听,结合带宽、延迟等参数判断。具体实现如下:

module.json5中添加权限:

"requestPermissions": [
  {
  "name": "ohos.permission.GET_NETWORK_INFO"
  }
]
let netConn = connection.createNetConnection();

// 先使用register接口注册网络状态变化事件。
netConn.register((error: BusinessError) => {
  console.error(JSON.stringify(error));
});

// 订阅网络能力变化事件(含弱网)
netConn.on('netCapabilitiesChange', (data) => {
  let currentKps: number = data.netCap?.linkDownBandwidthKbps as number

  // 带宽低于 500kbps 视为弱网
  if (currentKps < 500) {
    // 执行弱网处理逻辑
  }
});

原链接:

请问这个是弱网情况的监听订阅吗netUnavailable-华为开发者问答 | 华为开发者联盟 (huawei.com)

HarmonyOS Next是鸿蒙的独立演进版本,采用全新架构,不再兼容安卓APK。其核心技术特点包括:

  1. 原生鸿蒙应用使用ArkTS/ArkUI开发;
  2. 分布式能力升级,支持跨设备原子化服务;
  3. 全新声明式UI开发范式;
  4. 高性能方舟运行时。

开发者需注意:Next版本SDK与API存在不兼容变更,需使用DevEco Studio 4.0以上版本进行适配开发。当前重点优化了原生鸿蒙应用的启动性能、内存管理和多端协同能力。

  1. 监听弱网情况:使用@ohos.net.connection模块的on()方法监听networkStateChange事件,获取当前网络状态和信号强度,通过RTT或丢包率判断弱网。

  2. EntryAbility全局监听器:在EntryAbility的onCreate()中初始化EventHub实例,通过emit()和on()方法实现跨Ability事件通信,注意在onDestroy()中释放资源。

  3. LED弹幕效果:使用Text组件配合marquee属性实现滚动,通过Stack布局叠加多个Text,设置不同颜色和偏移量模拟LED点阵效果,使用动画控制器调节滚动速度。

  4. 未上架应用调试更新:使用AppGallery Connect的App Testing服务分发测试版本,或通过hdc命令手动安装新旧版本进行验证,检查版本号是否正常更新。

  5. ListItem单侧滑动:设置SwipeAction的direction属性为Left或Right,另一侧留空,通过edgeEffect控制滑动边界效果。

回到顶部