基于鸿蒙的UI开发

Ability是HarmonyOS应用程序的重要组成部分,分为FA(Feature Ability)和PA(Particle Ability)两种类型,进行HarmonyOS应用开发,首先要了解Ability如何使用:

  • FA支持Page Ability:Page模板是FA唯一支持的模板,用于提供与用户交互的能力,HarmonyOS提供了Java UI和JS UI两种UI框架:Java UI提供了细粒度的UI编程接口,使应用开发更加灵活;JS UI提供了相对高层的UI描述,使应用开发更加简单。
  • PA支持Service Ability和Data Ability:Service模板用于提供后台运行任务的能力;Data模板用于对外部提供统一的数据访问抽象

本文以JS UI框架为例

JS UI 框架

JS UI框架支持纯JavaScript、JavaScript和Java混合语言开发

一、JS UI框架概述

JS UI框架是一种跨设备的高性能UI开发框架,支持声明式编程和跨设备多态UI,适用于手机(Phone)、平板(Tablet)、智慧屏(TV)和智能穿戴(Wearable)应用开发

JS UI框架采用类HTML和CSS声明式编程语言作为页面布局和页面样式的开发语言

  • .hml结尾的HML模板文件,这个文件用来描述当前页面的文件布局结构
  • .css结尾的CSS样式文件,这个文件用于描述页面样式
  • .js结尾的JS文件,这个文件用于处理页面和用户的交互

整体架构:

JS UI框架包括应用层(Application)、前端框架层(Framework)、引擎层(Engine)和平台适配层(Porting Layer)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rOOpdsJ8-1665454269979)(https://gitee.com/xu-yanbo/picture/raw/master/hm1.png)]

  • 应用层Application表示开发者使用JS UI框架开发的FA应用,这里的FA应用特指JS FA应用
  • 前端框架层Framework主要完成前端页面解析,页面路由机制和自定义组件等能力
  • 引擎层Engine主要提供动画解析,渲染命令构建与绘制、事件管理等能力
  • 适配层Porting Layer主要完成对平台层进行抽象,提供抽象接口,可以对接到系统平台。比如:事件对接、渲染管线对接和系统生命周期对接等

二、JS FA应用开发目录及文件使用规则

JS FA指基于JavaScript或JavaScript和Java混合开发的FA

2.1 应用开发目录

新建的JS工程目录如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EI8AAMHH-1665454269981)(https://gitee.com/xu-yanbo/picture/raw/master/hm2.png)]

  • Java MainAbility :该程序里的AceAbility类是JS FA在HarmonyOS上运行环境的基类,开发者应用的入口
public class MainAbility extends AceAbility {
    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
    }

    @Override
    public void onStop() {
        super.onStop();
    }
}

应用通过AceAbility类中**setInstanceName()**接口设置该Ability的实例资源,并通过AceAbility窗口进行显示以及全局应用生命周期管理。

setInstanceName(String name)的参数“name”指实例名称,实例名称与config.json文件中module.js.name的值对应。若开发者未修改实例名,而使用了缺省值default,则无需调用此接口。若开发者修改了实例名,则需在应用Ability实例的onStart()中调用此接口,并将参数“name”设置为修改后的实例名称。

使用方法为:在MainAbility的onStart()中的super.onStart()前调用此接口。以JSComponentName作为实例名称,代码示例如下

public class MainAbility extends AceAbility {
    @Override
    public void onStart(Intent intent) {
        setInstanceName("JSComponentName");  // config.json配置文件中module.js.name的标签值。
        super.onStart(intent);
    }
}
  • common:该文件夹下存放了一些公共资源文件,比如:媒体资源,自定义组件和JS文件

  • componets:存放一些自定义组件

  • i18n:存放了多语言的json文件,用于配置不同语言场景资源内容,比如:应用应用文本词条,图片路径等资源,注意 i18n 是开发保留文件夹,不可重命名

    • i18n > en-US.json:此文件定义了在英文模式下页面显示的变量内容
    {
      "strings": {
        "hello": "Hello",
        "world": "World"
      }
    }
    
    • 同理,zh-CN.json中定义了中文模式下的页面内容
  • pages:pages文件夹下存放多个页面,每个页面由hml、css和js文件组成==预览的时候需要点击任一个页面才可完成==我们需要的页面都可以写在这个文件夹下面

    • index.hml:此文件定义了index页面的布局、index页面中用到的组件,以及这些组件的层级关系,例如:index.hml文件中包含了一个text组件,内容为“Hello World”文本。
    <div class = "container">
      <text class = "title">
        {{ $t('strings.hello') }} {{title}}
      </text>
    </div>
    
    • **index.css:**此文件定义了index页面的样式。例如:index.css文件定义了“container”和“title”的样式
    .container {
      flex-direction: column;
      justify-content: center;
      align-items: center;
    }
    .title {
      font-size: 100px;
    }
    
    • **index.js:**此文件定义了index页面的业务逻辑,比如数据绑定、事件处理等。例如:变量“title”赋值为字符串“World”
    export default {
      data: {
        title: '',
      },
      onInit() {
        this.title = this.$t('strings.world');
      },
    }
    
  • app.js:文件用于全局js逻辑和应用声明周期管理

  • resore config.json:应用配置,用于描述应用的全局配置信息、在具体设备上的配置信息和HAP的配置信息(折叠起来了)

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MX7Vkg0U-1665454269981)(https://gitee.com/xu-yanbo/picture/raw/master/hm3.png)]

    主体文件分为app、device、module三个部分

    • app:表示应用的全局配置信息

    • deviceConfig: 表示应用在具体设备上的配置信息

    • module 表示当前页面的配置信息,只对当前页面生效(比如我们可以在deviceType里修改设备类型

      "phone",
      "tv",
      "wearable"
      
2.2 文件使用规则
2.2.1 文件使用规则

应用资源可通过绝对路径或相对路径的方式进行访问,本开发框架中绝对路径以 “/” 开头,相对路径以 “./” 或 “…/” ,具体访问规则如下:

  • 引用代码文件,需使用相对路径,比如:…/common/xxx.js
  • 引用资源文件,推荐使用绝对路径。比如:/common/xxx.png
  • 公共代码文件和资源文件推荐放在 common 下,通过以上两条规则进行访问
  • CSS 样式文件中通过 url() 函数创建 数据类型,如:url(/common/xxx.png)
  • 如果代码文件A和文件B位于同一目录,则代码文件B引用资源文件时可使用相对路径,也可使用绝对路径
  • 如果代码文件A和文件B位于不同目录,则代码文件B引用资源文件时必须使用绝对路径。因为Webpack打包时,代码文件B的目录会发生变化
  • 在js文件中通过数据绑定的方式指定资源文件路径时,必须使用绝对路径
  • 在使用DevEco Studio进行应用开发时,目录结构中的可选文件夹需要开发者根据实际情况自行创建
  • pages页面文件名不能使用组件名称,比如:text.hml、button.hml等
2.2.2 媒体文件格式
格式支持版本支持文件类型
BMPAPI Version 3.bmp
JPEGAPI Version 3.jpg
PNGAPI Version 3.png
2.2.3 app.js

标签中包含了实例名称、页面路由信息,每个应用可以在 app.js 自定义应用级生命周期的实现逻辑

  • onCreate:在应用生成时被调用的生命周期函数
  • onDestory:在应用销毁时被调用的生命周期函数

三、构建用户界面

3.1 组件

组件(Component)是构建页面的核心,每个组件通过对数据和方法的简单封装,实现独立的可视、可交互功能单元。组件之间相互独立,随取随用,也可以在需求相同的地方重复使用

根据组件的分类,可分为四大类

组件类型主要组件
基础组件text、image、progress、rating、span、marquee、image-animator、divider、search、menu、chart
容器组件div、list、list-item、stack、swiper、tabs、tab-bar、tab-content、list-item-group、refresh、dialog
媒体组件video
画布组件canvas

相关使用方法可查官方文档

组件具体步骤:(以chart组件为例)
  • 创建pages.chart文件夹,包含 .hml、.js、.css 三个文件,文件必须命名为 index,否则的话会出现预览错误。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WvUDcAXM-1665454269982)(https://gitee.com/xu-yanbo/picture/raw/master/hm4.png)]

  • 配置路由,在config.json这个文件中

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mTnjoITh-1665454269983)(https://gitee.com/xu-yanbo/picture/raw/master/hm5.png)]

  • 预览页面,注意打开当前需要预览的页面文件夹下 hml、css、js 任意一个文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rN78PTUK-1665454269984)(https://gitee.com/xu-yanbo/picture/raw/master/hm6.png)]

:组件里展示的图片需要开发者自行添加图片资源,放置到“js > default > common”目录下,common目录需自行创建

3.2 构建布局
  • JS UI框架中,手机和智慧屏的基准宽度为720px(px为逻辑像素,非物理像素),实际显示效果会根据实际屏幕宽度进行缩放
  • JS UI 框架页面样式,系统基于 flex 弹性布局进行设置,默认就是 flex 弹性布局,需要注意,弹性布局会自动的拉升和压缩内部元素模块宽度、高度
  • 其换算关系为:组件的width设为100px时,在宽度为720物理像素的屏幕上,实际显示为100物理像素;在宽度为1440物理像素的屏幕上,实际显示为200物理像素
  • 穿戴设备是一个圆形的表盘,需要针对圆形容器特征专门设计对应的样式
  • 在 TV 上有一个黑色的背景,需要针对性的调整对应的 CSS 样式,颜色需要重点处理反转
/*
    在phone 设备上1px = 3物理像素
    在tv 设备上1px = 2物理像素
    在穿戴设备上 1px = 2物理像素
*/

一个页面的基本元素包含标题区域、文本区域、图片区域等,每个基本元素内还可以包含多个子元素,开发者根据需求还可以添加按钮、开关、进度条等组件。在构建页面布局时,需要对每个基本元素思考以下几个问题:

  • 该元素的尺寸和排列位置
  • 是否有重叠的元素
  • 是否需要设置对齐、内间距或者边界
  • 是否包含子元素及其排列位置
  • 是否需要容器组件及其类型

在这里插入图片描述

3.2.1 添加标题行和文本区域

实现标题和文本区域最常用的是基础组件text。text组件用于展示文本,可以设置不同的属性和样式,文本内容需要写在标签内容区,完整属性和样式信息请参考官方文档,代码示例如下

<!-- xxx.hml -->
<div class="container">
  <text class="title-text">{{headTitle}}</text>
  <text class="paragraph-text">{{paragraphFirst}}</text>
  <text class="paragraph-text">{{paragraphSecond}}</text>
</div>

/* xxx.css */
.container {
  flex-direction: column;
  margin-top: 20px;
  margin-left: 30px;
}
.title-text {
  color: #1a1a1a;
  font-size: 50px;
  margin-top: 40px;
  margin-bottom: 20px;
}
.paragraph-text {
  color: #000000;
  font-size: 35px;
  line-height: 60px;
}

// xxx.js
export default {
  data: {
    headTitle: 'Capture the Beauty in This Moment',
    paragraphFirst: 'Capture the beauty of light during the transition and fusion of ice and water. At the instant of movement and stillness, softness and rigidity, force and beauty, condensing moving moments.',
    paragraphSecond: 'Reflecting the purity of nature, the innovative design upgrades your visual entertainment and ergonomic comfort. Effortlessly capture what you see and let it speak for what you feel.',
  },
}

3.2.2 添加图片区域

添加图片区域通常用image组件来实现,使用的方法和text组件类似。图片资源建议放在“js > default > common”目录下,详细代码如下:

<!-- xxx.hml -->
<image class="img" src="{{middleImage}}"></image>

/* xxx.css */
.img {  
  margin-top: 30px;
  margin-bottom: 30px;
  height: 385px;
}

// xxx.js
export default {
  data: {
    middleImage: '/common/xxx.png',
  },
}

3.2.3 添加容器

要将页面的基本元素组装在一起,需要使用容器组件。在页面布局中常用到三种容器组件,分别是div、list和tabs。在页面结构相对简单时,可以直接用div作为容器,因为div作为单纯的布局容器,可以支持多种子组件,使用起来更为方便

list组件

当页面结构较为复杂时,如果使用div循环渲染,容易出现卡顿,因此推荐使用list组件代替div组件实现长列表布局,从而实现更加流畅的列表滚动体验。需要注意的是,list仅支持list-item作为子组件,具体的使用示例如下:

<!-- xxx.hml -->
<list class="list">
  <list-item type="listItem" for="{{textList}}">
    <text class="desc-text">{{$item.value}}</text>
  </list-item>
</list>

/* xxx.css */
.desc-text {
  width: 683.3px;
  font-size: 35.4px;
}

// xxx.js
export default {
  data: {
    textList:  [{value: 'JS FA'}],
  },
}

示例中list中只包含一个list-item,list-item中只有一个text组件。在实际应用中可以在list中加入多个list-item,同时list-item下可以包含多个其他子组件

Tabs组件

当页面经常需要动态加载时,推荐使用tabs组件。tabs组件支持change事件,在页签切换后触发。tabs组件仅支持一个tab-bar和一个tab-content。具体的使用示例如下

<!-- xxx.hml -->
<tabs>
  <tab-bar>
    <text>Home</text>
    <text>Index</text>
    <text>Detail</text>
  </tab-bar>
  <tab-content>
    <image src="{{homeImage}}"></image>
    <image src="{{indexImage}}"></image>
    <image src="{{detailImage}}"></image>
  </tab-content>
</tabs>

// xxx.js
export default {
  data: {
    homeImage: '/common/home.png',
    indexImage: '/common/index.png',
    detailImage: '/common/detail.png',
  },
}

tab-content组件用来展示页签的内容区,高度默认充满tabs剩余空间。tab-content支持scrollable属性,详见官网具体属性

3.3 构建页面布局(.hml)

我们可以在pages文件夹下的index.hml文件中构建页面布局,这里注意不是 HTML,而是 HML,并且文件名也是 hml 结尾。因为鸿蒙的运行环境并不是浏览器,所以不支持原生的 html 标签,虽然有些标签名字一样,但那些只是鸿蒙为了便利开发者而定的,底层实现完全不一样,它们会转换成原生的鸿蒙控件,当然它也有特殊的 hml 标签,方便进行布局。语法上,除了一些 html 属性不支持外,其他都一样。这一点,跟 React Native 的原理是一样的。鸿蒙支持的标签可以查看 API 文档

在进行代码开发之前,首先要对页面布局进行分析,将页面分解为不同的部分,用容器组件来承载

  • 页面结构使用 HTML 相同的标签进行嵌套,最外层是 div 容器
  • 文本内容放在<text>标签中才能呈现,否则不会呈现文本内容
  • 可以直接调用鸿蒙JS封装好的组件,比如switch
3.4 构建页面样式(.css)

鸿蒙中的 CSS 与普通的 CSS 还是有一些区别的,有些属性用法不同,并且不同的标签对于 CSS 属性的支持程度也不一样,可以参考 HML 部分中提到的 API 文档。在鸿蒙 TV 项目中,容器默认是 flex 布局,并且 flex-direction 为 row,行方向,这个需要注意一下
比如先给 text 标签设置默认的文本颜色,text 标签的 css 代码如下:

text {
  color: #414873;
}

其他的样式可以参考官方示例

  • 页面 CSS 支持 id、class、tag 选择器,建议使用 class 选择器
  • 页面样式系统基于 flex 弹性布局进行设置,默认就是 flex 弹性布局,需要注意,弹性布局会自动的拉升和压缩内部元素模块宽度、高度
  • 鸿蒙封装的 JS 组件,有一个专门的样式说明,这个和传统的 CSS 写法有很大的差异,这个尤其需要注意
3.5 构建页面逻辑(.js)

可在pages里的index.js文件中构建页面逻辑,这里的语法和方式跟 Vue 一样

3.6 页面路由

很多应用由多个页面组成,比如用户可以从音乐列表页面点击歌曲,跳转到该歌曲的播放界面。开发者需要通过页面路由将这些页面串联起来,按需实现跳转。

页面路由router根据页面的uri来找到目标页面,从而实现跳转。以最基础的两个页面之间的跳转为例,具体实现步骤如下:

  • 在“Project”窗口,打开“entry > src > main > js > default”,右键点击“pages”文件夹,选择“New > JS Page”,创建一个详情页
  • 调用router.push()路由到详情页
  • 调用router.back()回到首页
3.6.1 页面布局路由(hml)

比如我们要在pages文件夹下构建index的detail这两个页面均包含一个text组件和button组件:text组件用来指明当前页面,button组件用来实现两个页面之间的相互跳转。hml文件代码示例如下

<!-- index.hml -->
<div class="container">
  <text class="title">This is the index page.</text>
  <button type="capsule" value="Go to the second page" class="button" onclick="launch"></button>
</div>

<!-- detail.hml -->
<div class="container">
  <text class="title">This is the detail page.</text>
  <button type="capsule" value="Go back" class="button" onclick="launch"></button>
</div>

3.6.2 页面样式路由(css)

构建index和detail页面的页面样式,text组件和button组件居中显示,两个组件之间间距为50px。css代码如下(两个页面样式代码一致)

/* index.css */
/* detail.css */
.container {
  flex-direction: column;
  justify-content: center;
  align-items: center;
}

.title {
  font-size: 50px;
  margin-bottom: 50px;
}

3.6.3 实现页面跳转

为了使button组件的launch方法生效,需要在页面的js文件中实现跳转逻辑。调用router.push()接口将uri指定的页面添加到路由栈中,即跳转到uri指定的页面。在调用router方法之前,需要导入router模块。代码示例如下:

// index.js
import router from '@system.router';
export default {
  launch() {
    router.push ({
      uri: 'pages/detail/detail',
    });
  },
}

// index.js
import router from '@system.router';
export default {
  launch() {
    router.push ({
      uri: 'pages/detail/detail',
    });
  },
}

在这里插入图片描述

3.7 自定义组件使用
3.7.1 自定义组件创建

JS UI框架支持自定义组件,用户可根据业务需求将已有的组件进行扩展,增加自定义的私有属性和事件,封装成新的组件,方便在工程中多次调用,提高页面布局代码的可读性:

  • 定义一个专门存放自定义组件的文件夹 components.tabbar 并设置3个基础文件 tabbar.hml、tabbar.js、tabbar.css,需要注意的是:3个文件的文件名必须保持一致,不然会存在找不到文件的情况。该自定义组件的目的是给页面底部配置一个 tabbar 选项卡体验
  • 设置底部选项卡对应的 json 数据源,用来保存 icon 图片、标题,以及点击选中后的 icon 图片
// common.datas.tabbarItem.js
export default [
    {
        img:'common/images/home.png',
        simg:'common/images/home_s.png',
        name:'首页'
    },
    {
        img:'common/images/hot.png',
        simg:'common/images/hot_s.png',
        name:'热点'
    },
    {
        img:'common/images/us.png',
        simg:'common/images/us_s.png',
        name:'社区'
    },
    {
        img:'common/images/me.png',
        simg:'common/images/me_s.png',
        name:'我'
    }
]

<div class="container">
    <toolbar class="tabbar">
        <toolbar-item for="{{tabbarItems}}" icon='{{$item.img}}' value='{{$item.name}}' onclick="jump($idx)" ></toolbar-item>
    </toolbar>
</div>

  • 导入 tabbarItem 数据,并设置对应 data ,同时设置点击事件 动态设置点击对应 icon 选中
import tabbarItems from '../../common/datas/tabbarItem.js';
export default {
    data:{
        tabbarItems
    },
    jump(index){
        this.tabbarItems.forEach((item,index) => {
            item.img = tabbarItems[index].img;
        });
        this.tabbarItems[index].img = this.tabbarItems[index].simg;
    }
}

  • 设置 CSS 样式 将 tabbar 选项卡置底「tabbar.css」
.tabbar {
    position: fixed;
    left: 0;
    bottom: 0;
}

3.7.2 自定义组件调用

自定义组件是用户根据业务需求,将已有的组件组合,封装成的新组件,可以在工程中多次调用,提高代码的可读性

自定义组件通过element引入到宿主页面,使用方法:

<element name='comp' src='../../components/tabbar/tabbar.hml'></element>
<div class="container">
    <text class="title">
        首页
    </text>
    <comp></comp>
</div>

  • name 属性指自定义组件名称(非必填),组件名称对大小写不敏感,默认使用小写,src 属性指自定义组件hml 文件路径(必填),若没有设置 name 属性,则默认使用 hml 文件名作为组件名
  • 事件绑定:自定义组件中绑定子组件事件使用 (on|@)child1 语法,子组件中通过 this.$emit(‘child1’, { params: ‘传递参数’ }) 触发事件并进行传值,父组件执行 bindParentVmMethod 方法并接收子组件传递的参数

以上部分摘自鸿蒙开发官方文档

  • 25
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值