您现在的位置是:亿华云 > IT科技
用鸿蒙开发AI应用(六)UI篇
亿华云2025-10-02 18:49:19【IT科技】5人已围观
简介想了解更多内容,请访问:和华为官方合作共建的鸿蒙技术社区https://harmonyos.51cto.com/#zz前言上一篇,我们在鸿蒙上写了一个HDF驱动并操作了一下LED硬件,这一篇我们来尝试
想了解更多内容,用鸿I应用请访问:
和华为官方合作共建的蒙开鸿蒙技术社区
https://harmonyos.51cto.com/#zz
前言 上一篇,我们在鸿蒙上写了一个HDF驱动并操作了一下LED硬件,用鸿I应用这一篇我们来尝试一下构建一个有简单界面的蒙开App,体验一下鸿蒙的用鸿I应用前端开发流程。
环境准备
1. 安装DevEco Studio
解压相应的蒙开压缩包(文末附下载链接),这里以win10为例,用鸿I应用双击`deveco-studio-2.0.12.201.exe`




同意用户协议后,用鸿I应用就能正常启动了。蒙开


同样在`SDK Tools`中,蒙开选中新版的用鸿I应用`Previewer`。

点击Apply更新

新建项目
点击菜单`File->New Project...`,选择智慧屏`Smart Vision`,创建一个空模板应用。
目录结构
我们先分析一下目录结构,做`Android`开发的会倍感亲切。

1. APP
`HarmonyOS`的应用软件包以`APP Pack(Application Package)`形式发布,站群服务器它是由一个或多个`HAP(HarmonyOS Ability Package)`以及描述每个`HAP`属性的`pack.info`组成。`HAP`是`Ability`的部署包,`HarmonyOS`应用代码围绕`Ability`组件展开。
一个`HAP`是由代码、资源、第三方库及应用配置文件组成的模块包,可分为`entry`和`feature`两种模块类型。
- **entry**:应用的主模块。一个APP中,对于同一设备类型必须有且只有一个`entry`类型的`HAP`,可独立安装运行。
- **feature**:应用的动态特性模块。一个`APP`可以包含一个或多个`feature`类型的`HAP`,也可以不含。只有包含`Ability`的`HAP`才能够独立运行。

2. Ability
Ability是应用所具备的能力的抽象,一个应用可以包含一个或多个`Ability`。`Ability`分为两种类型:`FA(Feature Ability)`和`PA(Particle Ability)`。`FA/PA`是亿华云应用的基本组成单元,能够实现特定的业务功能。`FA`有`UI`界面,而`PA`无`UI`界面。
3. 资源文件
应用的资源文件(字符串、图片、音频等)统一存放于`resources`目录下,便于开发者使用和维护。`resources`目录包括两大类目录,一类为`base`目录与限定词目录,另一类为`rawfile`目录。
4. 配置文件
配置文件` (config.json) `是应用的`Ability`信息,用于声明应用的`Ability`,以及应用所需权限等信息。
- 应用的全局配置信息,包含应用的包名、生产厂商、版本号等基本信息。
- 应用在具体设备上的配置信息,包含应用的备份恢复、网络安全等能力。
- `HAP`包的配置信息,源码下载包含每个`Ability`必须定义的基本属性(如包名、类名、类型以及`Ability`提供的能力),以及应用访问系统或其他应用受保护部分所需的权限等。
5. JS UI 框架
`JS UI`框架是一种跨设备的高性能`UI`开发框架,支持声明式编程和跨设备多态`UI`。
- 声明式编程
`JS UI`框架采用类`HTML`和`CSS`声明式编程语言作为页面布局和页面样式的开发语言,页面业务逻辑则支持`ECMAScript`规范的`JavaScript`语言。`JS UI`框架提供
的声明式编程,可以让开发者避免编写`UI`状态切换的代码,视图配置信息更加直观。
- 跨设备
开发框架架构上支持`UI`跨设备显示能力,运行时自动映射到不同设备类型,开发者无感知,降低开发者多设备适配成本。
- 高性能
开发框架包含了许多核心的控件,如列表、图片和各类容器组件等,针对声明式语法进行了渲染流程的优化。
`JS UI`框架包括应用层`(Application)`、前端框架层`(Framework)`、引擎层`(Engine)`和平台适配层`(Porting Layer)`。

# 空气质量监测 UI
## 1. 创建首页面
空气质量监测App包含两个界面`(Page)`,工程创建完成后会生成一个名为`index`的`Page`,可以作为首页。

## 2. 创建详情页
在`pages`目录按右键,弹出的菜单中选择`New->JS Page`。


详情页创建完成后应用工程目录如下图所示,每个`Page`包括三个文件:布局文件`hml`、样式文件`css`、业务逻辑代码`js`。

## 3. 开发首页
应用首页主要展示城市的空气质量概况。首页总共有两屏(可以根据需求设置多屏),每屏显示一个城市的空气质量信息:主要包括AQI指数、城市名称、污染物指数、更新时间和信息来源等数据。
### 3.1 创建根节点
修改`entry/src/main/js/default/pages/index/index.hml`,加入根节点`div`:
<div class="container"> </div>### 3.2 创建样式
修改`entry/src/main/js/default/pages/index/index.css`
container { flex-direction: column; height: 480px; width: 960px; } 3.3 添加标题栏标题栏包括一个退出按钮和一个标题,两个控件是横向排列
<div class="container"> <div class="header" onclick="exitApp"> <image class="back" src="common/ic_back.png"></image> <text class="title"> 空气质量 </text> </div> </div>注意,这里要先导入common/ic_back.png图标资源。
3.4 添加标题栏样式
修改entry/src/main/js/default/pages/detail/detail.css,添加以下代码,设置组件的高度、边距、颜色等属性。
.header { width: 960px; height: 72px; } .back { width: 36px; height: 36px; margin-left: 39px; margin-top: 23px; } .title { width: 296px; height: 40px; margin-top: 20px; margin-left: 21px; color: #e6e6e6; }### 3.5 添加退出事件
`onclick="exitApp"` 设置了`div`组件的`click`事件,当在标题栏上触发点击事件时,就会执行函数`exitApp`,该函数位于`index.js`文件中,代码如下:
exitApp() { console.log(start exit); app.terminate(); console.log(end exit); }`app.terminate()`函数实现了程序退出功能;在使用该函数前,需要引入`app`模块,在`index.js`文件的最上方写如下代码:
import app from @system.app在 Previewer 窗口中,可以预览界面效果

### 3.6 滑动组件
实现城市空气质量信息的多屏左右滑动,需要使用`“swiper”`组件。
在根节点中添加一个子节点`swiper`, 修改`index.hml`
<swiper class="swiper" index="{ { swiperPage}}" duration="500" onchange="swiperChange"> </swiper> 添加样式,修改`index.css` .swiper { height: 385px; width: 960px; } 绑定`swiperPage`变量,`swiperChange`事件,修改`index.js` //引入router模块,用户页面跳转 import router from@system.router import app from @system.app export default { //定义参数 data: { //默认是第一页 swiperPage: 0 }, onInit () { }, exitApp(){ console.log(start exit); app.terminate(); console.log(end exit); }, //swiper滑动回调事件,保存当前swiper的index值,每次滑动都会将index值保存在swiperPage变量中 swiperChange (e) { this.swiperPage = e.index; } }在`swiper`中添加两个子组件`stack`(绝对布局),每个`stack`组件内分别添加`text、image、progress`等组件来显示对应的信息。
<div class="container"> <div class="header" onclick="exitApp"> <image class="back" src="common/ic_back.png"></image> <text class="title"> 空气质量 </text> </div> <swiper class="swiper" index="{ { swiperPage}}" duration="500" onchange="swiperChange"> <!--第一屏--> <stack class="swiper"> <!--空气质量--> <text class="airquality" style="color:{ { textColor1}};">{ { airData[0].airQuality}}</text> <!--城市名称--> <text class="location-text">{ { airData[0].location}}</text> <!--进度条--> <progress class="circleProgress" style="color:{ { textColor1}};background-Color:{ { bgColor1}};" type="arc" onclick="openDetail" percent="{ { percent1}}"> </progress> <!--云朵图片--> <image class="image" src="{ { src1}}"></image> <!--AQI数值--> <text class="pm25-value">{ { airData[0].detailData }}</text> <text class="pm25-name">AQI</text> <!--空气指标详细信息--> <div class="detail"> <div class="text-wrapper"> <text class="gas-name"> CO </text> <text class="gas-value"> 100 </text> </div> <div class="text-wrapper"> <text class="gas-name"> NO2 </text> <text class="gas-value"> 90 </text> </div> <div class="text-wrapper"> <text class="gas-name"> PM10 </text> <text class="gas-value"> 120 </text> </div> <div class="text-wrapper"> <text class="gas-name"> PM2.5 </text> <text class="gas-value"> 40 </text> </div> <div class="text-wrapper"> <text class="gas-name"> SO2 </text> <text class="gas-value"> 150 </text> </div> <input class="btn" type="button" onclick="openDetail" value="历史记录"></input> </div> <!--更新时间和网站等信息--> <div class="footer"> <text class="update-time"> 更新时间: 10:38 </text> <text class="info-source"> 信息来源: tianqi.com </text> </div> </stack> <!--第二屏--> <stack class="swiper"> <text class="airquality" style="color: { { textColor2}};">{ { airData[1].airQuality}}</text> <text class="location-text">{ { airData[1].location}}</text> <progress class="circle-progress" style="color: { { textColor2}};background-Color: { { bgColor2}};" type="arc" percent="{ { percent2}}"></progress> <image class="image" src="{ { src2}}"></image> <text class="aqi-value">{ { airData[1].detailData}}</text> <text class="aqi"> AQI </text> <div class="detail"> <div class="text-wrapper"> <text class="gas-name"> CO </text> <text class="gas-value"> 10 </text> </div> <div class="text-wrapper"> <text class="gas-name"> NO2 </text> <text class="gas-value"> 50 </text> </div> <div class="text-wrapper"> <text class="gas-name"> PM10 </text> <text class="gas-value"> 60 </text> </div> <div class="text-wrapper"> <text class="gas-name"> PM2.5 </text> <text class="gas-value"> 40 </text> </div> <div class="text-wrapper"> <text class="gas-name"> SO2 </text> <text class="gas-value"> 150 </text> </div> <input class="btn" type="button" onclick="openDetail" value="历史记录"></input> </div> <div class="footer"> <text class="update-time"> 更新时间: 10:38 </text> <text class="info-source"> 信息来源: tianqi.com </text> </div> </stack> </swiper> </div>### 3.7 页面位置指示器
添加页面位置指示器:由于当前`swiper`不支持设置`indicator`,需要开发者自己来实现该效果。在根节点中添加一个子组件`div`,并设置相应样式;然后在该`div`中添
加两个子组件`div`,设置两个`div`的`border-radius`,并在`swiper`滑动事件中动态改变对应`div`的背景色来实现该效果。
修改`index.hml`,在`swiper`组件后加入以下代码:
<div class="images"> <div class="circle-div" style="background-color: { { iconcheckedColor}};"></div> <div class="circle-div" style="background-color: { { iconUncheckedColor}};margin-left: 36px;"></div> </div>### 3.8 新增文字样式
修改 `index.css`
aqi-value { text-align: center; font-size: 65px; color: #f0ffff; width: 156px; height: 92px; top: 134px; left: 210px; } .aqi { text-align: center; color: #a2c4a2; width: 156px; height: 45px; top: 90px; left: 210px; } .airquality { top: 222px; text-align: center; width: 156px; height: 45px; left: 210px; } .image { top: 285px; left: 274px; width: 32px; height: 32px; } .location-text { text-align: center; color: #ffffff; width: 250px; height: 52px; font-size: 40px; left: 380px; top: 16px; } .container { flex-direction: column; height: 480px; width: 960px; } .circle-progress { center-x: 128px; center-y: 128px; radius: 128px; startAngle: 198; totalAngle: 320; strokeWidth: 24px; width: 256px; height: 256px; left: 160px; top: 58px; } .detail { width: 256px; height: 265px; left: 544px; top: 58px; flex-direction: column; } .text-wrapper { width: 256px; height: 35px; margin-top: 6px; } .gas-name { width: 128px; height: 35px; text-align: left; } .gas-value { width: 128px; height: 35px; text-align: right; } .btn { width: 180px; height: 50px; margin-top: 6px; margin-left: 38px; background-color: #1a1a1a; color: #1085CE; } .footer { top: 326px; width: 960px; height: 28px; } .header { width: 960px; height: 72px; } .back { width: 36px; height: 36px; margin-left: 39px; margin-top: 23px; } .title { width: 296px; height: 40px; margin-top: 20px; margin-left: 21px; color: #e6e6e6; } .swiper { height: 385px; width: 960px; } .images { width: 60px; height: 15px; margin-left: 450px; } .update-time { width: 480px; height: 28px; font-size: 20px; color: #A9A9A9; text-align: right; } .info-source { width: 450px; height: 28px; font-size: 20px; color: #A9A9A9; text-align: left; margin-left: 24px; } .circle-div { width: 12px; height: 12px; border-radius: 6px; } ### 3.9 实现页面逻辑修改`index.js`,绑定页面数据`data`。初始化时,根据不同的数值显示不同的字体和图片`onInit`。实现页面跳转`openDetail`,将当前页面索引传递给`detail`页面。滑动触发后`swiperChange`改变指示位置。
//引入router模块,用户页面跳转 import router from@system.router import app from @system.app export default { //定义参数 data: { //页面绑定数据 textColor1: "#00ff00", textColor2: "#00ff00", bgColor1: "#669966", bgColor2: "#669966", //默认是第一页 swiperPage: 0, percent1: 10, percent2: 90, iconUncheckedColor: #262626, iconcheckedColor: #ffffff, iconcheckedBR: 6px, src1: "common/cloud_green.png", src2: "common/cloud_green.png", airData: [ { location: "HangZhou", airQuality: "Good", detailData: 10 }, { location: "ShangHai", airQuality: "Unhealth", detailData: 90 } ] }, onInit () { //根据数值的不同,设置不同的字体、背景颜色和图片 if(this.airData[0].detailData > 100){ this.src1 = common/cloud_red.png; this.textColor1 = #ff0000; this.bgColor1 = #9d7462; } else if(50 < this.airData[0].detailData && this.airData[0].detailData <= 100){ this.src1 = common/cloud_yellow.png; this.textColor1 = #ecf19a; this.bgColor1 = #9d9d62; } if(this.airData[1].detailData > 100){ this.src2 = common/cloud_red.png; this.textColor2 = #ff0000; this.bgColor2 = #9d7462; } else if(50 < this.airData[1].detailData && this.airData[1].detailData <= 100){ this.src2 = common/cloud_yellow.png; this.textColor2 = #ecf19a; this.bgColor2 = #9d9d62; } if(this.selectedCityIndex){ this.swiperPage = this.selectedCityIndex; if(this.swiperPage == 0){ this.iconcheckedColor = #ffffff; this.iconUncheckedColor = #262626; }else{ this.iconcheckedColor = #262626; this.iconUncheckedColor = #ffffff; } } }, //跳转到详情页面 openDetail () { router.replace({ uri: pages/detail/detail, params: { selectedCityIndex:this.swiperPage} }); }, //退出应用 exitApp(){ console.log(start exit); app.terminate(); console.log(end exit); }, //swiper滑动回调事件,保存当前swiper的index值,每次滑动都会将index值保存在swiperPage变量中 swiperChange (e) { this.swiperPage = e.index; if(e.index == 0){ this.iconcheckedColor = #ffffff; this.iconUncheckedColor = #262626; }else{ this.iconcheckedColor = #262626; this.iconUncheckedColor = #ffffff; } } } 预览效果如下:

## 4. 开发详情页
详情页以图表的形式展示一周内空气质量指标值。本页面由两部分组成:标题栏和图表栏;在图表栏,考虑显示效果,我们使用多个`div`替代`chart`组件来实现图表功能。
### 4.1 添加标题栏
修改 `entry/src/main/js/default/pages/detail/detail.hml`
<div class="container"> <div class="header" onclick="backMain"> <image class="back" src="common/ic_back.png"></image> <text class="title"> 历史记录 </text> </div> <list class="chart-list"> </list> </div>### 4.2 添加图表栏
添加城市位置到`list-item-title`,图表到`list-item-chart`
<list class="chart-list"> <list-item class="list-item-title"> <text class="location">{ { location}}</text> </list-item> <list-item class="list-item-chart"> </list-item> </list> 4.3 添加图表 <div class="chart-wrapper" style="margin-left: 128px;"> <text class="gas-name">CO</text> <div class="chart"> <div class="chart-item" style="height: 78px;background-color: #00ff00;"></div> <div class="chart-item" style="height: 52px;background-color: #00ff00;"></div> <div class="chart-item" style="height: 155px;background-color: #ff0000;"></div> <div class="chart-item" style="height: 134px;background-color: #ff0000;"></div> <div class="chart-item" style="height: 98px;background-color: #FF7500;"></div> <div class="chart-item" style="height: 88px;background-color: #FF7500;"></div> <div class="chart-item" style="height: 144px;background-color: #ff0000;"></div> </div> <div class="white-line"></div> <div class="week"></div> </div> 4.4 添加样式 .location { text-align: center; color: #ffffff; width: 960px; height: 52px; font-size: 40px; } .container { height: 480px; width: 960px; flex-direction: column; } .header { width: 960px; height: 72px; } .back { width: 36px; height: 36px; margin-left: 39px; margin-top: 23px; } .title { width: 296px; height: 40px; margin-top: 20px; margin-left: 21px; color: #e6e6e6; } .chart-list { width: 960px; height: 408px; } .list-item-title { width: 960px; height: 52px; } .list-item-chart { width: 960px; height: 280px; } .chart-wrapper { width: 308px; height: 256px; flex-direction: column; } .gas-name { width: 308px; height: 35px; text-align: left; } .chart { width: 308px; height: 155px; margin-top: 10px; justify-content: flex-start; align-items: flex-end; } .chart-item { width: 24px; margin-left: 18px; border-radius: 3px; } .white-line { width: 308px; height: 2px; background-color: #ffffff; margin-top: 22px; } .week { width: 308px; height: 17px; margin-top: 6px; border-color: #ffffff; border-radius: 2px; margin-top: 6px; } .day { width: 26px; height: 17px; font-size: 10px; margin-left: 16px; text-align: center; } ### 4.5 实现页面跳转其中`onclick="backMain"`为返回主页事件,根据传递的页面索引,显示不同的位置数据,`detail.js`中的代码实现如下:
import router from @system.router export default { data: { location: }, onInit() { if (this.selectedCityIndex === 0) { this.location = 杭州; } else { this.location = 上海; } }, backMain() { router.replace({ uri: pages/index/index, params: { selectedCityIndex: this.selectedCityIndex } }); } }
5. 模拟器调试
菜单Tools->HVD Manager,可以打开云端的模拟器



可惜还没有可用于`smartVision`设备的模拟器,现阶段我们还只能烧录到设备中调试,总体上"富鸿蒙"的进度比较快,期待一波更新。
## 6. 编译打包
若开发手机端的`App`,则需要申请证书,对应用程序进行签名。这样才能发布到应用市场,才被允许安装到真机上运行。

`IPCamera`应用**暂时不支持签名模式**,所以需要将应用发布为未签名的应用安装包。
菜单`Build->Buildo APP(s)/Hap(s)->Build Release Hap(s)`,生成`Hap`文件。

输出文件为 `build/outputs/hap/release/smartVision/entry-release-smartVision-unsigned.hap`,改名为`MyUiApp.hap`便于安装。
## 7. 通过sdcard安装
### 7.1 复制安装包和工具
将IDE编译的未签名应用安装包和安装工具(`Z:\openharmony\out\my_hi3516dv300\dev_tools`)放在`sdcard`中,将`sdcard`插入开发板卡槽。

### 7.2 禁用签名校验
应用安装默认要校验签名,需要执行以下命令,关闭签名校验。
./sdcard/dev_tools/bin/bm set -s disable
7.3 安装应用
./sdcard/dev_tools/bin/bm install -p /sdcard/MyUiApp.hap
## 8. 通过NFS安装
每次插拔`sdcard`还是蛮不方便的,这里我们安装一个`NFS`服务器,让鸿蒙系统能直接访问`Win10`的目录,后续安装调试就会方便很多。
### 8.1 安装NFS服务器
我们先安装一个`haneWIN NFS服务器`, 双击文末网盘里的`nfs1169.exe`,一路下一步即可。

8.2 配置目录参数
编辑输出表文件,定义传输目录

右键管理员权限,重启所有服务,让配置生效。

### 8.4 设置防火墙
防火墙设置`111、1058、2049`这些端口的`TCP`和`UDP`,入站规则放行。

### 8.5 鸿蒙上挂载目录
主电脑的`ip`地址为`192.168.1.57`,`NFS`服务的别名为`nfs`,对应的目录为`D:\PycharmProjects\aiLearn\Harmony\tftp`
mkdir nfs mount 192.168.1.57:/nfs /nfs nfs 挂载到鸿蒙的刚新建的 `/nfs`目录下,我们可以复制安装包和安装工具
8.6 安装应用
cd nfs ./dev_tools/bin/bm set -s disable ./dev_tools/bin/bm install -p MyUiApp.hap前面做了这么多的铺垫,后续开发只要复制`hap`安装包,直接一条命令安装即可,非常方便。

# 运行程序
安装完成后,点击桌面上的`MyUiApp`就能看见界面效果了。

`Js UI框架`对开发者还是比较友好的,有小程序或快应用的开发经验,上手应该都比较顺滑。
不过`HarmonyOS Device`的支持库精简的非常严重,例如网络访问的`@system.request`和`@system.fetch`都不可用,这些功能在“富鸿蒙”的设备上开发就会比较方便。
# 资料下载

# 下一篇预告
> 本期主要介绍了一下JS框架下的界面开发,
> 下一篇我们将尝试熟悉更多的设备能力,
> 并打通从框架用户态到驱动内核态之间的联系,
> 敬请期待...

想了解更多内容,请访问:
和华为官方合作共建的鸿蒙技术社区
https://harmonyos.51cto.com/#zz
很赞哦!(8479)