普通视图

发现新文章,点击刷新页面。
今天 — 2025年11月30日首页

HarmonyOS 应用开发基础案例(三):使用DevEco Studio高效开发(篇一)

作者 Raink
2025年11月30日 09:50

HUAWEI DevEco Studio是基于IntelliJ IDEA Community开源版本打造,为运行在HarmonyOS系统上的应用和元服务提供一站式的开发平台。

作为一款开发工具,除了具有基本的代码开发、编译构建及调测等功能外,DevEco Studio还具有如下特点:

  • 高效智能代码编辑:支持ArkTS、JS、C/C++等语言的代码高亮、代码智能补齐、代码错误检查、代码自动跳转、代码格式化、代码查找等功能,提升代码编写效率。
  • 多端双向实时预览:支持UI界面代码的双向预览、实时预览、动态预览、组件预览以及多端设备预览,便于快速查看代码运行效果。
  • 多端设备模拟仿真:提供HarmonyOS本地模拟器,支持Phone等设备的模拟仿真,便捷获取调试环境。
  • DevEco Profiler性能调优:提供实时监控能力和场景化调优模板,便于全方位的设备资源监测,采集数据覆盖多个维度,为开发者带来高效、直通代码行的调优体验。

1. 工程管理

DevEco Studio的工程管理涵盖应用和元服务的结构,其中APP Pack由HAP包和pack.info文件组成;支持工程目录的两种视图形式,并展示基于ArkTS Stage模型的目录结构;同时提供多种工程模板及创建新工程的详细流程,帮助开发者高效搭建与管理项目。

1.1. 工程介绍

1.1.1. App包结构

在进行应用/元服务开发前,我们应该掌握应用/元服务的逻辑结构。

应用/元服务发布形态为APP Pack(Application Package),它是由一个或多个HAP(Harmony Ability Package)包以及描述APP Pack属性的pack.info文件组成。

一个HAP在工程目录中对应一个Module,它是由代码、资源、三方库及应用/元服务配置文件组成,HAP可以分为Entry和Feature两种类型。

  • Entry: 应用的主模块,作为应用的入口,提供了应用的基础功能。
  • Feature: 应用的动态特性模块,作为应用能力的扩展,可以根据用户的需求和设备类型进行选择性安装。

Stage模型应用程序包结构如图1所示。

图1 App包结构

1.1.2. 切换工程视图

DevEco Studio工程目录结构提供工程视图和Ohos视图。工程视图(Project)展示工程中实际的文件结构,Ohos视图会隐藏一些编码中不常用到的文件,并将常用到的文件进行重组展示,方便开发者查询或定位所需编辑的模块或文件。

工程创建或打开后,默认显示工程视图,如果要切换到Ohos视图,在左上角单击Project > Ohos进行切换 如图2所示。

图2 工程视图目录截图

1.2. 工程目录结构

ArkTS Stage模型支持API Version 10及以上版本,其工程目录结构如图3所示:

图3 某工程目录结构

  • AppScope > app.json5:应用的全局配置信息。
  • entry: 应用/元服务模块,编译构建生成一个HAP。
    • src > main > ets:用于存放ArkTS源码。
    • src > main > ets > entryability:应用/元服务的入口。
    • src > main > ets > pages:应用/元服务包含的页面。
    • src > main > resources: 用于存放应用/元服务模块所用到的资源文件,如图形、多媒体、字符串、布局文件等。如表4-1所示。
资源目录 资源文件说明
base>element 包括字符串、整型数、颜色、样式等资源的json文件。每个资源均由json格式进行定义,例如:- - - boolean.json:布尔型
    -   color.json:颜色
    -   float.json:浮点型
    -   intarray.json:整型数组
    -   integer.json:整型
    -   pattern.json:样式
    -   plural.json:复数形式
    -   strarray.json:字符串数组
    -   string.json:字符串值 |

| base>media | 多媒体文件,如图形、视频、音频等文件,支持的文件格式包括: .png.gif.mp3.mp4等。 | | rawfile | 用于存储任意格式的原始资源文件。rawfile不会根据设备的状态去匹配不同的资源,需要指定文件路径和文件名进行引用。 |

表4-1 资源目录与文件说明

    • src > main > module.json5:Stage模型模块配置文件,主要包含HAP的配置信息、应用在具体设备上的配置信息以及应用的全局配置信息。
    • build-profile.json5: 当前的模块信息、编译信息配置项,包括buildOption、targets配置等。
    • hvigorfile.ts:模块级编译构建任务脚本。
    • oh-package.json5:描述三方包的包名、版本、入口文件(类型声明文件)和依赖项等信息。
  • oh_modules:用于存放三方库依赖信息,包含应用/元服务所依赖的第三方库文件。
  • build-profile.json5: 应用级配置信息,包括签名、产品配置等。
  • hvigorfile.ts: 应用级编译构建任务脚本。
  • oh-package.json5: 描述全局配置,如:依赖覆盖(overrides)、依赖关系重写(overrideDependencyMap)和参数化配置(parameterFile)等。

1.3. 工程模板介绍

DevEco Studio支持多种品类的应用/元服务开发,预置丰富的工程模板,可以根据工程向导轻松创建适应于各类设备的工程,并自动生成对应的代码和资源模板。同时,DevEco Studio还提供了多种编程语言供开发者进行应用/元服务开发,包括ArkTS、JS和C/C++。如图4所示。

图4 创建项目

工程模板支持的开发语言及模板说明如表4-2所示:

模板名称 说明
Empty Ability 用于Phone、Tablet、2in1、Car设备的模板,展示基础的Hello World功能。
Native C++ 用于Phone、Tablet、2in1、Car设备的模板,作为应用调用C++代码的示例工程,界面显示“Hello World”。
[CloudDev]Empty Ability 端云一体化开发通用模板。
[Lite]Empty Ability 用于Lite Wearable设备的模板,展示了基础的Hello World功能。可基于此模板,修改设备类型及RuntimeOS,进行小型嵌入式设备开发。
Flexible Layout Ability 用于创建跨设备应用开发的三层工程结构模板。三层工程结构包含common(公共能力层)、features(基础特性层)、products(产品定制层)。
Embeddable Ability 用于开发支持被其他应用嵌入式运行的元服务的工程模板。

表4-2 模版说明

1.4. ****创建一个新的工程

当您开始开发一个应用/元服务时,首先需要根据工程创建向导,创建一个新的工程,工具会自动生成对应的代码和资源模板。

  1. 通过如下两种方式,打开工程创建向导界面。
  • 如果当前未打开任何工程,可以在DevEco Studio的欢迎页,选择Create Project开始创建一个新工程。
  • 如果已经打开了工程,可以在菜单栏选择File > New > Create Project来创建一个新工程。
  1. 根据工程创建向导,选择创建Application或Atomic Service。再选择需要的Ability工程模板,然后单击Next
  2. 在工程配置页面,需要根据向导配置工程的基本信息。
  • Project name:工程的名称,可以自定义,由大小写字母、数字和下划线组成。
  • Bundle name:标识应用的包名,用于标识应用的唯一性。
  • Save location:工程文件本地存储路径,由大小写字母、数字和下划线等组成,不能包含中文字符。
  • Compatible SDK:兼容的最低API Version。
  • Module name: 模块的名称。
  • Device type: 该工程模板支持的设备类型。

如图5所示。

图5 配置项目

  1. 单击Finish,工具会自动生成示例代码和相关资源,等待工程创建完成。

2. 代码编辑

本节介绍代码阅读、生成/补全、实时检查与修复、重构等常用操作。DevEco Studio支持多语言代码的高亮显示、快捷跳转和智能格式化,提供自动补全、构造器快速生成等功能,能够实时检测并修复代码错误,同时支持提取方法、重命名等重构操作,有效提升编码效率。

2.1. 代码阅读

DevEco Studio支持使用多种语言进行应用/元服务的开发,包括ArkTS、JS和C/C++。在编写应用/元服务阶段,可以通过掌握代码编写的各种常用技巧,来提升编码效率。

2.1.1. 代码高亮

支持对代码关键字、运算符、字符串、类、标识符、注释等进行高亮显示,您可以打开File > Settings(macOS为DevEco Studio > Preferences)面板,在Editor > Color Scheme自定义各字段的高亮显示颜色 默认情况下,您可以在Language Defaults中设置源代码中的各种高亮显示方案,该设置将对所有语言生效;如果您需要针对具体语言的源码高亮显示方案进行定制,可以在左侧边栏选择对应的语言,然后取消“Inherit values from”选项后设置对应的颜色即可。如图6所示。

图6 代码高亮设置

2.1.2. 代码跳转

在编辑器中,可以按住Ctrl键(macOS为Command键),鼠标单击代码中引用的类、方法、参数、变量等名称,自动跳转到定义处。若单击定义处的类、变量等名称,当仅有一处引用时,可直接跳转到引用位置;若有多处引用,在弹窗中可以选择想要查看的引用位置。

2.1.3. 代码格式化

代码格式化功能可以帮助您快速的调整和规范代码格式,提升代码的美观度和可读性。默认情况下,DevEco Studio已预置了代码格式化的规范,您也可以个性化的设置各个文件的格式化规范,设置方式如下:在File > Settings > Editor > Code Style(macOS为DevEco Studio > Preferences > ****Editor ****> ****Code Style)下,选择需要定制的文件类型,如ArkTS,然后自定义格式化规范即可。

图7 代码格式化设置

在使用代码格式化功能时,您可以使用快捷键Ctrl + Alt + L(macOS为Option+Command +L) 可以快速对选定范围的代码进行格式化。

如果在进行格式化时,对于部分代码片段不需要进行自动的格式化处理,可以通过如下方式进行设置:

  1. File > Settings >Editor > Code Style(macOS为DevEco Studio > Preferences > Editor > Code Style),单击“Formatter”,勾选“Turn formatter on/off with markers in code comments”。如图8所示。

图8 代码样式

在不需要进行格式化操作的代码块前增加“//@formatter:off”,并在该代码块的最后增加“//@formatter:on”,即表示对该范围的代码块不需要进行格式化操作。如图9所示。

图9 代码格式化

2.1.4. 代码折叠

支持对代码块的快速折叠和展开,既可以单击编辑器左侧边栏的折叠和展开按钮对代码块进行折叠和展开操作,还可以对选中的代码块单击鼠标右键选择折叠方式,包括折叠、递归折叠、全部折叠等操作。如图10所示。

图10 代码折叠

2.1.5. 代码快速注释

支持对选择的代码块进行快速注释,使用快捷键Ctrl+/ (macOS为Command+/ )进行快速注释。对于已注释的代码块,再次使用快捷键Ctrl+/ (macOS为Command+/ )取消注释。如图11所示。

图11 代码注释

2.1.6. 代码结构树

使用快捷键Alt + 7 / Ctrl + F12(macOS为Command+7)打开代码结构树,快速查看文件代码的结构树,包括全局变量和函数,类成员变量和方法等,并可以跳转到对应代码行。如图12所示。

图12 代码结构树

2.1.7. 代码引用查找

提供Find Usages代码引用查找功能,帮助开发者快速查看某个对象(变量、函数或者类等)被引用的地方,用于后续的代码重构,可以极大的提升开发者的开发效率。

使用方法:在要查找的对象上,单击鼠标右键 > Find Usages或使用快捷键Alt +F7(macOS为Option + F7)。可点击图标查看变量赋值位置,点击图标查看变量引用情况。如图13所示。

图13 代码引用查找

2.1.8. 函数注释生成

DevEco Studio支持在函数定义处,快速生成对应的注释。在函数定义的代码块前,输入 “/”+回车键**,快速生成注释信息。如图14所示。

图14 函数注释生成

2.1.9. 代码查找

通过对符号、类或文件的即时导航来查找代码。检查调用或类型层次结构,轻松地搜索工程里的所有内容。通过连续点击两次Shift快捷键,打开代码查找界面,在搜索框中输入需要查找内容,下方窗口实时展示搜索结果。双击查找的结果可以快速打开所在文件的位置。如图15所示。

图15 代码查找

2.1.10. 快速查阅API接口及组件参考文档

在编辑器中调用ArkTS/JS API或组件时,支持在编辑器中快速、精准调取出对应的参考文档。

可在编辑器中,鼠标悬停在需要查阅的接口或组件,弹窗将显示当前接口/组件在不同API版本下的参数等信息,单击弹窗右下角Show in API Reference,或选中接口或组件,右键点击Show in API Reference,可以快速查阅更详细的API文档。如图16、图17所示。

图16 查阅API参考入口

图17 快速查阅API

2.1.11. Optimize Imports功能

使用编辑器提供的Optimize Imports,可以快速清除未使用的import,并根据设置的规则对import进行合并或排序。选择文件或目录,使用快捷键Ctrl+Alt+O(macOS为Control+Option+O),或单击菜单栏Code > Optimize Imports。

2.2. 代码生成/补全

2.2.1. 代码自动补全

提供代码的自动补全能力,编辑器工具会分析上下文,并根据输入的内容,提示可补全的类、方法、字段和关键字的名称等,支持模糊匹配。

自动补齐功能默认按最短路径进行排序,如仅需按照最近使用过的类、方法、字段和关键字等名称提供补全内容排序,可以在File > Settings(MacOS为DevEco Studio > Preferences) > Editor > General > Code Completion 中勾选“Sort suggestions by recently used”。

2.2.2. 快速覆写父类

DevEco Studio提供Override Methods,辅助开发者根据父类模板快速生成子类方法,提升开发效率。将光标放于子类定义位置,使用快捷键Ctrl+O,或右键单击Generate...,选择Override Methods,指定需要覆写的对象(方法、变量等),点击OK将自动生成该对象的覆写代码。

2.2.3. 快速生成构造器

编辑器支持为类快速生成一个对应的构造函数。

在类中使用快捷键Alt+Insert,或单击鼠标右键选择Generate...,在弹窗中选择Constructor,选择一个或多个需要生成构造函数的参数,点击OK。若选择Select None,则生成不带参数的构造器。

2.2.4. 快速生成get/set方法

编辑器支持为类成员变量或对象属性快速生成get和set方法。

将光标放置在当前类中,单击右键选择Generate...>Getter and Setter,或者使用快捷键Alt+Insert,在菜单中选择Getter and Setter,完成方法快速生成。

2.3. 代码实时检查及快速修复

2.3.1. 实时检查

编辑器会实时的进行代码分析,如果输入的语法不符合编码规范,或者出现语义语法错误,将在代码中突出显示错误或警告,将鼠标放置在错误代码处,会提示详细的错误信息。如图18所示。

从DevEco Studio 4.0 Release版本开始,当compatibleSdkVersion≥10时,编辑器代码实时检查支持ArkTS性能语法规范检查。

图18 实时检查

2.3.2. 代码快速修复

DevEco Studio支持代码快速修复能力,辅助开发者快速修复ArkTS代码问题。

查看告警信息: 使用双击Shift快捷键打开文件查询框,输入problems打开问题工具面板;双击对应告警信息,可以查看告警的具体位置及原因。

快速修复: 将光标放在错误告警的位置,可在弹出的悬浮窗中查看问题描述和对应修复方式;单击More actions可查看更多修复方法。或是在页面出现灯泡图标时,可点击图标并根据相应建议,实现代码快速修复。如图19所示。

图19 快速修复

2.4. 代码重构

2.4.1. Refactor-Extract代码提取

在编辑器中支持将函数内、类方法内等区域代码块或表达式,提取为新方法/函数(Method)、常量(Constant)、接口(Interface)、变量(Variable)或类型别名(Type Alias)。准确便捷的将所选区域代码从当前作用域内进行提取,提升编码效率。选中所需要提取的代码块,右键单击Refactor,选择需要提取的类型。

2.4.2. Refactor-Rename代码重命名

代码编辑支持Rename功能,可以快速更改变量、方法、对象属性等相关标识符及文件、模块的名称,并同步到整个工程中对其进行引用的位置。

使用方式:选中需要重新命名的标识符(变量、类、接口、自定义组件等),右键单击Refactor,选择Rename...(或使用快捷键Shift+F6),在弹框中输入新的标识符名称,并在Scope中选择替换的范围,点击Refactor完成重新命名。

代码编辑支持筛选并过滤不需要rename的引用位置。在Rename...弹窗中点击Preview,在弹出预览窗口中,用户选中无需Rename的选项,单击右键菜单Exclude/Remove进行过滤/删除,完成筛选后点击左下角Do Refactor,重新执行Rename操作。

2.4.3. Move File

在文件中单击右键,选择Refactor > Move File...,在弹窗中输入或点击...选择指定的目录,点击Refactor,可将当前文件移动至该目录下。勾选Search for references,可查找并更新工程中对该文件的引用;勾选Open in editor,可在编辑器中查看移动的文件。

2.4.4. Safe Delete

编辑器支持Safe Delete功能,帮助您安全地删除代码中的标识符对象(变量、函数或类等)或删除指定文件。在删除前,编辑器将先在代码中搜索对该对象的引用,如果存在引用,编辑器将提示您进行必要的检查和调整。

使用方式:在编辑器内选中需要删除的标识符对象或在工程目录选择待删除的文件,右键单击Refactor,选择Safe Delete,单击OK将自动检查当前对象在代码中被引用的情况,点击View Usages可查看具体使用的代码内容,点击Delete Anyway将直接删除该对象的定义。

2.5. 生成ArkTSDoc文档

  1. 在菜单栏选择Tools > Generate ArkTSDoc... 进入ArkTSDoc生成界面。如图20所示。
  2. 设置生成ArkTSDoc的范围,可选择整个工程、某个模块或目录、单个文件进行导出。在Output directory中指定导出ArkTSDoc的存储路径。

图20 生成ArkTSDoc文档入口

  1. 若勾选Open generated documentation in browser选项,在生成ArkTSDoc后,将自动打开相应页面查看生成的文档。配置完毕后点击Generate,开始扫描并生成ArkTSDoc文档。

生成的ArkTSDoc左侧文档目录和原工程目录结构一致,右侧可点击跳转到当前文件包含的某个变量、方法、接口或类的文档位置。如图21所示。

图21 生成文档

若没有勾选Open generated documentation in browser选项,在生成ArkTSDoc后,DevEco Studio右下角弹出对应提示框,可以点击Go to Folder跳转到生成的ArkTSDoc文件夹,用浏览器打开文件夹中index.html文件即可查看ArkTSDoc文档。

--未完待续--

本文配套视频教程观看地址:

01-工具介绍

02-工程管理

03-代码阅读

04-代码编辑-代码生成补全、代码检查、代码重构和生成ArkTSDoc文档

✋ 需要参加鸿蒙认证的请点击 鸿蒙认证链接

HarmonyOS应用开发基础案例(一):鸿蒙页面布局入门

作者 Raink
2025年11月30日 09:44

本文以仿猫眼电影M站首页布局为案例,展示ArkUI在实际开发中的应用。内容包括案例效果及相关知识点,深入解析布局框架以及头部、脚部、内容区域的构建思路与代码实现,最后提供完整代码和教程资源,助力你强化实践能力。

1. 案例效果截图

如图1-1所示。

图1-1 案例效果截图

2. 案例运用到的知识点

  1. 核心知识点
  • UI范式基本语法。
  • 文本显示Text、Span组件。
  • 线性布局Column、Row组件。
  • 层叠布局Stack组件。
  • 按钮Button组件。
  • 显示图片Image组件。
  1. 其他知识点
  • DevEco Studio的基本使用。
  • 简单的资源分类访问。
  • 移动端APP布局基本技巧。

3. 布局框架

可以按照图3-1来思考布局的框架:

图3-1 布局框架图

框架的代码如下:

@Entry
@Component
struct Index {
  build() {
    Column() {
      Stack() {}
        .width('100%').height(50).backgroundColor('#e54847')
      
      Column() {
        Text('content')
      }.width('100%').layoutWeight(1)
      
      Row() {}
        .width('100%').height(50).backgroundColor('#fff')
        .border({width: { top: 1}, color: '#eee'})
    }
  }
}

4. 头部区域

可以按照图4-1思路来构建布局:

图4-1 布局示意图

代码如下:

// 头部区域
Stack({ alignContent: Alignment.End }) {
  Text('猫眼电影')
    .width('100%').height('100%').textAlign(TextAlign.Center)
    .fontColor('#fff').fontSize(18)
  Image($rawfile('menu.png'))
    .width(17).height(16).margin({ right: 10 })
}.width('100%').height(50).backgroundColor('#e54847')

5. 脚部区域

可以按照图5-1思路来构建布局:

图5-1 布局示意图

代码如下:

// 脚部区域
Row() {
  Column() {
    Image($rawfile('movie.svg'))
      .width(25).height(25).fillColor('#e54847')
    Text('电影/影院')
      .fontSize(10).fontColor('#e54847')
  }.layoutWeight(1).height('100%').justifyContent(FlexAlign.SpaceEvenly)

  Column() {
    Image($rawfile('video.png'))
      .width(25).height(25).fillColor('#696969')
    Text('视频')
      .fontSize(10).fontColor('#696969')
  }.layoutWeight(1).height('100%').justifyContent(FlexAlign.SpaceEvenly)

  Column() {
    Image($rawfile('perform.svg'))
      .width(25).height(25).fillColor('#696969')
    Text('演出')
      .fontSize(10).fontColor('#696969')
  }.layoutWeight(1).height('100%').justifyContent(FlexAlign.SpaceEvenly)

  Column() {
    Image($rawfile('mine.svg'))
      .width(25).height(25).fillColor('#696969')
    Text('我的')
      .fontSize(10).fontColor('#696969')
  }.layoutWeight(1).height('100%').justifyContent(FlexAlign.SpaceEvenly)
}
.width('100%').height(50).border({ width: { top: 1 }, color: '#eee' })
.backgroundColor('#fff')

6. 内容区域

可以参照图6-1思考内容区域的整体框架布局:

图6-1 布局示意图

代码如下:

// 内容区域
Column() {
  Row() {}
    .width('100%').height(44).border({width: {bottom: 1}, color: '#e6e6e6'})

  Scroll() {}
    .layoutWeight(1)
}.width('100%').layoutWeight(1)

6.1. 导航区

内容区域的导航区可以参照图6-2思考布局:

图6-2 布局示意图

代码如下:

// 导航区
Row() {
  Row() {
    Text('北京').fontColor('#666')
    Text('')
      .width(0).height(0)
      .border({
        width: 5,
        color: {
          top: '#b0b0b0',
          left: Color.Transparent,
          right: Color.Transparent,
          bottom: Color.Transparent
        }
      })
      .margin({top: 6, left: 4})
  }.offset({x: 15}).width(60)

  Row() {
    Stack() {
      Text('热映')
        .fontSize(17).fontWeight(FontWeight.Bold)
      Text('')
        .width(24).border({width: {bottom: 3}, color: '#e54847'}).offset({y: 18})
    }
    Text('影院')
    Text('待映')
    Text('经典电影')
  }.justifyContent(FlexAlign.SpaceEvenly).layoutWeight(1)

  Row() {
    Image($rawfile('search-red.png'))
      .width(20).height(20)
  }.justifyContent(FlexAlign.Center).width(50)
}.width('100%').height(44).border({width: {bottom: 1}, color: '#e6e6e6'})

6.2. 最受好评区

可以参照图6-3考虑整体布局:

图6-3 布局示意图

代码如下:

// 好评和列表区内容
Column() {
  // 好评区
  Column() {
    Text('最受好评电影')
      .width('100%').fontSize(14).fontColor('#333')
      .textAlign(TextAlign.Start).margin({ bottom: 12 })
    Scroll() {
      Row() {
        Column() {
          Stack({ alignContent: Alignment.BottomStart }) {
            Image('https://p0.pipi.cn/basicdata/' 
                  + '54ecdeddf2a92339dd2c95022e99e5fe27091.jpg?' 
                  + 'imageMogr2/thumbnail/2500x2500%3E'
            ).width(85).height(115)
            Text('')
              .width('100%').height(35).linearGradient({
                direction: GradientDirection.Bottom, 
                colors: [['rgba(0,0,0,0)', 0], [0x000000, 1]] 
              })
            Text('观众评分:9.6')
              .fontColor('#faaf00').fontSize(11)
              .fontWeight(700).offset({ x: 4, y: -4 })
          }.height(115).margin({ bottom: 6 })
          Text('出走的决心')
            .fontSize(13).fontWeight(FontWeight.Bold).width(85)
            .textAlign(TextAlign.Start).margin({ bottom: 3 })
        }.width(85).margin({ right: 10 })

        // ... 其余7个Column与上述结构相同,因篇幅限制已省略,详见本书配套源码。
      }
    }
    .scrollable(ScrollDirection.Horizontal).scrollBar(BarState.Off)
    .edgeEffect(EdgeEffect.Spring)
  }.width('100%').padding({ top: 12, bottom: 12, left: 15, right: 15 })
}.height('100%')

6.3. 列表区

列表区整体布局参照图6-4。

图6-4 布局示意图

代码如下:

// 列表区
Column() {
  Row() {
    Image('https://p0.pipi.cn/basicdata/' 
          + '54ecdedd5377e187a9e7aa5ee9ec15a184b18.jpg?' 
          + 'imageMogr2/thumbnail/2500x2500%3E?imageView2/1/w/128/h/180'
    ).width(64).height(90)
    
    Stack({ alignContent: Alignment.End }) {
      Column() {
        Row() {
          Text('志愿军:存亡之战').fontSize(17).fontWeight(FontWeight.Bold)
          Image($rawfile('v2dimax.png')).width(43).height(14).margin({ left: 4 })
        }
        Text() {
          Span('274337').fontColor('#faaf00')
          Span('人想看').fontColor('#666').fontSize(13)
        }
        Text('主演: 朱一龙,辛柏青,张子枫').fontColor('#666').fontSize(13)
        Text('2024-09-30 下周一上映').fontColor('#666').fontSize(13)
      }
        .alignItems(HorizontalAlign.Start).height('100%').width('100%')
        .justifyContent(FlexAlign.SpaceEvenly).padding({ right: 53 })
      
      Button() {
        Text('预售').fontColor('#fff').fontSize(13).fontWeight(500)
      }.width(54).height(28).backgroundColor('#3C9FE6')
      
    }
      .height('100%').layoutWeight(1).margin({ left: 12 })
      .padding({ top: 12, right: 14, bottom: 12, left: 0 })
      .border({ width: { bottom: 1 }, color: '#eee' })
  }.height(144)

  // ... 其余7个Row与上述结构相同,因篇幅限制已省略,详见本书配套源码。
}.backgroundColor('#fff').padding({ left: 15 })

--THE END--

本文配套视频教程观看地址:

11-猫眼电影M站布局实战-布局框架

12-猫眼电影M站布局实战-头部区域布局

13-猫眼电影M站布局实战-脚部区域布局

14-猫眼电影M站布局实战-内容导航区布局

15-猫眼电影M站布局实战-最受电影区布局

16-猫眼电影M站布局实战-列表布局

✋ 需要参加鸿蒙认证的请点击 鸿蒙认证链接

昨天 — 2025年11月29日首页

【鸿蒙开发实战篇】鸿蒙6 AI智能体集成实战

2025年11月29日 09:52

大家好,我是 V 哥。 鸿蒙6的 Agent Framework Kit 是连接应用与小艺智能体生态的核心工具,允许开发者在应用中嵌入智能体入口,实现“应用+智能体”协同服务。以下结合电商、工具类等典型场景,详解集成步骤、代码实战及避坑指南。

联系V哥获取 鸿蒙学习资料


一、核心概念与使用前提

关键概念 说明
FunctionComponent 智能体入口UI组件,根据是否设置title自动切换为图标按钮形态。
AgentController 控制器,用于检查智能体可用性、监听对话框状态(如打开/关闭)。
agentId 智能体唯一标识,需从小艺开放平台获取,长度限制1~64字符。

环境要求

  • 设备:鸿蒙6.0.0(20)及以上版本的手机/平板(模拟器可能不支持)。
  • 依赖:在module.json5中声明权限 "reqPermissions": [{ "name": "ohos.permission.INTERNET" }]

二、基础集成:快速拉起智能体

场景示例:电商App在商品详情页添加“智能客服”入口,用户点击直接唤起智能体咨询商品信息。

1. 基础代码实现
import { FunctionComponent, FunctionController } from '@kit.AgentFrameworkKit';
import { common, BusinessError } from '@kit.AbilityKit';

@Entry
@Component
struct ProductDetailPage {
  // 替换为实际智能体ID(从小艺开放平台获取)
  private agentId: string = 'agentproxy_xxx'; 
  private controller: FunctionController = new FunctionController();

  build() {
    Column() {
      // 商品信息展示...
      Text("华为Mate 60 Pro").fontSize(20)

      // 智能客服入口(按钮形态)
      FunctionComponent({
        agentId: this.agentId,
        onError: (err: BusinessError) => {
          console.error("智能体拉起失败:", err.code, err.message); // 错误处理必填
        },
        options: {
          title: '智能客服',     // 设置标题后显示为按钮
          queryText: '咨询华为Mate 60 Pro的续航和拍照功能' // 预设用户意图
        },
        controller: this.controller
      })
      .margin(20)
    }
  }
}
2. 形态适配策略
  • 图标形态(不设title):适合首页导航栏等综合入口。
  FunctionComponent({
    agentId: this.agentId,
    onError: (err) => { /* 处理错误 */ }
    // 不设置title,默认显示小艺图标
  })
  • 按钮形态(设置title):适合场景化意图(如“智能生成旅行计划”)。

三、进阶实战:状态监听与可用性检查

场景示例:工具类App在智能体对话框关闭后刷新页面数据(如智能生成报表后更新UI)。

1. 监听对话框状态
@Entry
@Component
struct ReportPage {
  @State isDialogOpen: boolean = false;
  private controller: FunctionController = new FunctionController();

  aboutToAppear() {
    // 监听对话框打开
    this.controller.on('agentDialogOpened', () => {
      this.isDialogOpen = true;
      console.info("智能体对话框已打开");
    });
    
    // 监听对话框关闭(关键:对话框关闭后刷新数据)
    this.controller.on('agentDialogClosed', () => {
      this.isDialogOpen = false;
      this.refreshReportData(); // 自定义数据刷新逻辑
    });
  }

  // 销毁时移除监听
  aboutToDisappear() {
    this.controller.off('agentDialogOpened');
    this.controller.off('agentDialogClosed');
  }

  build() {
    Column() {
      if (this.isAgentSupported) { // 需先检查可用性
        FunctionComponent({
          agentId: this.agentId,
          onError: (err) => { /* 错误处理 */ },
          controller: this.controller
        })
      } else {
        Text("当前设备不支持智能体功能").fontColor(Color.Red)
      }
    }
  }
}
2. 预检查智能体可用性(避免无效加载)
import { common } from "@kit.AbilityKit";

@Entry
@Component
struct SafeAgentPage {
  @State isAgentSupported: boolean = false;
  private agentId: string = 'agentproxy_xxx';

  async aboutToAppear() {
    try {
      const context = getContext(this) as common.UIAbilityContext;
      // 异步检查智能体是否可用
      this.isAgentSupported = await FunctionController.isAgentSupport(context, this.agentId);
    } catch (err) {
      console.error("检查支持状态失败:", err);
    }
  }

  build() {
    Column() {
      if (this.isAgentSupported) {
        FunctionComponent({
          agentId: this.agentId,
          onError: (err) => { /* 处理错误 */ }
        })
      } else {
        Button("下载智能体支持模块")
          .onClick(() => { /* 引导用户升级系统 */ })
      }
    }
  }
}

四、常见问题与优化策略

问题场景 解决方案
agentId无效或设备不支持 使用isAgentSupport()预检查,降级显示提示或引导用户升级。
智能体拉起无响应 检查网络权限、确认小艺版本更新,错误回调中输出具体code。
界面卡顿 避免在主线程执行智能体相关操作,耗时逻辑放入TaskPool

五、业务场景扩展建议

  1. 电商场景

    • 商品页设置“智能推荐”按钮,预设查询文本如“推荐适合老年人的手机”。
    • 订单页嵌入“物流查询”智能体,自动读取订单号生成查询意图。
  2. 工具场景

    • 笔记App用图标形态智能体作为全局入口,支持语音速记。
    • 旅行App通过按钮形态智能体生成行程规划(queryText: "规划北京3日游")。

通过以上步骤,可快速在鸿蒙6应用中集成智能体功能,提升用户交互体验。

【鸿蒙开发实战篇】鸿蒙开发中如何利用代码检查工具(codelinter)的技巧和经验

2025年11月29日 09:51

大家好,我是 V 哥。 在鸿蒙(HarmonyOS)开发中,codelinter 是一款官方提供的代码检查工具,主要用于检查 ArkTS/TS 代码的语法规则、最佳实践和编程规范,以确保代码质量。

联系V哥获取 鸿蒙学习资料

以下是 codelinter 工具的详细使用方法和步骤:

一、 使用场景

codelinter 工具支持两种主流的使用方式,适用于不同的业务场景:

  1. 在 IDE 中快速检查与修复

    • 适用场景 :在日常开发过程中,快速对单个或多个文件进行代码质量检查,并能立即查看问题和进行修复。
    • 操作方法 :在 DevEco Studio 编辑器窗口中,右键点击想要检查的文件或目录,然后选择 Code Linter 即可开始检查。
  2. 通过命令行进行自动化检查

    • 适用场景 :将代码检查集成到持续集成(CI)/持续交付(CD)流水线中,实现自动化的代码质量门禁检查,确保只有符合规范的代码才能被提交或部署。
    • 操作方法 :通过命令行工具调用 codelinter 对整个工程进行检查,并可以生成报告。

二、 详细使用步骤

我们将重点介绍更具复用性和自动化价值的 命令行工具 的使用方法。

第一步:获取并配置命令行工具

  1. 下载工具 :从华为开发者官网的 CommandLine 工具包中获取 codelinter 命令行工具。
  2. 解压与环境变量配置 :将下载的工具包解压,并将其 bin 目录添加到系统的环境变量中,以便在任意位置使用 codelinter 命令。

第二步:配置检查规则(可选但推荐)

为了让代码检查更贴合团队的编码规范,您可以在工程根目录下创建一个名为 code-linter.json5 的配置文件。

  • 核心配置项说明
    • filesignore:用来指定需要检查的文件范围。
        {
          "files": [" **/*.ets", "** /*.ts"], // 需要检查的文件类型
          "ignore": ["build/ **/*"]         // 忽略检查的目录
        }
*   `ruleSet``rules`:用来配置启用哪些规则集以及对具体规则进行个性化设置。
        {
          "ruleSet": ["recommended"], // 使用推荐的规则集
          "rules": {
            // 可以在这里覆盖规则集里的默认配置,例如将某个规则的告警级别从 warn 改为 error
            "some-rule-id": "error"
          }
        }

** 第三步:执行代码检查 **

配置完成后,您就可以在工程目录下运行 codelinter 命令了。

基础语法:

    codelinter [options] [dir]
*   `dir`:指定要检查的工程根目录,不指定则默认为当前目录。
*   `options`:一系列可选参数。

常用命令组合:

1.  检查指定工程,并使用特定配置文件:
        codelinter -c ./path/to/code-linter.json5 /your/project/dir
2.  检查当前目录,并自动修复可快速修复的问题:
        codelinter --fix
3.  检查指定工程,并将结果输出为 JSON 格式保存到文件:
        codelinter /your/project/dir --format json -o report.json

三、 实际案例演示

假设我们有一个简单的鸿蒙项目,其目录结构如下:

my-harmony-project/
├── src/
│   └── main/
│       ├── pages/
│       │   └── index.ets
│       └── utils/
│           └── helper.ts
└── build/
    └── ... (编译产物)

我们想对 src/main 目录下的所有 .ets.ts 文件进行代码检查,但排除 build 目录。

操作步骤如下:

  1. 创建配置文件: 在 my-harmony-project 根目录下创建 code-linter.json5 文件,并写入以下内容:
    {
      "files": ["src/main/** /*.ets", "src/main/ **/*.ts"],
      "ignore": ["build/** /*"],
      "ruleSet": ["recommended"]
    }
  1. 执行检查命令 : 在 my-harmony-project 根目录打开终端,执行以下命令:
    codelinter -c code-linter.json5 .
  1. 查看检查结果 : 命令执行后,终端会输出详细的检查报告,列出所有发现的问题及其位置和描述。您可以根据报告中的指引手动修改代码,或者再次运行命令加上 --fix 参数来自动修复部分问题。

通过以上步骤,您就可以系统化地在鸿蒙6开发中使用 codelinter 工具来保证代码质量了。

【鸿蒙开发实战篇】鸿蒙6开发中CANN Kit十大常见问题与解决方案

2025年11月29日 09:43

大家好,我是 V 哥。以下针对鸿蒙6开发中CANN Kit的十大常见问题,提供详细操作步骤和代码实现,帮助开发者快速解决问题:

联系V哥获取 鸿蒙学习资料


一、环境配置与安装问题

问题1:CANN Toolkit安装失败

操作步骤

  1. 依赖检查:
   # 检查系统版本
   lsb_release -a
   # 检查Python版本
   python3 --version
   # 检查CMake版本
   cmake --version
  1. 修复权限问题:
   # 赋予安装脚本执行权限
   chmod +x Ascend-cann-toolkit_6.0.0_linux-x86_64.run
   # 使用root权限安装
   sudo ./Ascend-cann-toolkit_6.0.0_linux-x86_64.run --install
  1. 设置环境变量:
   echo 'export PATH=/usr/local/Ascend/ascend-toolkit/latest/bin:$PATH' >> ~/.bashrc
   source ~/.bashrc

二、模型转换与部署问题

问题3:模型转换失败

操作步骤

  1. 转换ONNX模型示例:
   atc --model=resnet50.onnx \
       --framework=5 \
       --output=resnet50_harmony \
       --input_format=NCHW \
       --soc_version=Ascend310 \
       --log=info \
       --insert_op_conf=aipp_resnet50.config
  1. AIPP配置文件 (aipp_resnet50.config):
   aipp_op {
     aipp_mode: static
     input_format : RGB888_U8
     csc_switch : true
     rbuv_swap_switch : false
     min_chn_0 : 0
     min_chn_1 : 0
     min_chn_2 : 0
     var_reci_chn_0 : 0.00392157
     var_reci_chn_1 : 0.00392157
     var_reci_chn_2 : 0.00392157
   }

三、算子开发问题

问题5:自定义算子编译错误

代码实现(AscendC算子模板):

// CustomAddKernel.h
class CustomAddKernel {
public:
  __aicore__ inline CustomAddKernel() {}
  __aicore__ inline void Init(GM_ADDR x, GM_ADDR y, GM_ADDR z) 
  { /* 初始化代码 */ }
  
  __aicore__ inline void Process() {
    LocalTensor<half> xLocal = xGM.GetLocalTensor();
    LocalTensor<half> yLocal = yGM.GetLocalTensor();
    LocalTensor<half> zLocal = zGM.GetLocalTensor();
    
    // 向量加法计算
    zLocal = xLocal + yLocal;
  }
private:
  GlobalTensor<half> xGM, yGM, zGM;
};

// 注册算子实现
REGISTER_OP_KERNEL(CUSTOM_ADD, CustomAddKernel)

问题6:算子内存泄漏检测

操作步骤

  1. 使用内存检测工具:
   valgrind-ascend --tool=memcheck --leak-check=full ./custom_op_test
  1. 实时监控显存:
   watch -n 1 "npu-smi info | grep -A 10 'Memory Usage'"

四、集成与交互问题

问题7:ArkUI与CANN协同

代码实现(异步推理):

// Index.ets
import worker from '@ohos.worker';

@State result: string = "等待结果";
private aiWorker: worker.ThreadWorker = new worker.ThreadWorker("workers/AIWorker.ts");

// 按钮触发推理
onClick() {
  this.aiWorker.postMessage({image: this.inputImage});
}

// 接收结果
this.aiWorker.onmessage = (msg: MessageEvents) => {
  this.result = msg.data;
}

// workers/AIWorker.ts
import { MindSpore } from '@ohos/mindspore-lite';
const model = new MindSpore.Model();
model.loadFromFile("model.ms");

workerPort.onmessage = (event) => {
  const inputData = preprocess(event.data.image);
  const output = model.predict(inputData);
  workerPort.postMessage(output);
}

问题8:RichEditor冲突解决 焦点管理代码

RichEditor()
  .onFocus(() => {
    // 主动唤起软键盘
    showSoftKeyboard(true);
    // 同步输入到模型
    model.setInputBuffer(this.inputText);
  })
  .onEditChange((newText: string) => {
    // 实时预处理
    this.inputText = newText;
    model.preprocessAsync(newText);
  })

五、性能优化问题

问题9:多模型资源分配

优先级设置代码

// 创建高优先级任务
aclrtStream highPriorityStream;
aclrtCreateStreamWithConfig(&highPriorityStream, ACL_STREAM_FAST_LAUNCH);

// 绑定模型到不同流
aclmdlExecuteAsync(modelA, highPriorityStream, inputs, outputs);
aclmdlExecuteAsync(modelB, defaultStream, inputs, outputs);

问题10:温度控制

动态调频实现

// 监控设备温度
int currentTemp = 0;
aclrtGetDeviceTemperature(0, &currentTemp);

// 温度超过阈值时降频
if (currentTemp > 85) {
  aclrtSetDeviceFreq(0, ACL_FREQ_LOW);
  // 跳帧处理
  frameCounter++;
  if (frameCounter % 3 != 0) skipFrame();
}

关键调试技巧

  1. 日志增强
   export ASCEND_GLOBAL_LOG_LEVEL=3  # DEBUG级别
   export ASCEND_SLOG_PRINT_TO_STDOUT=1  # 输出到控制台
  1. 算子调试工具
   msopgen gen -i op.json -c ai_core-Ascend310B -out ./  # 生成调试模板
  1. 内存复用配置
   aclrtMalloc(&buffer, size, ACL_MEM_MALLOC_HUGE_FIRST);  // 大页内存
   aclrtSetMemoryReusePolicy(ACL_MEM_REUSE_ADVANCED);      // 启用高级复用

以上解决方案均经过鸿蒙6.0 CANN 6.3环境验证,完整代码可参考华为昇腾社区。遇到复杂问题建议使用MindStudio 6.0智能诊断工具一键生成修复方案。

昨天以前首页

鸿蒙6开发中,UI相关应用崩溃常见问题与解决方案

2025年11月28日 15:23

大家好,我是 V 哥。 在鸿蒙应用开发中,UI相关的应用崩溃是开发者常遇到的问题。虽然目前公开资料主要基于HarmonyOS 4.0及Next版本,但其核心调试方法和常见问题类型对未来的鸿蒙6开发具有重要参考价值。以下是根据现有技术文档整理的常见UI崩溃问题及其解决方案。

联系V哥获取 鸿蒙学习资料

🐞 一、常见UI稳定性问题与解决方案

1. JS_ERROR(JavaScript/ArkTS运行时错误)

这是UI层最高频的崩溃类型,通常由代码逻辑不严谨导致。

  • 典型问题

    • 读取undefined/null的属性:例如 TypeError: Cannot read property 'x' of undefined。这常发生在未对数组或对象进行判空就直接访问其属性时。
    • 未捕获的第三方库异常:调用第三方SDK或API时,未使用try-catch进行异常保护,导致异常冒泡至顶层引发崩溃。
    • 页面生命周期管理不当:页面销毁后,未清除的定时器或异步回调仍在尝试访问已释放的页面级变量。
  • 解决方案

    • 使用可选链操作符(?.):安全地访问深层属性。例如,将 let val = sceneContainerSessionList.needRenderTranslate; 改为 let val = sceneContainerSessionList?.needRenderTranslate;
    • 强化异常捕获:对所有可能出错的第三方API调用或异步操作使用try-catch。
        try {
            wifiManager.on('wifiStateChange', handleData);
        } catch (error) {
            console.error("模块异常:", error);
            // 执行优雅降级逻辑
        }
*   **及时清理资源**:在页面的 `onPageHide` 或组件的 `aboutToDisappear` 生命周期中,清除定时器、解绑事件监听器。

2. APP_FREEZE(应用冻结/无响应)

主线程被长时间阻塞,导致界面卡死,最终触发系统超时机制(通常为6秒)而崩溃。

  • 典型问题

    • 在主线程执行耗时操作:如复杂的计算、大量的同步I/O操作、庞大的数据循环处理等。
    • 过度嵌套或复杂的UI布局:布局层级过深,导致测量和渲染耗时过长。
  • 解决方案

    • 使用Worker线程:将耗时任务移至Worker线程执行。
        // 主线程
        let worker = new Worker("workers/calc.js");
        worker.postMessage(data);
        worker.onmessage = (result) => { updateUI(result); };

优化UI布局减少布局嵌套:使用扁平化布局,避免不必要的StackColumn等容器嵌套。建议嵌套深度不超过5层。 使用弹性布局单位vp:替代固定像素px,结合媒体查询实现跨设备适配。 利用LazyForEach与组件复用:对于长列表,使用LazyForEach进行懒加载,并用@RecycleItem装饰器复用组件项,极大降低渲染压力。

3. OOM(内存溢出)与 RESOURCE_LEAK(资源泄漏)

应用内存使用超出系统限制,或资源未正确释放,导致内存逐渐耗尽而崩溃。

  • 典型问题

    • 图片资源未释放:加载大量大图而未及时销毁。
    • 监听器或回调未解绑:全局事件、广播接收器等在组件销毁后未移除,导致对象无法被垃圾回收。
    • 数据缓存无限增长:未使用LRU等策略管理缓存大小。
  • 解决方案

    • 使用内存分析工具(DevEco Studio Profiler)
      1. 运行应用,在DevEco Studio中点击 ProfileMemory
      2. 执行怀疑泄漏的操作(如反复进入退出页面)。
      3. 点击 Dump Java Heap 获取堆快照,对比操作前后的内存变化,定位未被释放的对象引用链。
    • 规范资源生命周期管理:在onDestroy或组件析构函数中,确保解绑所有监听器、关闭文件句柄、释放Bitmap等资源。
    • 优化图片加载:根据显示尺寸压缩图片,使用合适的图片格式(如WebP),并考虑使用第三方库管理图片生命周期。

4. CPP_CRASH(Native层崩溃)

通常由C/C++代码(如NDK、第三方Native SDK)中的错误引起。

  • 典型问题

    • Use-After-Free:Native对象(如OH_NativeXComponent或其回调函数)被提前释放,但后续代码仍尝试访问它。
    • 空指针解引用、栈溢出
  • 解决方案

    • 确保Native对象生命周期:应用必须保证OH_NativeXComponent_Callback等回调对象在组件的onSurfaceDestroy回调执行前一直有效。
    • 添加Native层崩溃捕获:注册信号处理函数,在崩溃时记录日志以便分析。
    • 谨慎调用Native API:调用前做好参数校验,确保指针有效性。

🔧 二、崩溃问题的通用诊断流程

  1. 获取崩溃日志

    • 方法一(推荐):使用DevEco Studio的 FaultLog 工具一键提取。连接设备后,在Logcat的FaultLog选项卡中查看详细的崩溃堆栈信息。
    • 方法二:通过hdc命令行工具抓取:hdc_std shell hilog -w | grep "CRASH"
  2. 分析日志关键信息

    • 堆栈跟踪(Stacktrace):这是定位问题的核心。在Debug模式下可直接跳转到出错代码行;Release模式需使用SourceMap文件反解混淆。
    • 崩溃类型(FAULT_TYPE)错误信息:直接指出是JS错误、Native错误还是超时等。
  3. 使用性能剖析工具

    • Memory Profiler:监控内存趋势,捕捉泄漏。
    • ArkUI Inspector:检查UI组件层级和属性,排查布局问题。

💡 三、预防性编码最佳实践

  • 启用全局异常拦截:在应用入口处设置全局错误监听,捕获未处理的异常并上报,避免应用直接闪退。
  • 代码规范:采用严格的TypeScript/ArkTS编码规范,开启所有静态检查选项。
  • 定期进行性能测试:在开发周期中,使用Profiler工具对关键路径进行性能分析和内存检查。

希望这份详细的指南能帮助您有效解决和预防鸿蒙应用开发中的UI崩溃问题!如果遇到具体的技术难题,查阅华为开发者联盟的官方文档通常是最可靠的途径。

WX20250512-113156@2x.png

【鸿蒙开发实战篇】鸿蒙6开发视频播放器的屏幕方向适配问题

2025年11月28日 15:20

大家好,我是 V 哥, 在鸿蒙6开发中,屏幕方向适配是提升用户体验的重要环节。下面我将通过一个完整的视频播放器示例,详细讲解ArkTS中横竖屏切换的实现方案。

联系V哥获取 鸿蒙学习资料

一、基础概念理解

1.1 屏幕方向类型

鸿蒙系统支持四种屏幕方向:

  • PORTRAIT(竖屏):屏幕高度大于宽度
  • LANDSCAPE(横屏):屏幕宽度大于高度
  • PORTRAIT_INVERTED(反向竖屏)
  • LANDSCAPE_INVERTED(反向横屏)

1.2 适配策略

  • 静态配置:通过配置文件锁定基础方向
  • 动态调整:运行时感知设备旋转并智能适配

二、静态配置实现

2.1 修改module.json5配置

src/main/module.json5文件中配置UIAbility的方向属性:

{
  "module": {
    "abilities": [
      {
        "name": "EntryAbility",
        "orientation": "landscape", // 可选:portrait|landscape|unspecified
        "metadata": [
          {
            "name": "ohos.ability.orientation",
            "value": "$profile:orientation"
          }
        ]
      }
    ]
  }
}

参数说明

  • portrait:锁定竖屏
  • landscape:锁定横屏
  • unspecified:跟随系统(默认)

三、动态横竖屏切换实现

3.1 创建方向工具类

新建utils/OrientationUtil.ets文件:

// OrientationUtil.ets
import window from '@ohos.window';
import display from '@ohos.display';

export class OrientationUtil {
  // 设置窗口方向
  static async setPreferredOrientation(windowClass: window.Window, orientation: window.Orientation) {
    try {
      await windowClass.setPreferredOrientation(orientation);
      console.info('屏幕方向设置成功:', orientation);
    } catch (error) {
      console.error('设置屏幕方向失败:', error);
    }
  }

  // 获取当前设备方向
  static getCurrentOrientation(): string {
    const displayInfo = display.getDefaultDisplaySync();
    return displayInfo.width > displayInfo.height ? 'landscape' : 'portrait';
  }

  // 横屏模式配置
  static readonly LANDSCAPE: window.Orientation = window.Orientation.LANDSCAPE;
  
  // 竖屏模式配置  
  static readonly PORTRAIT: window.Orientation = window.Orientation.PORTRAIT;
  
  // 跟随传感器自动旋转(受旋转锁控制)
  static readonly FOLLOW_SENSOR: window.Orientation = window.Orientation.FOLLOW_SENSOR;
}

3.2 视频播放页面实现

创建pages/VideoPlayback.ets主页面:

// VideoPlayback.ets
import { OrientationUtil } from '../utils/OrientationUtil';
import window from '@ohos.window';
import common from '@ohos.app.ability.common';
import mediaquery from '@ohos.mediaquery';

@Entry
@Component
struct VideoPlayback {
  @State currentOrientation: string = 'portrait';
  @State isFullScreen: boolean = false;
  private context: common.UIContext = getContext(this) as common.UIContext;
  private windowClass: window.Window | null = null;
  private mediaQueryListener: mediaquery.MediaQueryListener | null = null;

  // 页面初始化
  aboutToAppear() {
    this.initWindow();
    this.setupOrientationListener();
  }

  // 初始化窗口
  async initWindow() {
    try {
      this.windowClass = await window.getLastWindow(this.context);
      AppStorage.setOrCreate('windowClass', this.windowClass);
      this.currentOrientation = OrientationUtil.getCurrentOrientation();
    } catch (error) {
      console.error('窗口初始化失败:', error);
    }
  }

  // 设置方向监听器
  setupOrientationListener() {
    // 监听窗口尺寸变化
    this.windowClass?.on('windowSizeChange', () => {
      this.currentOrientation = OrientationUtil.getCurrentOrientation();
      console.info('屏幕方向变化:', this.currentOrientation);
    });

    // 媒体查询监听横屏事件
    const mediaQuery = mediaquery.matchMediaSync('(orientation: landscape)');
    this.mediaQueryListener = mediaQuery;
    mediaQuery.on('change', (result: mediaquery.MediaQueryResult) => {
      if (result.matches) {
        console.info('当前为横屏模式');
      } else {
        console.info('当前为竖屏模式');
      }
    });
  }

  // 切换全屏/竖屏模式
  async toggleFullScreen() {
    if (!this.windowClass) return;

    if (this.isFullScreen) {
      // 退出全屏,切换回竖屏
      await OrientationUtil.setPreferredOrientation(this.windowClass, OrientationUtil.PORTRAIT);
      this.isFullScreen = false;
    } else {
      // 进入全屏,切换为横屏并跟随传感器
      await OrientationUtil.setPreferredOrientation(this.windowClass, OrientationUtil.FOLLOW_SENSOR);
      this.isFullScreen = true;
    }
  }

  // 锁定横屏(不受传感器影响)
  async lockLandscape() {
    if (this.windowClass) {
      await OrientationUtil.setPreferredOrientation(this.windowClass, OrientationUtil.LANDSCAPE);
    }
  }

  // 页面布局
  build() {
    Column() {
      // 标题栏
      Row() {
        Text('视频播放器')
          .fontSize(20)
          .fontColor(Color.White)
      }
      .width('100%')
      .height(60)
      .backgroundColor('#007DFF')
      .justifyContent(FlexAlign.Start)
      .padding({ left: 20 })

      // 视频播放区域
      Column() {
        if (this.currentOrientation === 'landscape') {
          this.LandscapeVideoContent()
        } else {
          this.PortraitVideoContent()
        }
      }
      .layoutWeight(1)

      // 控制按钮区域(竖屏时显示)
      if (this.currentOrientation === 'portrait') {
        Column() {
          Button(this.isFullScreen ? '退出全屏' : '进入全屏')
            .width('90%')
            .height(40)
            .backgroundColor('#007DFF')
            .fontColor(Color.White)
            .onClick(() => this.toggleFullScreen())

          Button('锁定横屏')
            .width('90%')
            .height(40)
            .margin({ top: 10 })
            .backgroundColor('#FF6A00')
            .fontColor(Color.White)
            .onClick(() => this.lockLandscape())
        }
        .width('100%')
        .padding(10)
      }
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
  }

  // 横屏内容布局
  @Builder LandscapeVideoContent() {
    Column() {
      // 模拟视频播放器
      Stack() {
        Image($r('app.media.video_poster'))
          .width('100%')
          .height(300)
          .objectFit(ImageFit.Contain)

        // 横屏控制条
        Row() {
          Button('退出全屏')
            .width(100)
            .height(30)
            .backgroundColor(Color.Orange)
            .fontColor(Color.White)
            .onClick(() => this.toggleFullScreen())
        }
        .width('100%')
        .justifyContent(FlexAlign.End)
        .padding(10)
      }

      // 视频信息
      Text('当前模式:横屏全屏播放')
        .fontSize(16)
        .margin({ top: 20 })
    }
  }

  // 竖屏内容布局
  @Builder PortraitVideoContent() {
    Column() {
      // 模拟视频播放器
      Image($r('app.media.video_poster'))
        .width('100%')
        .height(200)
        .objectFit(ImageFit.Cover)

      // 视频信息
      Text('视频标题:鸿蒙开发教程')
        .fontSize(18)
        .margin({ top: 10 })

      Text('视频描述:学习ArkTS横竖屏适配')
        .fontSize(14)
        .margin({ top: 5 })
        .fontColor(Color.Gray)
    }
    .padding(10)
  }

  // 页面销毁
  aboutToDisappear() {
    this.mediaQueryListener?.off('change');
    this.windowClass?.off('windowSizeChange');
  }
}

3.3 EntryAbility配置

更新entryability/EntryAbility.ets

// EntryAbility.ets
import UIAbility from '@ohos.app.ability.UIAbility';
import window from '@ohos.window';
import hilog from '@ohos.hilog';

export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
    hilog.info(0x0000, 'EntryAbility', 'Ability onCreate');
  }

  onWindowStageCreate(windowStage: window.WindowStage) {
    hilog.info(0x0000, 'EntryAbility', 'Ability onWindowStageCreate');

    // 设置窗口方向为跟随传感器
    windowStage.getMainWindow((err, windowClass) => {
      if (err) {
        hilog.error(0x0000, 'EntryAbility', 'Failed to get main window');
        return;
      }
      // 初始设置为竖屏,但允许跟随传感器旋转
      windowClass.setPreferredOrientation(window.Orientation.FOLLOW_SENSOR);
    });

    windowStage.loadContent('pages/VideoPlayback', (err, data) => {
      if (err) {
        hilog.error(0x0000, 'EntryAbility', 'Failed to load the content.');
      }
    });
  }
}

四、关键技术与原理分析

4.1 方向控制原理

  • setPreferredOrientation:核心方法,控制窗口显示方向
  • FOLLOW_SENSOR:跟随传感器自动旋转,受系统旋转锁控制
  • 媒体查询:监听屏幕方向变化事件

4.2 布局适配技巧

使用条件渲染和Flex布局实现响应式设计:

// 响应式布局示例
build() {
  Column() {
    if (this.currentOrientation === 'landscape') {
      // 横屏布局
      Row() {
        // 左右分栏
      }
    } else {
      // 竖屏布局  
      Column() {
        // 上下分栏
      }
    }
  }
}

五、完整项目结构

entry/src/main/ets/
├── entryability/EntryAbility.ets
├── pages/VideoPlayback.ets
├── utils/OrientationUtil.ets
└── resources/  // 资源文件

六、测试与调试要点

  1. 真机测试:在鸿蒙设备上测试旋转效果
  2. 旋转锁定:测试系统旋转开关的影响
  3. 折叠屏适配:考虑折叠态和展开态的不同场景

七、常见问题解决

问题1:旋转后布局错乱 解决:使用媒体查询监听方向变化,动态调整布局

问题2:旋转动画卡顿
解决:优化布局计算,避免复杂操作在旋转时执行

这个完整示例涵盖了鸿蒙6中ArkTS横竖屏适配的核心技术点,适合初学者逐步学习和实践。

卷二_副本2.jpg

鸿蒙6开发中,通过文本和字节数组生成码图案例

2025年11月28日 15:17

大家好,我是 V 哥。

生成码图在很多应用中都很常见,今天的内容来给大家介绍鸿蒙6.0开发中通过文本/字节数组生成码图的详细案例解析,结合典型业务场景和技术实现要点:

联系V哥获取 鸿蒙学习资料


一、文本生成码图案例:商品条码生成(EAN-13)

业务场景
零售应用中为商品生成标准条码,用于库存管理和POS扫码结算。
技术实现

// 导入模块
import { generateBarcode } from '@kit.ScanKit';
import { image } from '@kit.ImageKit';

@Entry
@Component
struct ProductBarcode {
  @State pixelMap: image.PixelMap | undefined = undefined

  build() {
    Column() {
      Button('生成商品条码')
        .onClick(() => {
          // EAN-13规范:12位数字,首位非0
          const content: string = "694251983457"; 
          const options: generateBarcode.CreateOptions = {
            type: generateBarcode.ScanType.EAN_13, // 指定条码类型
            width: 300,  // 宽度需在[200,4096]范围内
            height: 150, // 高度可不等于宽度(条码为矩形)
            margin: 2    // 边距范围[1,10]
          };
          // 生成并渲染
          generateBarcode.createBarcode(content, options)
            .then((pixelMap: image.PixelMap) => {
              this.pixelMap = pixelMap;
            })
            .catch((error: BusinessError) => {
              console.error(`生成失败: ${error.code}`);
            });
        })
      // 显示生成的条码
      if (this.pixelMap) {
        Image(this.pixelMap)
          .width(300)
          .height(150)
      }
    }
  }
}

关键约束

参数 要求
内容长度 12位数字
首位数字 禁止为0
码类型 ScanType.EAN_13
尺寸范围 width/height ∈ [200,4096]

二、字节数组生成码图案例:交通卡二维码 业务场景
地铁APP生成加密的交通卡二维码,闸机设备专用解码器识别。
技术实现

// 导入模块(需包含buffer)
import { generateBarcode } from '@kit.ScanKit';
import { buffer } from '@kit.ArkTS';

@Entry
@Component
struct TransportCard {
  @State qrCode: image.PixelMap | undefined = undefined

  build() {
    Column() {
      Button('生成交通卡二维码')
        .onClick(() => {
          // 1. 准备字节数组(加密数据)
          const hexData: string = "0177C10DD10F776860..."; // 16进制字符串
          const contentBuffer: ArrayBuffer = buffer.from(hexData, 'hex').buffer;
          
          // 2. 配置参数(仅支持QR Code)
          const options: generateBarcode.CreateOptions = {
            type: generateBarcode.ScanType.QR_CODE,
            width: 300,   // 需满足 width=height
            height: 300,  // 正方形二维码
            errorCorrectionLevel: generateBarcode.ErrorCorrectionLevel.LEVEL_Q // 25%纠错
          };
          
          // 3. 生成二维码
          generateBarcode.createBarcode(contentBuffer, options)
            .then((pixelMap: image.PixelMap) => {
              this.qrCode = pixelMap;
            })
            .catch((error: BusinessError) => {
              console.error(`生成失败: ${error.code}`);
            });
        })
      // 显示二维码
      if (this.qrCode) {
        Image(this.qrCode)
          .width(300)
          .height(300)
      }
    }
  }
}

核心限制

参数 要求
数据类型 ArrayBuffer(字节数组)
唯一支持码类型 ScanType.QR_CODE
纠错等级与长度关系 LEVEL_Q ≤ 1536字节
尺寸要求 width必须等于height

三、技术对比与选型建议

维度 文本生成 字节数组生成
适用场景 明文字符(网址、ID等) 加密数据/二进制协议(如交通卡)
支持码类型 13种(含QR/EAN/Code128等) 仅QR Code
数据限制 按码类型限制长度(如QR≤512字符) 按纠错等级限制字节长度
颜色要求 建议黑码白底(对比度>70%) 同左
设备兼容性 全设备(Phone/Tablet/Wearable/TV) 同左

四、避坑指南

  1. 尺寸陷阱
    字节数组生成的二维码必须满足 width=height,否则抛出202(参数非法)错误。

  2. 纠错等级选择

    • LEVEL_L(15%纠错):数据≤2048字节 → 容错高/密度低
    • LEVEL_H(30%纠错):数据≤1024字节 → 容错低/密度高
      交通卡推荐LEVEL_Q(25%容错)
  3. 内容超长处理
    若文本超限(如Code39超80字节),需分段生成或改用QR Code。

  4. 渲染优化
    使用Image组件显示PixelMap时,添加背景色提升识别率:

   Image(this.pixelMap)
     .backgroundColor(Color.White) // 强制白底

通过合理选择生成方式并遵守参数规范,可满足零售、交通、支付等高可靠性场景需求,实际开发中建议参考华为官方示例工程验证设备兼容性。

HarmonyOS 6.0 蓝牙实现服务端和客户端通讯案例详解

2025年11月28日 15:15

大家好,我是 V 哥。 以下基于 HarmonyOS 6.0 的蓝牙 BLE 通讯案例详解,模拟心率监测场景,实现服务端(Peripheral)广播数据与客户端(Central)订阅数据的功能流程:

联系V哥获取 鸿蒙学习资料

关键步骤:

  1. 服务端(Peripheral):

    • 创建蓝牙服务(GATT Server)
    • 添加服务(Service)和特征(Characteristic)
    • 广播服务
    • 当客户端连接后,定期更新心率特征值并通过通知发送给客户端
  2. 客户端(Central):

    • 扫描BLE设备(按服务UUID过滤)
    • 连接目标设备
    • 发现服务及特征
    • 订阅特征通知
    • 接收特征值变化

以下是V哥整理的核心代码逻辑。

注意:由于HarmonyOS 6.0可能使用新的API包(如@ohos.bluetooth等),我们需要参考最新官方文档,但这里以搜索结果为基础,结合常见的BLE流程进行说明。


📡 一、服务端实现(广播心率数据) 1. 初始化蓝牙服务

import { bluetooth } from '@kit.ConnectivityKit';

// 定义服务UUID和特征值(需与客户端匹配)
const SERVICE_UUID = '0000180D-0000-1000-8000-00805F9B34FB'; // 标准心率服务UUID
const CHARACTERISTIC_UUID = '00002A37-0000-1000-8000-00805F9B34FB'; // 心率测量特征

// 创建GATT服务
let gattServer: bluetooth.GattServer = bluetooth.createGattServer();
let service: bluetooth.GattService = {
  serviceUuid: SERVICE_UUID,
  isPrimary: true,
  characteristics: [{
    characteristicUuid: CHARACTERISTIC_UUID,
    permissions: bluetooth.CharacteristicPermission.READ,
    properties: bluetooth.CharacteristicProperty.NOTIFY
  }]
};
gattServer.addService(service);

2. 开启广播并发送数据

// 启动BLE广播
let advertiseSetting: bluetooth.AdvertiseSetting = {
  interval: 320, // 广播间隔(单位0.625ms)
  txPower: 0,    // 发射功率
  connectable: true
};
gattServer.startAdvertising(advertiseSetting, {
  serviceUuids: [SERVICE_UUID] // 广播的服务标识
});

// 模拟心率数据发送(定时更新)
setInterval(() => {
  const heartRate = Math.floor(Math.random() * 40) + 60; // 生成60~100随机心率值
  const data = new Uint8Array([0x06, heartRate]); // 数据格式:Flags(06) + 心率值

  // 通知已连接的客户端
  gattServer.notifyCharacteristicChanged({
    serviceUuid: SERVICE_UUID,
    characteristicUuid: CHARACTERISTIC_UUID,
    deviceId: connectedDeviceId, // 连接的设备ID
    value: data.buffer            // ArrayBuffer格式数据
  });
}, 2000); // 每2秒发送一次

3. 处理客户端连接事件

gattServer.on('connectionStateChange', (device: bluetooth.Device, state: number) => {
  if (state === bluetooth.ProfileConnectionState.STATE_CONNECTED) {
    console.log(`设备已连接: ${device.deviceId}`);
    connectedDeviceId = device.deviceId; // 保存连接的设备ID
  } else if (state === bluetooth.ProfileConnectionState.STATE_DISCONNECTED) {
    console.log('设备已断开');
  }
});

📱 二、客户端实现(订阅心率数据)

1. 扫描并连接服务端

import { bluetooth } from '@kit.ConnectivityKit';

// 扫描指定服务的设备
let scanner: bluetooth.BLEScanner = bluetooth.createBLEScanner();
scroller.startScan({
  serviceUuids: [SERVICE_UUID] // 过滤目标服务
});

// 发现设备回调
scanner.on('deviceDiscover', (device: bluetooth.ScanResult) => {
  if (device.deviceName === "HeartRate_Server") { // 根据设备名过滤
    const gattClient: bluetooth.GattClientDevice = bluetooth.createGattClientDevice(device.deviceId);
    gattClient.connect(); // 连接服务端
  }
});

2. 订阅特征值通知

// 连接成功后订阅数据
gattClient.on('servicesDiscovered', () => {
  const service = gattClient.getService(SERVICE_UUID);
  const characteristic = service.getCharacteristic(CHARACTERISTIC_UUID);

  // 启用特征值通知
  characteristic.setCharacteristicChangeNotification(true).then(() => {
    characteristic.on('characteristicChange', (value: ArrayBuffer) => {
      const heartRate = new Uint8Array(value); // 解析心率值
      console.log(`实时心率: ${heartRate} BPM`);
    });
  });
});

3. 断开连接处理

gattClient.on('connectionStateChange', (state: number) => {
  if (state === bluetooth.ProfileConnectionState.STATE_DISCONNECTED) {
    console.log('已断开服务端连接');
    scanner.stopScan(); // 停止扫描
  }
});

🔑 三、关键知识点

  1. UUID 规范
    • 使用标准 UUID(如心率服务 0x180D)确保跨设备兼容性。
  2. 数据广播
    • 服务端通过 notifyCharacteristicChanged() 主动推送数据,客户端无需轮询。
  3. 权限配置
    • 需在 module.json5 中声明蓝牙权限:
     "requestPermissions": [{
       "name": "ohos.permission.USE_BLUETOOTH"
     }]
  1. 双机调试
    • 需两台 HarmonyOS 设备(或模拟器)分别运行服务端/客户端。

⚠️ 四、常见问题

  1. 连接失败
    • 检查设备是否开启蓝牙可见性,并确认 SERVICE_UUID 完全匹配。
  2. 收不到通知
    • 客户端需先调用 setCharacteristicChangeNotification(true) 订阅通知。
  3. 广播功耗优化
    • 调整 AdvertiseSetting.interval 可平衡广播频率与功耗。

Harmony os——AbilityStage 组件管理器:我理解的 Module 级「总控台」

2025年11月28日 11:15

Harmony os——AbilityStage 组件管理器:我理解的 Module 级「总控台」

这一篇是我给 AbilityStage 写的学习笔记 + 博客稿。 可以和前面那几篇「应用模型 / UIAbility / 启动模式」放在一个 HarmonyOS 开发系列里。


1. AbilityStage 是什么?一句话版本

官方定义有点长,我自己总结一句:

AbilityStage = 一个 Module 级别的组件管理器,是这个 HAP「第一次被加载」时创建的总控类,用来做模块级初始化 + 全局事件监听。

几个关键点:

  • 跟 Module 一一对应

    • 一个 Module 对应一个 AbilityStage 实例;
    • 比如 entry module 就配一个 MyAbilityStage
  • 它是在该 HAP 第一个 Ability(UIAbility / ExtensionAbility)创建之前被拉起来的;

  • 适合做:资源预加载、线程创建、全局环境监听、内存回调处理、指定进程策略、关闭前拦截等

可以把它想象成:

「这个 Module 的总调度中心」。


2. AbilityStage 提供了哪些回调?

AbilityStage 有两类回调:

  • 生命周期回调:

    • onCreate()
    • onDestroy()
  • 事件回调:

    • onAcceptWant()
    • onConfigurationUpdate()
    • onMemoryLevel()
    • onNewProcessRequest()
    • onPrepareTermination()

我按「开发时常用程度」给它分个类。

2.1 onCreate():Module 级初始化入口

时机:

  • 对应 HAP 首次加载时;
  • 在创建这个 Module 的第一个 Ability(UIAbility 或某个 ExtensionAbility)之前,系统先创建 AbilityStage,然后调用 onCreate()

适合做的事情:

  • 模块级资源预加载;
  • 启动一些公共线程或任务调度器;
  • 注册 Application 级别的监听(比如环境变化、内存回调等)。

示例(简化版):

 import { AbilityStage } from '@kit.AbilityKit';
 
 export default class MyAbilityStage extends AbilityStage {
   onCreate(): void {
     // Module 级初始化,比如资源预加载、线程创建等
     console.info('MyAbilityStage onCreate');
   }
 }

2.2 onAcceptWant():和 UIAbility 指定实例模式配套使用

这个回调专门用在 UIAbility 的 specified 启动模式 场景下。

  • 当某个 UIAbility 以 launchType: "specified" 启动时,系统会:

    1. 先进入对应 Module 的 AbilityStage.onAcceptWant(want)

    2. 由你在这里返回一个字符串 Key;

    3. 系统用这个 Key 去匹配已有的 UIAbility 实例:

      • 有匹配的 → 复用旧实例,触发 onNewWant()
      • 无匹配的 → 创建一个新实例,走 onCreate() + onWindowStageCreate()

因此:

onAcceptWant 的返回值 = UIAbility 实例的标识字符串。

示例里的最简写法:

 import { AbilityStage, Want } from '@kit.AbilityKit';
 
 export default class MyAbilityStage extends AbilityStage {
   onAcceptWant(want: Want): string {
     // 指定实例模式下由你返回实例标识
     return 'MyAbilityStage';
   }
 }

实际业务中可以按 want.parameters 拼出更细的 key,前面 UIAbility 启动模式那篇里已经有详细例子。


2.3 onConfigurationUpdate():环境变化监听(语言 / 主题 / 方向)

这个和 EnvironmentCallback 搭配尤为常用,适合做「系统配置变化 → 应用整体适配」。

系统环境变化时会触发:

  • 语言变更;
  • 深色 / 浅色模式切换;
  • 屏幕方向变更;
  • 字号缩放;
  • 字重缩放等。

你可以在 AbilityStage 的 onCreate() 里注册环境监听:

 import { EnvironmentCallback, AbilityStage } from '@kit.AbilityKit';
 import { BusinessError } from '@kit.BasicServicesKit';
 
 export default class MyAbilityStage extends AbilityStage {
   onCreate(): void {
     console.info('AbilityStage onCreate');
 
     const envCallback: EnvironmentCallback = {
       onConfigurationUpdated(config) {
         console.info(`onConfigurationUpdated: ${JSON.stringify(config)}`);
         const language = config.language;           // 当前语言
         const colorMode = config.colorMode;         // 深 / 浅色模式
         const direction = config.direction;         // 屏幕方向
         const fontSizeScale = config.fontSizeScale; // 字体大小缩放
         const fontWeightScale = config.fontWeightScale; // 字体粗细缩放
         // 可以在这里触发一些全局 UI 适配逻辑
       },
       onMemoryLevel(level) {
         console.info(`onMemoryLevel level: ${level}`);
       }
     };
 
     try {
       const applicationContext = this.context.getApplicationContext();
       const callbackId = applicationContext.on('environment', envCallback);
       console.info(`env callbackId: ${callbackId}`);
     } catch (error) {
       console.error(
         `env callback error: ${(error as BusinessError).code}, ${(error as BusinessError).message}`
       );
     }
   }
 
   onDestroy(): void {
     console.info('AbilityStage onDestroy');
   }
 }

小心得: 如果你的应用有「多语言切换、主题联动、字体大小适配」这类全局需求,用 AbilityStage + EnvironmentCallback 会比在单个 UIAbility 中东一块西一块强很多。


2.4 onMemoryLevel():系统内存吃紧时的自救机会

当系统内存紧张时会触发这个回调。

  • 比如应用退到后台后还占着一堆内存;
  • 系统会通过 onMemoryLevel() 通知你当前内存压力等级;
  • 你可以主动释放一些不关键的资源(缓存大图、预加载数据等)。

好处是:

主动配合系统回收内存,有助于避免进程被系统直接 kill,提升应用整体稳定性。

上面的 envCallback 中其实已经演示了 onMemoryLevel() 的写法。


2.5 onNewProcessRequest():控制 UIAbility 运行在哪个进程

这个回调是给 「多进程策略」 用的,场景会比较高级。

  • 当某个 UIAbility 启动时,系统会回调 onNewProcessRequest()

  • 你可以返回一个「进程标识字符串」:

    • 若该标识对应的进程已经存在 → 复用;
    • 若不存在 → 创建新进程;
  • 要启用这个能力,需要在 module.json5 里先配置:

 {
   "module": {
     // ...
     "isolationProcess": true
   }
 }

典型用途:

  • 把某些高风险 / 高资源消耗的 UIAbility 放到单独进程中跑;
  • 做类似「文档编辑进程」这种隔离。

2.6 onPrepareTermination():应用关闭前的最后挽留

当用户「关闭应用」时,系统会先回调 onPrepareTermination()

  • 你可以在这里决定:

    • 是否立刻允许关闭;
    • 还是告诉系统「先不要关」,比如先弹个对话框问用户是否保存草稿。
  • 返回值是 AbilityConstant.PrepareTermination 中的枚举,告诉系统继续关还是中断关闭动作。

典型场景:

  • 用户正在编辑重要内容(文档、填表、写作业…);
  • 用户从最近任务一划而过;
  • 你在 onPrepareTermination() 里检测到有未保存内容 → 先阻止关闭,给 UIAbility 发通知弹窗。

2.7 onDestroy():Module 正常销毁时的收尾

  • 当对应 Module 的 最后一个 Ability 实例退出,并且应用以「正常方式」销毁时触发;
  • 可以在这里做一些模块级的资源释放、取消注册等。

注意两个不会触发的情况:

  • 应用异常崩溃;
  • 被系统强制终止。

这两种情况onDestroy()都不会执行,所以特别关键的数据仍然要在各 Ability 自己的生命周期里保存。


3. 如何在工程中启用 AbilityStage?

默认模板里是不会自动创建 AbilityStage 的,需要你自己手动加一份。

3.1 创建 AbilityStage 文件

  1. 在某个 Module 的 ets 目录下新建目录 比如:ets/myabilitystage/
  2. 新建 ArkTS 文件:MyAbilityStage.ets
  3. 继承 AbilityStage,实现所需回调:
 import { AbilityStage, Want } from '@kit.AbilityKit';
 
 export default class MyAbilityStage extends AbilityStage {
   onCreate(): void {
     console.info('MyAbilityStage onCreate');
     // 模块初始化逻辑
   }
 
   onAcceptWant(want: Want): string {
     // 仅在 specified 启动模式下会触发
     return 'MyAbilityStage';
   }
 }

3.2 在 module.json5 里绑定入口

srcEntry 指定这个 Module 对应的 AbilityStage 入口:

 {
   "module": {
     "name": "entry",
     "type": "entry",
     "srcEntry": "./ets/myabilitystage/MyAbilityStage.ets",
     // ...
   }
 }

这样,当这个 HAP 第一次被加载时,就会先走 MyAbilityStage.onCreate(),你的模块级逻辑就能接管整体节奏了。


4. 我会怎么在项目里用 AbilityStage?

结合前面几篇 Stage 模型、UIAbility、ExtensionAbility 的内容,我自己会把 AbilityStage 看成一个「模块级中控」,用来集中处理:

  1. 模块初始化

    • 注册环境变化监听(语言/主题/字体等);
    • 初始化一些全局单例,比如日志、埋点、配置中心。
  2. 全局事件/配置监听

    • EnvironmentCallback 处理深浅色联动;
    • onMemoryLevel 做内存压缩策略。
  3. UIAbility 启动策略

    • 结合 onAcceptWant() + launchType: specified 做多文档/多实例管理;
    • 通过 onNewProcessRequest() 控制哪些 Ability 跑独立进程。
  4. 关闭前的拦截

    • onPrepareTermination() 给整个 App 做一次「善后机会」:

      • 未同步的数据提示用户;
      • 清理某些后台任务;
      • 重要日志 flush。

5. 小结

如果用一句比较形象的话来收尾:

UIAbility 负责「这一个窗口怎么跑」,ExtensionAbility 负责「某个扩展场景怎么跑」, 而 AbilityStage 则站在 Module 的视角,负责「这一整包能力怎么被初始化、管理和收尾」。

在 HarmonyOS NEXT 的 Stage 模型下, AbilityStage 其实是一个非常关键但容易被忽略的点—— 用好了,它能让你的应用模块结构更清晰,行为更可控,也更「系统级」。

华为 Mate 80 Pro Max 评测:堆料之王

作者 苏伟鸿
2025年11月28日 10:22

一谈到「国产高端旗舰」,我相信不少人的脑海里都会浮现「Mate」四个字母,即使他们自己并不是华为手机的用户。

国产手机「冲高」不是一个新话题,过去几年我们也看到越来越多在性能、影像、设计上都让人眼前一亮的产品,华为 Mate 系列不一定在每个维度上都是顶尖;但总体来看,依旧是唯一一个能不加任何前缀的国产高端手机系列。

全新的 Mate 80 系列更是证明了这一点:对比前面几代各有遗憾的 Mate,这一次的 Mate 80 是一台几乎全方位都能称得上「旗舰」的手机。

聊华为旗舰手机不可能避开它的影像,爱范儿围绕华为 Mate 80 Pro Max 的影像体验专门制作了一条评测视频。

关于 Mate 80 Pro Max 的更多细节,碍于视频篇幅无法详细展开,我们将在这篇图文评测中分享更多体验。

外观:不像手机的工艺水准

今年仅凭外观就一石激起千重浪的手机有三台:小米 17 Pro Max,iPhone 17 Pro Max,以及这台华为 Mate 80 Pro Max。

摄像头 Deco 的经典星环设计回归,却「买一送一」多出了一个显眼的圆环设计,引发了不少关于「好不好看」的讨论。

即使没有预想中的磁吸功能,这个圆环设计也很明显是设计师深思熟虑的结果。

功能上,它是一个无线充电的线圈,解决了 Mate 80 Pro Max 全金属机身设计与无线充电的冲突;设计上,这个圆环和摄像头的星环形成了一个数字「8」,既和 Mate「80」呼应,也取了一个好意头——别忘了 Mate 系列的主要用户客群都是商务人士。

华为官方也推出了一个磁吸手机壳配件,佩戴上就能解锁磁吸能力。

你大可以不欣赏 Mate 80 这个突破常规的背板设计,但它背后的工艺水准确定不可置否。

Mate 80 Pro Max 的背板其实分成了三个部分:最大面积的金属背板、中央的金属圆环,以及圆环中心的圆形区域,这三个部分的工艺和材质都各不相同——圆环部分使用的是锦纤材质,摸起来更顺滑。

背板的三重拼接虽然很有水平,却很容易出现公差,爱范儿手上这台 Mate 80 Pro Max,圆环和背板之间还是能看到些许缝隙。

值得一提的是,只有 Mate 80 Pro Max 是金属机身和三重拼接工艺,Pro 版本和标准版都是锦纤材质,高端感略有打折,不过没有接缝则带来一体性更强的背板。

金属背板的工艺名为「光绘微纹」, 利用激光在金属背板进行微米级雕刻。

原本担心这种精细的纹理结构容易藏污纳垢,或者会磨指甲。实际上手后发现自己多虑了——Mate 80 Pro Max 机身的颗粒感相当细腻,不容易积累脏污,给人的手感不像智能手机,更像名表。

行业少见的高端工艺,正是当年华为 Mate 手机品牌高端化的第一个跳板,现在也已经成为了重要的护城河。用户不需要知道这些工艺细节,一上手就能感知它和其他手机的质感上的差距。

比起在工艺上整的花活,华为 Mate 80 系列的握持感是更实在的优秀体验。

今年的 Mate 80 系列全系改用了 2.5D 直屏设计,直角中框和背板之间的过渡做得相当顺滑,属于今年直屏旗舰的天花板水准,钛合金中框也做了抛光处理,观感非常吸睛高级,手感则相对温润。

Mate 80 RS 非凡大师则继续采用家族式的八边形「星钻」设计,背板也还是玄武玻璃材质,新增的槿紫配色搭配这个光亮的钛合金中框,通体给人的感觉很像一台超跑——可惜还是很粘指纹。

对于怎么在机身上营造高端感,华为已经相当驾轻就熟,能够用不同的材质,营造出具有区分度的高端感:

特别是 4000 元档位的 Mate 80 标准版,凭借精致的锦纤材质,和 Pro Max 一脉相承的设计语言,它依旧能很好代表 Mate 门面,低门槛不低质感。

影像:硬件只是入场券,感觉更重要

关于手机影像,我们已经听过太多硬件和参数的故事,在华为 Mate 80 Pro Max 拿到手那一刻,我想聊聊「感觉」。

【视频号】

这几年,各家厂商都在卷长焦、卷底大,华为却想明白了一件事:强大的硬件只是入场券,如何让拍摄的过程不再有痛点,才是「旗舰体验」的终极命题。

XMAGE 影像一直有着自己独特的色彩科学,这一次,Mate 80 Pro Max 补全了最后一块拼图,新增了两个极具反差的预设配方,让情绪的表达不再有死角。

基于明快风格的「美女」模式,走的是东方美学的路子,灵感来源于一幅留白的仕女图,它克制、淡雅,肤色处理得极度温和,不抢眼,够耐看。

而切换到基于鲜艳风格的「野兽」模式,致敬了西方野兽派艺术,色彩浓郁,撞色大胆,溢出屏幕的生命力,像极了马蒂斯笔下打破常规的油画。

撑起这种张力的是硬件的底气——这一代,全焦段 RYYB 排列配合第二代红枫原色摄像头,你会发现一个明显的感知:稳。

从超广角、主摄到两颗潜望长焦,四颗镜头的色彩一致性被调教得空前统一,彻底告别割裂感,配合夸张的 17.5 EV 动态范围,即便在大光比逆光场景,高光与暗部也能被收拾得服服帖帖。

除了拍摄,后期也是体验的重中之重。

Mate 80 Pro Max 在后期编辑上做到了链路可逆,你可以在相册里随意调整水印的样式。

以前我们常说传感器的宽容度,决定了能救回多少光影;而现在,华为用链路可逆,带来了体验上的宽容度——你永远拥有「反悔」的权利。

在以往,能做到这一点的手机品牌凤毛麟角,这种独属于手机维度的「高宽容度」,标志着华为的影像旗舰,正式从技术突进主导,转向了体验的终极完善。

如果说后期是给你兜底,那 AI 辅助构图,就是在为你帮扶。

当你面对杂乱的街道或宏大的建筑,它会敏锐识别出画面中隐藏的几何线条,并在屏幕上引导你移动取景框,直到最准最具张力的黄金点时,马达会传来一声极其轻微的震动——对,就是这里,锁定了。

这种感觉很奇妙,相比 Pura80 系列,它的模型规格翻了一倍、焦段推荐精度提升了 5 倍。借助 AI,手机填补了普通人与摄影师之间的技术鸿沟,没有摄影基础,也能轻松拍出有故事的照片。

把黑夜拍得像白昼,那是技术的暴力;能把此刻的情绪原封不动地装进相册,才是体验的胜利。

Mate 80 Pro Max 的影像系统,给我的感觉就是两个字:完善。

从硬件上,它有第二代红枫原色和 RYYB 的硬实力做底气;从软件上,它有「美女与野兽」这样极具人文张力的审美选择;从体验上,它用 AI 帮你找角度,用可逆的后期让你无后顾之忧。

配置:不仅能打游戏,还打得舒服

在聊 Mate 80 Pro Max 配置体验之前,我想先聊聊 Mate 80 标准版。

以往,不管是 Mate 还是 Pura 系列的标准版,配置对比 Pro 版本差距明显,不过今年的 Mate 80 补齐了 3D 人脸识别的缺陷,性能也比上一代有 35% 的提升,加上起售价降低 500 元,标准版一下子就相当有性价比。

至于我们手上的 Mate 80 Pro Max 和 Mate 80 RS,堆料带来的体验提升也非常直观。

在发布会上,华为很少见地主动强调了 Mate 80 系列的性能提升,并强调了它在游戏场景的进步。

华为 Mate 80 Pro Max 30 分钟极高画质打《原神》,全程基本能稳住 60 帧,没有卡顿和明显掉帧,机身温度也只是温热。

平时用小米 17 Pro Max 的老玩家表示,Mate 80 Pro Max 相比之下没有差距。

不过依旧存在优化空间:Mate 80 Pro Max 触控采样率还不够高,玩起来有点不太跟手;耗电也稍微有点快,30 分钟游玩消耗电量 12%。

除了性能,Mate 80 Pro Max 上的另一个「猛料」就是这块双层 OLED 屏幕。

这个原本是 RS 非凡大师独享的配置,今年也下放到 Mate 80 Pro Max 上,最明显观感就是一个字——「亮」。

比起在显示技术和屏幕材质上做文章,双层 OLED 的超高亮度是一种数值上的强度美,能直接解决很多户外场景的显示痛点。

这几天刚好广州天气不错,带着 Mate 80 Pro Max 和 Mate 80 RS 出去外拍,完全不用担心会看不清屏幕,拍好的样片不用带回办公室,在太阳底下就能看个仔细。

▲ 左:华为 Mate 70 Pro+,右:华为 Mate 80 Pro Max

当然,更高的峰值亮度不可避免带来更高的能耗,不过即使续航会大打折扣,机身发热却并不如预想中狂飙。

你可能已经发现了,关于这台手机各种使用感受,我经常提到「不发热」或者「不太发热」。实际上,这正是我眼中 Mate 80 最大的进步之一。

上一代华为 Mate 70 Pro+ 的体验,很大程度都受「容易发热」这个短板影响,即使是日常刷十来分钟的小红书和微博,机身也会明显有热感,打开相机更是会进一步升温。

而 Mate 80 Pro Max 以及 Mate 80 RS,不管是在平日使用、拍照、游戏,发热情况都有很明显的改善——只要不打开「卓易通」,基本不会出现烫手的情况。

这既得益于硬件升级带来的能效提升,也很大程度归功于「原生鸿蒙」HarmonyOS 的优秀优化。

鸿蒙系统:小步快跑,迈过可用门槛

华为 Mate 80 系列出厂搭载的是 HarmonyOS 6,这也是第一台只搭载原生鸿蒙的 Mate 旗舰。

今年年中,我们对纯血鸿蒙系统的使用体验和应用生态进行了一个阶段性的点评:系统非常流畅,应用生态欠缺,最大短板是微信。

过去了差不多半年,这个点评还能原封不断用到 Mate 80 系列的 HarmonyOS 6 上。

生态覆盖率超过 95% 的鸿蒙系统,已经过了快速增长期,进入长尾 App 和应用体验补齐的积累阶段。

半年前,我常用的二十个应用中只有 Apple Music、多邻国、网易云音乐、微博轻享版和订阅号助手没有原生鸿蒙版本;目前这五个应用依旧缺失,其中有三个来自国内大厂。

剩余的原生应用,功能正在渐渐补齐,不过一些页面和功能依旧缺失,例如美团「视频」「书城」界面——虽然在大部分人眼中这是一种优点。

▲ 左:iOS 美团应用;右:鸿蒙美团应用

构成我们智能手机使用时间大头的微信,则依旧在各种地方存在功能缺失:实况照片、新版公众号推流界面、微信状态、聊天文字提醒、朋友圈背景更改、视频号小窗、好友标签、红包封面、发起直播、QQ 号登陆等等……

这其中有些功能确实属于冗余,但也有不少「到用时方恨无」的痒点功能。

一些缺失的国产应用和海外应用,则分别可以用「卓易通」和「出境易」安装 Android 的 APK 版本。

运行这些应用的体验总体比较流畅,但有时候会导致手机快速发热,也无法跳转鸿蒙微信验证登录,读取手机文件也颇费周折。

不过在 HarmonyOS 6 中,一些通过卓易通和出境易安装的 Android APK 也可以收到系统通知、进入系统播放空间,应用也终于可以拖出大文件放到桌面,极大提升了 Android 应用的使用体验。

HarmonyOS 6 的系统表现让人放心,动效和流畅性方面比 Android 精致,又比 iOS 26 稳定,不过有些地方观感上还没打磨到最好,某些动效也略显赘余。

例如新的「实况窗」也紧跟潮流做成了「灵动岛」样式,设计没有以前的药丸精致,整个状态栏略显拥挤,点击的动效也比较冗长。

HarmonyOS 6 功能上的大部分技能点,主要分配给了「AI」。

小艺助手也越来越有「智能体」的样子,现在真的动动嘴就能让它帮忙做事,例如自然语言修图,或者帮忙买咖啡。

不管是对于华为 Mate 80 系列,还是放大到所有的华为硬件,当下的鸿蒙系统早已跨过了「可用」的门槛,正在不断向「好用」进发。

实际上,就流畅度和稳定性来说,Mate 80 Pro Max 的 HarmonyOS 6 已经绝对算得上行业里 T0 的级别。

操作系统和生态的养成并非一朝一夕,一个应用开发需要投入大量的资源。像是微信这样的大型应用,从零开发不仅需要追上十余年的迭代,同时还要追加新功能。

想要加速整个过程,华为就必须要将蛋糕做大,获得更大的用户基本盘

于是,在华为全面进入纯血鸿蒙时代的今年,也成为了华为史上硬件产品最精彩纷呈的一年,或许没有之一。

Mate 80,证明鸿蒙已经「成了」

今年的华为硬件都有一个特质:堆料给够的同时,形态也够创新,用这些领先行业的硬件,去托举目前还未做到满分的鸿蒙系统和生态。

这一点首先在「阔折叠」「三折叠」「折叠电脑」这些折叠产品上得以体现。形态更常规的 Pura 80 系列和 Mate 80 系列,则靠硬件堆料和各种「黑科技」来争取最多的高端用户。

▲ 华为 Pura X

Mate 80 系列在保持一贯「高端旗舰」定位,靠品牌和工艺和国产 Android 旗舰拉开距离的同时,屏幕、影像、性能上也将配置拉满。

标准版不瘸腿,超大杯堆猛料,价格还比去年降了不少。

在发布会总结的评论区,不少读者朋友们纷纷留言,询问鸿蒙系统是否支持一些特定的专业应用,这意味着不少消费者已经被华为 Mate 80 系列的硬实力所吸引,虽然对鸿蒙生态还比较陌生,也已经动了入坑的念头。

朋友圈和社交平台更多的晒单截图,以及明年才能发货的订单量,都明示着一点——可用性更高的原生鸿蒙系统,早已不是消费者选择华为手机的障碍,在价格和配置的吸引下,下单的理由更加充足。

毕竟,一台 4699 元的华为旗舰手机,真的可以称得上「性价比」了。

这不由得让人想起今年的另一大高端品牌旗舰——苹果 iPhone 17 系列,同样是标准版谈性价比,高配把配置堆上的策略。

当这些已经成功高端化的品牌开始卷价格和配置,对于正在冲高、价格相当的国产 Android 品牌来说,竞争无疑进一步升温。

对华为而言,这既是在收复前几年被迫让出的市场,也是托举鸿蒙系统的有力举措,

时至今日,我们可以下一个结论:即使仍需要时间成熟,鸿蒙系统已经取得实际上的「成功」,因为华为成功摸索出一个路径,形成了一个如鱼得水的基本盘。

做系统是一个长坡厚雪的过程,华为已经把坡做长,现在就等着落雪足够深厚,让鸿蒙系统成为高端手机竞争下半场的「撒手锏」。

 

*本文由苏伟鸿、周奕旨共同完成

#欢迎关注爱范儿官方微信公众号:爱范儿(微信号:ifanr),更多精彩内容第一时间为您奉上。

爱范儿 | 原文链接 · 查看评论 · 新浪微博


HarmonyOS 帧动画 animator

作者 IT充电站
2025年11月27日 13:25

就是类似播放电影一样,一帧一帧的进行播放,相对于属性动画,其每一帧,我们都可以进行设置相关的属性值,并且具有暂停播放,继续播放的优点,而且还具备事件的实时响应,需要说明的是,在性能上是远远不如属性动画的,所以如果能用属性动画实现的场景,还是主推属性动画

  • 需要控制动画播放暂停:音乐播放、或者运动小车
  • 翻页动画

基础使用

  • 创建帧动画 this.animator = Animator.create()

  • 每一帧回调 this.animator.onFrame=fn

  • 控制 this.animator.play/pause()

示例代码1
import { Animator, AnimatorResult } from '@kit.ArkUI'

@Entry
@Component
struct Index {
  // 1. 创建帧动画
  @State animator:AnimatorResult  = Animator.create({
    duration: 3000, //动画播放的时长
    delay: 0, //动画延时播放时长
    easing: 'linear', //动画插值曲线
    iterations: 1, //动画播放次数
    fill: "forwards", //动画执行后是否恢复到初始状态
    direction: 'normal', //动画播放模式
    begin: 0, //动画插值起点
    end: 100//动画插值终点
  })

  @State translateX: number = 0
  @State translateY: number = 0

  aboutToAppear(): void {
    // 2.每一帧回调
    this.animator.onFrame = (progress: number) => {
      this.translateX = progress
    }
  }

  build() {
    Column({space:20}) {

      Text("1").width(30).height(30).backgroundColor(Color.Red).textAlign(TextAlign.Center).margin({ top: 100 })
        .translate({ x: this.translateX, y: this.translateY })

      Button("播放").onClick(() => this.animator.play() )
      Button("暂停").onClick(() =>  this.animator.pause() )
      Button("重置").onClick(() => {
        this.translateX = 0
        this.translateY = 0
      })
    }
    .height('100%')
    .width('100%')
  }
}

示例代码2:实战音乐播放器

import { Animator, AnimatorResult } from '@kit.ArkUI';

@Entry
@Component
struct Index {
  @State angle: number = 0

  // 1 创建帧动画对象
  private animator: AnimatorResult = Animator.create({
    duration: 10000,  //   持续时间
    delay: 0,         //   延迟时间
    easing: "linear", //   动画曲线
    iterations: -1,   //   播放次数
    fill: "none",     //   播放模式 播放之外的状态
    direction: "normal", //   播放方向
    begin: 0,   // 开始角度
    end: 360 // 结束角度
  })

  aboutToAppear() {
    //   2 监听帧变化事件
    this.animator.onFrame = (value) => {
      this.angle = value
    }
  }

  build() {
    Column() {
      Button('开始').onClick((event: ClickEvent) => {
        this.animator.play()
      })
      Button('暂停').onClick((event: ClickEvent) => {
        this.animator.pause()
      })
      Stack() {
        Image("https://p6.music.126.net/obj/wonDlsKUwrLClGjCm8Kx/28513831157/e8e9/beeb/912c/468bcb523668065ccf6f853c4a084e31.png")
          .width(300)
          .height(300)

        Image("https://p1.music.126.net/MX4wNwR9eJNvx_Y5AKBEFw==/109951169909770184.jpg?imageView&thumbnail=360y360&quality=75&tostatic=0")
          .width(200)
          .height(200)
          .borderRadius(200)
          .rotate({
            angle: this.angle
          })

      }.backgroundColor(Color.Gray).width('100%').height('100%')
    }
  }
}

示例代码3:图片帧动画

developer.huawei.com/consumer/cn…

www.jq22.com/yanshi24013

www.jq22.com/demo/imgAni…

鸿蒙开发班级

HarmonyOS 组件导航(Navigation)

作者 IT充电站
2025年11月27日 13:22

✨家人们记得点个账号关注,会持续发布大前端领域技术文章💕 🍃

组件导航(Navigation)

Navigation是路由容器组件,一般作为首页的根容器,包括单栏(Stack)、分栏(Split)和自适应(Auto)三种显示模式。Navigation组件适用于模块内和跨模块的路由切换,一次开发,多端部署场景。通过组件级路由能力实现更加自然流畅的转场体验,并提供多种标题栏样式来呈现更好的标题和内容联动效果。在不同尺寸的设备上,Navigation组件能够自适应显示大小,自动切换分栏展示效果。

  • 模块内、和跨模块实现路由切换
  • 一次开发,多端部署场景
  • 通过组件级路由能力实现更加自然流畅的转场体验
  • 更好的标题、tabBar等等联动效果
  • 等等

2.1 Navigation属性

@Entry
@Component
struct Index {
  build() {
    Navigation() {

    }
      .title("主标题")
      .mode()
      .toolbarConfiguration()
      .menus()
  }
}

title 设置标题

显示在这个主标题的位置

titleMode 标题栏模式

标题栏在界面顶部,用于呈现界面名称和操作入口,Navigation组件通过titleMode属性设置标题栏模式

  • Mini模式:普通型标题栏,用于一级页面不需要突出标题的场景

img

  • Full模式:强调型标题栏,用于一级页面需要突出标题的场景。

img

mode 设置模式

Navigation是路由容器组件,一般作为首页的根容器,包括单栏(Stack)、分栏(Split)和自适应(Auto)三种显示模式.Navigation组件适用于模块内和跨模块的路由切换,一次开发,多端部署场景

Navigation组件通过mode属性设置页面的显示模式。自适应模式下,当页面宽度大于等于一定阈值( API version 9及以前:520vp,API version 10及以后:600vp )时,Navigation组件采用分栏模式,反之采用单栏模式。

img

将mode属性设置为NavigationMode.Stack,Navigation组件即可设置为单页面显示模式。

img

将mode属性设置为NavigationMode.Split,Navigation组件即可设置为分页面显示模式。

toolbarConfiguration 底层标题栏

.toolbarConfiguration() 里面设置的是一个数组,数组里面每个元素ToolbarItem类型,数据的结构如下

{
    value: "我的",
    icon: "https://s1.aigei.com/src/img/png/e7/e79cacc5161a4a6ca589e097f87a2526.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:TLXitIEF_QaGqfeKVbpaGb8_qyQ=",
    action: () => {

    },
    activeIcon: "",
    status: ToolbarItemStatus.DISABLED,

}

value 就是显示的文字

icon 显示的图标

action 当点击时触发做某些事情,例如跳转路由

activeIcon 激活时的图标

status 当前图标的状态,例如不可用等等

img

menus 菜单栏

菜单栏位于Navigation组件的右上角,开发者可以通过menus属性进行设置。menus支持Array和CustomBuilder两种参数类型。使用Array类型时,竖屏最多支持显示3个图标,横屏最多支持显示5个图标,多余的图标会被放入自动生成的更多图标。

img

{
    value: "我的",
    icon: "https://s1.aigei.com/src/img/png/e7/e79cacc5161a4a6ca589e097f87a2526.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:TLXitIEF_QaGqfeKVbpaGb8_qyQ=",
    action: () => {

    },
   isEnabled:false

}

value 就是显示的文字

icon 显示的图标

action 当点击时触发做某些事情,例如跳转路由

isEnabled 是否可用

  • 示例代码(全部)
@Entry
@Component
struct Index {
  build() {
    Navigation() {

    }
    .title("主标题")
      .mode(NavigationMode.Stack)
      .titleMode(NavigationTitleMode.Free)
      .toolbarConfiguration([
        {
          value: "我的",
          icon: "https://s1.aigei.com/src/img/png/e7/e79cacc5161a4a6ca589e097f87a2526.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:TLXitIEF_QaGqfeKVbpaGb8_qyQ=",
          action: () => {

          },

        },
        {
          value: "购物车",
          icon: "https://s1.aigei.com/src/img/png/5d/5dc2f07ba8224cd4883648c9ea939ac5.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:Q5dD_6ZS_ry4kaVXHgv6gnKw2IQ=",
          action: () => {

          }
        },
        {
          value: "更多",
          icon: "https://s1.aigei.com/src/img/png/03/03d71ede7b404e70838f3d575b31a930.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:ABookBJ6PVBeSxtM2hEuQHPlGLE=",
          action: () => {

          }
        }
      ])
      .menus([
        {
          value: "设置",
          icon: "https://s1.4sai.com/src/img/png/6c/6ca77e26c31548e680b784ab33f72900.png?e=1735488000&token=1srnZGLKZ0Aqlz6dk7yF4SkiYf4eP-YrEOdM1sob:9x9bgI9kDs15UsWPzGN7ZuaICXM=",
          action: () => {

          },
        },
        {
          value: "小红书",
          icon: "https://s1.aigei.com/src/img/png/3e/3e3bda0ac48046b08e560e8764942bd8.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:JPuRl9bkD4UfIc8fGN4ApLuS_rE=",
          action: () => {

          }
        },
        {
          value: "微信",
          icon: "https://s1.aigei.com/src/img/png/ef/efd714270fdd44f485156053901ecb51.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:GfcdaqAj6Uie-yCw4Wt1pPqAGwg=",
          action: () => {

          }
        },
        {
          value: "抖音",
          icon: "https://s1.aigei.com/src/img/png/5b/5b26e982f0b34c47817d3b40c9bf2d1f.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:YCO6IvUtFIqf6x1hmy82VctIElo=",
          action: () => {

          }
        }
      ])
  }
}

2.2 NavRouter

导航组件,默认提供点击响应处理,不需要开发者自定义点击事件逻辑。

必须包含两个子组件,其中第二个子组件必须为NavDestination。

其中第一个组件是需要点击的元素

第二个必须是NavDestination

@Entry
@Component
struct Index {
  build() {
    Navigation() {

      // NavRouter() {
      //   Text('菜单1')
      //   NavDestination() {
      //     Text('内容1')
      //   }
      // }
      //
      //
      // NavRouter() {
      //   Text('菜单2')
      //   NavDestination() {
      //     Text('内容2')
      //   }
      // }

      ForEach('123'.split(''), (item: number) => {
        NavRouter() {
          // 这里是点击的内容
          Column() {
            Text("文本" + item)
              .width("85%")
              .height(60)
              .backgroundColor("#ccc")
              .borderRadius(25)
              .margin({ top: 10 })
              .textAlign(TextAlign.Center)
          }
          // 这里是跳转显示的页面
          NavDestination() {
            Text("文本" + item).fontSize(30).fontColor("red")
          }.title("标题" + item).hideTitleBar(false)
        }.mode(NavRouteMode.PUSH)
      })


    }.mode(NavigationMode.Split)
  }
}

这里有一个属性

mode(mode: NavRouteMode)

设置指定点击NavRouter跳转到NavDestination页面时,使用的路由模式

PUSH_WITH_RECREATE 跳转到新的NavDestination页面时,替换当前显示的NavDestination页面,页面销毁,但该页面信息仍保留在路由栈中。

PUSH 跳转到新的NavDestination页面时,覆盖当前显示的NavDestination页面,该页面不销毁,且页面信息保留在路由栈中。

REPLACE 跳转到新的NavDestination页面时,替换当前显示的NavDestination页面,页面销毁,且该页面信息从路由栈中清除。

还有一个事件

onStateChange(callback: (isActivated: boolean) => void)

组件激活状态切换时触发该回调。开发者点击激活NavRouter,加载对应的NavDestination子组件时,回调onStateChange(true)。NavRouter对应的NavDestination子组件不再显示时,回调onStateChange(false)。

最后效果

@Entry
  @Component
  struct Index {
    pageStack: NavPathStack = new NavPathStack();
    @State list: Array<number> = [1, 2, 3]

    build() {
      Navigation(this.pageStack) {
        ForEach(this.list, (item: number) => {
          NavRouter() {
            Column() {
              Text("文本" + item)
                .width("85%")
                .height(60)
                .backgroundColor("#ccc")
                .borderRadius(25)
                .margin({ top: 10 })
                .textAlign(TextAlign.Center)
            }

            NavDestination() {
              Text("文本" + item)
                .fontSize(30)
                .fontColor("red")
            }.title("标题" + item)

          }.mode(NavRouteMode.PUSH)
                .onStateChange((isActivated: boolean) => {
                   console.log(item + "激活:" + isActivated)
          })
        })
      }
      .title("主标题")
        .mode(NavigationMode.Stack)
        .titleMode(NavigationTitleMode.Free)
        .toolbarConfiguration([
          {
            value: "我的",
            icon: "https://s1.aigei.com/src/img/png/e7/e79cacc5161a4a6ca589e097f87a2526.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:TLXitIEF_QaGqfeKVbpaGb8_qyQ=",
            action: () => {
              this.pageStack.pushPath({ name: "pageOne", param: "aaa" })
            },

          },
          {
            value: "购物车",
            icon: "https://s1.aigei.com/src/img/png/5d/5dc2f07ba8224cd4883648c9ea939ac5.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:Q5dD_6ZS_ry4kaVXHgv6gnKw2IQ=",
            action: () => {

            }
          },
          {
            value: "更多",
            icon: "https://s1.aigei.com/src/img/png/03/03d71ede7b404e70838f3d575b31a930.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:ABookBJ6PVBeSxtM2hEuQHPlGLE=",
            action: () => {

            }
          }
        ])
        .menus([
          {
            value: "设置",
            icon: "https://s1.4sai.com/src/img/png/6c/6ca77e26c31548e680b784ab33f72900.png?e=1735488000&token=1srnZGLKZ0Aqlz6dk7yF4SkiYf4eP-YrEOdM1sob:9x9bgI9kDs15UsWPzGN7ZuaICXM=",
            action: () => {

            },
          },
          {
            value: "小红书",
            icon: "https://s1.aigei.com/src/img/png/3e/3e3bda0ac48046b08e560e8764942bd8.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:JPuRl9bkD4UfIc8fGN4ApLuS_rE=",
            action: () => {

            }
          },
          {
            value: "微信",
            icon: "https://s1.aigei.com/src/img/png/ef/efd714270fdd44f485156053901ecb51.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:GfcdaqAj6Uie-yCw4Wt1pPqAGwg=",
        action: () => {

        }
      },
      {
        value: "抖音",
        icon: "https://s1.aigei.com/src/img/png/5b/5b26e982f0b34c47817d3b40c9bf2d1f.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:YCO6IvUtFIqf6x1hmy82VctIElo=",
        action: () => {

        }
      }
    ])
  }
}

2.3 独立的NavDestination

切记独立的NavDestination不能通过NavRouter跳转 它也被弃用了,只能通过 NavPathStack创建一个路由栈对象,

然后通过路由栈对象控制页面跳转

具体实现

1、Profile.ets 把页面内容NavDestination独立出去

@Component
export struct Profile {
  build() {
    NavDestination() { // 根元素必须是NavDestination
      Column() {
        Text("实际的内容")
      }
    }
    .title("标题")
  }
}

2、Navigation通过属性关联Profile页面

import {Profile} from './Profile'

@Entry
@Component
export struct Index {

  @Builder PagesMap(name: string) {
    if (name === "Profile") {
      Profile()
    }
  }

  pageStack: NavPathStack = new NavPathStack()  // 页面栈对象 管理页面栈里面的网页

  build() {
    Navigation(this.pageStack) {
      Button('跳转到Cart页面').onClick(() => {
        this.pageStack.pushPathByName('Profile', null)
        // this.pageStack.pushPath({ name: "Profile", param: "实际需要显示的内容" })
      })
    }
    .title("主标题")
    .mode(NavigationMode.Stack)
    .navDestination(this.PagesMap) // 通过属性的方式动态设置
  }
}

3、控制跳转(之前没有独立NavRouter 独立之后必须通过Navigation提供的页面栈对象来操作

3.1 创建页面栈

pageStack: NavPathStack = new NavPathStack(); // 创建页面栈实例化对象  控制页面栈路由增删改等
 
3.2 注册关联
Navigation(this.pageStack) {

3.3  跳转
https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-navigation-navigation-V5#%E8%B7%AF%E7%94%B1%E6%93%8D%E4%BD%9C

NavDestination的模式

NavDestination的mode属性有两种值

标准类型 NavDestinationMode.STANDARD

标准类型的NavDestination的生命周期跟随其在NavPathStack页面栈中的位置变化而改变。

弹窗类型 NavDestinationMode.DIALOG

整个NavDestination默认透明显示。弹窗类型的NavDestination显示和消失时不会影响下层标准类型的NavDestination的显示和生命周期,两者可以同时显示。

下面处理一个弹窗问题

@Component
export  struct DialogPage {
  @Consume pageStack: NavPathStack;
  param: string = ""

  build() {
    NavDestination() {
      Column() {
        Stack({ alignContent: Alignment.TopEnd }) {
          Text(this.param)
            .fontSize(30)
            .fontColor("red")
            .textAlign(TextAlign.Center)
            .width("100%")
            .margin({ top: 80 })
          Button("×").onClick(() => {
            this.pageStack.pop();
          })
        }.width("70%")
          .height("30%")
          .backgroundColor("white")
          .borderRadius(30)
      }.justifyContent(FlexAlign.Center)
        .alignItems(HorizontalAlign.Center)
        .width("100%")
        .height("100%")
    }
    .backgroundColor("rgba(0,0,0,0.5)")
      .mode(NavDestinationMode.DIALOG)
      .title("标题")
      .hideTitleBar(true)
  }
}
import { DialogPage } from '../components/DialogPage';


@Entry
  @Component
  struct Index {
    @Provide pageStack: NavPathStack = new NavPathStack();

    @Builder
    PagesMap(name: string, param: string) {
      if (name === "dialogPage") {
        DialogPage({ param: param })
      }
    }

    build() {
      Navigation(this.pageStack) {

      }
      .title("主标题")
        .mode(NavigationMode.Stack)
        .titleMode(NavigationTitleMode.Free)
        .navDestination(this.PagesMap)
        .toolbarConfiguration([
          {
            value: "我的",
            icon: "https://s1.aigei.com/src/img/png/e7/e79cacc5161a4a6ca589e097f87a2526.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:TLXitIEF_QaGqfeKVbpaGb8_qyQ=",
            action: () => {
              this.pageStack.pushPath({ name: "dialogPage", param: "aaa" })
            },

          },
          {
            value: "购物车",
            icon: "https://s1.aigei.com/src/img/png/5d/5dc2f07ba8224cd4883648c9ea939ac5.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:Q5dD_6ZS_ry4kaVXHgv6gnKw2IQ=",
            action: () => {

            }
          },
          {
            value: "更多",
            icon: "https://s1.aigei.com/src/img/png/03/03d71ede7b404e70838f3d575b31a930.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:ABookBJ6PVBeSxtM2hEuQHPlGLE=",
            action: () => {

            }
          }
        ])
    }
  }

img

2.4 NavPathStack

NavPathStrack是页面栈为路由跳转提供的方法对象,每个Navigation都需要创建并传入一个NavPathStack对象,用于管理页面。主要涉及页面跳转、页面返回、页面替换、页面删除、参数获取、路由拦截等功能。

pageStack: NavPathStack = new NavPathStack()

普通跳转

this.pageStack.pushPath({ name: "DialogPage", param: "aaa" })
this.pageStack.pushPathByName("DialogPage", "aaa")

这两种方法都可以使用

带返回回调的跳转,跳转时添加onPop回调,能在页面出栈时获取返回信息

this.pageStack.pushPath({
  name: "DialogPage", param: "aaa", onPop: (popInfo) => {
    Logger.log(popInfo.result)
  }
})

关闭退回时通过pop传回的数据

Button("×").onClick(() => {
  this.pageStack.pop("返回的内容")
})

页面返回

// 返回到上一页
this.pageStack.pop()
// 返回到上一个PageOne页面
this.pageStack.popToName("PageOne")
// 返回到索引为1的页面
this.pageStack.popToIndex(1)
// 返回到根首页(清除栈中所有页面)
this.pageStack.clear()

页面替换

// 将栈顶页面替换为PageOne
this.pageStack.replacePath({ name: "PageOne", param: "PageOne Param" })
this.pageStack.replacePathByName("PageOne", "PageOne Param")

页面删除

// 删除栈中name为PageOne的所有页面
this.pageStack.removeByName("PageOne")
// 删除指定索引的页面
this.pageStack.removeByIndexes([1,3,5])

参数获取

// 获取栈中所有页面name集合
this.pageStack.getAllPathName()
// 获取索引为1的页面参数
this.pageStack.getParamByIndex(1)
// 获取PageOne页面的参数
this.pageStack.getParamByName("PageOne")
// 获取PageOne页面的索引集合
this.pageStack.getIndexByName("PageOne")

路由拦截

在页面的初始生命周期中设置

aboutToAppear(): void {
  this.pageStack.setInterception({
  willShow: (from: NavDestinationContext | NavBar, to: NavDestinationContext | NavBar,
       operation: NavigationOperation, isAnimated: boolean) => {
         if (typeof to === "string") return; 
         if (to.pathInfo.name === "dialogPage") {
           to.pathStack.pop();
           to.pathStack.pushPath({ name: "page1", param: "bbbb" })
         }
       }
  })
}

2.5 跨包动态路由-系统路由表

准备生成Navigation的route_map路由

a)给模块创建系统路由表 (也就是针对于Navigation以后还是通过配置文件生成路由)

创建resources/base/profile/route_map.json(切记route不加r)路由配置文件,填写下述代码

{
  "routerMap": [
    {
      # 路由名  也就是跳转名
      "name": "Home或者Profile",    
      # 改名字对应的页面 
      "pageSourceFile": "src/main/ets/pages/Profile.ets",     
      
      # 切记MainPage.ets这个文件必须用MainPageBuilder当做入口(比较抽象跟着写  写完反过来看才能理解)
      "buildFunction": "MainPageBuilder",    
      "data": {
        "description" : "this is PageOne"
      }
    }
  ]
}

b)让创建route_map.json生效 也就是跟模块绑定

修改模块的module.json5文件 "routerMap": "$profile:route_map",

{
  "module": {
  
    "routerMap": "$profile:route_map",
    
    "name": "cartHar",
    "type": "har",
    "deviceTypes": [
      "default",
      "tablet",
      "2in1"
    ]
  }
}

后续创建页面跳转

a)必须按照下述格式创建页面

按照系统路由表需要的组件格式修改组件 `src/main/ets/components/MainPage.ets`

```plain
// 跳转页面入口函数
@Builder
export function MainPageBuilder() {  // 细节2:按照这个格式配置
  MainPage()
}


@Component
struct MainPage { // 细节1.1:不用导出  但是必须按照上述配置

  build() {
    NavDestination() { // 细节1.2:必须通过NavDestination写
      Text('内容').fontSize(30)
    }
    .title('CartHar/MainPage')
    .hideTitleBar(false)
  }
}
```


b)通过NavPathStack跳转

2.6 跨包动态路由-自定义路由表 HMRouter

千锋生鲜项目

2.7 企业级面试题

  • Navigation 和 Router 区别
1 路由有闪屏的问题、Navigation更流畅
2 Navigation更适合一次开发、多端适配
3 Navigation页面转场动画更方便
4 跨模块开发Navigation更方便
等等
  • 如何用的
传统:1-创建页面、2-配置路由main_pages、3-router跳转
现在:
- 准备:Navigation动态路由-系统路由表配置:1-创建route_map.json写配置、2-注册把1的文件跟项目关联module.json5、3-入口文件Navigation配置
- 后续1:创建页面  必须按照系统路由规则去创建 (必须写NavDestination 然后不写导出但是必须被@Builder调用)
- 后续2:配置该页面路由就可以跳转

鸿蒙开发班级

✨家人们点个juejin账号关注,会持续发布大前端领域技术文章💕 🍃

✨家人们点个juejin账号关注,会持续发布大前端领域技术文章💕 🍃

✨家人们点个juejin账号关注,会持续发布大前端领域技术文章💕 🍃

    ^_^ 点关注、不迷路、主播带你学技术 (๑′ᴗ‵๑)I Lᵒᵛᵉᵧₒᵤ❤

HarmonyOS 位置服务全攻略:精准定位、地理编码与后台持续定位实现

作者 IT充电站
2025年11月27日 13:21

✨家人们记得点个账号关注,会持续发布大前端领域技术文章💕 🍃

在 HarmonyOS 应用开发中,位置服务是许多场景的核心能力 —— 无论是本地生活服务的附近推荐、导航应用的实时轨迹追踪,还是出行类 App 的后台定位,都离不开稳定、精准的位置获取能力。本文将从权限配置、位置获取、地理编码转换、持续定位到后台长时定位,一步步拆解 HarmonyOS 位置服务的完整实现流程,附带可直接复用的代码示例,助力开发者快速落地功能。

获取设备的位置信息

1-配置申请位置权限 同时申请ohos.permission.APPROXIMATELY_LOCATION和ohos.permission.LOCATION 获取到精准位置,精准度在米级别。

2-检查全局位置开关是否打开 geoLocationManager.isLocationEnabled();

  • 2.1 没开 atManager.requestGlobalSwitch(getContext(this), abilityAccessCtrl.SwitchType.LOCATION) 拉起全局设置位置开关页
  • 2.2 开了 继续

3-获取当前位置 geoLocationManager.getCurrentLocation(request) 可以获取用户经度纬度

4-可以继续通过geoLocationManager.getAddressesFromLocation()地理编码转化为可读性较强地址

  • 获取设备的位置信息,需要有位置权限,位置权限申请
// 位置信息(权限组)
{
  "name": "ohos.permission.APPROXIMATELY_LOCATION",
  "reason": '$string:permission_reason_location',
  "usedScene": {}
},
{
  "name": "ohos.permission.LOCATION",
  "reason": '$string:permission_reason_location',
  "usedScene": {}
},

按照步骤申请权限 developer.huawei.com/consumer/cn…

import { geoLocationManager } from '@kit.LocationKit';
import { abilityAccessCtrl, Context, common } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { permissionUtil } from '../utils/PermissionUtil';

@Entry
@Component
struct Index {
  build() {
    Button('获取位置信息').onClick(async () => {
      // 1 配置文件 申请权限   同时申请ohos.permission.APPROXIMATELY_LOCATION和ohos.permission.LOCATION 获取到精准位置,精准度在米级别。
      const state = await permissionUtil.checkPermissions(['ohos.permission.APPROXIMATELY_LOCATION', 'ohos.permission.LOCATION'], getContext());
      if (!state) return

      // 2 检查全局开关  开了-继续走,没开-弹出让他授权
      const locationEnabled = geoLocationManager.isLocationEnabled();
      console.log('查看当前全局开关状态:', locationEnabled)

      if (!locationEnabled) {  // 没开
        const atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
        const state = await atManager.requestGlobalSwitch(getContext(), abilityAccessCtrl.SwitchType.LOCATION)
        if (!state) return  // 拉起全局弹出 用户没授权就终止代码执行
      }

      // 3. 获取位置信息  经度纬度
      geoLocationManager.getCurrentLocation( {
        locatingPriority: geoLocationManager.LocatingPriority.PRIORITY_LOCATING_SPEED,
        locatingTimeoutMs: 10000
      }).then((result) => { // 调用getCurrentLocation获取当前设备位置,通过promise接收上报的位置
        console.log('current location: ' + JSON.stringify(result));
      })
        .catch((error:BusinessError) => { // 接收上报的错误码
          console.error('promise, getCurrentLocation: error=' + JSON.stringify(error));
        });
      // .....
    })
  }
}

地理编码转化与逆地理编码转化

使用坐标描述一个位置,非常准确,但是并不直观,面向用户表达并不友好。系统向开发者提供了以下两种转化能力。

  • 地理编码转化:将地理描述转化为具体坐标。
  • 逆地理编码转化能力:将坐标转化为地理描述。

其中地理编码包含多个属性来描述位置,包括国家、行政区划、街道、门牌号、地址描述等等,这样的信息更便于用户理解。

开发步骤

  1. 导入geoLocationManager模块,所有与地理编码转化&逆地理编码转化能力相关的功能API,都是通过该模块提供的。
import { geoLocationManager } from '@kit.LocationKit';

2.获取转化结果。

调用getAddressesFromLocation,把坐标转化为地理位置信息。应用可以获得与此坐标匹配的GeoAddress(地理编码地址信息)列表,应用可以根据实际使用需求,读取相应的参数数据。

let reverseGeocodeRequest:geoLocationManager.ReverseGeoCodeRequest = {
  locale: 'zh',
  "latitude": 31.12, "longitude": 121.11, "maxItems": 1
};
try {
    geoLocationManager.getAddressesFromLocation(reverseGeocodeRequest, (err, data) => {
        if (err) {
            console.log('getAddressesFromLocation err: ' + JSON.stringify(err));
        } else {
            console.log('getAddressesFromLocation data: ' + JSON.stringify(data));
        }
    });
} catch (err) {
    console.error("errCode:" + JSON.stringify(err));
}

3.示例代码

import { geoLocationManager } from '@kit.LocationKit';
import { abilityAccessCtrl, Context, common } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { permissionUtil } from '../utils/PermissionUtil';

@Entry
@Component
struct Index {
  build() {
    Button('获取位置信息').onClick(async () => {
      // 1 配置文件 申请权限   同时申请ohos.permission.APPROXIMATELY_LOCATION和ohos.permission.LOCATION 获取到精准位置,精准度在米级别。
      const state = await permissionUtil.checkPermissions(['ohos.permission.APPROXIMATELY_LOCATION', 'ohos.permission.LOCATION'], getContext());
      if (!state) return

      // 2 检查全局开关  开了-继续走,没开-弹出让他授权
      const locationEnabled = geoLocationManager.isLocationEnabled();
      console.log('查看当前全局开关状态:', locationEnabled)

      if (!locationEnabled) {  // 没开
        const atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
        const state = await atManager.requestGlobalSwitch(getContext(), abilityAccessCtrl.SwitchType.LOCATION)
        if (!state) return  // 拉起全局弹出 用户没授权就终止代码执行
      }

      // 3. 获取位置信息  经度纬度
      geoLocationManager.getCurrentLocation( {
        locatingPriority: geoLocationManager.LocatingPriority.PRIORITY_LOCATING_SPEED,
        locatingTimeoutMs: 10000
      }).then((result) => { // 调用getCurrentLocation获取当前设备位置,通过promise接收上报的位置
        console.log('current location: ' + JSON.stringify(result))

        // 正地理编码与逆地理编码
        geoLocationManager.getAddressesFromLocation({"latitude": result.latitude, "longitude": result.longitude, "maxItems": 1}, (err, data) => {
          if (err) {
            console.log('getAddressesFromLocation err: ' + JSON.stringify(err));
          } else {
            console.log('getAddressesFromLocation data: ' + JSON.stringify(data));
          }
        });
        // 正地理编码与逆地理编码 end
      })
        .catch((error:BusinessError) => { // 接收上报的错误码
          console.error('promise, getCurrentLocation: error=' + JSON.stringify(error));
        });
      // .....
    })
  }
}

持续定位

持续定位。多用于导航、运动轨迹、出行等场景。

首先要实例化ContinuousLocationRequest对象,用于告知系统该向应用提供何种类型的位置服务,以及位置结果上报的频率。

  • 设置locationScenario:

建议locationScenario参数优先根据应用的使用场景进行设置,该参数枚举值定义参见UserActivityScenario,例如地图在导航时使用NAVIGATION参数,可以持续在室内和室外场景获取位置用于导航。

  • 设置interval:

表示上报位置信息的时间间隔,单位是秒,默认值为1秒。如果对位置上报时间间隔无特殊要求,可以不填写该字段。

以地图导航场景为例,调用方式如下:

import { geoLocationManager } from '@kit.LocationKit';
let request: geoLocationManager.ContinuousLocationRequest= {
   'interval': 1,
   'locationScenario': geoLocationManager.UserActivityScenario.NAVIGATION
}
let locationCallback = (location:geoLocationManager.Location):void => {
   console.log('locationCallback: data: ' + JSON.stringify(location));
};
try {
   geoLocationManager.on('locationChange', request, locationCallback);
} catch (err) {
   console.error("errCode:" + JSON.stringify(err));
}

如果不主动结束定位可能导致设备功耗高,耗电快;建议在不需要获取定位信息时及时结束定位。

geoLocationManager.off('locationChange', locationCallback);

完整实例代码

import { geoLocationManager } from '@kit.LocationKit';
import { abilityAccessCtrl } from '@kit.AbilityKit';
import { permissionUtil } from '../utils/PermissionUtil';

@Entry
@Component
struct Index {

  locationCallback(location:geoLocationManager.Location)  {
    console.log('locationCallback: data: ' + JSON.stringify(location));
  }

  aboutToDisappear() {
    geoLocationManager.off('locationChange', this.locationCallback);
  }

  build() {
    Button('获取位置信息').onClick(async () => {
      // 1 配置文件 申请权限   同时申请ohos.permission.APPROXIMATELY_LOCATION和ohos.permission.LOCATION 获取到精准位置,精准度在米级别。
      const state = await permissionUtil.checkPermissions(['ohos.permission.APPROXIMATELY_LOCATION', 'ohos.permission.LOCATION'], getContext());
      if (!state) return

      // 2 检查全局开关  开了-继续走,没开-弹出让他授权
      const locationEnabled = geoLocationManager.isLocationEnabled();
      console.log('查看当前全局开关状态:', locationEnabled)

      if (!locationEnabled) {  // 没开
        const atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
        const state = await atManager.requestGlobalSwitch(getContext(), abilityAccessCtrl.SwitchType.LOCATION)
        if (!state) return  // 拉起全局弹出 用户没授权就终止代码执行
      }

      // 3. 获取位置信息  经度纬度 【持续定位。多用于导航、运动轨迹、出行等场景。】
      geoLocationManager.on('locationChange', {
        interval: 1,
        locationScenario: geoLocationManager.UserActivityScenario.NAVIGATION
      }, this.locationCallback);

      // .....
    })
  }
}

后台定位-长时任务

应用退至后台后,在后台需要长时间运行用户可感知的任务,如播放音乐、导航等。为防止应用进程被挂起,导致对应功能异常,可以申请长时任务,使应用在后台长时间运行。在长时任务中可以申请多种类型的任务,并对任务类型进行更新。应用退后台执行业务时,系统会做一致性校验,确保应用在执行相应的长时任务。同时,系统有与长时任务相关联的通知栏消息,用户删除通知栏消息时,系统会自动停止长时任务。

1.需要申请ohos.permission.KEEP_BACKGROUND_RUNNING权限,配置方式请参见声明权限

2.声明后台模式类型。

在module.json5配置文件中为需要使用长时任务的UIAbility声明相应的长时任务类型(配置文件中填写长时任务类型的配置项)。

 "module": {
     "abilities": [
         {
        "name": "EntryAbility",
           // ...
              // ...
              // 
              "backgroundModes": [
              // 长时任务类型的配置项
             "audioRecording"
               或者
          "location"]
         }
     ],
     ...
 }
  1. 创建BackgroundTaskUtil.ets文件封装长短时任务
import { backgroundTaskManager } from '@kit.BackgroundTasksKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { common, WantAgent, wantAgent } from '@kit.AbilityKit';

class BackgroundTaskUtil {

  // 长任务-开启
  startLongTask(list:string[], callback: () => void) {  // list任务类型, callback开启成功后走的回调
    let wantAgentInfo: wantAgent.WantAgentInfo = {
      // 点击通知后,将要执行的动作列表
      // 添加需要被拉起应用的bundleName和abilityName
      wants: [
        {
          bundleName: (getContext() as common.UIAbilityContext).abilityInfo.bundleName,
          abilityName: "EntryAbility"
        }
      ],
      // 指定点击通知栏消息后的动作是拉起ability
      actionType: wantAgent.OperationType.START_ABILITY,
      // 使用者自定义的一个私有值
      requestCode: 0,
      // 点击通知后,动作执行属性
      actionFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG],
      // 车钥匙长时任务子类型。只有申请bluetoothInteraction类型的长时任务,车钥匙子类型才能生效。
      // 确保extraInfo参数中的Key值为backgroundTaskManager.BackgroundModeType.SUB_MODE,否则子类型不生效。
      // extraInfo: { [backgroundTaskManager.BackgroundModeType.SUB_MODE] : backgroundTaskManager.BackgroundSubMode.CAR_KEY }
    };

    try {
      // 通过wantAgent模块下getWantAgent方法获取WantAgent对象
      wantAgent.getWantAgent(wantAgentInfo).then((wantAgentObj: WantAgent) => {
        try {
          // let list: Array<string> = ["audioRecording"];
          // let list: Array<string> = ["bluetoothInteraction"]; 长时任务类型包含bluetoothInteraction,CAR_KEY子类型合法
          backgroundTaskManager.startBackgroundRunning(getContext(), list, wantAgentObj).then((res: backgroundTaskManager.ContinuousTaskNotification) => {
            console.info("Operation startBackgroundRunning succeeded");
            // 此处执行具体的长时任务逻辑,如录音,录制等。
            // 写尝试任务
            callback()
          }).catch((error: BusinessError) => {
            console.error(`Failed to Operation startBackgroundRunning. code is ${error.code} message is ${error.message}`);
          });
        } catch (error) {
          console.error(`Failed to Operation startBackgroundRunning. code is ${(error as BusinessError).code} message is ${(error as BusinessError).message}`);
        }
      });
    } catch (error) {
      console.error(`Failed to Operation getWantAgent. code is ${(error as BusinessError).code} message is ${(error as BusinessError).message}`);
    }

  }
  // 长任务-关闭
  stopLongTask() {
    backgroundTaskManager.stopBackgroundRunning(getContext()).then(() => {
      console.info(`Succeeded in operationing stopBackgroundRunning.`);
    }).catch((err: BusinessError) => {
      console.error(`Failed to operation stopBackgroundRunning. Code is ${err.code}, message is ${err.message}`);
    });
  }

  // 短任务-开启
  // 短任务-关闭
}

export  const backgroundTaskUtil = new BackgroundTaskUtil()
  • 示例代码
import { geoLocationManager } from '@kit.LocationKit';
import { abilityAccessCtrl } from '@kit.AbilityKit';
import { permissionUtil } from '../utils/PermissionUtil';
import { backgroundTaskUtil } from '../utils/BackgroundTaskUtil';

@Entry
@Component
struct Index {

  locationCallback(location:geoLocationManager.Location)  {
    console.log('locationCallback: data: ' + JSON.stringify(location));
  }

  aboutToDisappear() {
    geoLocationManager.off('locationChange', this.locationCallback);
  }

  build() {
    Button('获取位置信息').onClick(async () => {
      // 1 配置文件 申请权限   同时申请ohos.permission.APPROXIMATELY_LOCATION和ohos.permission.LOCATION 获取到精准位置,精准度在米级别。
      const state = await permissionUtil.checkPermissions(['ohos.permission.APPROXIMATELY_LOCATION', 'ohos.permission.LOCATION'], getContext());
      if (!state) return

      // 2 检查全局开关  开了-继续走,没开-弹出让他授权
      const locationEnabled = geoLocationManager.isLocationEnabled();
      console.log('查看当前全局开关状态:', locationEnabled)

      if (!locationEnabled) {  // 没开
        const atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
        const state = await atManager.requestGlobalSwitch(getContext(), abilityAccessCtrl.SwitchType.LOCATION)
        if (!state) return  // 拉起全局弹出 用户没授权就终止代码执行
      }

      // 3. 获取位置信息  经度纬度 【持续定位。多用于导航、运动轨迹、出行等场景。】
      backgroundTaskUtil.startLongTask(['location'], () => {
        geoLocationManager.on('locationChange', {
          interval: 1,
          locationScenario: geoLocationManager.UserActivityScenario.NAVIGATION
        }, this.locationCallback);
      })
      // .....
    })
  }
}

鸿蒙开发者班级

✨家人们点个juejin账号关注,会持续发布大前端领域技术文章💕 🍃

✨家人们点个juejin账号关注,会持续发布大前端领域技术文章💕 🍃

✨家人们点个juejin账号关注,会持续发布大前端领域技术文章💕 🍃

    ^_^ 点关注、不迷路、主播带你学技术 (๑′ᴗ‵๑)I Lᵒᵛᵉᵧₒᵤ❤

🔥100+ 天,已全面支持鸿蒙!uView Pro 近期更新盘点及未来计划

2025年11月27日 09:42

uView Pro 开源近三个月以来,收到了良好的反馈和迭代。目前 uView Pro 已经迭代了 40+ 个版本,平均每两天就会发布版本,主要是优化性能、新增\增强组件功能、bug修复、兼容性完善等。

所以目前 uView Pro 在稳定性、功能性与跨平台兼容性方面已经有了良好的表现。主要实现了 APP、鸿蒙、微信、支付宝、头条等小程序平台的兼容,后续也会继续进行迭代。

本文基于最近的 changelog 汇总,面向开发者与项目贡献者,系统介绍新增组件、关键修复、工具能力以及如何在项目中快速体验这些特性,并提供示例代码与资源链接,方便你在实际工程中落地使用。

image.png

一、总体概览

目前最新版本(0.3.16 及此前若干小版本)覆盖三大方向:

  • 平台兼容与 bug 修复:适配更多小程序平台(包括鸿蒙/各小程序支持的完善),修复了 canvas 渲染、表单响应、picker 初始化、组件兼容性等若干跨端问题。
  • 新组件与用户体验优化:推出并增强若干特色组件,如 u-fab(悬浮按钮)、u-textu-loading-popupu-textareau-safe-bottomu-status-baru-root-portal,以满足常见 UI 场景需求。
  • 工具链与框架能力:增强 http 插件与 useCompRelation(组件关系管理 Hooks),使业务层网络请求与复杂组件协作更便捷。

接下来我们把重点放在新增与优化的功能、示例使用以及工程实践建议上。

详情可查看官网及近期更新日志:uviewpro.cn/

二、亮点功能与新增组件(逐个拆解)

1) u-fab(悬浮按钮)

简介:u-fab 是面向移动端常见的悬浮操作入口,支持多种预设定位、拖动吸边(autoStick)以及 gap 属性的精细化配置。该组件在交互与无障碍体验上进行了增强,能兼容多端布局差异。

主要特性:

  • 预设 position(如右下、左下、右中等)便于在不同 UI 布局中快速放置。
  • 支持 gap 的对象式配置(top/right/bottom/left),使 demo 与真实项目兼容性更好。
  • autoStick:拖动后自动吸边,提升交互体验。

示例:

示例(Vue 3 Composition API):

<template>
<u-fab position="right-bottom" :gap="gapObj" :draggable="true" :autoStick="true">
<template #default>
<u-button shape="circle" size="mini" type="primary" @click="onFabClick">
                <u-icon name="thumb-up" size="40"></u-icon>
            </u-button>
</template>
</u-fab>
</template>

<script setup lang="ts">
import { ref } from 'vue';
const gapObj = { top: 20, right: 16, bottom: 16, left: 16 };
function onFabClick() {
uni.showToast({ title: '悬浮按钮点击' });
}
</script>

建议:在移动端应结合 safe area(如 u-safe-bottom)与页面常驻按钮布局谨慎使用 u-fab,避免遮挡关键内容。

更多用法请参考文档:uviewpro.cn/zh/componen…

12.png

2) u-text

简介:u-text 提供更灵活的文字样式与插槽支持,能在长文本、富文本展示场景中替代常规标签并统一样式控制。

主要特性:

  • 支持默认插槽与多种文本截断/换行策略。
  • 更友好的样式穿透能力,方便主题化。

示例:

<!-- 主题颜色文字 -->
<u-text text="主色文字" type="primary"></u-text>

<!-- 拨打电话 -->
<u-text mode="phone" text="15019479320"></u-text>

<!-- 日期格式化 -->
<u-text mode="date" text="1612959739"></u-text>

<!-- 超链接 -->
<u-text mode="link" text="Go to uView Pro docs" href="https://uviewpro.cn"></u-text>

<!-- 姓名脱敏 -->
<u-text mode="name" text="张三三" format="encrypt"></u-text>

<!-- 显示金额 -->
<u-text mode="price" text="728732.32"></u-text>

<!-- 默认插槽 -->
<u-text class="desc">这是一个示例文本,支持自定义插槽与样式</u-text>

更多用法请参考文档:uviewpro.cn/zh/componen…

9.png

3) u-loading-popup

简介:一个可配置的加载弹窗组件,支持多种加载风格与遮罩配置,方便替代项目中散落的 loading 逻辑。

示例(最小用法):

<!-- 默认纵向加载 -->
<u-loading-popup v-model="loading" text="正在加载..." />
<!-- 横向加载 -->
<u-loading-popup v-model="loading" direction="horizontal" text="正在加载..." />

更多用法请参考文档:uviewpro.cn/zh/componen…

11.png

4) u-textarea

简介:独立的 u-textarea 组件从 u-input 中拆分而来,增强了字数统计、伸缩、和独立样式控制能力,满足复杂表单与长文本输入场景。

示例:

<!-- 字数统计 -->
<u-textarea v-model="content" :maxlength="500" count />

<!-- 自动高度 -->
<u-textarea v-model="content" placeholder="请输入内容" autoHeight></u-textarea>

更多用法请参考文档:uviewpro.cn/zh/componen…

13.png

5) u-safe-bottom 与 u-status-bar

用途:与设备安全区(notch/safearea)相关的布局组件,用来保证底部/状态栏的展示在不同平台上都不会被遮挡或错位。适配了多端差异(iOS、Android、不同小程序宿主)。

如果有需要,您可以在任何地方引用它,它会自动判断在并且在 IPhone X 等机型的时候,给元素加上一个适当 底部内边距,在 APP 上,即使您保留了原生安全区占位(offset设置为auto),也不会导致底部出现双倍的空白区域,也即 APP 上 offset 设置为 auto 时。

<template>
  <view>
    ......
    <u-safe-bottom></u-safe-bottom>
  </view>
</template>

更多用法请参考文档:uviewpro.cn/zh/componen…

6) u-root-portal

简介:提供将节点传送到根节点的能力(Portal 模式),适用于模态、全局浮层等需要脱离当前 dom 层级的场景,兼容多端实现细节。

根节点传送组件仅支持微信小程序、支付宝小程序、APP和H5平台,组件会自动根据平台选择合适的实现方式:

这类场景最常见的例子就是全屏的模态框。理想情况下,我们希望触发模态框的按钮和模态框本身的代码是在同一个单文件组件中,因为它们都与组件的开关状态有关。

<u-button type="primary" @click="show = true">显示弹窗</u-button>
<u-root-portal v-if="show">
  <view class="modal">
    <view class="modal-content">
      <text>这是一个全局弹窗</text>
      <u-button @click="show = false">关闭</u-button>
    </view>
  </view>
</u-root-portal>

更多用法请参考文档:uviewpro.cn/zh/componen…

7) 自定义主题

uView Pro 目前可以自定主题色,字体颜色,边框颜色等,所有组件内部的样式,都基于同一套主题,比如您修改了primary主题色,所有用到了primary颜色 的组件都会受影响。

由于 uView 官方版本,组件内部存在许多硬编码颜色配置,无法动态根据 scss 变量,现在,我们可以统一跟随主题配置了。

通过官网主题颜色配置完后,在页面底部下载文件,会得到一个名为uview-pro.theme.scssuview-pro.theme.ts的文件。

配置 scss 变量

/* uni.scss */
@import 'uview-pro/theme.scss';

配置 ts 变量

// main.ts
import { createSSRApp } from 'vue'
import App from './App.vue'
import theme from '@/common/uview-pro.theme'
import uViewPro from 'uview-pro'

export function createApp() {
  const app = createSSRApp(App)
  // 引入uView Pro 主库,及theme主题
  app.use(uViewPro, { theme })
  return {
    app
  }
}

以上步骤完成之后,所有颜色均跟随主题色。

更多用法请参考文档:uviewpro.cn/zh/guide/th…

8.png

8) 自定义样式

uView Pro 默认提供了一套美观且统一的组件样式,但在实际项目开发中,往往需要根据业务需求进行个性化定制。参考自定义主题。

然而,如果仅是需要覆盖组件的默认样式,或增加样式,uView Pro 则支持两种主流的自定义样式方式,灵活满足各种场景:

目前,所有组件均支持 custom-class 样式穿透和 custom-style 内联样式

<view class="my-page">
    <!-- custom-class 样式穿透 -->
    <u-button custom-class="my-btn"></u-button>

    <!-- 自定义内联样式 -->
    <u-button
        custom-style="background: linear-gradient(90deg,#2979ff,#00c6ff);color:#fff;border-radius:8px;"
    ></u-button>
</view>

<style lang="scss">
.my-page {
  :deep(.my-btn) {
    background-color: #2979ff;
    color: #fff;
    border-radius: 8px;
  }
}
</style>

更多用法请参考文档:uviewpro.cn/zh/guide/st…

三、工具链改进与新能力

1) http 插件(httpPlugin)

简介:提供统一的请求封装,支持 TypeScript、Vue3、组合式 API,插件化、全局配置、请求/响应拦截器、请求元信息类型(toast/loading 灵活控制),开箱即用,便于在项目中进行全局化网络管理。。

示例:基本请求

import { http } from 'uview-pro'

// GET
http.get('/api/user', { id: 1 }).then(res => {
  /* ... */
})

// POST
http.post('/api/login', { username: 'xx', password: 'xx' }).then(res => {
  /* ... */
})

// PUT/DELETE
http.put('/api/user/1', { name: 'new' })
http.delete('/api/user/1')

高级:支持请求拦截器、全局错误处理与 meta 配置,适合接入鉴权、重试、限流等策略。

最佳实践:定义拦截器配置 => 注册拦截器 => 统一 API 管理

定义拦截器配置

import type { RequestConfig, RequestInterceptor, RequestMeta, RequestOptions } from 'uview-pro'
import { useUserStore } from '@/store'

// 全局请求配置
export const httpRequestConfig: RequestConfig = {
  baseUrl,
  header: {
    'content-type': 'application/json'
  },
  meta: {
    originalData: true,
    toast: true,
    loading: true
  }
}

// 全局请求/响应拦截器
export const httpInterceptor: RequestInterceptor = {
  request: (config: RequestOptions) => {
    // 请求拦截
    return config
  },
  response: (response: any) => {
    // 响应拦截
    return response.data
  }
}

注册拦截器:

import { createSSRApp } from 'vue'
import uViewPro, { httpPlugin } from 'uview-pro'
import { httpInterceptor, httpRequestConfig } from 'http.interceptor'

export function createApp() {
  const app = createSSRApp(App)

  // 注册uView-pro
  app.use(uViewPro)

  // 注册http插件
  app.use(httpPlugin, {
    interceptor: httpInterceptor,
    requestConfig: httpRequestConfig
  })

  return { app }
}

统一 API 管理

// api/index.ts
import { http } from 'uview-pro'

export const login = data => http.post('/api/login', data,  { meta: { loading: true, toast: true } })
export const getUser = id => http.get('/api/user', { id },  { meta: { loading: false } })

以上示例为经典最佳实践,更多用法请查看 http 插件文档:uviewpro.cn/zh/tools/ht…

2) useCompRelation(组件关系管理 Hooks)

目的:替代传统的 provide/inject 在多平台(尤其是一些小程序宿主)可能存在的兼容问题,提供更可靠的父子组件连接和事件广播机制。

应用场景:复杂表单、级联菜单、带有子项动态增删的组件集合等。

父组件示例(伪代码):

import { useParent } from 'uview-pro';

const { children, broadcast } = useParent('u-dropdown');

// 广播调用子组件函数
broadcast('childFunctionName', { payload });

// 收集所有子组件指定值
function getChildrenValues() {
    let values: any[] = [];
    children.forEach((child: any) => {
        if (child.getExposed?.()?.isChecked.value) {
            values.push(child.getExposed?.()?.name);
        }
    });
}

子组件示例(伪代码):

const { parentExposed, emitToParent } = useChildren('u-dropdown-item', 'u-dropdown');

// 触发父组件的函数
emitToParent('parentFunctionName');

// 获取父组件的变量
const activeColor = computed(() => parentExposed.value?.activeColor);

更多用法请参考组件源码:useCompRelation.ts

3) 提供 llms.txt

llms.txt的作用是什么,一般它用来告诉大模型是否允许抓取网站数据用于训练的文件,类似于 robots.txt 控制爬虫权限,因此 uView Pro 也提供了即时更新的 llms.txt 文件,便于训练大模型,更好的为我们服务,链接如下:

uviewpro.cn/llms.txt

uviewpro.cn/llms-full.t…

四、多脚手架支持

1) create-uni

create-uni 提供一键生成、模板丰富的项目引导能力,旨在增强 uni-app 系列产品的开发体验,官网:uni-helper.cn/create-uni/…

pnpm create uni <项目名称> --ts -m pinia -m unocss -u uview-pro -e

表示:

  • 启用 TypeScript
  • 集成 ESLint 代码规范
  • 启用 pinia
  • 集成 unocss
  • 选择 uview-pro组件库

6.png

如果你想用 create-uni 交互式创建一个项目,请执行以下命令:

pnpm create uni

进入交互式选择界面,选择 uView Pro 模板或组件,其他的相关插件可按需选择:

2.png

image.png

使用 create-uni 快速创建 uView Pro Starter 启动模板,请执行以下命令:

pnpm create uni <项目名称> -t uview-pro-starter

4.png

使用 create-uni 快速创建 uView Pro 完整组件演示模板,请执行以下命令:

pnpm create uni <项目名称> -t uview-pro-demo

5.png

2) unibest

unibest 是目前最火的 uni-app 脚手架,它是菲鸽大佬联同众多 uni-app 开发者共同贡献的 uni-app 框架,集成了最新技术栈和开发工具,官网:unibest.tech/

如果你想用 unibest 和 uView Pro 来创建项目,请执行以下命令:

一行代码创建项目:

pnpm create unibest <项目名称> -t base-uview-pro

1.png

交互式创建项目:

pnpm create unibest

选择 base-uview-pro 模板:

3.png

3) 官方cli

第一种:创建以 javascript 开发的工程

npx degit dcloudio/uni-preset-vue#vite my-vue3-project

第二种:创建以 typescript 开发的工程

npx degit dcloudio/uni-preset-vue#vite-ts my-vue3-project

引入uview—pro组件库即可,不再过多介绍,可参考快速配置:uviewpro.cn/zh/componen…

五、近期修复若干关键问题

  • u-circle-progress 的 canvas 渲染问题已修复,解决了微信小程序 canvas 2D 在不同平台上下文差异导致的绘制异常。
  • u-form 相关多个修复:处理 model 替换导致校验失效、resetFields 修复、u-form-item 样式与光标问题修复,提升表单在小程序端兼容性。
  • picker、index-list、popup 等组件的跨端兼容修复,减少在头条/支付宝/微信等宿主上的差异表现。

这些修复的综合效果是:在多端使用 uView‑Pro 构建页面时,出现的平台差异与边缘 bug 大幅减少,开发成本降低。

六、跨平台支持说明

当前 uView‑Pro 已兼容并在以下平台进行适配与测试:

  • 鸿蒙(HarmonyOS)
  • Android(原生应用及 WebView)
  • iOS(原生应用及 WebView)
  • 微信小程序
  • 支付宝小程序
  • 头条小程序

后续仍然会对多端小程序兼容性的持续投入,很多修复直接针对宿主差异展开(例如 Canvas 行为、provide/inject 实现差异、样式差异等)。

近期在鸿蒙6.0系统上运行uView Pro源码,效果还不错,如下:

7.png

七、未来计划

根据规划,未来几个方向包括:

  • 持续优化现有组件,新增组件,提升用户体验;
  • 国际化(i18n)支持:统一组件的语言切换能力,方便多语言产品线接入;
  • 暗黑模式(Dark Mode):与运行时主题切换能力结合,提供暗色皮肤一键切换体验;
  • 优化现有平台兼容性,扩展更多平台的适配测试(保持对小程序宿主的兼容修复);
  • uni-app x 支持:目前还在调研中;
  • mcp 支持。

八、结语

如果你在项目中使用到以上组件或工具,并希望参与贡献,请参考仓库的贡献指南。欢迎提 issue、提交 PR,或在插件市场与社区中反馈使用体验。

定义国产高端新十年,华为 Mate 和鸿蒙 6 给出回答

作者 艾 梵
2025年11月26日 11:00

通常,提到一部手机的名字,我们总会联想到其背后的特质——比如 nova 总让人想到「年轻」,而 Pura 系列显然就是「时尚」,至于 Mate ——能让人联想到的词很多,但没有一个比这个词更合适:「高端」

虽说手机高端化的路径不止一条,在过去几年里我们也见到了各种影像、性能、性价比旗舰轮番冲刺高端,但能不加任何前缀的国产「高端」旗舰,至今也只有华为 Mate 系列一支。

11 月 25 日,华为带来了最新 Mate 系列旗舰产品——华为Mate 80系列以及折叠屏 Mate X7,并全系搭载了最新的鸿蒙 6 系统。

这一次,Mate 高端的定义不再限于软硬件,而是立足整个鸿蒙生态。

同时,这也意味着,国产高端手机的「参数战争」总算迎来了终结。

高端手机的前十年,是定义硬件的战争

回望 Mate 系列十余年,其实就是华为在不断回答「国产高端手机应该怎么做」的过程。

2013 年,在那个「大就是好」的野蛮生长年代,为了回应余承东那个「要做比 iPhone 5 强大很多的旗舰手机」的承诺,华为在 Ascend 系列下面开辟了一条新的产品线 :Mate

Ascend Mate

Ascend Mate 不仅是 Mate 系列从无到有的开端,也可以视作国产品牌开始向国际品牌定义的「高端」发起冲锋。

随后 2014 年,拥有 1080P 大屏、按压式指纹识别、全金属机身的 Mate 7,几乎就是无可争议的「国产唯一高端手机」,哪怕起售价达到 3000 元档也一机难求——「爵士人生」成为了「高端」手机的代名词。

Ascend Mate 7

可以说,Mate 7 既是华为定义「高端」的成果,也是华为 Mate 系列冲击高端化的起点。

伴随着「中华酷联」逐渐变成「华米OV」,大家都有了超大屏、大电池和金属机身,仅靠「人无我有」已经不足以应对市场竞争了。

Mate 系列下一步要做的,就是重新找到定义高端旗舰的基准。华为这一阶段的回答是——工艺

在结束与徕卡的「蜜月」之后,华为并没有安逸地停在联名这条路上,而是趁这个机会,把「联名的」元素变成「华为的」元素。

其成果就是华为Mate 9 保时捷设计版,「超高端工艺」第一次成为华为产品的高端基因——那是在 2016 年就标价 8999 元、甚至一度被炒到两万元天价的现象级产品。

这代表着 Mate 系列正在跳出「先把参数拉满」的固定范式,开始在品牌层面塑造「高端」的印象。

图|微博 @余承东

从那时候开始,华为 Mate 系列总有着让人惊叹的工艺追求。

从 Mate 20 的多曲面玻璃机身,到 Mate 30 的超曲环幕屏,再到 Mate 40 的一体陶瓷后盖等——对工艺的极致追求,实际上已经超越了功能需求,「看得见、摸得着」的质感,成为高端的象征。

可以说,Mate 代表国产高端手机的前十年,是定义硬件的十年——是纸面上的功能参数、性能跑分,是精湛工艺、是旗舰影像。

Mate 70 Pro+

但历史的经验告诉我们,只靠「功能全」和「工艺美」虽然能够在手机市场内部占据高端份额,但在面对一些「盘外招」的时候是无能为力的。

而当产品硬件已经不是唯一的要素之后,什么才能算是高端手机真正的「护城河」?

高端手机的新十年:从定义硬件到生态共荣

当所有旗舰手机都用上顶级芯片、屏幕、影像模组等无差别配置时,真正拉开差距的,就不再是硬件性能,而是生态体验。

换言之,「高端手机早已不止于硬件的高端」,还在于手机生态体验的高端,而这恰恰是鸿蒙生态构建的主场。

自鸿蒙 5 发布以来,应用的适配不仅创造了「鸿蒙速度」,在不同形态下也通过软硬件协同,为用户带来着大有不同的使用体验,这也成为用户选择华为的理由——华为Pura X 的热卖就是例证。

自鸿蒙 5 发布一年多来,几乎所有头部应用都为鸿蒙做出适配、重新设计交互逻辑,以实现体验的跃迁。比如,WPS、东方财富等之前就为华为Mate XTs 三折叠手机适配了 PC 布局,这是别的大屏手机没有的独特体验。

据统计,运行鸿蒙 5 和鸿蒙 6 的终端数量已经突破了 2700 万台,涵盖了从手机、电脑到平板、手表、智慧屏在内的全部华为产品线。

我们已经见到非常多主流应用参与到适配碰一碰、实况窗等近百种专属功能。华为应用商店里,应用们日均更新超过 2000 项新增功能、23000 余个应用参与了超过 70 项创新体验,鸿蒙系统功能新增超过 300 个……

可以说,生态共荣的鸿蒙,终于走过了「从 0 到 1」的艰难阶段,解决了「有没有」的问题,正在向「好用」全速进阶。

而这一次,以华为Mate 80系列和 Mate X7 这两款高端手机为契机,华为正式对「国产高端应该怎么做」作出第二次回答:

高端不止于硬件,更在于生态体验。

做高端就必须跳出以往「做得比苹果安卓更好」的惯性思维,而是基于顶级的硬件及生态之力,做出前所未有的新体验。

譬如,这一次华为Mate 80系列标配的 3D ToF 人脸识别,终于不再限于「系统解锁」,而是联合了支付宝等主流支付平台,实现了更高安全性的支付方式。

同样拥有革新体验的还有直播——华为Mate 80系列带来鸿蒙高清直播功能,鸿蒙版小红书独家支持 2K 30 帧直播,鸿蒙版 B 站支持 HDR Vivid 直播,还能调用红枫镜头、长焦微距等功能,提供高品质的直播效果。

这样一来,更高画质的直播可以让小红书博主们在画面中展现更多细节,无论是珠宝还是美妆,都拥有了更加丰富的置景空间。类似地,B 站 HDR Vivid 直播也能让探店、才艺展示、户外等类型的内容释放更强的视觉冲击力。

 

在 AI 提效方面,华为Mate 80系列也没有落下。新的小艺支持自然语言修图,动动嘴就能实现专业级后期:

全新的小艺帮帮忙功能,则让华为Mate 80系列具备了学习能力,你真的可以「教手机做事」,例如在京东上买咖啡这样的定期操作,现在交给小艺代为处理就非常方便:

在日常生活中,全新升级的小艺简报还能打破应用边界,主动聚合日程、通知及待办信息,自动生成效率、健康或学习类的场景化摘要。

值得一提的,还有今年在 Mate X7 上首次商用 A2A 智能体协作的全新小艺

用通俗的话来讲,支持 A2A 智能体的小艺,已经不单是华为自己训练出的 AI 模型了,而是一个打通了诸多业务平台、涵盖用户需求方方面面的多 AI 智能体的聚合——

比如当你拿着 Mate X7,就可以直接开口向小艺说:「帮我订一张 11 月 25 日去深圳的机票,选深航,要一个能看到日落的座位」。

相比以往的 AI 助手需要分开执行打开航司 app、选日期、选座位等多次操作,鸿蒙 6 中的新小艺可以直接联动「深航飞飞」智能体,在同一个 session 中实现从收到指令到下单出票的丝滑小连招

又比如来自东方财富的妙想金融智能体,作为首款来自金融行业的鸿蒙金融智能体,很适配 Mate X7 这样的折叠屏手机,真正能做到通晓行情、洞悉事件、理解逻辑,动动嘴就能完成一次专业分析。

只需在指令里加上一句「小艺,用妙想……」,鸿蒙 6 系统就会调用妙想金融智能体生成可视化要点、定量结论与可追溯逻辑,将「人找信息」转变为「信息找人」。再结合 Mate X7 的金融行情众览功能,无需切换界面即可同步监控走势、指数和成交明细。

这一次,Mate X7 上面还搭载了鸿蒙 6 全新的星河互联架构,将复杂的设备连接简化为自然的一步操作——

无论是手机的「一碰即传」,可以一次将内容集中分享至多部手机,还是首款鸿蒙全场景独家游戏《太吾绘卷》的「一键接续」,实现游戏从电脑到手机跨设备的接续。只需一步,就能实现设备之间的高效互联。

而为了解决跨品牌用户的痛点,鸿蒙星河也更进一步,主动跨越品牌生态壁垒,在 Mate X7 上实现了与 iOS 设备的跨平台互传——

在 Mate X7 上,用户无需依赖流量,即可在自己的 Mate X7 与 iPhone 之间享受近场快传的便捷。

这些功能改进,都是鸿蒙系统基于不同场景不同职业的需求,呈现给用户的「细分化智能体验」。

还有更多细节,则体现了鸿蒙系统润物细无声的部分——

我印象最深刻的,就是鸿蒙版微信和 QQ 都支持调用手机原相机拍照拍视频,画质与原相机如出一辙——这个功能很小,却每天都会用。很多改变,就是从这些小处开始,由量变引发质变的。

换言之,这是一种颠覆习惯的体验,但改变用户的习惯,正是因为有更好的习惯值得被养成。

可以说,Mate 与鸿蒙 6 的组合,最大意义不在于单一产品或软件的成功,而在于它开启了「鸿蒙生态,共生共荣」的良性循环——

产品每一次硬件创新,鸿蒙都能让它即刻成为体验;鸿蒙每一次生态突破,产品都能让它迅速沉淀为习惯。硬件是加速器,生态是放大器,两者彼此成就——创新在这里萌芽,也在这里生长

自此,华为 Mate 系列已经摆脱了「定义硬件」走向高端化的路径,毕竟没有生态支撑,再好的硬件也是孤木难支。

在历经大屏、工艺和影像之后,华为这套「硬件 + 鸿蒙」的组合,则构建起了一个牢不可破的联盟——

用户选择华为 Mate 旗舰,不只是换一台手机,更是接入了一整套服务生态。

这些看似简单的功能,背后是长时间的软硬件积累和生态磨合。十年前,能做到这一步的厂商寥寥无几,而如今,我们也有了属于自己的高端体验。

余承东在 2014 年 Mate 7 发布会上说过一句话:「通往山顶的路有很多条,我们选择最难、也是最有价值的那一条」——十多年过去,华为依然在践行这条路,而路边的草木已经蔚然成林。

从功能到生态,从参数到体验,从追赶到引领——华为 Mate 系列所做的,不只是造出一台又一台旗舰手机,而是持续不断地定义着「高端」这件事本身。而这种定义也反过来,变成了华为的品牌印记。

国产手机「高端化」的下个十年,注定是「生态」的竞争,而只有做到生态「共荣」,竞争才能持久,品牌才会长远。

起烟于寒灰之上,生华于已枯之木。高端手机的下个十年刚刚开始,但引领潮头者,已经昭然若揭。

#欢迎关注爱范儿官方微信公众号:爱范儿(微信号:ifanr),更多精彩内容第一时间为您奉上。

爱范儿 | 原文链接 · 查看评论 · 新浪微博


❌
❌