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

“答开发者问”之HarmonyOS鸿蒙Next技术问题解析 第10期 向所有参与社区互助的开发者致以最诚挚的感谢!

特别感谢本期优质答复贡献者:@小夜呆呆、@zhongcx

社区的蓬勃发展,离不开每一位积极参与者的贡献。本期“答开发者问”栏目,精选自广大热心开发者针对提问帖所贡献的众多优质答复之中。它们不仅是智慧与经验的璀璨结晶,更是“众人拾柴火焰高”这一真理的生动体现。

在此,我们由衷地感谢每一位热心参与、乐于分享的开发者,是你们的热情与智慧,让这个社区充满了生机与活力,每一次的解答都是对技术探索精神的最好诠释。同时,我们也诚挚邀请更多的开发者加入到这场智慧碰撞的盛宴中来。无论是抛出难题寻求解答,还是慷慨解囊分享经验,您的每一份参与都将为鸿蒙开发者社区注入新的活力,推动我们共同前行,在技术的海洋中扬帆远航。

本期问题如下:

  1. 对于超长文本显示,如何让scroll组件始终显示底部?
  2. 如何获取当前定位的城市?
  3. 图片裁剪如何实现?
  4. 使用EventHub进行数据通信可以反向操作吗,比如控件端由发送消息改为接收消息?
  5. $rawfile()使用三元表达式展示图片时图片不刷新?

答开发者问系列汇总:

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

往期问题回顾:

“答开发者问”之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)

注意:

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

7 回复

问题五:$rawfile()使用三元表达式展示图片时图片不刷新?

问题描述: 请问$rawfile()支持三元表达式吗,比如$rawfile(item.isClick?‘picture1’:‘picture2’),我试了图片路径没有问题,点击之后isClick属性也变了,但是展示的图片没有变。

解决方案: 因为你是数组里面套的对象,所以@State状态管理并未刷新UI,可以使用最新的局部属性更新@ObservedV2+@Trace,参考这个文档

根据你目前的代码最简单的处理方式就是通过加一行代码this.commentList.splice(index,1,item) 让当前这一条数据更新,demo如下:

interface Comment {
  img: string,
  name: string,
  grade: string,
  content: string,
  isClick: boolean,
  time: string,
}

@Entry
@Component
struct Demo {
  @State commentList: Comment[] = [
    {
      img: "demo/background.png",
      name: '张三',
      grade: 'demo/level_1.png',
      content: '今天天气不错',
      isClick: false,
      time: '2021-01-01',
    },
  ];
  build() {
    Column(){
      Column(){
        List(){
          ForEach(this.commentList, (item:Comment, index) => {
            ListItem(){
              Flex(){
                Image($rawfile(`${item.img}`))
                  .width(20)
                  .borderRadius(20)
                  .margin({
                    top: 10
                  })
                Column(){
                  Flex({
                    alignItems: ItemAlign.Center
                  }){
                    Text(`${item.name}`)
                    Image($rawfile(`${item.grade}`))
                      .width(30)
                  }
                  Text(`${item.content}`)
                  Flex({
                    justifyContent: FlexAlign.SpaceBetween
                  }){
                    Text(`${item.time}`)
                    Image($rawfile(item.isClick?'demo/like_bottom_select.png':'demo/like_bottom_unselect.png'))
                      .syncLoad(true)
                      .onClick(() =>{
                        this.commentList[index].isClick = !this.commentList[index].isClick
                        this.commentList.splice(index,1,item)
                      })
                      .width(20)
                      .aspectRatio(1)
                  }
                }
                .margin({
                  left: 10
                })
                .height(100)
                .alignItems(HorizontalAlign.Start)
                .justifyContent(FlexAlign.SpaceAround)
              }
            }
          })
        }
        .listDirection(Axis.Vertical)
      }
      .width('100%')
      .height('100%')
      .justifyContent(FlexAlign.Start)
    }
    .height('100%')
  }
}

原链接: 请问Image的图片地址可以使用三元表达式吗?-华为开发者问答 | 华为开发者联盟 (huawei.com)

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


问题四:使用EventHub进行数据通信可以反向操作吗,比如控件端由发送消息改为接收消息?

问题描述:

官方举的例子都是从UIability订阅,从控件方发送消息,那可以反向吗?比如UIAbility发送消息,控件那端订阅,获取消息。

解决方案:

你这种写法可以用日志打印出来看下。 可以,但EventHub需要上下文而emitter不需要,而上下文context的方法获取方式不一样。

在UIability中的写法是:

this.context.eventHub.on()//监听
this.context.eventHub.emit()//发送

在pages的组件中写法是:

getContext().eventHub.on()//监听
getContext().eventHub.emit()//发送

封装了一个工具类,只能通过真机来调试,demo代码如下: 工具类

import Emitter from '@ohos.events.emitter';

/**
 * `MyEmitterUtil` 是一个针对 HarmonyOS 的事件驱动编程封装类,主要用于组件间的通信和数据传递。
 *
 * 使用要求:
 *   - API 版本:api 11
 * 示例用法:
 * 1. 父组件绑定、解绑、向子组件发送事件:
 *    ```typescript
 *    aboutToAppear() {
 *      this.myEmitterUtil.onFather((eventData: EmitterData) => {
 *        console.info('父组件监听结果: ', JSON.stringify(eventData));
 *        // 判断事件类型并执行相应操作...
 *      });
 *    }
 *
 *    aboutToDisappear() {
 *      this.myEmitterUtil.offFather();
 *    }
 *
 *    // 向子组件发送事件
 *    this.myEmitterUtil.emitChild(MyEmitterUtil.UPDATE_DETAIL, "携带的测试数据");
 *    ```
 *
 * 2. 子组件绑定、解绑、向父组件发送事件:
 *    ```typescript
 *    aboutToAppear() {
 *      this.myEmitterUtil.onChild((eventData: EmitterData) => {
 *        console.info('子组件监听结果: ', JSON.stringify(eventData));
 *        // 判断事件类型并执行相应操作...
 *      });
 *    }
 *
 *    aboutToDisappear() {
 *      this.myEmitterUtil.offChild();
 *    }
 *
 *    // 向父组件发送事件
 *    this.myEmitterUtil.emitFather(MyEmitterUtil.UPDATE_LIST, "测试");
 *    this.myEmitterUtil.emitFather(MyEmitterUtil.UPDATE_LIST_2, "测试2");
 *    ```
 *
 * 参考文档:
 *   - 请查阅 HarmonyOS 开发文档了解详细信息。
 */

export class MyEmitterUtil {
  static readonly UPDATE_LIST = "UPDATE_LIST";
  static readonly UPDATE_LIST_2 = "UPDATE_LIST_2";
  static readonly UPDATE_DETAIL = "UPDATE_DETAIL";
  private static EVENT_ID_COUNTER: number = 0; // 自动递增,生成唯一的事件ID
  private readonly eventIdFather: number;
  private readonly eventIdChild: number;

  constructor() {
    this.eventIdFather = MyEmitterUtil.EVENT_ID_COUNTER++;
    this.eventIdChild = MyEmitterUtil.EVENT_ID_COUNTER++;
    console.info(`事件ID(父组件): ${this.eventIdFather}`);
    console.info(`事件ID(子组件): ${this.eventIdChild}`);
  }

  /**
   * 在组件的`aboutToAppear`生命周期钩子中调用,监听父组件事件
   * @param callback 事件回调函数,接受一个`EmitterData`对象作为参数
   */
  onFather(callback: (eventData: EmitterData) => void) {
    Emitter.on({ eventId: this.eventIdFather }, (event) => {
      if (event.data) {
        callback(new EmitterData(event.data.flag, event.data.data));
      }
    });
  }

  /**
   * 在组件的`aboutToDisappear`生命周期钩子中调用,解除父组件事件监听
   */
  offFather() {
    Emitter.off(this.eventIdFather);
  }

  /**
   * 在组件的`aboutToAppear`生命周期钩子中调用,监听子组件事件
   * @param callback 事件回调函数,接受一个`EmitterData`对象作为参数
   */
  onChild(callback: (eventData: EmitterData) => void) {
    Emitter.on({ eventId: this.eventIdChild }, (event) => {
      if (event.data) {
        callback(new EmitterData(event.data.flag, event.data.data));
      }
    });
  }

  /**
   * 在组件的`aboutToDisappear`生命周期钩子中调用,解除子组件事件监听
   */
  offChild() {
    Emitter.off(this.eventIdChild);
  }

  /**
   * 向父组件发送事件
   * @param flag 事件类型标识
   * @param data 事件携带的数据
   */
  emitFather(flag: string, data: string) {
    Emitter.emit(
      { eventId: this.eventIdFather, priority: Emitter.EventPriority.IMMEDIATE },
      { data: { flag, data } }
    );
  }

  /**
   * 向子组件发送事件
   * @param flag 事件类型标识
   * @param data 事件携带的数据
   */
  emitChild(flag: string, data: string) {
    Emitter.emit(
      { eventId: this.eventIdChild, priority: Emitter.EventPriority.IMMEDIATE },
      { data: { flag, data } }
    );
  }
}

/**
 * 用于封装事件数据的类
 */
export class EmitterData {
  flag: string = "";
  data: string = "";

  constructor(flag: string, data: string) {
    this.flag = flag;
    this.data = data;
  }
}

调用示例

import { MyEmitterUtil } from '../util/MyEmitterUtil'

@Component
struct MyView {
  @Prop controller: MyEmitterUtil
  @State info: string = ""

  aboutToAppear(): void {
    this.controller.onChild((eventData) => {
      console.info('====eventData', JSON.stringify(eventData))
      if (eventData.flag == "ABC") {
        this.info = eventData.data
      }
    })
  }

  build() {
    Column() {
      Text('子组件接收到的数据:' + this.info)
      Button('向父组件发送数据').onClick(() => {
        this.controller.emitFather("CCC", "EEE")
      })
    }
  }
}

@Entry
@Component
struct Page81 {
  controller: MyEmitterUtil = new MyEmitterUtil()
  @State info: string = ""

  aboutToAppear(): void {
    this.controller.onFather((eventData) => {
      console.info('====eventData', JSON.stringify(eventData))
      if (eventData.flag == "CCC") {
        this.info = eventData.data
      }
    })
  }

  build() {
    Row() {
      Column() {
        Text('父组件接收到的数据:' + this.info)
        Button('向子组件发送数据').onClick(() => {
          this.controller.emitChild("ABC", "conter")
        })
        MyView({ controller: this.controller })

      }
      .width('100%')
    }
    .height('100%')
  }
}

问题三:图片裁剪如何实现?

问题描述: 请教下图片裁剪如何实现呢?

解决方案: 图片裁剪/偏移/旋转/翻转/透明度变化等场景可以参考下如下demo:

import { image } from '@kit.ImageKit'
interface GeneratedTypeLiteralInterface_1 {
  textContent;
  action;
}
@Entry
@Component
struct Index {
  @State imagePixelMap: PixelMap | undefined = undefined
  @State edit: boolean = false
  aboutToAppear(): void {
  }
  @Builder
  buttonModel($$: GeneratedTypeLiteralInterface_1) {
    Button($$.textContent)
      .fontSize(14)
      .height(30)
      .width(60)
      .borderRadius(10)
      .backgroundColor('#E8A027')
      .onClick(() => {
        $$.action
        this.edit = true
      })
  }
  async get_pixelmap() {
    // 获取resourceManager资源管理
    const context = getContext(this)
    const resourceMgr = context.resourceManager
    // 获取rawfile文件夹下httpimage.PNG的ArrayBuffer
    const fileData = await resourceMgr.getMediaContent($r('app.media.ic_splash6'))
    const buffer = fileData.buffer
    // 创建imageSource
    const imageSource = image.createImageSource(buffer)
    // 创建PixelMap
    const pixelMap = await imageSource.createPixelMap()
    return pixelMap
  }
  // 对pixelMap进行裁剪
  async crop_image() {
    let pixelMap = await this.get_pixelmap()
    // x:裁剪起始点横坐标0
    // y:裁剪起始点纵坐标0
    // height:裁剪高度300,方向为从上往下(裁剪后的图片高度为300)
    // width:裁剪宽度300,方向为从左到右(裁剪后的图片宽度为300)
    pixelMap.crop({ x: 0, y: 0, size: { height: 300, width: 300 } })
    this.imagePixelMap = pixelMap
  }
  // 对pixelMap进行缩放
  async scale_image() {
    let pixelMap = await this.get_pixelmap()
    pixelMap.scale(0.5, 0.5)
    this.imagePixelMap = pixelMap
  }
  // 对pixelMap进行偏移
  async translate_image() {
    let pixelMap = await this.get_pixelmap()
    pixelMap.translate(100, 100);
    this.imagePixelMap = pixelMap
  }
  // 对pixelMap进行旋转
  async rotate_image() {
    let pixelMap = await this.get_pixelmap()
    pixelMap.rotate(90);
    this.imagePixelMap = pixelMap
  }
  // 对pixelMap进行翻转
  // 垂直翻转
  async flip_image() {
    let pixelMap = await this.get_pixelmap()
    pixelMap.flip(false, true);
    this.imagePixelMap = pixelMap
  }
  // 对pixelMap进行透明度调整
  async opacity_image() {
    let pixelMap = await this.get_pixelmap()
    pixelMap.opacity(0.5);
    this.imagePixelMap = pixelMap
  }
  build() {
    Column() {
      Row() {
        Button("裁剪").onClick(() => {
          this.crop_image()
        })
      }
      Row() {
        Image($r('app.media.ic_splash6'))
          .objectFit(ImageFit.Contain)
          .width('50%')
        Image(this.imagePixelMap)
          .objectFit(ImageFit.Contain)
          .width('50%')
      }.height('70%').width('100%')
    }.height('100%').width('100%')
  }
}

原链接: 请教下,图片裁剪如何实现呢?-华为开发者问答 | 华为开发者联盟 (huawei.com)

问题二:如何获取当前定位的城市?

问题描述:

位置定位中,如何获取当前定位的城市?比如在北京,需要获取到「北京」或者 『北京市』?

解决方案:

运行下面的demo代码,可以获取当前城市:

import { abilityAccessCtrl, common, Permissions } from '@kit.AbilityKit';
import { geoLocationManager } from '@kit.LocationKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { intl } from '@kit.LocalizationKit';
import { hilog } from '@kit.PerformanceAnalysisKit';

export class Logger {
  private static domain: number = 0xFF00;
  private static prefix: string = 'Base64ImageToAlbum';
  private static format: string = '%{public}s';

  static debug(...args: string[]): void {
    hilog.debug(Logger.domain, Logger.prefix, Logger.format, args);
  }

  static info(...args: string[]): void {
    hilog.info(Logger.domain, Logger.prefix, Logger.format, args);
  }

  static warn(...args: string[]): void {
    hilog.warn(Logger.domain, Logger.prefix, Logger.format, args);
  }

  static error(...args: string[]): void {
    hilog.error(Logger.domain, Logger.prefix, Logger.format, args);
  }
}

const REQUEST_PERMISSIONS: Array<Permissions> = [
  'ohos.permission.APPROXIMATELY_LOCATION',
  'ohos.permission.LOCATION'
]

@Entry
@Component
struct Index {
  private context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
  @State message: string = '当前城市:';
  @State locale: string = new intl.Locale().language;

  getCurrentLocation() {
    let atManager = abilityAccessCtrl.createAtManager();
    try {
      atManager.requestPermissionsFromUser(this.context, REQUEST_PERMISSIONS).then(data => {
        if (data.authResults[0] !== 0 || data.authResults[1] !== 0) {
          return;
        }

        let locationChange = (err: BusinessError, location: geoLocationManager.Location) => {
          if (location.latitude === 0 && location.longitude === 0) {
            return;
          }

          let reverseGeocodeRequest: geoLocationManager.ReverseGeoCodeRequest = {
            'locale': this.locale.toString().includes('zh') ? 'zh' : 'en',
            'latitude': location.latitude,
            'longitude': location.longitude,
            'maxItems': 1
          };
          geoLocationManager.getAddressesFromLocation(reverseGeocodeRequest).then(data => {
            if (data[0].placeName) {
              this.message = this.message + data[0].locality;
            }
          }).catch(err => {
            Logger.error('GetAddressesFromLocation err ' + JSON.stringify(err));
          });
        };

        geoLocationManager.getCurrentLocation(locationChange);
      }).catch(err => {
        Logger.error(`req permissions failed, error.code:${err.name}, error message: ${err.message}.`);
      })
    } catch (err) {
      Logger.error(`req permissions failed, error.code:${err.code}, error message: ${err.message}.`);
    }
  }

  build() {
    Row() {
      Column() {
        Button("获取当前位置")
          .fontSize(20)
          .onClick(() => {
            this.getCurrentLocation()
          })

        Text(this.message)
          .fontSize(30)
          .fontWeight(FontWeight.Bold)

      }
      .width('100%')
    }
    .height('100%')
  }
}

module.json5中申请权限:

"requestPermissions": [
  {
    "name": "ohos.permission.APPROXIMATELY_LOCATION",
    "reason": "$string:reason",
    "usedScene": {
      "abilities": [
        "EntryAbility"
      ],
      "when": "inuse"
    }
  },
  {
    "name": "ohos.permission.LOCATION",
    "reason": "$string:reason",
    "usedScene": {
      "abilities": [
        "EntryAbility"
      ],
      "when": "inuse"
    }
  }
]

参考文档:
https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/location-permission-guidelines-V5

原链接:
位置定位中,如何获取当前定位的城市?比如在北京,需要获取到「北京」或者 『北京市』?-华为开发者问答 | 华为开发者联盟 (huawei.com)

问题一:对于超长文本显示,如何让scroll组件始终显示底部?

问题描述: 希望实现一个类似shell窗口的组件来显示动态文字并一直聚焦在底部。我使用scroll组件实现了这个功能,但是在不操作滚动的情况下显示的始终是顶部,如何设置scroll组件让他一直显示底部或者是否有其他方案能实现这个功能?

解决方案: 使用list组件和scroller的getItemRect来实现,demo如下:

@Entry
@ComponentV2
struct Index {
  @Local list: number[] = []
  scroller: Scroller = new Scroller()

  build() {
    Column() {
      Button('add').onClick(() => {
        this.list.push(this.list.length)

        this.scroller.scrollTo({
          yOffset: this.scroller.getItemRect(0).height,
          xOffset: 0
        })
      })
      Scroll(this.scroller) {
        Column() {
          ForEach(this.list, (item: number) => {
            Text(item.toString())
              .width('100%')
              .height('300vp')
              .backgroundColor(Color.Yellow)
          })
        }
      }.backgroundColor(Color.Pink)
    }.size({ width: '100%', height: '100%' })
    .justifyContent(FlexAlign.End)
  }
}

原链接: 超长文本显示,期望scroll组件总是显示底部-华为开发者问答 | 华为开发者联盟 (huawei.com)

在“答开发者问”之HarmonyOS鸿蒙Next技术问题解析第10期中,主要讨论了鸿蒙Next的技术问题和解决方案。以下是关键点:

分布式任务调度

鸿蒙Next通过分布式任务调度实现跨设备任务协同,开发者需使用分布式任务调度API进行任务分发与执行。

设备虚拟化

鸿蒙Next支持设备虚拟化,开发者可通过设备虚拟化API在单设备上模拟多设备环境,便于调试和测试。

安全机制

鸿蒙Next强化了安全机制,包括应用沙箱、数据加密和权限管理,开发者需遵守安全开发规范。

性能优化

鸿蒙Next对系统性能进行了优化,包括启动速度、内存管理和电池续航,开发者需关注性能调优建议。

开发工具

鸿蒙Next提供了新的开发工具,如DevEco Studio 3.0,支持更高效的代码编写和调试。

API变更

鸿蒙Next对部分API进行了更新和优化,开发者需查阅最新API文档以适应变化。

兼容性

鸿蒙Next保持对旧版本应用的兼容性,但建议开发者更新应用以利用新特性。

在“答开发者问”之HarmonyOS鸿蒙Next技术问题解析第10期中,核心议题围绕HarmonyOS的分布式能力、系统优化及开发者工具升级。分布式能力提升了跨设备协同效率,系统优化则显著提升了性能与稳定性。开发者工具方面,新增了多项调试与性能分析功能,助力开发者更高效地构建应用。此外,本期还针对开发者提出的具体技术难题,提供了详尽的解决方案与最佳实践建议。

回到顶部
AI 助手
你好,我是IT营的 AI 助手
您可以尝试点击下方的快捷入口开启体验!