体验一下使用 ArkUI 进行 HarmonyOS 开发并与 Compose 简单对比

前言

最近几年各个技术公众号和技术群都在唱衰原生安卓开发,疯狂贩卖焦虑。

搞得我也焦虑的不行,在谷歌的 Compose 推出后就赶紧去学,但是又觉得好像 Compose 的热度也不算太高,又去学 Flutter 。

转头两个都还没学明白呢,大佬们又在说鸿蒙下次更新不兼容安卓了,再不学鸿蒙开发就等着失业吧。

啊?这?这能忍?这必须学啊!

于是抽出时间来简单了解了一下使用 ArkUI 的鸿蒙应用开发。

编写第一个鸿蒙应用

搭建环境

鸿蒙应用开发有它自己的一个 IDE ,叫做 DevEco Studio

可以在 官网 下载到,按照你自己的系统选择对应的安装包后直接下载安装即可。

安装过程不用我多介绍吧,毕竟都是老程序员了,这点基础知识还是得有吧。

然后,等你安装完打开你就会发现,这个 IDE 怎么会这么眼熟呢?

眼熟就对了啊,因为:

1.png

它也是基于 Intellij PLatform 开发的。

安装好后,首次打开 DevEco Studio 会要求你进行环境下载和配置,因为鸿蒙开发使用的 ArkUI 框架是基于 ArkTS 语言的,而 ArkTS 语言是 TS 语言的超集,所以我们需要配置对应的环境。

按照 IDE 提示首先下载并安装 Node.js 和 Ohpm ,因为上面说过 ArkTS 是基于 TS 的超集,所以需要 Node.js 的支持;而 Ohpm 是华为自己的依赖管理工具。

安装好 Node.js 和 Ohpm 后需要安装鸿蒙的 SDK ,依旧是按照 IDE 提示直接下载安装即可。

安装完成后即可正常使用 IDE 了。

Hello, World!

打开刚安装配置好的 DevEco Studio,在欢迎页面选择 Create Project - Application - Empty Ability 然后点击 Next

2.jpg

在这个页面会要求填写一些应用的基本信息,和安卓开发类似:

  • Project name 是项目名称
  • Bundle name 是包名,也是应用的唯一 ID
  • Save location 就是项目保存位置
  • Compile SDK 要使用的编译 SDK 版本
  • Model 应用程序开发模型,Stage 是新版的模型,FA 是老版本使用的模型,这里选择 Stage 即可,他俩的区别看 这里 (吐槽一下,华为的文档链接居然是错的,还得我自己去搜索正确的文档)

修改完后点击 Finsh 即可创建我们的第一个 Hello World 项目。

打开这个项目的主页UI文件:

./entry/src/main/ets/pages/index.ets

然后在右边侧栏点击 Previewer 按钮即可打开 UI 预览:

3.jpg

如果想要运行这个程序的话,我们需要创建一个模拟器:

4.jpg

在顶栏菜单中的运行按钮旁边点击 No devies 下拉框,选择 Device Manager,然后按照提示创建一个模拟器并启动。

(吐槽一下华为魔改的这个 IDE 明明我已经创建过模拟器了,却不能像 Android Studio 一样直接在运行菜单中选择这个模拟器并直接一键启动运行,非得手动进入 Device Manager 里面启动了模拟器才能运行程序)

启动好模拟器后,运行菜单应该已经默认选中了这个模拟器,点击运行图标运行即可:

5.jpg

这样我们就能看到它的运行效果了:

6.jpg

项目结构

接下来,我们来了解一下 ArkUI 的项目的结构,不然我们连需要改哪儿个文件都不知道了,哈哈。

项目整体结构如图:

7.jpg

其中,

AppScope

这个目录存放应用全局所需要的资源文件。

在其下有一个 app.json5 配置文件,该文件是应用的全局配置文件,一个典型的全局配置文件包含以下内容:

{
  "app": {
    "bundleName": "com.example.myapplication",
    "vendor": "example",
    "versionCode": 1000000,
    "versionName": "1.0.0",
    "icon": "$media:app_icon",
    "label": "$string:app_name"
  }
}

其中的配置分别表示:

  • bundleName 包名。
  • vendor 应用程序供应商。
  • versionCode 数字版本号。
  • versionName 版本名称。
  • icon 应用图标。
  • label 应用名。

另外,在该目录下还有一个 resources 文件夹,用于存放公共资源文件,它包括两个文件夹:

  • ./base/element 主要存放公共的字符串、布局文件等资源。
  • ./base/media 存放全局公共的多媒体资源文件。
entry

这个是 entry 模块的目录,在这个项目中 entry 即为主模块。

在这个模块中可以存放代码、资源等。

其中,./entry/src 包含两个文件夹:

  • main/ 主目录
  • ohosTest/ 单测目录

在 ./entry/src/main 文件夹中包括以下目录和文件:

  • ets/ 用于存放ets代码
  • resources/ 存放模块内的多媒体及布局文件等
  • module.json5 模块的配置文件

而 ./entry/src/main/ets 下又有以下目录:

  • entryability/ 存放 ability 文件,用于当前ability应用逻辑和生命周期管理。
  • pages/ 存放UI界面相关代码文件,初始会生成一个Index页面。

而 ./entry/src/main/resources 目录用于存放模块公共的多媒体、字符串及布局文件等资源,分别存放在element、media文件夹中。

在 ./entry 目录下还有几个文件:

  • build-profile.json5 模块级配置信息,包括编译构建配置项。
  • hvigorfile.ts 模块级构建脚本。
  • oh-package.json5 模块级依赖配置信息文件。
oh_modules

该目录是项目的依赖包,存放工程依赖的源文件。

一般不需要我们去手动更改。

build-profile.json5

该文件是项目级配置信息,包括签名、产品配置等。

hvigorfile.ts

该文件是工程级编译构建任务脚本,hvigor是基于任务管理机制实现的一款全新的自动化构建工具,主要提供任务注册编排,工程模型管理、配置管理等核心能力。

oh-package.json5

该文件是项目级依赖配置文件,用于记录引入包的配置信息。

简单的代码理解

上面简单梳理了一下项目的目录结构,相信大家心里也大概有个底了,其实和安卓项目也大差不差,都是这么一回事儿。

只是依赖管理系统变了(安卓是 gradle ),配置文件变了(安卓是 groovy 或 kts 鸿蒙是 json)。

其他结构对于安卓开发者来说基本属于一看就懂。

接下来我们来看下代码结构。

首先,我们得先找到代码的入口点。

通过上面的介绍,我们已经知道了这个项目的主模块是 entry,所以入口点需要在这个模块目录中寻找。

在该模块的配置文件 module.json5 中,配置了入口 ability

8.jpg

也就是说,这个 ./ets/entryability/EntryAbility.ts 文件就是我们要找的入口点。

打开这个文件,安卓开发者理解起来应该不难,似乎,这个 ability 的概念有点像 Activity ?

9.jpg

可以看到这个 EntryAbility 继承自 UIAbility 并实现了其中的 onCreateonDestroyonWindowStageCreateonWindowStageDestroyonForegroundonBackground 等方法,显然,这些方法就是这个 ability 的生命周期嘛。

然后在 onWindowStageCreate 生命周期中通过 windowStage.loadContent 加载了 pages/Index 的内容。

我们再来看看这个 pages/Index 的内容:

10.jpg

这个就是 ArtUI 的内容了,看起来还是非常的熟悉是吧?也是一看就懂。

Row 嵌套 Column 再添加一个 Text 组件,构成了这个 Hello, World 的 UI 界面。

接下来,我们简要介绍一下上述代码出现的几个要点。

Ability

在上文中,我们说感觉 Ability 有点类似于安卓的 Aciticity ,这其实有一丝道理,但是又不完全对。

在上述代码中,我们使用的是继承自 Ability 的 UIAbility,根据官方介绍:

UIAbility是一种包含用户界面的应用组件,主要用于和用户进行交互。UIAbility也是系统调度的单元,为应用提供窗口在其中绘制界面。

每一个UIAbility实例,都对应于一个最近任务列表中的任务。

一个应用可以有一个UIAbility,也可以有多个UIAbility。

一个UIAbility可以对应于多个页面。

11.png

从这段介绍中可以看出,虽然 Activity 和 UIAbility 都是程序的 UI 界面的承载组件,且每个程序都可以有一个或多个 UIAbility(Activity),每个 UIAbility(Activity) 也可以对应一个或多个页面。但是,多个 Activity 并不会出现多个页面在最近任务栏中(一般情况下),而一个 UIAbility 就对应了最近任务栏中的一个页面。

另外,和 Activity 一样,UIAbility 也是系统调度的单元,具有自己的生命周期,UIAbility 的生命周期如下:

12.png

它的生命周期流程和 Acticity 类似。

最后,UIAbility 也具有不同的启动模式,但是相比 Activity 的启动模式概念略有不同。

它只有三种启动模式:singleton(单实例模式)、multiton(多实例模式)和specified(指定实例模式)

singleton 模式会确保当前应用进程中的同一个 UIAbility 仅有一个,当以该模式启动某个 UIAbility 时,如果进程中已存在相同的 UIAbility 实例,则会直接复用该实例。

multiton 模式会在每次以该模式启动 UIAbility 时都创建一个新的 UIAbility 实例,而无论这个 UIAbility 是否已经在当前进程中存在。

specified 模式则可以在创建 UIAbility 时指定一个 key 与之绑定,之后打开新的 UIAbility 时都需要提供一个 key,如果该 key 存在已经绑定了的 UIAbility 则会直接复用这个 UIAbility,否则用这个 key 创建一个新的 UIAbility。

ArkUI

接着,我们再来看看具体的 UI 部分(pages/Index),显然这里的 UI 使用的是 ArkUI 实现的。

在 ArkUI 中通过 struct 关键字来声明一个 UI 组件:

struct Index { }

然后通过 @Component 装饰表明 Index 是一个自定义的 ArkUI 组件:

@Component
struct Index { }

同样的,还有一个叫做 @Entry 的装饰用于指明该组件是该页面的入口组件:

@Entry
@Component
struct Index { }

经过上述操作,我们已经声明好了 Index 页面的入口自定义组件。

接下来只需要在 Index 添加描述这个组件的内容即可,为此,我们需要在其中添加一个 build() { }

@Entry
@Component
struct Index {
  // ……
  build() {
    // …… 在此编写布局
  }
}

我们可以在 build 中添加内置 UI 组件或自定义组件用于描述我们的页面组成,例如样例中的居中显示一个 “Hello, World” 文本:

@Entry
@Component
struct Index {
  build() {
    Row() {
      Column() {
        Text("hello, world")
      }
      .width('100%')
    }
    .height('100%')
  }
}

13.jpg

我们还可以通过链式调用的方式,修改组件的参数:

Text(”hello, world“)
  .fontSize(50)
  .fontWeight(FontWeight.Bold)

这样,就把文本修改为字号 50,字重 加粗。

当然,上面的界面是没有状态的,作为声明式 UI 当然要给它定义一个状态了。

首先,我们声明一个 message 变量表示需要显示的文字:

message: string = 'Hello World'

然后,为该变量加上 @State 装饰即表示将该变量与 UI 绑定,当该变量值发生改变时,使用到该变量的 UI 也会自动重新渲染:

@State message: string = 'Hello World'

为了能够看到状态改变的效果,我们为这个文本添加一个点击事件,并在点击后更改 message 值:

Text(this.message)
  .fontSize(50)
  .fontWeight(FontWeight.Bold)
  .onClick(() => {
    this.message = `我被点击了 ${this.count}`;
    this.count++;
  })

现在,只要我们点击这个文本,它就会自动变更为 “我被点击了 xx 次”,其中 xx 是点击次数:

14.jpg

该部分完整代码如下:

@Entry
@Component
struct Index {
  count: number = 1
  @State message: string = 'Hello World'

  build() {
    Row() {
      Column() {
        Text(this.message)
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
          .onClick(() => {
            this.message = `我被点击了 ${this.count}`;
            this.count++;
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

对比 Compose

在上文中,我们已经完成了一个简单的使用 ArkUI 的鸿蒙应用,接下来,我们用 Compose 实现一个同样布局同样功能的应用做一个简单的对比。

首先是 UI 布局的承载者,

在 Compose 中使用的是 Activity:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Greeting()
        }
    }
}

在 Activity 的 onCreate 生命周期通过 setContent 设置具体的 UI 实现。

而在鸿蒙中则是使用 UIAbility:

export default class EntryAbility extends UIAbility {
  
  onWindowStageCreate(windowStage: window.WindowStage) {
    windowStage.loadContent('pages/Index', (err, data) => {});
  }
}

在 UiAbility 的 onWindowStageCreate 生命周期,通过 windowStage.loadContent 设置具体的 UI 实现。

而在具体的 UI 实现部分,Compose 代码如下:

@Composable
fun Greeting() {
    var count by remember { mutableStateOf(1) }
    var message by remember { mutableStateOf("Hello, World") }

    Row(
        modifier = Modifier.fillMaxHeight(),
        verticalAlignment = Alignment.CenterVertically
    ) {
        Column(
            modifier = Modifier.fillMaxWidth(),
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            Text(
                text = message,
                fontSize = 50.sp,
                fontWeight = FontWeight.Bold,
                modifier = Modifier.clickable {
                    message = "我被点击了 $count 次"
                    count++
                }
            )
        }
    }
}

ArkUI 代码如下:

@Entry
@Component
struct Index {
  count: number = 1
  @State message: string = 'Hello World'

  build() {
    Row() {
      Column() {
        Text(this.message)
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
          .onClick(() => {
            this.message = `我被点击了 ${this.count}`;
            this.count++;
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

需要注意的是,在 Compose 的实现中,我是为了尽可能的完全模拟 ArkUI 的界面才这样写的,实际上如果想要实现文本居中根本不需要嵌套 RowColumn ,直接使用其中任意一个然后:

Row(
    modifier = Modifier.fillMaxSize(),
    verticalAlignment = Alignment.CenterVertically,
    horizontalArrangement = Arrangement.Center
) {
     // ……
}   

另外,其实 ArkUI 也不用嵌套,也是直接使用任意一个,然后:

Row() {
  // ……
}
.height('100%')
.width('100%')
.justifyContent(FlexAlign.Center)

猜测这里的 Hello World 示例使用了 Row 嵌套 Column 是为了更多的展示 ArkUI 的特性吧。

另外,这里的 ColumnRow 在默认对齐方式上也有所区别,以 Column 为例,在 ArkUI 中默认是主轴(垂直方向)在 Top ,副轴(水平方向)居中;而 Compose 中则是默认垂直方向(主轴)在 Top,水平方向(副轴)在 Start 。

对于自定义组件的声明方式也有所不同,在 ArkUI 中是通过 struct 结构附加 @Component ;而 Compsoe 则是通过在一个函数上附加 @Composable 表示。

通过上述 Compose 和 ArkUI 的代码对比,我们还可以看出,ArkUI 声明有状态的变量只需要在前面加上 @State 装饰即可; Compose 则需要将其置于 mutableStateOf() 中,并且 Compose 在 Composeable 作用域中的变量还需要包裹在 remember 中,否则每次重组(UI变化)这个变量都会被重复初始化。

另外,对于组件参数的修改,在 Compose 中是通过直接给这个组件函数的参数传值实现,例如修改文本的字号和字重:

Text(
    text = message,
    fontSize = 50.sp,
    fontWeight = FontWeight.Bold,
)

当然,每个组件需要修改的参数也许会有很多,所以不可能每个参数都要给组件函数加一个形参,这会让函数很臃肿,所以在 Compose 中几乎每个组件都有一个 modifier 参数,这个参数中集成了非常多的常用参数修改,例如上述例子中的添加点击监听就是通过 modifier 添加的。

而这个 modifier 的参数修改也是类似于 ArkUI 的链式调用。

对于 ArkUI 来说,所有参数的修改则完全是使用在组件函数之后链式调用来实现的,例如修改字号和字重:

Text(this.message)
  .fontSize(50)
  .fontWeight(FontWeight.Bold)

以上就是对 Compose 和 ArkUI 的简单对比。

总结

经过上述的简单了解,我们可以发现,其实不管是 Android 的 Jetpack Compose 还是 Apple 的 SwiftUI 还是刚才说的鸿蒙的 ArtUI,其实核心思想都是差不多的(毕竟都是声明式 UI 的思想),对于我们普通程序员来说写起来也是差不多的感觉,无非是语法和部分风格上略微有区别。

另外前言中说的事件既是段子也是一部分现实,但是还是希望各位能弄清楚自己的定位,有一个明确的规划,不要东一榔头西一棒子的看见啥有热度就学啥。

当然,有余力的话多了解了解其他技术还是挺有帮助的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/233360.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

机器学习入门笔记

文章目录 背景具体步骤1.环境搭建2.写个demo1.数据处理2.分割数据集3.用模型训练数据,并得到预测结果4.绘制结果5.评估 背景 最近学习了一些关于机器学习的内容,做个笔记。 具体步骤 1.环境搭建 需要用到的工具:pycharm,anaco…

YOLOv5目标检测

文章目录 软硬件环境前言安装GPU环境安装pytorch的GPU版本YOLOv5测试v3.0版本参考资料 软硬件环境 ubuntu 18.04 64bitanaconda with 3.7nvidia gtx 1070Ticuda 10.1pytorch 1.5YOLOv5 前言 YOLOv4还没有退热,YOLOv5就已经来了! 6月9日&#xff0c…

【力扣】141和142环形链表

141.环形链表 法一:快慢指针 思路: 用两个指针slow,fast,后者能比前者多走一步路,那判断是不是有环,只需要判断是否会相遇。 就是有一个能比乌龟跑2倍快的兔子,两小只都在有环的路上跑,那是不是肯定会相…

流水号的获取

软件中,常常使用流水号,通常流水号是一组参数的组合,如:评估报告的编号结构: 区编号-机构类型-年份-性别-流水 如:03-01-2023-W-0001 03-01-2023-M-0002 03-01-2023-M-0003 。。。。。。 编程时&#xff0c…

提示msvcp90.dll丢失的解决方法,找不到msvcp90.dll问题全方面分析

今天我想和大家分享的主题是关于在使用软件时遇到的一个问题——msvcp90.dll丢失。相信很多老师在使用电脑时都遇到过这个问题,那么接下来我将从三个方面为大家介绍:msvcp90.dll文件是什么、msvcp90.dll丢失原因以及msvcp90.dll丢失的5个解决方案。 首先…

中科院分区和JCR分区有什么区别

文章目录 名词解释学科划分不同参考的影响因子不同期刊分区不同期刊分区阈值不同 名词解释 中科院分区:又称“中科院JCR分区”,是中国科学院文献情报中心世界科学前沿分析中心的科学研究成果,期刊分区表数据每年底(每年12月中下旬…

windows错误事件 98、41、7000、55、153解决办法

事件错误:98、55、153 疑难解答清单 在系统事件日志中,搜索新技术文件系统 (NTFS) 和磁盘相关的警告和错误。 例如,事件 ID 55、153 或 98。 管理员身份打开CMD,运行命令 chkdsk /scan 并检查结果。 该 chkdsk /scan 命令是只读…

[渗透测试学习] Devvortex - HackTheBox

文章目录 信息搜集解题步骤提交flag 信息搜集 扫描端口 nmap -sV -sC -p- -v --min-rate 1000 10.10.11.242发现80端口有http服务,并且是nginx服务 尝试访问web界面,发现跳转到http://devvortex.htb/无法访问 我们用vim添加该域名即可 sudo vim /etc/…

课后作业7.3.1:构造一个自己的小操作系统

构造一个自己的 mini 操作系统 任务描述 请实现如下功能: 1.写一个命令解释器程序 mysh.c ,其功能是接收用户输入的命令并给出反馈。要求该程序既支持内部命令 cd、sync、exit ;也支持外部命令,即可以接收 cat、ls 等命令,然后执行相应的可执行程序。要求首先在 Ubuntu 中…

电源小白入门学习1——电源系统架构和相关指标

电源小白入门学习1——电源系统架构和相关指标 电源系统架构电源系统的指标及测量方法电源的效率电源的静态电流输出电压调整率纹波测量的注意事项动态负载测试 在开始本期内容之气,我先简单介绍一下我们电源小白学习系列内容:首先我是一个嵌入式小白&am…

FF-A架构精讲-目录

第二章 Introduction第三章 Software architecture第四章 Concepts第五章 Setup第六章 Identification and Discovery第七章 Message passin第八章 Partition runtime models第九章 Interrupt management第十章 Notifications第十一章 Memory Management第十二章 Interface ove…

Mybatis、Mybatis整合Spring的流程图

Mybatis 注意MapperProxy里面有invoke方法,当进到invoker方法会拿到 二、mybatis整合Spring 1、当我们的拿到的【Dao】其实就是【MapperProxy】,执行Dao的方法时,会被MapperProxy的【Invoke方法拦截】 2、图上已经标注了MapperProxy包含哪些…

java的多态

文章目录 多态的概念继承语法子类中访问父类的成员变量子类中访问父类的成员方法如果子类中存在与父类中相同的成员时,那如何在子类中访问父类相同名称的成员呢?子类构造方法 final 关键字重写向上转移和向下转型向上转型向下转型 多态 多态的概念 就是…

WorkPlus即时通讯,让沟通零障碍!企业协作更高效

如今,随着信息技术的快速发展,企业对于高效沟通和即时协作的需求也日益增长。在这个数字化时代,WorkPlus作为一款领先的企业级移动办公平台,以其强大的即时通讯功能和卓越的用户体验,成功为企业打造了高效沟通的新时代…

Linux-帮助命令的使用和练习(type、man、help、info详解)

目录 5.3.1 type-判断是否为内部命令 5.3.2 man-查看详细文档 5.3.3 help-查看shell内部命令的帮助信息 5.3.4 --help-查看系统外部命令帮助信息 5.3.5 info-查看info格式的帮助指令 5.3.6 /usr/share/doc-存储软件包的文档信息 平时我们看到的命令大多数都可以查看帮助文…

基于PLC的污水处理控制系统的设计(论文+源码)

1.系统设计 污水由进水系统通过粗格栅和清污机进行初步排除大块杂质物体以及漂浮物等,到达除砂池中。在除砂池系统中细格栅进一步净化污水中的细小颗粒物体,将污水中的细小沙粒滤除后进入氧化沟反应池。在该氧化沟系统中进行生化处理,分解污…

class006 二分搜索【算法】

class006 二分搜索【算法】 算法讲解006【入门】二分搜索 code1 有序数组中是否存在一个数字 // 有序数组中是否存在一个数字 package class006;import java.util.Arrays;// 有序数组中是否存在一个数字 public class Code01_FindNumber {// 为了验证public static void mai…

Java8流式编程详解

简介 java8提供的流式编程使得我们对于集合的处理不再是临时集合加上各种还能for循环,取而代之的是更加简洁高效的流水线操作,所以笔者就以这篇文章总结一下流式编程中常见的操作。 前置铺垫 后文示例操作中,我们都会基于这个菜肴类的集合…

使用C语言操作kafka ---- librdkafka

1 安装librdkafka git clone https://github.com/edenhill/librdkafka.git cd librdkafka git checkout v1.7.0 ./configure make sudo make install sudo ldconfig 在librdkafka的examples目录下会有示例程序。比如consumer的启动需要下列参数 ./consumer <broker> &…

前言-计算机概述

1 计算机作用&#xff1f; 计算机已经成为人们日常生活中不可缺少的产物&#xff0c;具体作用如下 1&#xff09;信息处理 电脑可以处理、存储和检索大量的信息&#xff0c;例如文档、音频、视频等等&#xff0c;这使得信息传播和共享变得更加容易和高效。 2&#xff09;通讯 …