用鸿蒙开发AI应用(六)UI篇
前言
上一篇,我们在鸿蒙上写了一个HDF驱动并操作了一下LED硬件,这一篇我们来尝试一下构建一个有简单界面的App,体验一下鸿蒙的前端开发流程。
环境准备
1. 安装DevEco Studio
解压相应的压缩包(文末附下载链接),这里以win10为例,双击
deveco-studio-2.0.12.201.exe
指定安装目录
设置可选快捷方式和环境变量
一路下一步即可。
同意用户协议后,就能正常启动了。
2. 更新sdk
在菜单
Setting->HarmonyOS SDK->SDK Platforms中,选择
Js和
Java,安装新版的
SDK。
同样在
SDK Tools中,选中新版的
Previewer
点击
Apply更新
新建项目
点击菜单
File->New Project...,选择智慧屏
Smart Vision,创建一个空模板应用。
填入项目名称
MyUiAPP,点击完成就能创建一个工程了。
遇到
gradle下载太慢或版本差异的,可以直接在以下网址用工具下载
https://services.gradle.org/distributions/
目录结构
我们先分析一下目录 20000 结构,做
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。
输入页面名称
detail,
详情页创建完成后应用工程目录如下图所示,每个
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,可以打开云端的模拟器
注册华为开发者账号,授权登录后
就能看到模拟器列表了,相比
beta版只有
Phone、
TV和
Wearable三种,增加了不少的设备。
可惜还没有可用于
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 配置目录参数
编辑输出表文件,定义传输目录
# exports example # C:\ftp -range 192.168.1.1 192.168.1.10 # c:\public -public -readonly # c:\tools -readonly 192.168.1.4 D:\PycharmProjects\aiLearn\Harmony\tftp -public -name:nfs
8.3 重启服务
右键管理员权限,重启所有服务,让配置生效。
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 install -p MyUiApp.hap
前面做了这么多的铺垫,后续开发只要复制
hap安装包,直接一条命令安装即可,非常方便。
运行程序
安装完成后,点击桌面上的
MyUiApp就能看见界面效果了。
Js UI框架对开发者还是比较友好的,有小程序或快应用的开发经验,上手应该都比较顺滑。
不过
HarmonyOS Device的支持库精简的非常严重,例如网络访问的
@system.request和
@system.fetch都不可用,这些功能在“富鸿蒙”的设备上开发就会比较方便。
资料下载
本期相关文件资料,可在公众号“深度觉醒”,后台回复:“ohos06”,获取下载链接。
下一篇
本期主要介绍了一下JS框架下的界面开发,
下一篇我们将尝试熟悉更多的设备能力,
并打通从框架用户态到驱动内核态之间的联系,
敬请期待...
往期推荐
- 用鸿蒙开发AI应用(二)系统篇
- AI应用开发基础傻瓜书系列附录-基本数学导数公式
- 【AI开发第一步】微软认知服务API应用
- OS开发UI篇—ios应用数据存储方式(归档)
- AI应用开发实战系列之三:手写识别应用入门
- iOS开发UI篇—ios应用数据存储方式(归档)
- iOS开发UI篇—在UITableview的应用中使用动态单元格来完成app应用程序管理界面的搭建
- iOS开发UI篇—实现一个简单的手势解锁应用(基本) - 文顶顶
- 探秘国内首个端云协同的多模态AI应用开发平台——华为云HiLens
- AI应用开发基础傻瓜书系列1-神经网络的基本工作原理
- iOS开发UI篇—ios应用数据存储方式(归档)
- iOS开发UI篇—在UITableview的应用中使用动态单元格来完成app应用程序管理界面的搭建
- iOS开发UI篇—在UITableview的应用中使用动态单元格来完成app应用程序管理界面的搭建
- iOS开发UI篇—使用xib自定义UItableviewcell实现一个简单的团购应用界面布局
- iOS开发UI篇—ios应用数据存储方式(归档)
- iOS开发UI篇—使用picker View控件完成一个简单的选餐应用
- 如何通过WebRTC开发实时互动AI视频应用
- [AI开发]将深度学习技术应用到实际项目
- iOS开发UI篇—使用picker View控件完成一个简单的选餐应用
- iOS开发UI篇—ios应用数据存储方式(偏好设置)