前言:
这篇文章主要讲解一下我的初期HarmonyOS的学习所得和知识分享,并用一个简单的示例对
所概括的知识点进行讲解,如果有什么内容问题或者错误请及时指出,感谢各位的阅读啦,跟着许
小白的视角进入咱们的知识分享啦啦啦:
一:引入的简单示例项目
1. 简单示例项目的要求:
对于一个界面中类似于下面界面中,一个屏幕无法显示全部的内容,当你进行向上手滑的时
候会自动加载后边的内容,不往下滑动的时候数据不进行加载,这样做可以使手机屏幕上的数据是
实时的,跟着你手的滑动进行更新的后边展示内容(缓解后端数据库的压力)
(此处进行动画展示应该比较好点,但技术受限哈)
2. 整个简单项目的完整代码:
//自定义的文章类
class Article {
public id: number
public title: string
public content: string
constructor(id: number, title: string, content: string) {
this.id = id
this.title = title
this.content = content
}
}
//定义一篇文章的子主组件
@Component
struct ArticleComponent {
article: Article = new Article(0, '', '')
build() {
Row() {
Image($r('app.media.startIcon'))
.width(80)
.height(80)
.margin({ right: 20 })
Column() {
Text(this.article.title)
.fontSize(20)
.margin({ bottom: 8 })
Text(this.article.content)
.fontSize(16)
.fontColor(Color.Gray)
.margin({ bottom: 8 })
}.alignItems(HorizontalAlign.Start)
.width('90%')
.height('100%')
}
.padding(20)
.borderRadius(12)
.backgroundColor('#FFECECEC')
.height(120)
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
}
}
@Entry
@Component
struct MyParent {
@State islListReachEnd: boolean = false
@State
article_array: Array<Article> = [
//初始化的时候先建成五篇文章
new Article(1, '第1篇文章', '文章的内容和介绍'),
new Article(2, '第2篇文章', '文章的内容和介绍'),
new Article(3, '第3篇文章', '文章的内容和介绍'),
new Article(4, '第4篇文章', '文章的内容和介绍'),
new Article(5, '第5篇文章', '文章的内容和介绍')
]
build() {
Column() {
List() {
ForEach(this.article_array, (item: Article) => {
ArticleComponent({ article: item })
.margin({ top: 15 })
})
}.onReachEnd(() => {
//到达列表的底部
this.islListReachEnd = true
})
.parallelGesture(PanGesture({ direction: PanDirection.Up, distance: 80 })
.onActionStart(() => {
//检测到向上滑动的趋势
if (this.islListReachEnd) {
let count = this.article_array.length
let new_id = count += 1
this.article_array.push(new Article(new_id, '第' + new_id + '篇文章', '文章的内容和介绍'))
this.islListReachEnd = false
}
}))
.padding(20)
.scrollBar(BarState.Off)
}
.width('100%')
.height('100%')
}
}
接下来我会对于整个项目的思路以及涉及的相关组件会单独讲解,大概学习过的可以尝TS基
础的和ArkUI框架的可以理解尝试,讲解的过程中会加入个人的理解。
二: 整体的布局和思路
1.ArkTS的基本组成(了解)
-
装饰器: 用于装饰类、结构、方法以及变量,并赋予其特殊的含义。如上述示例中@Entry、@Component和@State都是装饰器,@Component表示自定义组件,@Entry表示该自定义组件为入口组件,@State表示组件中的状态变量,状态变量变化会触发UI刷新。
-
UI描述:以声明式的方式来描述UI的结构,例如build()方法中的代码块。
-
自定义组件:可复用的UI单元,可组合其他组件,如上述被@Component装饰的struct Hello。
-
系统组件:ArkUI框架中默认内置的基础和容器组件,可直接被开发者调用,比如示例中的Column、Text、Divider、Button。
-
属性方法:组件可以通过链式调用配置多项属性,如fontSize()、width()、height()、backgroundColor()等。
-
事件方法:组件可以通过链式调用设置多个事件的响应逻辑,如跟随在Button后面的onClick()。
-
系统组件、属性方法、事件方法具体使用可参考基于ArkTS的声明式开发范式。
2. 项目构建思路:
上述的整体布局肯定为列布局为主,行布局为副,再看每一篇文章都看成一个组件,组件
为行排列的形式,期中包括图标,标题和内容(就可以将其抽象一个类,采用面向对象的思想进行
处理,将其作为一个模板),通过观察发现每一个组件的大体都是相同的,便可以通过循环渲染实
现页面的构建:
抽象为
3. 构建的原则
大事化小,小事化了,了了解决
三:具体内容的讲解
1. class类的创建
根据上面构建的思路,创建每个组件的模板(java中面向对象的思想,有点抽象)
//自定义的文章类
class Article {
//属性
public id: number //内容中显示的编号
public title: string //内容的标题、
public content: string //内容的内容
//构造函数
constructor(id: number, title: string, content: string) {
this.id = id
this.title = title
this.content = content
}
}
2.子组件的创建
定义了子组件,用于显示一篇文章的标题和内容。组件的布局通过一个水平排列的Row
容器实
现,容器内包含一个图片和一个垂直排列的Column
。Column
内包含文章的标题和内容,通过设置各
种样式和布局属性来美化组件的外观。
@Component
struct ArticleComponent {
article: Article = new Article(0, '', '')
build() {
Row() {
Image($r('app.media.startIcon'))
.width(80)
.height(80)
.margin({ right: 20 })
Column() {
Text(this.article.title)
.fontSize(20)
.margin({ bottom: 8 })
Text(this.article.content)
.fontSize(16)
.fontColor(Color.Gray)
.margin({ bottom: 8 })
}.alignItems(HorizontalAlign.Start)
.width('90%')
.height('100%')
}
.padding(20)
.borderRadius(12)
.backgroundColor('#FFECECEC')
.height(120)
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
}
}
注意基本的框架一定好构思好,确定好相关的容器组件和使用的组件,后边根据美观的程度
不断的调整相应的属性方法,达到理想的程度,初学的时候不强迫花大量时间去记忆属性方法,可
以进行官网上直接查找,里面有各种的属性可以调用。
3.入口组件的书写
就是相当于将子组件在此处实现一个整合的作用,小的积木已经拼好了需要将他整合成为整
个建筑,并对于整个页面进行布局。
@Entry
@Component
struct MyParent {
@State islListReachEnd: boolean = false
@State
article_array: Array<Article> = [
//初始化的时候先建成五篇文章
new Article(1, '第1篇文章', '文章的内容和介绍'),
new Article(2, '第2篇文章', '文章的内容和介绍'),
new Article(3, '第3篇文章', '文章的内容和介绍'),
new Article(4, '第4篇文章', '文章的内容和介绍'),
new Article(5, '第5篇文章', '文章的内容和介绍')
]
build() {
Column() {
List() {
ForEach(this.article_array, (item: Article) => {
ArticleComponent({ article: item })
.margin({ top: 15 })
})
}.onReachEnd(() => {
//到达列表的底部
this.islListReachEnd = true
})
.parallelGesture(PanGesture({ direction: PanDirection.Up, distance: 80 })
.onActionStart(() => {
//检测到向上滑动的趋势
if (this.islListReachEnd) {
let count = this.article_array.length
let new_id = count += 1
this.article_array.push(new Article(new_id, '第' + new_id + '篇文章', '文章的内容和介绍'))
this.islListReachEnd = false
}
}))
.padding(20)
.scrollBar(BarState.Off)
}
.width('100%')
.height('100%')
}
}
四:涉及相关的知识点和难点
1. 知识点介绍
(1):@Entry
装饰器和@Component
装饰器的理解
这是大家刚开始接触时就已经出现的装饰器,但是可能仍然不能理解其具体的作用,我将用
通俗的语言讲解:
@Entry
装饰器通常用于指定应用程序的入口组件。这个组件是应用启动时第一个被加载和渲
染的组件。在C语言中相当于main函数的作用,作为标记一个组件为应用程序的入口点。
@Component
装饰器用于声明一个类或结构体是一个组件。它可以包含子组件、状态、属性和
构建UI的方法。理解为子组件,相当于C语言函数的说明,声明一个类或结构体是一个组件,在其
中可以书写相关的方法解决所化小的问题。
(2):List组件
List
组件用于显示一组可滚动的元素,是容器组件。它通常用于呈现大量数据,如文章、图
片或其他项目。同时支持纵向的排列,也可以及进行横向的排列,除了一些基本的属性,列出其他
的一些属性:
parallelGesture()用于处理手势事件,如滑动、平移等。
scrollBar()设置滚动条的状态,如启用或禁用
onReachEnd()当列表滚动到底部时触发的事件处理函数
List
容器不一定需要包含 ListItem
组件。你可以直接使用 ForEach
遍历数组并渲染自定义的子组件
(3):组件状态装饰器
虽然上述代码中只涉及了@State,但是作为一个比较易理解的知识点(相对我哈),我会系
统的讲解相关的知识点。
这些组件状态装饰器的作用是协同父子组件或其他关联组件之间共享和同步那些数据,哪些
数据又是独立的。通俗来讲就是既要满足有些关联组件的同步性同时要满足其独立性,所以不同的
修饰器起不同的作用。
1. @State装饰器
@State装饰的变量,或称为状态变量,一旦变量拥有了状态属性,就可以触发其直接绑定UI
组件的刷新。当状态改变时,UI会发生对应的渲染改变。
@State装饰的变量生命周期与其所属自定义组件的生命周期相同。
@State必须本地初始化。
基本的运行状态:当状态变量被改变时,查询依赖该状态变量的组件,和该状态变量不相关
的组件或者UI描述不会发生重新渲染,从而实现页面渲染的按需更新。
以下将用两种简单的使用场景进行说明:
装饰简单类型的变量
@Entry
@Component
struct MyComponent {
@State count: number = 0;
build() {
Button(`click times: ${this.count}`)
.onClick(() => {
this.count += 1;
})
}
}
count被@State装饰成为状态变量,count的改变引起Button组件的刷新:当状态变量count改
变时,查询到只有Button组件关联了它;执行Button组件的更新方法,实现按需刷新。
装饰class对象类型的变量
class Model {
public value: string;
constructor(value: string) {
this.value = value;
}
}
@Component
struct MyComponent {
@State title: Model = new Model('Hello World');
@State count: number = 0;
private increaseBy: number = 1;
build() {
Column() {
Text(`${this.title.value}`)
.margin(10)
Button(`Click to change title`)
.onClick(() => {
// @State变量的更新将触发上面的Text组件内容更新
this.title.value = this.title.value === 'Hello ArkUI' ? 'Hello World' : 'Hello ArkUI';
})
.width(300)
.margin(10)
Button(`Click to increase count = ${this.count}`)
.onClick(() => {
// @State变量的更新将触发该Button组件的内容更新
this.count += this.increaseBy;
})
.width(300)
.margin(10)
}
}
}
@Entry
@Component
struct EntryComponent {
build() {
Column() {
// 此处指定的参数都将在初始渲染时覆盖本地定义的默认值,并不是所有的参数都需要从父组件初始化
MyComponent({ count: 1, increaseBy: 2 })
.width(300)
MyComponent({ title: new Model('Hello World 2'), count: 7 })
}
}
}
自定义组件MyComponent定义了被@State装饰的状态变量count和title,其中title的类型为自
定义类Model。如果count或title的值发生变化,则查询MyComponent中使用该状态变量的UI组
件,并进行重新渲染。EntryComponent中有多个MyComponent组件实例,第一个MyComponent
内部状态的更改不会影响第二个MyComponent。
注意特殊情况
class Parent {
son: string = '000';
}
@Entry
@Component
struct Test {
@State son: string = '111';
@State parent: Parent = new Parent();
aboutToAppear(): void {
this.parent.son = this.son;
}
build() {
Column() {
Text(`${this.son}`);
Text(`${this.parent.son}`);
Button('change')
.onClick(() => {
this.parent.son = '222';
})
}
}
}
一定要注意哈:状态变量只能影响其直接绑定的UI组件的刷新
Button('change'),此时第一行文本'111'不会更新,第二行文本'111'更新为'222',因为son是简
单类型String,简单类型是值拷贝,所以点击按钮改变的是parent中的son值,不会影响this.son的
值。
2.@Prop装饰器
@Prop装饰的变量可以和父组件建立单向的同步关系。@Prop装饰的变量是可变的,但是变
化不会同步回其父组件。
!!!!@Prop装饰器不能在@Entry装饰的自定义组件中使用
子组件@Prop更新时,更新仅停留在当前子组件,不会同步回父组件;当父组件的数据源更
新时,子组件的@Prop装饰的变量将被来自父组件的数据源重置,所有@Prop装饰的本地的修改
将被父组件的更新覆盖。
@Prop需要被初始化,如果没有进行本地初始化的,则必须通过父组件进行初始化。如果进
行了本地初始化,那么是可以不通过父组件进行初始化的。
装饰简单类型的变量
@Component
struct CountDownComponent {
@Prop count: number = 0;
costOfOneAttempt: number = 1;
build() {
Column() {
if (this.count > 0) {
Text(`You have ${this.count} Nuggets left`)
} else {
Text('Game over!')
}
// @Prop装饰的变量不会同步给父组件
Button(`Try again`).onClick(() => {
this.count -= this.costOfOneAttempt;
})
}
}
}
@Entry
@Component
struct ParentComponent {
@State countDownStartValue: number = 10;
build() {
Column() {
Text(`Grant ${this.countDownStartValue} nuggets to play.`)
// 父组件的数据源的修改会同步给子组件
Button(`+1 - Nuggets in New Game`).onClick(() => {
this.countDownStartValue += 1;
})
// 父组件的修改会同步给子组件
Button(`-1 - Nuggets in New Game`).onClick(() => {
this.countDownStartValue -= 1;
})
CountDownComponent({ count: this.countDownStartValue, costOfOneAttempt: 2 })
}
}
}
能明显的区分@State和@Prop的区别:@State到子组件@Prop简单数据同步,父组件
ParentComponent的状态变量countDownStartValue初始化子组件CountDownComponent中
@Prop装饰的count。
父组件的@State装饰的countDownStartValue值会变化,这将触发父组件重新渲染,在父组
件重新渲染过程中会刷新使用countDownStartValue状态变量的UI组件并单向同步更新
CountDownComponent子组件中的count值;
3.@Link装饰器
子组件中被@Link装饰的变量与其父组件中对应的数据源建立双向数据绑定。
@Link装饰器不能在@Entry装饰的自定义组件中使用。
简单类型和类对象类型
class GreenButtonState {
width: number = 0;
constructor(width: number) {
this.width = width;
}
}
@Component
struct GreenButton {
@Link greenButtonState: GreenButtonState;
build() {
Button('Green Button')
.width(this.greenButtonState.width)
.height(40)
.backgroundColor('#64bb5c')
.fontColor('#FFFFFF,90%')
.onClick(() => {
if (this.greenButtonState.width < 700) {
// 更新class的属性,变化可以被观察到同步回父组件
this.greenButtonState.width += 60;
} else {
// 更新class,变化可以被观察到同步回父组件
this.greenButtonState = new GreenButtonState(180);
}
})
}
}
@Component
struct YellowButton {
@Link yellowButtonState: number;
build() {
Button('Yellow Button')
.width(this.yellowButtonState)
.height(40)
.backgroundColor('#f7ce00')
.fontColor('#FFFFFF,90%')
.onClick(() => {
// 子组件的简单类型可以同步回父组件
this.yellowButtonState += 40.0;
})
}
}
@Entry
@Component
struct ShufflingContainer {
@State greenButtonState: GreenButtonState = new GreenButtonState(180);
@State yellowButtonProp: number = 180;
build() {
Column() {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center }) {
// 简单类型从父组件@State向子组件@Link数据同步
Button('Parent View: Set yellowButton')
.width(312)
.height(40)
.margin(12)
.fontColor('#FFFFFF,90%')
.onClick(() => {
this.yellowButtonProp = (this.yellowButtonProp < 700) ? this.yellowButtonProp + 40 : 100;
})
// class类型从父组件@State向子组件@Link数据同步
Button('Parent View: Set GreenButton')
.width(312)
.height(40)
.margin(12)
.fontColor('#FFFFFF,90%')
.onClick(() => {
this.greenButtonState.width = (this.greenButtonState.width < 700) ? this.greenButtonState.width + 100 : 100;
})
// class类型初始化@Link
GreenButton({ greenButtonState: $greenButtonState }).margin(12)
// 简单类型初始化@Link
YellowButton({ yellowButtonState: $yellowButtonProp }).margin(12)
}
}
}
}
子组件GreenButton和YellowButton中的Button,子组件会发生相应变化,将变化同步给父组
件。因为@Link是双向同步,会将变化同步给@State。
后语:
感谢能阅读到此处的,内容量有点大,有些地方做的有点粗,有问题请及时指出哈,希望我
们一起努力哈!!!路漫漫其修远兮,吾将上下而求索!