“答开发者问”之HarmonyOS鸿蒙Next技术问题解析 第10期
“答开发者问”之HarmonyOS鸿蒙Next技术问题解析 第10期 向所有参与社区互助的开发者致以最诚挚的感谢!
特别感谢本期优质答复贡献者:@小夜呆呆、@zhongcx
社区的蓬勃发展,离不开每一位积极参与者的贡献。本期“答开发者问”栏目,精选自广大热心开发者针对提问帖所贡献的众多优质答复之中。它们不仅是智慧与经验的璀璨结晶,更是“众人拾柴火焰高”这一真理的生动体现。
在此,我们由衷地感谢每一位热心参与、乐于分享的开发者,是你们的热情与智慧,让这个社区充满了生机与活力,每一次的解答都是对技术探索精神的最好诠释。同时,我们也诚挚邀请更多的开发者加入到这场智慧碰撞的盛宴中来。无论是抛出难题寻求解答,还是慷慨解囊分享经验,您的每一份参与都将为鸿蒙开发者社区注入新的活力,推动我们共同前行,在技术的海洋中扬帆远航。
本期问题如下:
- 对于超长文本显示,如何让scroll组件始终显示底部?
- 如何获取当前定位的城市?
- 图片裁剪如何实现?
- 使用EventHub进行数据通信可以反向操作吗,比如控件端由发送消息改为接收消息?
- $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)
注意:
开发者小伙伴们,规范提问,高效沟通!更快得到问题答案的秘诀来啦,点击链接直达
问题五:$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的分布式能力、系统优化及开发者工具升级。分布式能力提升了跨设备协同效率,系统优化则显著提升了性能与稳定性。开发者工具方面,新增了多项调试与性能分析功能,助力开发者更高效地构建应用。此外,本期还针对开发者提出的具体技术难题,提供了详尽的解决方案与最佳实践建议。