首先要知道鸿蒙的APP是怎么构成的?
HarmonyOS的应用软件包以APP Pack(Application Package)形式发布,它是由一个或多个HAP(HarmonyOS Ability Package)以及描述每个HAP属性的pack.info组成。HAP是Ability的部署包,HarmonyOS应用代码围绕Ability组件展开。
一个HAP是由代码、资源、第三方库及应用配置文件组成的模块包,可分为entry和feature两种模块类型,如下图所示。
首先来看一张图,很普通,我相信每一个人一开始新建使用Java语言的鸿蒙项目都是这样的目录结构,前面我们说到HUAWEI DevEco Studio是基于IntelliJ IDEA Community开源版本所打造出来的。那么它就会跟IDEA 和Android Studio 会有点像,那么同样的项目结构也比较像,同样的我在AS中也新建了一个项目,下面是对比图。
打开我之前创建的鸿蒙HelloWorld项目
左边的是DS,右边是AS。基本上差不太多,但是还有点点差点,下面说一下
DS的主模块是entry,AS的主模块是app,对于功能中两个build.gradle的功能是类似的,entry/app下面的build.gradle对当前模块进行控制,而工程根目录下的工程级build.gradle用于工程的全局设置。其中我们对AS中AndroidManifest.xml是很熟悉的,但是对于DS中用了另一种文件,那就是config.json,这是一个应用清单文件,用于描述应用的全局配置信息、在具体设备上的配置信息和HAP的配置信息。它的地位就相当于AS中的AndroidManifest.xml。
打开这个文件来看看里面有什么内容:截图截不全,所以我就直接把里面的代码贴出来了:
{
"app": {
"bundleName": "com.llw.helloworld",
"vendor": "llw",
"version": {
"code": 1,
"name": "1.0"
},
"apiVersion": {
"compatible": 3,
"target": 3
}
},
"deviceConfig": {
},
"module": {
"package": "com.llw.helloworld",
"name": ".HelloWorld",
"reqCapabilities": [
"video_support"
],
"deviceType": [
"wearable"
],
"distro": {
"deliveryWithInstall": true,
"moduleName": "entry",
"moduleType": "entry"
},
"abilities": [
{
"skills": [
{
"entities": [
"entity.system.home"
],
"actions": [
"action.system.home"
]
}
],
"orientation": "landscape",
"formEnabled": false,
"name": "com.llw.helloworld.MainAbility",
"icon": "$media:icon",
"description": "$string:mainability_description",
"label": "HelloWorld",
"type": "page",
"launchType": "standard"
}
]
}
}
它的数据结构是JSON格式的,这和我们平时通过接口请求返回的数据比较的类,然后收缩一下,就比较清晰了。
可以看到主要的分为三个部分:app、deviceConfig、module,你可以理解为三个对象。
① app 表示应用的全局配置信息。同一个应用的不同HAP包的“app”配置必须保持一致。(简单说就是你所有模块里面的config.json中的app对象都要一模一样)
② deviceConfig 表示应用在具体设备上的配置信息。(目前的设备有TV、智能手表、运动手表)
③ module 表示HAP包的配置信息。该标签下的配置只对当前HAP包生效。(简单说就是你当前模块的配置信息)
是不是觉得清晰了那么一丢丢,好继续往里面看,下面我们展开这个app对象,看一下里面可以配置哪些属性。
下面也是一个个说明:
app对象就说完了,下面说deviceConfig。
从图片上来看,里面是个空的,空的你说个锤子啊!冷静、冷静,现在是空的不代表以后就是空的,其实它是由参数的,这个需要看官网上的文档了。
介绍是这样的deviceConfig包含在具体设备上的应用配置信息,可以包含default、car、tv、wearable、liteWearable、smartVision等属性。default标签内的配置是适用于所有设备通用,其他设备类型如果有特殊的需求,则需要在该设备类型的标签下进行配置。虽然它目前支持的模拟设备只有tv、wearable、liteWearable,但是具体的设备还有一些其他的。下面也来介绍一下,先来看一下里面的内容有哪些
"deviceConfig": {
"default": {
"process": "com.llw.helloworld.hiworld",
"directLaunch": false,
"supportBackup": false,
"network": {
"usesCleartext": true,
"securityConfig": {
"domainSettings": {
"cleartextPermitted": true,
"domains": [
{
"subDomains": true,
"name": "example.ohos.com"
}
]
}
}
}
}
}
然后收缩一下,如下:
之前提到deviceConfig中有default、car、tv、wearable、liteWearable、smartVision。这六个对象,但是图片上只有一个,你仿佛你在骗我!我翻译一下
default代表的就是所有设备,而其他的五个,和这个default对象里面的内容时一模一样的,只是名字不一样,比如你把这个default改成car,那就是对应汽车的设备配置,改成tv就是对应电视的设备配置。比如:
所以自由度很高,可以根据自己的实际需求,你想怎么玩就怎么玩。下面讲一下里面具体的对象属性:
下面来看module对象的内部结构。
"module": {
"package": "com.llw.helloworld",
"name": ".HelloWorld",
"reqCapabilities": [
"video_support"
],
"deviceType": [
"wearable"
],
"distro": {
"deliveryWithInstall": true,
"moduleName": "entry",
"moduleType": "entry"
},
"abilities": [
{
"skills": [
{
"entities": [
"entity.system.home"
],
"actions": [
"action.system.home"
]
}
],
"orientation": "landscape",
"formEnabled": false,
"name": "com.llw.helloworld.MainAbility",
"icon": "$media:icon",
"description": "$string:mainability_description",
"label": "HelloWorld",
"type": "page",
"launchType": "standard"
}
]
}
妈耶,这个module里面的东西比app和deviceConfig两个对象里面加起来还要多。(555555~ 我不想学鸿蒙了,我放弃了,现在下车还来得及吗?别想了,车门我已经焊死了,雅蠛蝶!!!)
收缩一下,这么一看好像内容也不是很多,对吧!画外音:对个屁!
distro 表示HAP发布的具体描述。这是一个对象,该标签仅适用于智慧屏、智能穿戴、车机。不可省缺。它里面还有
abilities 表示当前模块内的所有Ability。采用对象数组格式,其中每个元素表示一个Ability对象。可缺省,缺省值为空。下面来看看里面有哪些属性
可以看到这个里面有一个对象数组skills,其余就是就是一些配置的属性了。skills 里面还有两个字符串数组entities和actiions,下面先从这个skills 来分析。
skills 打过游戏的都知道,这是技能的意思。不过在这里是表示Ability能够接收的Intent的特征。 这个Intent的用法其实和Android里面是比较像的,比如我们跳转到手机的短信页面就可以通过Intent,还有打电话也可以通过Intent,类似这样的特征。可缺省,缺省值为空。从上图我们知道里面还有两个字符串对象。
"uris": [
{
"scheme": "http",
"host": "www.hefeng.com",
"port": "8080",
"path": "query/goodweather/today",
"type": "text"
}
]
里面的对象属性值就是字面意思,都是可以省缺的,省缺值为空。你可以根据不同的Uri配置不同的对象,都放到这个对象数组里面就可以了。下面看abilities中的其他属性。
至此,这个config.json的基本信息我们就都知道了,不得不说看起来比AS 的AndroidManifest.xml要复杂一些,看得我是头皮发麻。可能用的熟悉了就好了。你说呢?
应用的主模块,类似与AS的app模块,一个APP中,对于同一设备类型必须有且只有一个entry类型的HAP,可独立安装运行。它里面的内容如下:
从图片上来看是和AS的app里面的内容差不多,那就来看看不同的地方在哪里。
首先当然是资源文件夹resources,其实和AS的res是差不多的,只不过DS这里用了全拼。(AS指Android Studio,DS 指DevEco Studio),展开resources如下:
可以看到有两个文件夹,
限定词目录的命名要求
限定词类型 |
含义与取值说明 |
---|---|
语言 |
表示设备使用的语言类型,由2个小写字母组成。例如:zh表示中文,en表示英语。 详细取值范围,参见ISO 639-1(ISO制定的语言编码标准)。 |
文字 |
表示设备使用的文字类型,由1个大写字母(首字母)和3个小写字母组成。例如:Hans表示简体中文,Hant表示繁体中文。 详细取值范围,参见ISO 15924(ISO制定的文字编码标准)。 |
国家或地区 |
表示用户所在的国家或地区,由2~3个大写字母或者3个数字组成。例如:CN表示中国,GB表示英国。 详细取值范围,参见ISO 3166-1(ISO制定的国家和地区编码标准)。 |
横竖屏 |
表示设备的屏幕方向,取值如下:
|
设备类型 |
表示设备的类型,取值如下:
|
屏幕密度 |
表示设备的屏幕密度(单位为dpi),取值如下:
|
资源组目录
base目录与限定词目录下面可以创建资源组目录(包括element、media、animation、layout、graphic、profile),用于存放特定类型的资源文件
资源组目录 |
目录说明 |
资源文件 |
---|---|---|
element |
表示元素资源,以下每一类数据都采用相应的JSON文件来表征。
|
element目录中的文件名称建议与下面的文件名保持一致。每个文件中只能包含同一类型的数据。
|
media |
表示媒体资源,包括图片、音频、视频等非文本格式的文件。 |
文件名可自定义,例如:icon.png。 |
animation |
表示动画资源,采用XML文件格式。 |
文件名可自定义,例如:zoom_in.xml。 |
layout |
表示布局资源,采用XML文件格式。 |
文件名可自定义,例如:home_layout.xml。 |
graphic |
表示可绘制资源,采用XML文件格式。 |
文件名可自定义,例如:notifications_dark.xml。 |
profile |
表示其他类型文件,以原始文件形式保存。 |
文件名可自定义。 |
rawfile 这个文件是干嘛的呢?支持创建多层子目录,目录名称可以自定义,文件夹内可以自由放置各类资源文件。rawfile目录的文件不会根据设备状态去匹配不同的资源。这个里面就是一个综合体,相当于包含了AS的drawable和mipmap以及其他的一些文件,只不过它没有做尺寸上的细分,需要开发者自行细分做不同设备的适配,不过既然是华为里面的东西,我相信到时候会有一个系统的关于这方面的讲解的。
系统资源文件
系统资源名称 |
含义 |
类型 |
---|---|---|
ic_app |
表示HarmonyOS应用的默认图标。 |
媒体 |
request_location_reminder_title |
表示“请求使用设备定位功能”的提示标题。 |
字符串 |
request_location_reminder_content |
表示“请求使用设备定位功能”的提示内容,即:请在下拉快捷栏打开"位置信息"开关。 |
字符串 |
右键点击“base”文件夹,选择“New > Directory”,命名为“layout”。
点击OK,然后右键点击“layout”文件夹,选择“New >Layout Resources File”,命名为“main_layout.xml”。
单击Finish,完成布局创建。
布局如下:
"1.0" encoding="utf-8"?>
xmlns:ohos="http://schemas.huawei.com/res/ohos"
ohos:width="match_parent"
ohos:height="match_parent"
ohos:orientation="vertical">
这都是什么鬼东西,我咋看不懂呢?你可能会问ohos是个啥?我就这么告诉你,你把它当成AS中布局文件中的android就可以了。ohos我估计是鸿蒙系统对于布局的一个属性标识。除了这个,还有DirectionalLayout是什么布局呢?这位朋友你的问题咋这么多呢?嗯?
DirectionalLayout是方向布局,闻所未闻?那么还有没有其他的一些布局呢?当然有,如下图所示:
我们创建xml的时候默认的是DirectionalLayout,这个布局表用于单一方向排列,你可以理解为AS的线性布局,还有五种布局分别是什么呢?AdaptiveBoxLayout(自适应盒式布局)、DependentLayout(相关布局,你可以理解为是AS的相对布局)、PositionLayout(位置布局,相当于绝对布局)、StackLayout(堆叠布局)、TableLayout(表格布局)。
好了,上面看你装逼是装完了,会用吗?这个问题问得好,不是全会!哎,先别动手,放下手中的刀,作为刚学的哪能都会啊,而且我现在只是简单的讲解一下,到时候具体的使用肯定也是要大费周章才能讲完,这个解释怎么样?讲真的,你信我,只要我都会使用之后,我绝对会另写一篇文章来单独讲这个布局,好不好?回到刚才的布局页面。修改一下后如下所示:
"1.0" encoding="utf-8"?>
xmlns:ohos="http://schemas.huawei.com/res/ohos"
ohos:width="match_parent"
ohos:height="match_parent"
ohos:background_element="#000000">
ohos:id="$+id:text"
ohos:width="match_content"
ohos:height="match_content"
ohos:center_in_parent="true"
ohos:text="Hello World"
ohos:text_color="white"
ohos:text_size="32fp"/>
可以看到,我把DirectionalLayout改成DependentLayout之后再里面放了一个Text和一个Button,
首先来看我们不熟悉的属性值:
ohos:width="match_content"
ohos:height="match_content"
宽高为match_content,表示组件大小与它的内容占据的大小范围相适应,简单说,就是自适应大小,类似于AS的wrap_content,这么一说就好理解了。
ohos:center_in_parent="true"
这个从字面意思看是在父布局内居中的意思。
ohos:text_size="32fp"
文字大小使用fp,尺寸大小使用vp
ohos:background_element="$graphic:button_element"
背景,里面的值通过引用“button_element”来显示的,你可能会奇怪,为什么要加一个element,单独用background不行吗?你别忘了,开发语言是支持JS的,所以你知道为啥有一个element了吗?
可以看到它引用graphic下面的button_element,没有就来创建一个,需要在“base”目录下创建“graphic”文件夹,在“graphic”文件夹中新建一个“button_element.xml”文件。
代码如下:
"1.0" encoding="utf-8"?>
xmlns:ohos="http://schemas.huawei.com/res/ohos"
ohos:shape="oval">
ohos:color="#007DFF"/>
那么布局就已经写好了。下面打开MainAbilitySlice.java
里面的代码如下:
package com.llw.helloworld.slice;
import ohos.aafwk.ability.AbilitySlice;
import ohos.aafwk.content.Intent;
import ohos.agp.components.DirectionalLayout;
import ohos.agp.components.DirectionalLayout.LayoutConfig;
import ohos.agp.components.Text;
import ohos.agp.colors.RgbColor;
import ohos.agp.components.element.ShapeElement;
import ohos.agp.utils.Color;
import ohos.agp.utils.TextAlignment;
public class MainAbilitySlice extends AbilitySlice {
private DirectionalLayout myLayout = new DirectionalLayout(this);
@Override
public void onStart(Intent intent) {
super.onStart(intent);
LayoutConfig config = new LayoutConfig(LayoutConfig.MATCH_PARENT, LayoutConfig.MATCH_PARENT);
myLayout.setLayoutConfig(config);
ShapeElement element = new ShapeElement();
element.setRgbColor(new RgbColor(255, 255, 255));
myLayout.setBackground(element);
Text text = new Text(this);
text.setLayoutConfig(config);
text.setText("Hello World");
text.setTextColor(new Color(0xFF000000));
text.setTextSize(50);
text.setTextAlignment(TextAlignment.CENTER);
myLayout.addComponent(text);
super.setUIContent(myLayout);
}
@Override
public void onActive() {
super.onActive();
}
@Override
public void onForeground(Intent intent) {
super.onForeground(intent);
}
}
只要看里面的onStart方法里面的代码,因为我们一开始运行项目就会有一个Hello World!但是之前里面没有布局啊,所以默认的项目是在代码中创建布局的,
下面来看看这个代码的意思,注释已经写在里面了
下面清理掉onStart中的方法,增加
super.setUIContent(ResourceTable.Layout_main_layout); // 加载XML布局
添加我们刚才创建的布局。然后运行到远程模拟器上面,这个确实也比较烦就是需要登录才能行。运行出来的效果如下:
APP只有一个页面可不行啊,下面来新建一个页面。
右键点击“com.example.myapplication”文件夹,选择“New > Ability > Empty Feature Ability(Java)”。
改一下Ability的名字
点击finish,然后你打开这个SecondAbilitySlice.java之后你会发现里面的代码也帮你生成好了,也是Hello World。首先把它的待删掉。
删除之后,如下所示:
package com.llw.helloworld.slice;
import ohos.aafwk.ability.AbilitySlice;
import ohos.aafwk.content.Intent;
public class SecondAbilitySlice extends AbilitySlice {
@Override
protected void onStart(Intent intent) {
super.onStart(intent);
}
@Override
protected void onActive() {
super.onActive();
}
@Override
protected void onForeground(Intent intent) {
super.onForeground(intent);
}
}
添加代码后如下所示:每一行添加的代码都有注释:
package com.llw.helloworld.slice;
import ohos.aafwk.ability.AbilitySlice;
import ohos.aafwk.content.Intent;
import ohos.agp.colors.RgbColor;
import ohos.agp.components.DependentLayout;
import ohos.agp.components.Text;
import ohos.agp.components.element.ShapeElement;
import ohos.agp.utils.Color;
import java.math.MathContext;
import static ohos.agp.components.ComponentContainer.LayoutConfig.MATCH_CONTENT;
import static ohos.agp.components.ComponentContainer.LayoutConfig.MATCH_PARENT;
public class SecondAbilitySlice extends AbilitySlice {
@Override
protected void onStart(Intent intent) {
super.onStart(intent);
/* 使用代码设置布局 */
//声明一个相关布局
DependentLayout myLayout = new DependentLayout(this);
//设置布局的大小 宽高占满屏幕
myLayout.setWidth(MATCH_PARENT);
myLayout.setHeight(MATCH_PARENT);
//创建一个形状元素对象
ShapeElement element = new ShapeElement();
//设置颜色
element.setRgbColor(new RgbColor(0,0,0));
//设置布局的背景
myLayout.setBackground(element);
//创建一个文本组件
Text text = new Text(this);
//设置文本组件显示的内容
text.setText("How are you?");
//设置宽度占满父布局
text.setWidth(MATCH_PARENT);
//设置文字大小
text.setTextSize(55);
//设置文字颜色
text.setTextColor(Color.WHITE);
//设置文本的布局 宽高自适应
DependentLayout.LayoutConfig textConfig = new DependentLayout.LayoutConfig(MATCH_CONTENT,MATCH_CONTENT);
//添加显示规则 显示在布局的中央
textConfig.addRule(DependentLayout.LayoutConfig.CENTER_IN_PARENT);
//给Text组件设置布局配置
text.setLayoutConfig(textConfig);
//然后把Text组件添加到布局中
myLayout.addComponent(text);
//最后加载布局
super.setUIContent(myLayout);
}
@Override
protected void onActive() {
super.onActive();
}
@Override
protected void onForeground(Intent intent) {
super.onForeground(intent);
}
}
打开第一个页面的“MainAbilitySlice.java”文件,重写onStart()方法添加按钮的响应逻辑,实现点击按钮跳转到下一页。
package com.llw.helloworld.slice;
import com.llw.helloworld.ResourceTable;
import ohos.aafwk.ability.AbilitySlice;
import ohos.aafwk.content.Intent;
import ohos.aafwk.content.Operation;
import ohos.agp.components.Button;
import ohos.agp.components.Component;
import ohos.agp.components.DirectionalLayout;
import ohos.agp.components.DirectionalLayout.LayoutConfig;
import ohos.agp.components.Text;
import ohos.agp.colors.RgbColor;
import ohos.agp.components.element.ShapeElement;
import ohos.agp.utils.Color;
import ohos.agp.utils.TextAlignment;
/** @author llw
* @noinspection All */
public class MainAbilitySlice extends AbilitySlice {
@Override
public void onStart(Intent intent) {
super.onStart(intent);
// 加载XML布局
super.setUIContent(ResourceTable.Layout_main_layout);
//这一步就类似与findViewById 初始化组件
Button button = (Button) findComponentById(ResourceTable.Id_button);
if(button != null){
//设置点击监听
button.setClickedListener(new Component.ClickedListener() {
@Override
public void onClick(Component component) {
// 还是通过Intent来跳转
Intent secondIntent = new Intent();
// 指定待启动FA的bundleName和abilityName
Operation operation = new Intent.OperationBuilder()
// 设备id
.withDeviceId("")
// 应用的包名 怎么跳转个页面搞得这么麻烦呢?
.withBundleName("com.llw.helloworld")
// 跳转目标的路径名 通常是包名+类名 或者 . + 类名
.withAbilityName("com.llw.helloworld.SecondAbility")
// .withAbilityName(".SecondAbility")
.build();
// 设置操作方式
secondIntent.setOperation(operation);
// 通过AbilitySlice的startAbility接口实现启动另一个页面
startAbility(secondIntent);
}
});
}
}
@Override
public void onActive() {
super.onActive();
}
@Override
public void onForeground(Intent intent) {
super.onForeground(intent);
}
}
运行效果如下:用手机拍的,莫要见怪呀。
OK,现在就比只写一个HelloWorld来说要进步一些了