-
一、问题背景在鸿蒙应用开发中,经常会遇到需要对指定 UI 组件进行截图的场景(如生成分享卡片、保存页面关键内容等)。开发者在实现过程中容易遇到组件未挂载、图片加载未完成、截图超时、备用方案缺失等问题,导致截图功能不稳定甚至失效。本文基于鸿蒙@kit.ImageKit提供完整的截图解决方案,包含主备两套截图逻辑、错误码精准处理、资源释放等核心功能,确保截图功能健壮可用。二、核心技术依赖本次方案基于鸿蒙的两个核心 Kit,无需额外引入第三方依赖:@kit.ImageKit:提供PixelMap(截图结果承载)、ComponentSnapshot(组件截图核心类)相关能力,是截图功能的基础。@kit.BasicServicesKit:提供BusinessError,用于精准捕获和处理截图过程中的业务错误。三、完整实现代码 import { image } from "@kit.ImageKit";import { BusinessError } from '@kit.BasicServicesKit';@Componentexport struct ImagePage { @State screenshotResult: image.PixelMap | undefined = undefined; @State screenshotStatus: string = '等待截图'; // 直接在组件中管理截图逻辑,避免工具类问题 private uiContext: UIContext = this.getUIContext(); @Builder TargetComponent() { Column() { Text('这是要截图的内容') .fontSize(20) .fontColor(Color.Black) .margin(10) Image($r('app.media.background')) .width(100) .height(100) .syncLoad(true) // 关键:强制同步加载图片 .margin(10) } .padding(20) .backgroundColor(Color.White) // .border({ width: 2, color: Color.Gray }) .alignItems(HorizontalAlign.Center) } /** * 截图方法 - 直接使用组件内的 UIContext */ async capture(builder: () => void): Promise<boolean> { try { console.info('开始截图流程...'); // 1. 获取 ComponentSnapshot 对象 const componentSnapshot = this.uiContext.getComponentSnapshot(); if (!componentSnapshot) { console.error('无法获取 ComponentSnapshot 对象'); return false; } // 2. 检查 createFromBuilder 方法是否存在 if (typeof componentSnapshot.createFromBuilder !== 'function') { console.error('createFromBuilder 方法不存在,尝试使用其他方法'); return await this.alternativeCaptureMethod(); } console.info('使用 createFromBuilder 方法截图...'); // 3. 执行截图 const pixelMap = await componentSnapshot.createFromBuilder( builder, 800, // 增加延迟时间确保组件构建完成 true, // 检查图片状态 { scale: 0.8, waitUntilRenderFinished: true } ); // 4. 验证结果 if (!pixelMap) { console.error('截图返回的 PixelMap 为 undefined'); return false; } this.screenshotResult = pixelMap; console.info(`截图成功,像素字节数: ${pixelMap.getPixelBytesNumber()}`); return true; } catch (error) { console.error('截图失败详细错误:'); console.error('错误消息:', error.message); console.error('错误代码:', error.code); console.error('错误名称:', error.name); // 根据错误代码提供具体建议 this.handleSpecificError(error); return false; } } /** * 备用截图方法 - 使用 get 方法截图已挂载的组件 */ async alternativeCaptureMethod(): Promise<boolean> { try { console.info('尝试备用截图方法...'); const componentSnapshot = this.uiContext.getComponentSnapshot(); // 给目标组件添加 ID const pixelMap = await componentSnapshot.get('targetComponent', { scale: 0.8, waitUntilRenderFinished: true }); this.screenshotResult = pixelMap; return true; } catch (error) { console.error('备用方法也失败:', error); return false; } } /** * 处理特定错误 */ handleSpecificError(error:BusinessError): void { if (error.code === 100001) { console.error('错误 100001: 组件ID无效或组件未挂载'); } else if (error.code === 160001) { console.error('错误 160001: 图片加载未完成,建议增加延迟或设置 syncLoad=true'); } else if (error.code === 160002) { console.error('错误 160002: 截图超时,建议减少缩放比例或组件复杂度'); } else if (error.code === 401) { console.error('错误 401: 参数错误,检查参数类型和格式'); } } build() { Column() { Text('截图功能演示') .fontSize(24) .fontWeight(FontWeight.Bold) .margin(20) Text(`截图状态: ${this.screenshotStatus}`) .fontSize(16) .fontColor(this.screenshotStatus.includes('成功') ? Color.Green : Color.Red) .margin(10) Button('开始截图') .width(200) .height(50) .fontSize(18) .onClick(async () => { this.screenshotStatus = '截图进行中...'; // 直接调用组件内的方法 const success = await this.capture((): void => this.TargetComponent()); if (success) { this.screenshotStatus = '截图成功!'; } else { this.screenshotStatus = '截图失败,请重试'; } }) .margin(20) // 显示截图结果 if (this.screenshotResult) { Text('截图预览:') .fontSize(18) .margin(10) Image(this.screenshotResult) .width(300) .height(200) .border({ width: 2, color: Color.Blue }) .margin(10) Button('释放资源') .onClick(() => { if (this.screenshotResult) { this.screenshotResult.release(); this.screenshotResult = undefined; this.screenshotStatus = '资源已释放'; } }) .margin(10) } // 调试信息区域 Text('调试信息:') .fontSize(14) .fontColor(Color.Gray) .margin({ top: 20 }) Text(`UIContext: ${this.uiContext ? '已初始化' : '未初始化'}`) .fontSize(12) .fontColor(Color.Gray) Text(`截图结果: ${this.screenshotResult ? '有数据' : '无数据'}`) .fontSize(12) .fontColor(Color.Gray) } .width('100%') .height('100%') .alignItems(HorizontalAlign.Center) .backgroundColor(Color.White) }} 四、关键技术点解析1. 核心截图逻辑(主方案:createFromBuilder)基于组件内UIContext获取ComponentSnapshot截图实例,避免跨组件传递上下文导致的问题。直接传入@Builder构建的目标组件,无需组件提前挂载,灵活性更高。配置关键参数:800ms延迟确保组件构建、syncLoad=true强制图片同步加载、waitUntilRenderFinished=true等待渲染完成,大幅提升截图成功率。2. 备用截图逻辑(降级方案:componentSnapshot.get)当主方案createFromBuilder方法不可用(兼容性场景)时,自动切换至备用方案。针对已挂载的组件,通过组件 ID(targetComponent)进行截图,作为主方案的兜底保障,提升功能健壮性。3. 精准错误处理针对鸿蒙截图常见错误码(100001、160001、160002、401)进行分类处理,明确错误原因和修复建议。捕获BusinessError详细信息(消息、代码、名称),方便开发者调试排查问题。4. 内存优化(PixelMap.release())PixelMap对象会占用一定内存,截图完成后不再使用时,需调用release()方法释放资源,避免内存泄漏,尤其在频繁截图场景下至关重要。五、常见问题解决方案截图空白 / 图片未显示:给Image组件添加syncLoad=true,强制图片同步加载,避免截图时图片还在异步加载中。截图超时(错误 160002):减少组件复杂度(拆分复杂组件)、降低缩放比例(如从1.0调整为0.8)、延长截图延迟时间。组件 ID 无效(错误 100001):确保备用方案中组件 ID 与目标组件配置一致,且组件已完成挂载(避免在组件初始化前调用截图)。ComponentSnapshot获取失败:确保在组件挂载后获取UIContext,避免在组件初始化阶段调用截图逻辑。六、总结本方案实现了鸿蒙应用中 UI 组件截图的完整功能,具备以下优势:主备两套逻辑,兼顾灵活性和兼容性,确保截图功能稳定可用。关键参数优化,大幅提升截图成功率,解决常见的空白、超时问题。精准错误处理和内存优化,方便调试且避免资源泄漏。代码结构清晰,可直接复用,支持快速集成到各类鸿蒙应用中。适用于鸿蒙 ArkTS(Stage 模型)应用开发,支持截图预览、资源释放、状态提示等完整流程,满足绝大多数场景下的组件截图需求。
-
在鸿蒙分布式应用开发中,需要实现设备间的自动发现、认证和连接,以构建跨设备协同体验。开发者常遇到以下具体问题:发现机制分散:蓝牙、Wi-Fi P2P、局域网发现多种技术并存协议栈不统一:不同设备支持的连接协议有差异安全机制严格:分布式安全要求导致连接流程复杂解决方案环境配置# 1. 确保开发环境配置正确# 检查DevEco Studio版本# 2. 创建鸿蒙项目# 选择Application -> Empty Ability# 模型选择:Stage# 开发语言:ArkTS# API版本:9+ 1.1 设备管理封装类// DeviceManager.ts - 鸿蒙设备管理器import deviceManager from '@ohos.distributedDeviceManager';import { BusinessError } from '@ohos.base';import { common } from '@kit.AbilityKit';export class HarmonyDeviceManager { private deviceDiscovery: deviceManager.DeviceDiscovery | null = null; private deviceList: deviceManager.DeviceBasicInfo[] = []; private authCallback: deviceManager.AuthCallback | null = null; private connectionCallback: deviceManager.DeviceConnectCallback | null = null; // 初始化设备管理器 async initDeviceManager(context: common.UIAbilityContext): Promise<void> { try { console.info('[DeviceManager] Initializing device manager...'); // 创建设备管理器实例 const manager = deviceManager.createDeviceManager(context.bundleName, { bundleName: context.bundleName, callback: (action: deviceManager.DeviceStateChangeAction, device: deviceManager.DeviceBasicInfo) => { this.handleDeviceStateChange(action, device); } }); // 设置连接状态回调 this.setupConnectionCallback(); console.info('[DeviceManager] Initialization completed'); } catch (error) { console.error('[DeviceManager] Initialization failed:', error); } } // 开始发现设备 async startDiscovery(options?: deviceManager.DiscoveryOptions): Promise<void> { try { const defaultOptions: deviceManager.DiscoveryOptions = { discoveryMode: deviceManager.DiscoveryMode.DISCOVERY_MODE_ACTIVE, medium: deviceManager.ExchangeMedium.COAP, freq: deviceManager.ExchangeFreq.LOW, isActiveDiscover: true, ...options }; this.deviceDiscovery = await deviceManager.startDeviceDiscovery(defaultOptions); // 监听设备发现事件 this.deviceDiscovery.on('discoverSuccess', (device: deviceManager.DeviceBasicInfo) => { this.onDeviceDiscovered(device); }); this.deviceDiscovery.on('discoverFail', (errorCode: number) => { console.error(`[DeviceManager] Discovery failed: ${errorCode}`); }); } catch (error) { console.error('[DeviceManager] Start discovery failed:', error); } } // 设备发现回调 private onDeviceDiscovered(device: deviceManager.DeviceBasicInfo): void { // 去重处理 const existingIndex = this.deviceList.findIndex(d => d.deviceId === device.deviceId); if (existingIndex === -1) { this.deviceList.push(device); console.info(`[DeviceManager] New device discovered: ${device.deviceName} (${device.deviceId})`); // 触发设备更新事件 this.emitDeviceListUpdated(); } } // 连接设备 async connectDevice(deviceId: string, authParam?: deviceManager.AuthParam): Promise<boolean> { try { console.info(`[DeviceManager] Connecting to device: ${deviceId}`); const authParam: deviceManager.AuthParam = { authType: deviceManager.AuthType.PIN_CODE, appIcon: '', appThumbnail: '', ...authParam }; // 发起认证请求 await deviceManager.authenticateDevice(authParam, { onSuccess: (data: { deviceId: string; pinCode?: string }) => { console.info(`[DeviceManager] Authentication success: ${data.deviceId}`); this.onAuthSuccess(deviceId); }, onError: (error: BusinessError) => { console.error(`[DeviceManager] Authentication failed: ${JSON.stringify(error)}`); } }); return true; } catch (error) { console.error('[DeviceManager] Connect device failed:', error); return false; } } // 认证成功处理 private onAuthSuccess(deviceId: string): void { // 建立连接 deviceManager.connectDevice( deviceId, deviceManager.ConnectType.TYPE_WIFI_P2P, this.connectionCallback! ).then(() => { console.info(`[DeviceManager] Device ${deviceId} connected successfully`); }).catch((error: BusinessError) => { console.error(`[DeviceManager] Connect failed: ${JSON.stringify(error)}`); }); } // 设置连接回调 private setupConnectionCallback(): void { this.connectionCallback = { onConnect: (deviceInfo: deviceManager.DeviceBasicInfo) => { console.info(`[DeviceManager] Device connected: ${deviceInfo.deviceName}`); this.emitDeviceConnected(deviceInfo); }, onDisconnect: (deviceInfo: deviceManager.DeviceBasicInfo) => { console.info(`[DeviceManager] Device disconnected: ${deviceInfo.deviceName}`); this.emitDeviceDisconnected(deviceInfo); } }; } // 设备状态变更处理 private handleDeviceStateChange( action: deviceManager.DeviceStateChangeAction, device: deviceManager.DeviceBasicInfo ): void { switch (action) { case deviceManager.DeviceStateChangeAction.ONLINE: console.info(`[DeviceManager] Device online: ${device.deviceName}`); break; case deviceManager.DeviceStateChangeAction.OFFLINE: case deviceManager.DeviceStateChangeAction.READY_OFFLINE: console.info(`[DeviceManager] Device offline: ${device.deviceName}`); break; } } // 获取设备列表 getDevices(): deviceManager.DeviceBasicInfo[] { return [...this.deviceList]; } // 停止发现 async stopDiscovery(): Promise<void> { if (this.deviceDiscovery) { await this.deviceDiscovery.stop(); this.deviceDiscovery = null; } } // 清理资源 release(): void { this.stopDiscovery(); this.deviceList = []; } // 事件发射器(简化版) private emitDeviceListUpdated(): void { // 实际实现中可使用EventEmitter } private emitDeviceConnected(device: deviceManager.DeviceBasicInfo): void { // 实际实现中可使用EventEmitter } private emitDeviceDisconnected(device: deviceManager.DeviceBasicInfo): void { // 实际实现中可使用EventEmitter }}1.2 UI组件封装// DeviceListComponent.ets - 设备列表组件@Componentexport struct DeviceListComponent { @State deviceList: Array<DeviceItem> = []; private deviceManager: HarmonyDeviceManager = new HarmonyDeviceManager(); aboutToAppear(): void { this.initDeviceDiscovery(); } // 初始化设备发现 async initDeviceDiscovery(): Promise<void> { // 请求权限 await this.requestPermissions(); // 初始化设备管理器 await this.deviceManager.initDeviceManager(getContext(this) as common.UIAbilityContext); // 开始发现设备 await this.deviceManager.startDiscovery(); // 定时刷新设备列表 setInterval(() => { this.deviceList = this.deviceManager.getDevices().map(device => ({ id: device.deviceId, name: device.deviceName || 'Unknown Device', type: this.getDeviceType(device.deviceType), isConnected: false // 实际应从设备管理器获取连接状态 })); }, 2000); } // 请求必要权限 async requestPermissions(): Promise<void> { const permissions: Array<string> = [ 'ohos.permission.DISTRIBUTED_DATASYNC', 'ohos.permission.DISTRIBUTED_DEVICE_STATE_CHANGE', 'ohos.permission.GET_NETWORK_INFO' ]; for (const permission of permissions) { try { const result = await abilityAccessCtrl.requestPermissionsFromUser( getContext(this) as common.UIAbilityContext, [permission] ); console.info(`[DeviceList] Permission ${permission} granted: ${result.authResults[0] === 0}`); } catch (error) { console.error(`[DeviceList] Permission request failed: ${error}`); } } } // 连接设备 async connectToDevice(deviceId: string): Promise<void> { const success = await this.deviceManager.connectDevice(deviceId); if (success) { promptAction.showToast({ message: '设备连接成功' }); } else { promptAction.showToast({ message: '设备连接失败' }); } } // 获取设备类型图标 getDeviceType(deviceType: number): string { switch (deviceType) { case 0x00: return 'phone'; // 手机 case 0x01: return 'tablet'; // 平板 case 0x02: return 'tv'; // 智慧屏 case 0x03: return 'watch'; // 手表 default: return 'device'; } } build() { Column() { // 标题 Text('附近设备') .fontSize(20) .fontWeight(FontWeight.Bold) .margin({ top: 20, bottom: 20 }) // 设备列表 List({ space: 10 }) { ForEach(this.deviceList, (device: DeviceItem) => { ListItem() { DeviceItemComponent({ device: device, onConnect: (id: string) => { this.connectToDevice(id); }}) } }) } .layoutWeight(1) // 操作按钮 Row() { Button('重新扫描') .onClick(() => { this.deviceManager.stopDiscovery(); this.deviceManager.startDiscovery(); }) .margin({ right: 10 }) Button('停止发现') .onClick(() => { this.deviceManager.stopDiscovery(); }) } .justifyContent(FlexAlign.Center) .margin({ top: 20, bottom: 20 }) } }}// 设备项组件@Componentstruct DeviceItemComponent { private device: DeviceItem = { id: '', name: '', type: '', isConnected: false }; private onConnect?: (deviceId: string) => void; build() { Row() { // 设备图标 Image($r(`app.media.ic_device_${this.device.type}`)) .width(40) .height(40) .margin({ right: 15 }) // 设备信息 Column() { Text(this.device.name) .fontSize(16) .fontWeight(FontWeight.Medium) Text(`设备ID: ${this.device.id.substring(0, 8)}...`) .fontSize(12) .fontColor(Color.Gray) } .layoutWeight(1) .alignItems(HorizontalAlign.Start) // 连接按钮 Button(this.device.isConnected ? '已连接' : '连接') .enabled(!this.device.isConnected) .onClick(() => { if (this.onConnect) { this.onConnect(this.device.id); } }) } .padding(15) .backgroundColor(Color.White) .borderRadius(8) .shadow({ radius: 4, color: Color.Black, offsetX: 0, offsetY: 2 }) }} 1.3 权限配置文件// module.json5{ "module": { "requestPermissions": [ { "name": "ohos.permission.DISTRIBUTED_DATASYNC", "reason": "需要同步数据到其他设备", "usedScene": { "abilities": ["EntryAbility"], "when": "always" } }, { "name": "ohos.permission.DISTRIBUTED_DEVICE_STATE_CHANGE", "reason": "需要监听设备状态变化", "usedScene": { "abilities": ["EntryAbility"], "when": "always" } }, { "name": "ohos.permission.GET_NETWORK_INFO", "reason": "需要获取网络信息进行设备发现", "usedScene": { "abilities": ["EntryAbility"], "when": "always" } } ], "abilities": [ { "name": "EntryAbility", "srcEntry": "./ets/entryability/EntryAbility.ets", "permissions": [ "ohos.permission.DISTRIBUTED_DATASYNC", "ohos.permission.DISTRIBUTED_DEVICE_STATE_CHANGE" ] } ] }} 步骤1:添加依赖// oh-package.json5{ "dependencies": { "@ohos/distributedDeviceManager": "file:../feature/distributed_device_manager" }} 步骤2:配置设备能力// module.json5{ "module": { "name": "entry", "type": "entry", "deviceTypes": ["phone", "tablet", "tv", "wearable"], "distributedNotificationEnabled": true, "distributedPermissions": { "com.example.myapp": { "data": { "access": ["read", "write"], "uri": "dataability:///com.example.myapp.DataAbility" } } } }} 步骤3:实现设备发现服务// DeviceDiscoveryService.tsexport class DeviceDiscoveryService { private static instance: DeviceDiscoveryService; private discoveryCallbacks: Array<(devices: DeviceBasicInfo[]) => void> = []; static getInstance(): DeviceDiscoveryService { if (!DeviceDiscoveryService.instance) { DeviceDiscoveryService.instance = new DeviceDiscoveryService(); } return DeviceDiscoveryService.instance; } // 统一发现接口 async discoverNearbyDevices(options: DiscoveryOptions = {}): Promise<DeviceBasicInfo[]> { const devices: DeviceBasicInfo[] = []; // 多协议并行发现 await Promise.all([ this.discoverViaBluetooth(devices, options), this.discoverViaWiFi(devices, options), this.discoverViaCoap(devices, options) ]); // 去重和排序 return this.deduplicateAndSortDevices(devices); } private async discoverViaBluetooth( devices: DeviceBasicInfo[], options: DiscoveryOptions ): Promise<void> { // 蓝牙发现实现 } private async discoverViaWiFi( devices: DeviceBasicInfo[], options: DiscoveryOptions ): Promise<void> { // Wi-Fi发现实现 } private async discoverViaCoap( devices: DeviceBasicInfo[], options: DiscoveryOptions ): Promise<void> { // CoAP发现实现 }} 步骤5:实现连接管理// ConnectionManager.tsexport class ConnectionManager { private connections: Map<string, DeviceConnection> = new Map(); // 建立连接 async establishConnection( deviceId: string, options: ConnectionOptions ): Promise<DeviceConnection> { const connection: DeviceConnection = { deviceId, status: 'connecting', timestamp: Date.now(), retryCount: 0 }; this.connections.set(deviceId, connection); try { // 1. 设备认证 await this.authenticateDevice(deviceId, options.authType); // 2. 建立传输通道 const channel = await this.createChannel(deviceId, options.channelType); // 3. 启动心跳检测 this.startHeartbeat(deviceId); connection.status = 'connected'; connection.channel = channel; console.info(`[ConnectionManager] Device ${deviceId} connected successfully`); return connection; } catch (error) { connection.status = 'failed'; connection.error = error as Error; // 重试逻辑 if (connection.retryCount < options.maxRetries || 3) { connection.retryCount++; return this.establishConnection(deviceId, options); } throw error; } }} 测试环境:2台华为P60,HarmonyOS 4.0测试场景:设备发现与连接 可复用组件清单HarmonyDeviceManager - 核心设备管理类DeviceListComponent - 设备列表UI组件ConnectionManager - 连接状态管理DeviceDiscoveryService - 统一发现服务PermissionHelper - 权限管理工具示例配置文件 - 权限、能力配置模板 最佳实践总结统一入口:封装所有设备操作到一个管理器事件驱动:使用观察者模式监听设备状态变化错误处理:统一的错误处理重试机制权限管理:按需请求,优雅降级状态管理:使用状态机管理连接生命周期多协议支持:自动选择最优发现协议资源释放:合理释放不使用的资源
-
按照要求实现了接口,但都是0分。
-
35期提交作品出现这个是为什么哇
-
安装时提示“这个程序不支持该版本的计算机运行。”是因为我的电脑win7版本、64位操作系统吗?
-
运行模拟器就报这个错误
-
您好,请问2025年华为软挑初赛证书怎么获取?
-
一、 关键技术难点总结1.1 问题说明从相册选择图片后,获取不到照片的位置信息。1.2 原因分析图片的经纬度信息存储在EXIF里,对应的key是:GPS_LONGITUDE和GPS_LATITUDE。图片工具当前主要提供图片EXIF信息的读取与编辑能力。EXIF(Exchangeable image file format)是专门为数码相机的照片设定的文件格式,可以记录数码照片的属性信息和拍摄数据。当前仅支持JPEG格式图片。1.3 解决思路在图库等应用中,需要查看或修改数码照片的EXIF信息。由于摄像机的手动镜头的参数无法自动写入到EXIF信息中或者因为相机断电等原因经常会导致拍摄时间出错,这时候就需要手动修改错误的EXIF数据,即可使用本功能。1.4 解决方案1、鸿蒙中图片怎么读取exif信息获取图片信息,需要先将图库图片拷贝到沙箱路径中。当需要调用图片信息时,使用PhotoViewPicker选择指定的图片资源,文件选择成功后,返回PhotoSelectResult结果集。将图库图片复制到沙箱中的参考代码如下:async photoPick() {try {let PhotoSelectOptions = new photoAccessHelper.PhotoSelectOptions();PhotoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE;PhotoSelectOptions.maxSelectNumber = 5;let photoPicker = new photoAccessHelper.PhotoViewPicker();photoPicker.select(PhotoSelectOptions).then(async (PhotoSelectResult: photoAccessHelper.PhotoSelectResult) => {console.info('PhotoViewPicker.select successfully, PhotoSelectResult uri: ’ +JSON.stringify(PhotoSelectResult));let file1 = fs.openSync(PhotoSelectResult.photoUris[0])fs.copyFileSync(file1.fd, data/storage/el2/base/haps/entry/files/${file1.name})let file2 = fs.openSync(data/storage/el2/base/haps/entry/files/${file1.name}, fs.OpenMode.READ_WRITE)console.log(file fd ==> ${file2.fd} | file path ==> ${file2.path})this.filePath = file2.path}).catch((err: BusinessError) => {console.error('PhotoViewPicker.select failed with err: ’ + JSON.stringify(err));});} catch (error) {let err: BusinessError = error as BusinessError;console.error('PhotoViewPicker failed with err: ’ + JSON.stringify(err));}}2、将图片拷贝到沙箱路径中后,使用ImageSource对图片进行解码,再通过getImageInfo获取图片信息或getImageProperty获取指定的图片属性值。注意:getImageProperty仅支持JPEG、PNG和HEIF12+(不同硬件设备支持情况不同)文件,且需要包含exif信息,获取的属性值必须是图片属性中存在的,不存在的话则会返回ImagePropertyOptions中的设定值defaultValue。参考代码如下:private async getImageCoordinates() {console.log(‘输出:filePath’,this.filePath)let imageSource = image.createImageSource(this.filePath);const keys = [image.PropertyKey.GPS_LATITUDE,image.PropertyKey.GPS_LONGITUDE,image.PropertyKey.GPS_LATITUDE_REF,image.PropertyKey.GPS_LONGITUDE_REF]imageSource.getImageProperties(keys).then((data) => {console.info('批量获取图片中的指定属性键的值success: ',JSON.stringify(data));// 提取GPS数据const latitudeDms = data[image.PropertyKey.GPS_LATITUDE] as string;const longitudeDms = data[image.PropertyKey.GPS_LONGITUDE] as string;const latitudeRef = data[image.PropertyKey.GPS_LATITUDE_REF] as string;const longitudeRef = data[image.PropertyKey.GPS_LONGITUDE_REF] as string; // 转换为十进制 const latitude = this.dmsToDecimal(latitudeDms, latitudeRef); const longitude = this.dmsToDecimal(longitudeDms, longitudeRef); // 输出结果 console.log('原始GPS数据:'); console.log('纬度:', latitudeDms, latitudeRef); console.log('经度:', longitudeDms, longitudeRef); console.log('转换后的十进制坐标:'); console.log('纬度:', latitude); console.log('经度:', longitude); console.log('坐标格式:', `${latitude}, ${longitude}`); }).catch((err: BusinessError) => { console.error('批量获取图片中的指定属性键的值error: ', JSON.stringify(err)); }); // 获取指定序号的图片信息 let imageInfo = imageSource.getImageInfoSync(0); console.log('获取指定序号的图片信息', JSON.stringify(imageInfo)) if (imageInfo == undefined) { console.error('Failed to obtain the image information.'); } else { console.info('Succeeded in obtaining the image information.', JSON.stringify(imageInfo)); }}// 将度分秒格式的GPS坐标转换为十进制格式dmsToDecimal(dms: string, ref: string): number {// 去除空格并按逗号分割const parts = dms.replace(/\s+/g, ‘’).split(‘,’);if (parts.length !== 3) return NaN;// 解析度、分、秒const deg = parseFloat(parts[0]);const min = parseFloat(parts[1]);const sec = parseFloat(parts[2]);// 计算十进制值let decimal = deg + min / 60 + sec / 3600;// 根据参考方向调整正负if (ref === ‘S’ || ref === ‘W’) {decimal = -decimal;}return decimal;}3、参考链接:获取图片经纬度信息:https://developer.huawei.com/consumer/cn/forum/topic/0208180370773931585?fid=0109140870620153026,https://developer.huawei.com/consumer/cn/forum/topic/0202178283677576203
-
1.问题说明:播放视频时无法自动连播,声音会重复2.原因分析:没有监听播放结束状态和释放视频资源3.解决思路:(1) Swiper 滑动切换机制(2) 视频播放器生命周期管理(3) 自动播放下一条视频机制4.解决方案:整体架构设计短视频连续播放功能主要通过以下核心组件协同实现:ShortVideoPage:页面主体,负责视频列表管理、滑动切换控制和全局状态协调ShortVideoView:单个视频播放组件,独立管理自己的播放器实例和播放状态Swiper:垂直滑动组件,实现视频间的流畅切换体验核心实现机制详解(1) Swiper 滑动切换机制 Swiper(this.swiperController) { LazyForEach(this.lazyVideoListData, (item: ChannelItemBean, index: number) => { ShortVideoView({ // ... 参数传递 }) })}.vertical(true) // 垂直滑动模式,模拟抖音式浏览体验.index(this.curIndex) // 当前播放视频索引,用于定位当前播放项.onChange((index: number) => { this.onSwiperItemChange(index) // 滑动切换时的回调处理,包括数据更新和统计上报})(2) 视频播放器生命周期管理每个 ShortVideoView 组件采用独立的播放器生命周期管理策略: // ShortVideoView.ets@Link @Watch('createOrFinish') selectedVideoUrl: string; // 当前选中播放的视频URL// 基于选中状态精确控制播放器的创建和销毁createOrFinish(): void { if (this.videoUrl == this.selectedVideoUrl) { this.init(); // 初始化并开始播放当前视频 } else { this.getStop(); this.release(); // 停止并释放非当前播放的视频资源 }}3) 自动播放下一条视频机制通过事件发射器机制实现自动播放下一条视频: // 在 ShortVideoView.ets 中,播放完成时发送事件case 'completed': emitter.emit(EventConst.SHORT_VIDEO_SHOW_NEXT) // 发送播放完成事件通知 this.removeRecentVideo() // 移除已播放完成的视频进度记录 this.callbackComplete?.(); this.showLoading = false break;// 在 ShortVideoPage.ets 中监听事件并处理自动播放private onEmitter() { // 注册监听播放完成事件,实现续播下一条视频 emitter.on(EventConst.SHORT_VIDEO_SHOW_NEXT, () => { this.onAutoNext() })}private onAutoNext() { Logger.info(TAG, `onAutoNext`) this.swiperController.showNext() // 自动滑动到下一个视频项 this.atype = 'auto' // 标记为自动播放类型 this.ptype = 'order' // 标记为顺序播放模式}完整播放流程初始化播放:进入页面时,根据当前索引初始化第一个视频播放器,加载并播放视频内容滑动切换:用户上下滑动时,Swiper 的 onChange 回调更新当前索引,并触发相应业务逻辑播放器精细化管理:新显示的视频组件通过 @Watch 监听 selectedVideoUrl 变化调用 createOrFinish 方法决定是否初始化播放器实例隐藏的视频组件会自动停止播放并释放系统资源,避免资源浪费自动连续播放:当前视频播放完成后,通过事件机制通知页面自动切换到下一个视频,实现无缝续播体验关键优化技术点资源智能管理:通过 @Watch 和 createOrFinish 方法精确控制播放器的创建和释放,确保同时只有一个视频在播放,大大节省系统资源懒加载机制:使用 LazyForEach 实现视频列表的按需加载,提升页面初始化性能全局状态共享:通过 @Provide/@Consume 实现播放状态、控制器显示等全局状态的实时同步事件驱动架构:利用 emitter 实现组件间解耦通信,提高系统可维护性播放进度续播:通过 RECENT_VIDEOS 存储播放进度,支持用户中断后继续观看性能监控统计:完善的日志记录和播放数据统计,便于问题排查和用户体验优化这种精心设计的架构确保了在垂直滑动浏览短视频时能够提供流畅的连续播放体验,同时通过精细化的资源管理避免了系统性能问题。
-
router.pushUrl({ url:url }) this.getUIContext().getRouter().pushNamedRoute({ name:"qrCode" })//都不管用
-
数据库相关文章:https://developer.huawei.com/consumer/cn/blog/topic/03191259102976177对于数据库中的数据类型处理时布尔值,如果直接给表格定义如:db.execDML(‘ALTER TABLE tb_user ADD COLUMN is_student boolean’)@TableField({name:“is_student”,type:FieldType.BOOLEAN})这样定义是没有值的我们需要定义成为db.execDML(‘ALTER TABLE tb_user ADD COLUMN is_student integer’);@TableField({name:“is_student”,type:FieldType.NUMBER})使用和取值都不会影响,因为ORM 框架会自动转换
-
一、关键技术难点总结关键技术难点总结,说明问题,痛点总结,技术总结,效果总结。0.1 问题说明在鸿蒙应用开发中,使用 TextInput 组件进行页面渲染后,该组件会自动获取焦点,弹出输入法键盘。这一默认行为在部分场景下不符合用户预期,例如表单页面初始加载时无需立即输入、列表中包含 TextInput 项时滚动触发不必要聚焦等,影响用户操作体验。0.2 原因分析组件默认属性配置:TextInput 组件在鸿蒙 ArkUI 框架中存在默认的焦点获取机制,当组件渲染完成后,系统会自动将焦点分配给该组件,触发输入法弹出。缺乏直接控制 API:框架未提供专门用于禁用 TextInput 自动聚焦的属性或方法,开发者无法通过简单配置直接关闭该行为。页面渲染时序影响:在页面布局复杂或组件动态加载场景中,组件渲染完成的时序与焦点分配逻辑存在关联,手动干预焦点的时机难以精准控制,易出现聚焦逻辑失效的情况。0.3 解决思路利用焦点管理 API:通过鸿蒙框架提供的焦点管理相关接口,在 TextInput 组件渲染完成后主动移除其焦点状态。动态控制焦点属性:结合组件的生命周期函数,在组件加载完成后动态设置焦点相关属性,覆盖默认的自动聚焦行为。布局层级优化:通过调整组件在页面布局中的层级或父容器属性,减少系统默认焦点分配机制对 TextInput 组件的影响。0.4 解决方案基于焦点管理 API 的控制方法借助鸿蒙 ArkUI 提供的FocusScope和focusControl模块,实现对 TextInput 焦点的手动控制。在组件渲染完成后,通过focusControl.clearFocus()方法清除焦点,阻止自动聚焦。 import { FocusScope, focusControl } from '@ohos.arkui.focus';import { onPageShow } from '@ohos.arkui.page';@Entry@Componentstruct TextInputPage { private textInputController: TextInputController = new TextInputController(); onPageShow() { // 页面显示后清除焦点 focusControl.clearFocus(); } build() { FocusScope() { Column() { TextInput({ placeholder: '请输入内容', controller: this.textInputController }) .width('90%') .height(40) .border({ width: 1 }) } .width('100%') .height('100%') .padding(16) } }} 结合组件生命周期的动态控制利用组件的aboutToAppear或onAppear生命周期函数,在组件即将显示或已显示时,通过设置focusable属性为false临时禁用焦点,待组件稳定后根据需要再启用。 @Componentstruct CustomTextInput { @State isFocusable: boolean = false; onAppear() { // 组件显示后延迟设置可聚焦,避免初始自动聚焦 setTimeout(() => { this.isFocusable = true; }, 100); } build() { TextInput({ placeholder: '请输入内容' }) .width('90%') .height(40) .border({ width: 1 }) .focusable(this.isFocusable) }}@Entry@Componentstruct TextInputPage { build() { Column() { CustomTextInput() } .width('100%') .height('100%') .padding(16) }}
-
1.引入glide依赖 implementation 'io.openharmony.tpc.thirdlib:glide:1.0.3'2.核心代码: //new RoundedCorners(10)圆角 //new CircleCrop()-圆形 RequestOptions requestOptions = RequestOptions.centerCropTransform().transform(new CircleCrop()).diskCacheStrategy(DiskCacheStrategy.NONE)//不做磁盘缓存 .skipMemoryCache(true).placeholder(ResourceTable.Media_boy).error(ResourceTable.Media_boy);//不做内存缓存; Glide.with(getContext()) .load("https://xxx.png") .apply(requestOptions) .into(user_icon);
-
使用split分割字符串的时候,数组会比实际长度大1 原因:是会多出一个字符 如这个例子:const count = ('AAAA').split('A').length 此时结果是5 原始字符串: AAAA 分隔符: A 分割过程: "" + A + "" + A + "" + A + "" + A + "" 结果数组: ["", "", "", "", ""]
-
AppStorageV2是在应用UI启动时会被创建的单例。它的目的是为了提供应用状态数据的中心存储,这些状态数据在应用级别都是可访问的。AppStorageV2将在应用运行过程保留其数据。数据通过唯一的键字符串值访问。需要注意的是,AppStorage与AppStorageV2之间的数据互不共享。AppStorageV2可以修改connect的返回值,实现与UI组件的同步。AppStorageV2支持应用的主线程内多个UIAbility实例间的状态共享。AppStorageV2是ArkUI中用于应用全局UI状态存储的模块,它提供了持久化存储和管理应用状态的能力。以下是AppStorageV2的详细用法: connect:AppStorageV2提供了connect方法,用于将键值对数据存储在应用内存中。如果给定的key已经存在于AppStorageV2中,返回对应的值;否则,通过获取默认值的构造器构造默认值,并返回。// 将key为SampleClass、value为new SampleClass()对象的键值对存储到内存中,并赋值给as1 const as1: SampleClass|undefined = AppStorageV2.connect(SampleClass, () => new SampleClass()); // 将key为key_as2、value为new SampleClass()对象的键值对存储到内存中,并赋值给as2 const as2: SampleClass = AppStorageV2.connect(SampleClass, 'key_as2', () => new SampleClass())!; // key为SampleClass已经在AppStorageV2中,将key为SampleClass的值返回给as3 const as3: SampleClass = AppStorageV2.connect(SampleClass) as SampleClass;remove:移除数据使用remove方法可以从AppStorageV2中删除指定的键值对数据:// 从AppStorageV2中删除key为key_as2的键值对数据 AppStorageV2.remove('key_as2'); // 从AppStorageV2中删除key为SampleClass的键值对数据 AppStorageV2.remove(SampleClass);keys:获取所有键使用keys方法可以获取AppStorageV2中的所有key:// 获取AppStorageV2中的所有key const keys: Array<string> = AppStorageV2.keys();以下是一个完整的示例,展示了如何使用AppStorageV2进行状态管理:import { AppStorageV2 } from '@kit.ArkUI'; @ObservedV2 class SampleClass { @Trace p: number = 0 ;} // 将key为SampleClass、value为new SampleClass()对象的键值对存储到内存中,并赋值给as1 const as1: SampleClass | undefined = AppStorageV2.connect(SampleClass, () => new SampleClass()); // 将key为key_as2、value为new SampleClass()对象的键值对存储到内存中,并赋值给as2 const as2: SampleClass = AppStorageV2.connect(SampleClass, 'key_as2', () => new SampleClass())!; // key为SampleClass已经在AppStorageV2中,将key为SampleClass的值返回给as3 const as3: SampleClass = AppStorageV2.connect(SampleClass) as SampleClass; // 从AppStorageV2中删除key为key_as2的键值对数据 AppStorageV2.remove('key_as2'); // 获取AppStorageV2中的所有keyconst keys: Array<string> = AppStorageV2.keys();通过以上方法,开发者可以方便地在ArkUI应用中进行全局状态管理。
推荐直播
-
HDC深度解读系列 - Serverless与MCP融合创新,构建AI应用全新智能中枢2025/08/20 周三 16:30-18:00
张昆鹏 HCDG北京核心组代表
HDC2025期间,华为云展示了Serverless与MCP融合创新的解决方案,本期访谈直播,由华为云开发者专家(HCDE)兼华为云开发者社区组织HCDG北京核心组代表张鹏先生主持,华为云PaaS服务产品部 Serverless总监Ewen为大家深度解读华为云Serverless与MCP如何融合构建AI应用全新智能中枢
回顾中 -
关于RISC-V生态发展的思考2025/09/02 周二 17:00-18:00
中国科学院计算技术研究所副所长包云岗教授
中科院包云岗老师将在本次直播中,探讨处理器生态的关键要素及其联系,分享过去几年推动RISC-V生态建设实践过程中的经验与教训。
回顾中 -
一键搞定华为云万级资源,3步轻松管理企业成本2025/09/09 周二 15:00-16:00
阿言 华为云交易产品经理
本直播重点介绍如何一键续费万级资源,3步轻松管理成本,帮助提升日常管理效率!
回顾中
热门标签