velaOS快应用快速入门

查看原文
最近摸索了一下小米 vela 快应用的编写，写个教程顺便做个归纳 蹭个 hyperOS 的热度。

本文章使用初学者 毕竟我也是。

概述

在介绍之前，需要了解一个 vela app 基本的架构。

项目结构

基本架构：

Copy
├── manifest.json
├── app.ux
├── pages
│ ├── index
| | ├── index.ux
| | ├── index.css
| | └── index.js
│ └── detail
| ├── detail.ux
| ├── detail.css
| └── detail.js
├── i18n
| ├── defaults.json
| ├── zh-CN.json
| └── en-US.json
└── common
├── style.css
├── utils.js
└── logo.png

其中，

manifest.json记录 app 的基本信息

例如以下属性依次是：

package: 包名，自己命名。

name: app 名称，会在手表上显示。

versionName: 版本名称，自己命名。

minPlatformVersion: 最低 api 版本。

icon: 图标文件路径

{% hideToggle 资源和文件访问规则 %}

资源和文件访问规则

应用资源路径分为绝对路径和相对路径，以 "/" 开头的路径表示绝对路径，比如 /common/a.png，不以 "/" 开头的路径是相对路径，比如 a.png 和 ../common/a.png 等。

应用资源文件分为代码文件和资源文件，代码文件是指 .js/.css/.ux 等包含代码的文件，其他文件则是资源文件，这类文件一般只当作数据来使用，比如图片、视频等。

在代码文件中，导入其他代码文件时，使用相对路径，比如：../common/component.ux；

在代码文件中，引用资源文件 (如：图片、视频等) 时，一般情况下使用相对路径，比如： ./abc.png；

当代码文件需要被导入时，如果导入文件与被导入文件在同一个目录，被导入文件引用资源文件时可以使用相对路径，但如果不在同一目录，必须使用绝对路径，因为被导入文件编译时会被复制到导入文件中，编译后目录会发生变化。比如 a.css 文件被 b.ux 导入，如果 a.css 与 b.ux 在同一个目录，a.css 引用资源文件时可以写相对路径：abc.png，如果不在同一个目录，必须写绝对路径：/common/abc.png，再比如当 a.ux 文件被 b.ux 文件导入时，如果 a.ux 与 b.ux 在同一个目录，a.ux 引用资源文件时可以写相对路径：a.png，如果不在同一个目录，a.ux 引用资源必须写绝对路径：/common/abc.png；

在 CSS 中，与前端开发一致，使用 url (PATH) 的方式访问资源文件，如：url (/common/abc.png)。

{% endhideToggle %}

features： 调用的接口声明，有些敏感接口只有声明才可使用。

designWidth： 设计稿的尺寸，具体看页面样式与布局。

router: app 中每个页面的定义。

Copy
{
"package": "com.genkaim.muyu",
"name": "电子木鱼",
"versionName": "1.1.0",
"versionCode": 4,
"minPlatformVersion": 1000,
"icon": "/common/logo.png",
"deviceTypeList": [
"watch"
],
"features": [
{ "name": "system.storage" },
{ "name": "system.file" },
{ "name": "system.prompt" },
{ "name": "system.vibrator" }
],
"config": {
"logLevel": "log",
"designWidth": 600
},
"router": {
"entry": "pages/index",
"pages": {
"pages/index": {
"component": "index"
},
"pages/settings": {
"component": "settings"
},
"pages/confirm": {
"component": "confirm"
},
"pages/about": {
"component": "about"
}
}
}
}

app.ux为 app 基本的 js 语法例如生命周期中，app 的 onCreate、onDestroy 在这调用

在下面的代码中，会在 app 打开时和退出时分别 log “onCreate” 和 "onDestroy"。

Copy

export default {
data: {
a: 1
},
onCreate() {
console.log("onCreate")
},
onDestroy() {
console.log("onDestroy")
}
}


pages下每个文件夹为当前页面的资源文件，包含.ux等等。

i18n语言文件。

common存放公用资源文件，比如 logo。

生命周期

这里借用一下官方的图。

简单来说，app 生命周期就是当打开 app 时，一次进行下面步骤，页面 onShow 前，先经历 onInit 和 onReady 两个阶段（这在 export default 中是具体的两个函数）。

当 app 退出时，为 onDestroy。

ux文件的基本认识

下面是一个简单的.ux文件的内容:

Copy


<text class="title">确定要清除所有功德吗</text>






import prompt from '@system.prompt'
export default {
goBack(event) {
router.back()
},
clearData(event) {
router.back()
}
}



.yes, .no {
width: 500px;
height: 150px;
color: white;
font-size: 40px;
font-weight: bold;
margin-top: 60px;
}
.page {
position: absolute;
background-color: black;
display: flex;
flex-direction: column;
display: flex;
justify-content: center; /* 水平居中 */
align-items: center; /* 垂直居中 */
}
.back {
position: absolute;
left: 0px;
top: 10px;
background-color: black;
color: white;
font-size: 80px;
font-weight: bold;
background-image: url('/common/icon-back.png');
}
.yes {
margin-top: 50px;
background-color: rgb(44, 44, 44);
}
.no {
margin-top: 30px;
}
.title {
color: white;
font-size: 50px;
font-weight: bold;
top: 5px;
}


template 标签： 类似于 HTML 的语言，vela 提供多种组件。例如这里出现两种不同的组件：

​ text 和 input，分别会被渲染为文本块和按钮。

​ 其中：

​ 1.1 class 为自己定义的类名 type 为组件类型，这里为按钮。

​ 1.2 button 标签的 value 值为按钮上显示的文字。

​ 1.3 onclick 为事件绑定，意思为满足” 点按 “这一条件时，会调用指定函数，函数在 export default 定义。

为什么要在 template 下再包一层 div 呢？

因为 template 下只允许存在一个根节点，必须先创建一个节点，内部再放并列节点。

script 脚本： 支持 ES5 / ES6 语法，这里的 export default 中，引用了一个模块来实现返回的功能。

**style 层叠样式表：** 用选择器选择并设置属性即可。

简单示例：电子木鱼基本功能实现

基本功能：

页面元素：电子木鱼（图片），显示功德数值功能，” 功德 + 1“动画。

实现点按木鱼计数功能。

页面资源：单击下载

页面布局

放置木鱼图片可以用 img 组件或者 css 实现，这里使用的前者。

​ 因为需要实现点按反馈的功能，所以需要绑定 onclick 事件。

Copy


计数功能可以通过 text 组件显示，这里注意，动态的数据（变量）可以通过大括号的方式显示，例如下面的代码可以实现显示功德计数（变量在下文）。

Copy
<text class="cnt" >功德 {{localCnt}}</text>

而动画的实现则是通过 text 组件 +if 条件渲染+ 绑定动画（下文）实现的。

if 条件渲染指 if 内表达式的值为 true 时，会渲染当前元素。

Copy
<text if="{{plus}}" class="showPlus">功德+1</text>

脚本

数据处理：

​ 在上文中，需要几个变量：localCnt (int) 用于计数功德，plus (bool) 用于指示消息的显示。

​ 定义变量需要这么写：

​ 其中，

属性 | 类型 | 描述
public | Object | 页面级组件的数据模型，影响传入数据的覆盖机制：public 内定义的属性允许被传入的数据覆盖，如果外部传入数据的某个属性未被声明，在 public 中不会新增这个属性
protected | Object | 页面级组件的数据模型，影响传入数据的覆盖机制：protected 内定义的属性，允许被应用内部页面请求传递的数据覆盖，不允许被应用外部请求传递的数据覆盖
private | Object | 页面级组件的数据模型，影响传入数据的覆盖机制：private 内定义的属性不允许被覆盖

Copy

export default {
public: {
localCnt: 0,
plus: false
}
}


在上文中，需要定义 rpPlusPlus 函数，实现功德 + 1

​ 其中：

​ 通过 this.Name 访问当前页面数据对象。

​ 并设置 plus 为 true，在 400ms 后设置为 false，即让其显示 400ms，因为它绑定了动画（下文），看 起来的效果就是文字向上移动然后消失。

Copy
rpPlusPlus(event) {
this.plus = true;
this.localCnt++;
setTimeout(() => {
this.plus = false;
}, 400);
}

css

绑定动画：

​ 上文已经定义了 class 为 showPlus，所以通过.showPlus选择元素：

Copy
.showPlus {
position: absolute;/*定位*/
right: 70px;
top: 210px;
color: white;/*字体颜色*/
font-weight: bold;/*字体粗细*/
animation-name: moveUp;/*字体绑定动画*/
animation-delay: 0s;/*动画延迟时间*/
animation-duration: 200ms;/*动画持续时间*/
animation-iteration-count: 1;/*动画持续次数*/
}
@keyframes moveUp {/*定义动画*/
0% {
transform: translateY(0);
}
99% {
transform: translateY(-40px);
}
100% {
visibility: hidden;
}
}

cnt、page 和 muyu 元素：

Copy
.muyu {
position: absolute;/*定位*/
height: 350px;
top: 250px;
background-color: transparent;/*背景颜色*/
width: 450px;/*大小*/
}
.cnt {
position: absolute;
bottom: 100px;/*定位*/
font-size: 70px;/*字体大小*/
color: white;/*字体颜色*/
}
.page {
background-color: black;/*背景颜色*/
display: flex;
flex-direction: column;
justify-content: center; /* 水平居中 */
align-items: center; /* 垂直居中 */
height: 100%; /* 确保容器占满整个视窗 */
}

完整代码：

Copy



<text if="{{plus}}" class="showPlus">功德+1</text>
<text class="cnt" >功德 {{localCnt}}</text>




export default {
public: {
localCnt: 0,
plus: false
},
rpPlusPlus(event) {
this.plus = true;
this.localCnt++;
setTimeout(() => {
this.plus = false;
}, 400);
}
}



.page {
background-color: black;/*背景颜色*/
display: flex;
flex-direction: column;
justify-content: center; /* 水平居中 */
align-items: center; /* 垂直居中 */
height: 100%; /* 确保容器占满整个视窗 */
}
.showPlus {
position: absolute;/*定位*/
right: 70px;
top: 210px;
color: white;/*字体颜色*/
font-weight: bold;/*字体粗细*/
animation-name: moveUp;/*字体绑定动画*/
animation-delay: 0s;/*动画延迟时间*/
animation-duration: 200ms;/*动画持续时间*/
animation-iteration-count: 1;/*动画持续次数*/
}
@keyframes moveUp {/*定义动画*/
0% {
transform: translateY(0);
}
99% {
transform: translateY(-40px);
}
100% {
visibility: hidden;
}
}
.muyu {
position: absolute;/*定位*/
height: 350px;
top: 250px;
background-color: transparent;/*背景颜色*/
width: 450px;/*大小*/
}
.cnt {
font-size: 70px;/*字体大小*/
bottom: 100px;/*定位*/
position: absolute;
color: white;
}


如有错误，欢迎指出。