Flutter 鸿蒙应用AR功能集成实战:多平台AR框架+模拟模式,打造增强现实体验

Flutter 鸿蒙应用AR功能集成实战:多平台AR框架+模拟模式,打造增强现实体验

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net


📄 文章摘要

本文为 Flutter for OpenHarmony 跨平台应用开发任务 41 实战教程,完整实现应用AR增强现实功能,通过多平台AR框架设计与模拟AR模式,在鸿蒙设备上打造基础AR体验。基于前序启动优化、数据验证、权限管理等能力,完成了AR服务框架封装、权限管理集成、AR场景组件开发、交互功能实现、展示页面开发全流程落地,同时实现了多平台支持、模拟平面检测、3D物体放置、视角旋转等核心能力。所有代码在 macOS + DevEco Studio 环境开发,兼容开源鸿蒙真机与模拟器,通过模拟模式解决了鸿蒙系统暂不支持ARCore/ARKit的问题,可直接集成到现有项目,为应用增添增强现实体验。


📋 文章目录

📝 前言

🎯 功能目标与技术要点

📝 步骤1:创建多平台AR服务框架

📝 步骤2:实现AR权限管理

📝 步骤3:开发AR场景组件与交互功能

📝 步骤4:创建AR展示页面

📝 步骤5:集成到主应用与国际化适配

📸 运行效果展示

⚠️ 鸿蒙平台兼容性注意事项

✅ 开源鸿蒙设备验证结果

💡 功能亮点与扩展方向

🎯 全文总结


📝 前言

增强现实(AR)是当前移动应用的热门技术趋势,能为用户带来沉浸式的交互体验。但在开源鸿蒙平台上,由于系统暂不直接支持ARCore或ARKit,直接集成主流AR库存在兼容性问题。为了在鸿蒙设备上实现AR功能,同时保持多平台兼容性,本次开发任务41:集成AR功能,实现增强现实体验,核心目标是设计灵活的多平台AR框架,通过模拟AR模式在鸿蒙设备上提供基础AR体验,同时为未来支持鸿蒙原生AR预留扩展空间。

整体方案基于抽象接口设计,支持多种AR后端(ARCore、ARKit、Web、模拟),通过模拟模式解决鸿蒙设备的兼容性问题,同时深度集成前序实现的权限管理能力,无需复杂的原生对接,可快速集成到现有项目,实现"多平台框架+模拟模式+完整交互"的AR体验闭环。


🎯 功能目标与技术要点

一、核心目标

  1. 设计灵活的多平台AR框架,支持ARCore、ARKit、Web、模拟等多种后端

  2. 集成项目现有的权限管理,实现相机权限的自动申请与状态处理

  3. 实现AR场景的显示与交互,包括平面检测、3D物体放置、视角旋转等

  4. 通过模拟AR模式,在不支持ARCore/ARKit的鸿蒙设备上提供基础AR体验

  5. 完成全量中英文国际化适配,覆盖所有AR相关文本

  6. 全量兼容开源鸿蒙设备,验证AR功能的实际效果

二、核心技术要点

  • 多平台框架:基于抽象接口设计,支持ARCore、ARKit、Web、模拟等多种AR后端

  • 模拟模式:SimulatedARBackend,在不支持原生AR的设备上提供模拟的AR体验

  • 核心数据结构:ARAnchor(锚点)、ARPlane(平面)、ARHitResult(点击测试)、ARLightEstimate(光照估计)

  • 权限管理:集成项目现有的PermissionService,自动申请相机权限

  • 交互功能:点击放置物体、拖动旋转视角、平面检测、物体类型选择

  • UI组件:ARSceneWidget、ARObjectPicker、ARPlacementIndicator、ARInfoPanel

  • 鸿蒙兼容:通过模拟模式解决鸿蒙系统暂不支持ARCore/ARKit的问题

  • 国际化:支持中英文无缝切换,覆盖所有AR相关文本


📝 步骤1:创建多平台AR服务框架

首先在 lib/services/ 目录下创建 ar_service.dart,设计多平台AR框架,定义核心数据结构、抽象接口、多种后端实现(包括模拟AR后端),为整个AR功能奠定基础。

1.1 核心数据结构与枚举定义

首先定义AR平台类型、核心数据模型(ARAnchor、ARPlane、ARHitResult、ARLightEstimate)。

1.2 AR后端抽象接口

定义ARBackend抽象接口,包含初始化、平面检测、点击测试、锚点管理、状态监听等核心方法。

1.3 模拟AR后端实现

实现SimulatedARBackend,在不支持原生AR的设备上提供模拟的AR体验,包括模拟平面检测、物体放置、视角旋转等。

1.4 AR服务封装

封装ARService,统一管理AR后端、权限、状态,提供简洁的调用接口。

核心代码结构(简化版):

import 'dart:async';

import 'package:flutter/foundation.dart';

import 'package:flutter/material.dart';

import 'permission_service.dart';

/// AR平台类型枚举

enum ARPlatform {

arcore, // Android ARCore

arkit, // iOS ARKit

openharmony, // 鸿蒙系统

web, // Web平台

simulated // 模拟AR

}

/// AR锚点模型

class ARAnchor {

final String id;

final Offset position;

final double rotation;

final String? objectType;

const ARAnchor({

required this.id,

required this.position,

this.rotation = 0.0,

this.objectType,

});

}

/// AR平面模型

class ARPlane {

final String id;

final Offset center;

final Size size;

final bool isHorizontal;

const ARPlane({

required this.id,

required this.center,

required this.size,

this.isHorizontal = true,

});

}

/// AR点击测试结果

class ARHitResult {

final ARPlane? plane;

final Offset position;

final double distance;

const ARHitResult({

this.plane,

required this.position,

required this.distance,

});

}

/// AR光照估计

class ARLightEstimate {

final double ambientIntensity;

final Color? ambientColor;

const ARLightEstimate({

this.ambientIntensity = 1.0,

this.ambientColor,

});

}

/// AR后端抽象接口

abstract class ARBackend {

Future initialize();

void dispose();

Stream<List> get planeStream;

Stream get lightEstimateStream;

Future<List> hitTest(Offset position);

Future addAnchor(Offset position, {String? objectType});

Future removeAnchor(String anchorId);

List get anchors;

void updateRotation(double delta);

double get currentRotation;

}

/// 模拟AR后端(用于鸿蒙等不支持原生AR的平台)

class SimulatedARBackend implements ARBackend {

final StreamController<List> _planeController = StreamController.broadcast();

final StreamController _lightController = StreamController.broadcast();

final List _anchors = [];

final List _planes = [];

double _rotation = 0.0;

bool _isInitialized = false;

@override

Future initialize() async {

if (_isInitialized) return true;

// 模拟初始化

await Future.delayed(const Duration(milliseconds: 500));

// 生成模拟平面

_planes.add(const ARPlane(

id: 'simulated_plane_1',

center: Offset(0.5, 0.6),

size: Size(0.8, 0.8),

isHorizontal: true,

));

_planeController.add(List.unmodifiable(_planes));

_lightController.add(const ARLightEstimate(ambientIntensity: 1.0));

_isInitialized = true;

return true;

}

@override

Stream<List> get planeStream => _planeController.stream;

@override

Stream get lightEstimateStream => _lightController.stream;

@override

Future<List> hitTest(Offset position) async {

// 模拟点击测试

return [

ARHitResult(

plane: _planes.firstOrNull,

position: position,

distance: 1.0,

),

];

}

@override

Future addAnchor(Offset position, {String? objectType}) async {

final anchor = ARAnchor(

id: DateTime.now().millisecondsSinceEpoch.toString(),

position: position,

rotation: _rotation,

objectType: objectType,

);

_anchors.add(anchor);

return anchor;

}

@override

Future removeAnchor(String anchorId) async {

_anchors.removeWhere((a) => a.id == anchorId);

}

@override

List get anchors => List.unmodifiable(_anchors);

@override

void updateRotation(double delta) {

_rotation += delta;

}

@override

double get currentRotation => _rotation;

@override

void dispose() {

_planeController.close();

_lightController.close();

}

}

/// AR服务

class ARService {

ARBackend? _backend;

final PermissionService _permissionService = PermissionService.instance;

bool _isInitialized = false;

/// 单例实例

static final ARService instance = ARService._internal();

ARService._internal();

/// 获取当前AR平台

ARPlatform get currentPlatform {

// 根据平台返回对应类型,鸿蒙返回simulated

return ARPlatform.simulated;

}

/// 是否支持原生AR

bool get supportsNativeAR => false;

/// 初始化AR服务

Future initialize() async {

if (_isInitialized) return true;

// 1. 检查并请求相机权限

final hasPermission = await _checkAndRequestPermission();

if (!hasPermission) {

return false;

}

// 2. 创建AR后端(鸿蒙使用模拟后端)

_backend = SimulatedARBackend();

// 3. 初始化后端

final success = await _backend!.initialize();

_isInitialized = success;

return success;

}

/// 检查并请求相机权限

Future _checkAndRequestPermission() async {

// 集成项目现有的权限服务

// 简化实现,实际使用项目的PermissionService

return true;

}

// 其他方法代理到_backend

ARBackend? get backend => _backend;

bool get isInitialized => _isInitialized;

}


📝 步骤2:实现AR权限管理

AR功能需要相机权限,直接集成项目前序实现的 PermissionService,实现权限状态检查、自动申请、权限拒绝处理等能力。

2.1 权限集成实现

在 ARService 中集成 PermissionService,在初始化时自动检查并请求相机权限,处理权限拒绝的场景,引导用户到系统设置。

2.2 权限状态UI提示

在AR场景组件中,根据权限状态显示不同的UI:

  • 权限已授权:显示AR场景

  • 权限未申请:显示权限申请按钮

  • 权限已拒绝:显示权限说明与引导按钮

  • 权限永久拒绝:显示引导到系统设置的按钮


📝 步骤3:开发AR场景组件与交互功能

在 lib/widgets/ 目录下创建 ar_widgets.dart,封装AR场景相关的UI组件,实现完整的AR交互功能。

3.1 核心AR组件

  • ARSceneWidget:AR场景主组件,负责相机预览(或模拟场景)、平面显示、锚点渲染

  • ARObjectPicker:AR物体选择器,支持选择立方体、球体、圆柱体等不同类型的3D物体

  • ARPlacementIndicator:放置指示器,在检测到的平面上显示放置位置提示

  • ARInfoPanel:AR信息面板,显示平面数量、锚点数量、光照估计等信息

  • ARControlButton:控制按钮,包括重置、暂停、恢复等

3.2 交互功能实现

  • 点击放置物体:监听屏幕点击事件,执行点击测试,在点击位置添加AR锚点

  • 拖动旋转视角:监听水平拖动事件,更新AR场景的旋转角度

  • 平面检测显示:监听平面流,在检测到的平面上绘制网格

  • 物体类型切换:通过物体选择器切换要放置的3D物体类型

  • 场景控制:重置场景(清除所有锚点)、暂停/恢复平面检测

核心组件结构(简化版):

import 'package:flutter/material.dart';

import '.../services/ar_service.dart';

/// AR场景主组件

class ARSceneWidget extends StatefulWidget {

const ARSceneWidget({super.key});

@override

State createState() => _ARSceneWidgetState();

}

class _ARSceneWidgetState extends State {

final ARService _arService = ARService.instance;

final List _planes = [];

final List _anchors = [];

String _selectedObjectType = 'cube';

bool _isPaused = false;

@override

void initState() {

super.initState();

_initializeAR();

}

Future _initializeAR() async {

final success = await _arService.initialize();

if (success && mounted) {

_listenToStreams();

}

}

void _listenToStreams() {

_arService.backend?.planeStream.listen((planes) {

if (mounted) {

setState(() => _planes.clear()...addAll(planes));

}

});

}

void _handleTap(TapUpDetails details) {

if (_isPaused) return;

final renderBox = context.findRenderObject() as RenderBox;

final localPosition = renderBox.globalToLocal(details.globalPosition);

_arService.backend?.hitTest(localPosition).then((results) {

if (results.isNotEmpty && mounted) {

_arService.backend?.addAnchor(

results.first.position,

objectType: _selectedObjectType,

).then((anchor) {

if (mounted) {

setState(() => _anchors.add(anchor));

}

});

}

});

}

void _handlePanUpdate(DragUpdateDetails details) {

if (_isPaused) return;

_arService.backend?.updateRotation(details.delta.dx * 0.01);

if (mounted) setState(() {});

}

void _resetScene() {

for (final anchor in _anchors) {

_arService.backend?.removeAnchor(anchor.id);

}

if (mounted) {

setState(() => _anchors.clear());

}

}

@override

Widget build(BuildContext context) {

if (!_arService.isInitialized) {

return const Center(child: CircularProgressIndicator());

}

return GestureDetector(

onTapUp: _handleTap,

onPanUpdate: _handlePanUpdate,

child: Stack(

children: [

// 模拟AR背景

Container(color: Colors.grey.shade200),

// 绘制平面

..._planes.map((plane) => _buildPlane(plane)),

// 绘制锚点

..._anchors.map((anchor) => _buildAnchor(anchor)),

// 放置指示器

if (_planes.isNotEmpty) _buildPlacementIndicator(),

// 控制按钮

_buildControls(),

],

),

);

}

Widget _buildPlane(ARPlane plane) {

return Positioned(

left: plane.center.dx * MediaQuery.of(context).size.width - plane.size.width * MediaQuery.of(context).size.width / 2,

top: plane.center.dy * MediaQuery.of(context).size.height - plane.size.height * MediaQuery.of(context).size.height / 2,

child: Container(

width: plane.size.width * MediaQuery.of(context).size.width,

height: plane.size.height * MediaQuery.of(context).size.height,

decoration: BoxDecoration(

border: Border.all(color: Colors.blue.withOpacity(0.5), width: 2),

color: Colors.blue.withOpacity(0.1),

),

child: CustomPaint(painter: _GridPainter()),

),

);

}

Widget _buildAnchor(ARAnchor anchor) {

final size = MediaQuery.of(context).size;

return Positioned(

left: anchor.position.dx * size.width - 25,

top: anchor.position.dy * size.height - 25,

child: Transform.rotate(

angle: anchor.rotation,

child: _build3DObject(anchor.objectType ?? 'cube'),

),

);

}

Widget _build3DObject(String type) {

switch (type) {

case 'sphere':

return Container(

width: 50,

height: 50,

decoration: BoxDecoration(

shape: BoxShape.circle,

color: Colors.green,

boxShadow: [

BoxShadow(

color: Colors.black.withOpacity(0.3),

blurRadius: 10,

offset: const Offset(0, 5),

),

],

),

);

case 'cylinder':

return Container(

width: 40,

height: 60,

decoration: BoxDecoration(

color: Colors.orange,

borderRadius: BorderRadius.circular(20),

boxShadow: [

BoxShadow(

color: Colors.black.withOpacity(0.3),

blurRadius: 10,

offset: const Offset(0, 5),

),

],

),

);

default: // cube

return Container(

width: 50,

height: 50,

decoration: BoxDecoration(

color: Colors.blue,

boxShadow: [

BoxShadow(

color: Colors.black.withOpacity(0.3),

blurRadius: 10,

offset: const Offset(0, 5),

),

],

),

);

}

}

Widget _buildPlacementIndicator() {

return Center(

child: Container(

width: 60,

height: 60,

decoration: BoxDecoration(

shape: BoxShape.circle,

border: Border.all(color: Colors.white, width: 3),

),

child: const Icon(Icons.add, color: Colors.white, size: 30),

),

);

}

Widget _buildControls() {

return Positioned(

bottom: 20,

left: 20,

right: 20,

child: Row(

mainAxisAlignment: MainAxisAlignment.spaceEvenly,

children: [

ARControlButton(

icon: _isPaused ? Icons.play_arrow : Icons.pause,

onPressed: () => setState(() => _isPaused = !_isPaused),

),

ARObjectPicker(

selectedType: _selectedObjectType,

onTypeSelected: (type) => setState(() => _selectedObjectType = type),

),

ARControlButton(

icon: Icons.refresh,

onPressed: _resetScene,

),

],

),

);

}

}

/// 网格绘制器

class _GridPainter extends CustomPainter {

@override

void paint(Canvas canvas, Size size) {

final paint = Paint()

...color = Colors.blue.withOpacity(0.3)

...strokeWidth = 1;

const step = 20.0;

for (double x = 0; x < size.width; x += step) {

canvas.drawLine(Offset(x, 0), Offset(x, size.height), paint);

}

for (double y = 0; y < size.height; y += step) {

canvas.drawLine(Offset(0, y), Offset(size.width, y), paint);

}

}

@override

bool shouldRepaint(covariant CustomPainter oldDelegate) => false;

}

/// 控制按钮

class ARControlButton extends StatelessWidget {

final IconData icon;

final VoidCallback onPressed;

const ARControlButton({

super.key,

required this.icon,

required this.onPressed,

});

@override

Widget build(BuildContext context) {

return Container(

decoration: BoxDecoration(

color: Colors.black.withOpacity(0.5),

shape: BoxShape.circle,

),

child: IconButton(

icon: Icon(icon, color: Colors.white),

onPressed: onPressed,

),

);

}

}

/// 物体选择器

class ARObjectPicker extends StatelessWidget {

final String selectedType;

final Function(String) onTypeSelected;

const ARObjectPicker({

super.key,

required this.selectedType,

required this.onTypeSelected,

});

@override

Widget build(BuildContext context) {

return PopupMenuButton(

initialValue: selectedType,

onSelected: onTypeSelected,

itemBuilder: (context) => [

const PopupMenuItem(value: 'cube', child: Text('立方体')),

const PopupMenuItem(value: 'sphere', child: Text('球体')),

const PopupMenuItem(value: 'cylinder', child: Text('圆柱体')),

],

child: Container(

padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),

decoration: BoxDecoration(

color: Colors.black.withOpacity(0.5),

borderRadius: BorderRadius.circular(20),

),

child: const Row(

mainAxisSize: MainAxisSize.min,

children: [

Icon(Icons.category, color: Colors.white),

SizedBox(width: 8),

Text('选择物体', style: TextStyle(color: Colors.white)),

],

),

),

);

}

}


📝 步骤4:创建AR展示页面

在 lib/screens/ 目录下创建 ar_showcase_page.dart,实现AR展示页面,包含AR体验入口、功能说明、设置对话框等,同时在 lib/utils/localization.dart 中添加国际化支持。

4.1 AR展示页面结构

  • AR体验入口:功能卡片展示,点击进入AR场景

  • 功能说明:列出AR功能的特点与使用说明

  • 设置对话框:AR相关设置,比如物体类型默认值、是否显示平面网格等

  • 权限引导:如果没有相机权限,显示权限引导

4.2 国际化适配

在 localization.dart 中添加AR功能相关的中英文翻译文本,覆盖所有AR相关的页面文本、提示语、按钮文案。


📝 步骤5:集成到主应用与国际化适配

5.1 注册页面路由

在主应用的路由配置中添加AR展示页面路由:

MaterialApp(

routes: {

// 其他已有路由

'/arShowcase': (context) => const ARShowcasePage(),

},

);

5.2 添加设置页面入口

在应用的设置页面添加AR体验功能入口:

ListTile(

leading: const Icon(Icons.view_in_ar),

title: Text(AppLocalizations.of(context)!.arExperience),

onTap: () {

Navigator.pushNamed(context, '/arShowcase');

},

)


📸 运行效果展示

  1. 模拟AR模式:在鸿蒙设备上自动切换到模拟AR模式,提供基础AR体验

  2. 平面检测显示:模拟检测到的平面,显示蓝色网格,直观展示可放置区域

  3. 3D物体放置:点击屏幕放置立方体、球体、圆柱体等3D物体,支持切换物体类型

  4. 视角旋转交互:水平拖动屏幕旋转视角,3D物体跟随旋转

  5. 放置指示器:在屏幕中心显示放置指示器,提示可放置位置

  6. 场景控制:支持暂停/恢复平面检测、重置场景(清除所有物体)

  7. 信息面板:显示平面数量、锚点数量、光照估计等AR信息

  8. 鸿蒙设备适配:所有页面在鸿蒙设备上无布局溢出,交互流畅


⚠️ 鸿蒙平台兼容性注意事项

  1. ARCore/ARKit不支持:鸿蒙系统暂不直接支持ARCore或ARKit,本次实现使用模拟AR模式,提供基础体验

  2. 相机权限申请:需在 module.json5 中声明相机权限 ohos.permission.CAMERA,同时使用项目现有的权限服务申请

  3. 相机预览:模拟模式下使用纯色背景替代真实相机预览,未来可集成鸿蒙原生相机API

  4. 性能优化:模拟模式下避免同时放置过多3D物体,防止卡顿,建议限制锚点数量

  5. 未来扩展:鸿蒙系统未来可能推出原生AR SDK,当前框架已预留扩展接口,可快速切换到原生AR后端

  6. 权限引导:用户拒绝相机权限后,需提供清晰的引导,说明权限用途,引导用户到系统设置开启


✅ 开源鸿蒙设备验证结果

本次功能验证分别在OpenHarmony API 10 虚拟机和真机上进行,全流程测试所有功能的可用性、稳定性、兼容性,测试结果如下:

  • AR服务框架初始化正常,自动选择模拟AR后端

  • 权限管理集成正常,相机权限申请、状态检查、拒绝处理均正常

  • 模拟AR模式正常工作,平面检测、物体放置、视角旋转均正常

  • AR场景组件正常显示,无布局溢出、无渲染异常

  • 交互功能正常,点击放置物体、拖动旋转视角、物体类型切换均正常

  • AR展示页面正常,功能卡片、说明、设置对话框均正常

  • 国际化适配正常,中英文语言切换正常,所有文本均正确适配

  • 连续多次使用AR功能,无内存泄漏、无应用崩溃,稳定性表现优异

  • 所有功能在不同系统版本、不同尺寸的鸿蒙真机上均正常运行,无平台兼容性问题


💡 功能亮点与扩展方向

核心功能亮点

  1. 灵活的多平台AR框架:基于抽象接口设计,支持ARCore、ARKit、Web、模拟等多种后端

  2. 模拟AR模式:在不支持原生AR的鸿蒙设备上提供基础AR体验,解决兼容性问题

  3. 完整的权限管理:深度集成项目现有的权限服务,自动申请相机权限,处理拒绝场景

  4. 丰富的交互功能:点击放置物体、拖动旋转视角、平面检测、物体类型切换

  5. 可复用的UI组件:封装开箱即用的AR场景组件,无需重复开发

  6. 预留扩展空间:框架设计预留了鸿蒙原生AR的扩展接口,未来可快速切换

  7. 纯Dart实现:无原生依赖,100%兼容鸿蒙设备,易于集成

  8. 全量国际化适配:支持中英文无缝切换,适配多语言场景

功能扩展方向

  1. 鸿蒙原生AR集成:等待鸿蒙系统推出原生AR SDK,快速切换到原生AR后端

  2. 3D模型支持:支持加载自定义3D模型(GLB、GLTF格式),丰富AR内容

  3. 图像识别与追踪:实现图像识别功能,识别特定图片后显示AR内容

  4. 云锚点支持:集成云锚点服务,实现多设备共享AR场景

  5. 多人AR体验:支持多人同时参与同一AR场景,实现互动

  6. AR录制与分享:支持录制AR体验视频,分享给他人

  7. 光照与阴影:实现更真实的光照估计与阴影渲染,提升AR真实感

  8. 性能优化:优化模拟模式的性能,支持更多锚点与更复杂的3D物体


🎯 全文总结

本次任务 41 完整实现了 Flutter 鸿蒙应用AR功能集成,通过灵活的多平台AR框架设计与模拟AR模式,在鸿蒙设备上成功打造了基础AR体验,解决了鸿蒙系统暂不支持ARCore/ARKit的问题,同时为未来支持鸿蒙原生AR预留了扩展空间。

整套方案基于抽象接口设计,支持多种AR后端,深度集成了前序实现的权限管理能力,无原生依赖、兼容性强、易于扩展。从验证结果看,模拟AR模式在鸿蒙设备上运行稳定,交互流畅,提供了完整的基础AR体验。

作为一名大一新生,这次实战不仅提升了我 Flutter 抽象设计、状态管理、交互开发的能力,也让我对AR技术、多平台兼容设计有了更深入的理解。本文记录的开发流程、代码实现和鸿蒙平台兼容性注意事项,均经过 OpenHarmony 设备的全流程验证,代码可直接复用,希望能帮助其他刚接触 Flutter 鸿蒙开发的同学,快速实现应用的AR功能,打造有趣的增强现实体验。

相关推荐
南村群童欺我老无力.2 小时前
鸿蒙PC开发的Slider组件blockSize参数的类型要求
华为·harmonyos
zhangjikuan893 小时前
Flutter备忘
flutter
前端技术3 小时前
华为余承东:鸿蒙终端设备数突破5500万
java·前端·javascript·人工智能·python·华为·harmonyos
代码论斤卖3 小时前
OpenHarmony的watchdog service频繁崩溃问题分析
linux·harmonyos
Lanren的编程日记3 小时前
Flutter 鸿蒙应用权限管理功能实战:标准化权限申请与状态管控,提升用户信任度
flutter·华为·harmonyos
想你依然心痛3 小时前
HarmonyOS 6(API 23)实战:基于 HDS 沉浸光感与悬浮导航打造“光影工作台“多窗口协作系统
microsoft·华为·harmonyos·悬浮导航·沉浸光感
Ww.xh3 小时前
OpenHarmony API 9 升级到 API 10 权限与接口变更实战指南
服务器·华为·harmonyos
枫叶丹44 小时前
【HarmonyOS 6.0】ArkWeb新特性:PDF加载成功/失败回调及滚动到底部监听
华为·pdf·harmonyos
南村群童欺我老无力.4 小时前
鸿蒙 - Progress进度条从手工拼装到原生组件的重构
华为·重构·harmonyos