目录
[1 -> Web组件开发](#1 -> Web组件开发 "#1%20-%3E%C2%A0Web%E7%BB%84%E4%BB%B6%E5%BC%80%E5%8F%91")
[1.1 -> 创建组件](#1.1 -> 创建组件 "#1.1%20-%3E%C2%A0%E5%88%9B%E5%BB%BA%E7%BB%84%E4%BB%B6")
[1.2 -> 设置样式和属性](#1.2 -> 设置样式和属性 "#1.2%20-%3E%C2%A0%E8%AE%BE%E7%BD%AE%E6%A0%B7%E5%BC%8F%E5%92%8C%E5%B1%9E%E6%80%A7")
[1.3 -> 添加事件和方法](#1.3 -> 添加事件和方法 "#1.3%20-%3E%C2%A0%E6%B7%BB%E5%8A%A0%E4%BA%8B%E4%BB%B6%E5%92%8C%E6%96%B9%E6%B3%95")
[1.4 -> 场景示例](#1.4 -> 场景示例 "#1.4%20-%3E%C2%A0%E5%9C%BA%E6%99%AF%E7%A4%BA%E4%BE%8B")
[2 -> 性能提升的推荐方法](#2 -> 性能提升的推荐方法 "#2%20-%3E%C2%A0%E6%80%A7%E8%83%BD%E6%8F%90%E5%8D%87%E7%9A%84%E6%8E%A8%E8%8D%90%E6%96%B9%E6%B3%95")
[2.1 -> 推荐使用数据懒加载](#2.1 -> 推荐使用数据懒加载 "#2.1%20-%3E%C2%A0%E6%8E%A8%E8%8D%90%E4%BD%BF%E7%94%A8%E6%95%B0%E6%8D%AE%E6%87%92%E5%8A%A0%E8%BD%BD")
[2.2 -> 使用条件渲染替代显隐控制](#2.2 -> 使用条件渲染替代显隐控制 "#2.2%20-%3E%C2%A0%E4%BD%BF%E7%94%A8%E6%9D%A1%E4%BB%B6%E6%B8%B2%E6%9F%93%E6%9B%BF%E4%BB%A3%E6%98%BE%E9%9A%90%E6%8E%A7%E5%88%B6")
[2.3 -> 使用Column/Row替代Flex](#2.3 -> 使用Column/Row替代Flex "#2.3%20-%3E%C2%A0%E4%BD%BF%E7%94%A8Column%2FRow%E6%9B%BF%E4%BB%A3Flex")
[2.4 -> 设置List组件的宽高](#2.4 -> 设置List组件的宽高 "#2.4%20-%3E%C2%A0%E8%AE%BE%E7%BD%AEList%E7%BB%84%E4%BB%B6%E7%9A%84%E5%AE%BD%E9%AB%98")
[2.5 -> 减少应用滑动白块](#2.5 -> 减少应用滑动白块 "#ZH-CN_TOPIC_0000001436097337__%E5%87%8F%E5%B0%91%E5%BA%94%E7%94%A8%E6%BB%91%E5%8A%A8%E7%99%BD%E5%9D%97")
编辑
1 -> Web组件开发
1.1 -> 创建组件
在pages目录下创建一个Web组件。在Web组件中通过src指定引用的网页路径,controller为组件的控制器,通过controller绑定Web组件,用于调用Web组件的方法。
less
// test.ets
@Entry
@Component
struct WebComponent {
controller: WebController = new WebController();
build() {
Column() {
Web({ src: 'https://www.example.com', controller: this.controller })
}
}
}
1.2 -> 设置样式和属性
Web组件的使用需要添加丰富的页面样式和功能属性。设置height、padding样式可为Web组件添加高和内边距,设置fileAccess属性可为Web组件添加文件访问权限,设置javaScriptAccess属性为true使Web组件具有执行JavaScript代码的能力。
scss
// test.ets
@Entry
@Component
struct WebComponent {
fileAccess: boolean = true;
controller: WebController = new WebController();
build() {
Column() {
Text('Hello world!')
.fontSize(20)
Web({ src: 'https://www.example.com', controller: this.controller })
// 设置高和内边距
.height(500)
.padding(20)
// 设置文件访问权限和脚本执行权限
.fileAccess(this.fileAccess)
.javaScriptAccess(true)
Text('End')
.fontSize(20)
}
}
}
1.3 -> 添加事件和方法
为Web组件添加onProgressChange事件,该事件回调Web引擎加载页面的进度值。将该进度值赋值给Progress组件的value,控制Progress组件的状态。当进度为100%时隐藏Progress组件,Web页面加载完成。
kotlin
// test.ets
@Entry
@Component
struct WebComponent {
@State progress: number = 0;
@State hideProgress: boolean = true;
fileAccess: boolean = true;
controller: WebController = new WebController();
build() {
Column() {
Text('Hello world!')
.fontSize(20)
Progress({value: this.progress, total: 100})
.color('#0000ff')
.visibility(this.hideProgress ? Visibility.None : Visibility.Visible)
Web({ src: 'https://www.example.com', controller: this.controller })
.fileAccess(this.fileAccess)
.javaScriptAccess(true)
.height(500)
.padding(20)
// 添加onProgressChange事件
.onProgressChange(e => {
this.progress = e.newProgress;
// 当进度100%时,进度条消失
if (this.progress === 100) {
this.hideProgress = true;
} else {
this.hideProgress = false;
}
})
Text('End')
.fontSize(20)
}
}
}
在onPageEnd事件中添加runJavaScript方法。onPageEnd事件是网页加载完成时的回调,runJavaScript方法可以执行HTML中的JavaScript脚本。当页面加载完成时,触发onPageEnd事件,调用HTML文件中的test方法,在控制台打印信息。
kotlin
// test.ets
@Entry
@Component
struct WebComponent {
@State progress: number = 0;
@State hideProgress: boolean = true;
fileAccess: boolean = true;
// 定义Web组件的控制器controller
controller: WebController = new WebController();
build() {
Column() {
Text('Hello world!')
.fontSize(20)
Progress({value: this.progress, total: 100})
.color('#0000ff')
.visibility(this.hideProgress ? Visibility.None : Visibility.Visible)
// 初始化Web组件,并绑定controller
Web({ src: $rawfile('index.html'), controller: this.controller })
.fileAccess(this.fileAccess)
.javaScriptAccess(true)
.height(500)
.padding(20)
.onProgressChange(e => {
this.progress = e.newProgress;
if (this.progress === 100) {
this.hideProgress = true;
} else {
this.hideProgress = false;
}
})
.onPageEnd(e => {
// test()在index.html中定义
this.controller.runJavaScript({ script: 'test()' });
console.info('url: ', e.url);
})
Text('End')
.fontSize(20)
}
}
}
在main/resources/rawfile目录下创建一个HTML文件。
xml
<!-- index.html -->
<!DOCTYPE html>
<html>
<meta charset="utf-8">
<body>
Hello world!
</body>
<script type="text/javascript">
function test() {
console.info('Ark Web Component');
}
</script>
</html>
1.4 -> 场景示例
该场景实现了Web组件中视频的动态播放。首先在HTML页面内嵌入视频资源,再使用Web组件的控制器调用onActive和onInactive方法激活和暂停页面渲染。点击onInactive按钮,Web页面停止渲染,视频暂停播放;点击onActive按钮,激活Web组件,视频继续播放。
less
// test.ets
@Entry
@Component
struct WebComponent {
controller: WebController = new WebController();
build() {
Column() {
Row() {
Button('onActive').onClick(() => {
console.info("Web Component onActive");
this.controller.onActive();
})
Button('onInactive').onClick(() => {
console.info("Web Component onInactive");
this.controller.onInactive();
})
}
Web({ src: $rawfile('index.html'), controller: this.controller })
.fileAccess(true)
}
}
}
xml
<!-- index.html -->
<!DOCTYPE html>
<html>
<meta charset="utf-8">
<body>
<video width="320" height="240" controls="controls" muted="muted" autoplay="autoplay">
<!-- test.mp4视频文件放在main/resources/rawfile目录下 -->
<source src="test.mp4" type="video/mp4">
</video>
</body>
</html>
2 -> 性能提升的推荐方法
2.1 -> 推荐使用数据懒加载
在使用长列表时,如果直接采用循环渲染方式,如下所示,会一次性加载所有的列表元素,一方面会导致页面启动时间过长,影响用户体验,另一方面也会增加服务器的压力和流量,加重系统负担。
less
@Entry
@Component
struct MyComponent {
@State arr: number[] = Array.from(Array(100), (v,k) =>k); //构造0-99的数组
build() {
List() {
ForEach(this.arr, (item: number) => {
ListItem() {
Text(`item value: ${item}`)
}
}, (item: number) => item.toString())
}
}
}
上述代码会在页面加载时将100个列表元素全部加载,这并非我们需要的,我们希望从数据源中按需迭代加载数据并创建相应组件,因此需要使用数据懒加载,如下所示:
kotlin
class BasicDataSource implements IDataSource {
private listeners: DataChangeListener[] = []
public totalCount(): number {
return 0
}
public getData(index: number): any {
return undefined
}
registerDataChangeListener(listener: DataChangeListener): void {
if (this.listeners.indexOf(listener) < 0) {
console.info('add listener')
this.listeners.push(listener)
}
}
unregisterDataChangeListener(listener: DataChangeListener): void {
const pos = this.listeners.indexOf(listener);
if (pos >= 0) {
console.info('remove listener')
this.listeners.splice(pos, 1)
}
}
notifyDataReload(): void {
this.listeners.forEach(listener => {
listener.onDataReloaded()
})
}
notifyDataAdd(index: number): void {
this.listeners.forEach(listener => {
listener.onDataAdd(index)
})
}
notifyDataChange(index: number): void {
this.listeners.forEach(listener => {
listener.onDataChange(index)
})
}
notifyDataDelete(index: number): void {
this.listeners.forEach(listener => {
listener.onDataDelete(index)
})
}
notifyDataMove(from: number, to: number): void {
this.listeners.forEach(listener => {
listener.onDataMove(from, to)
})
}
}
class MyDataSource extends BasicDataSource {
private dataArray: string[] = ['item value: 0', 'item value: 1', 'item value: 2']
public totalCount(): number {
return this.dataArray.length
}
public getData(index: number): any {
return this.dataArray[index]
}
public addData(index: number, data: string): void {
this.dataArray.splice(index, 0, data)
this.notifyDataAdd(index)
}
public pushData(data: string): void {
this.dataArray.push(data)
this.notifyDataAdd(this.dataArray.length - 1)
}
}
@Entry
@Component
struct MyComponent {
private data: MyDataSource = new MyDataSource()
build() {
List() {
LazyForEach(this.data, (item: string) => {
ListItem() {
Row() {
Text(item).fontSize(20).margin({ left: 10 })
}
}
.onClick(() => {
this.data.pushData('item value: ' + this.data.totalCount())
})
}, item => item)
}
}
}
上述代码在页面加载时仅初始化加载三个列表元素,之后每点击一次列表元素,将增加一个列表元素。
2.2 -> 使用条件渲染替代显隐控制
如下所示,在使用visibility通用属性控制组件的显隐状态时,仍存在组件的重新创建过程,造成性能上的损耗。
scss
@Entry
@Component
struct MyComponent {
@State isVisible: Visibility = Visibility.Visible;
build() {
Column() {
Button("显隐切换")
.onClick(() => {
if (this.isVisible == Visibility.Visible) {
this.isVisible = Visibility.None
} else {
this.isVisible = Visibility.Visible
}
})
Row().visibility(this.isVisible)
.width(300).height(300).backgroundColor(Color.Pink)
}.width('100%')
}
}
要避免这一问题,可使用if条件渲染代替visibility属性变换,如下所示:
scss
@Entry
@Component
struct MyComponent {
@State isVisible: boolean = true;
build() {
Column() {
Button("显隐切换")
.onClick(() => {
this.isVisible = !this.isVisible
})
if (this.isVisible) {
Row()
.width(300).height(300).backgroundColor(Color.Pink)
}
}.width('100%')
}
}
2.3 -> 使用Column/Row替代Flex
由于Flex容器组件默认情况下存在shrink导致二次布局,这会在一定程度上造成页面渲染上的性能劣化。
scss
@Entry
@Component
struct MyComponent {
build() {
Flex({ direction: FlexDirection.Column }) {
Flex().width(300).height(200).backgroundColor(Color.Pink)
Flex().width(300).height(200).backgroundColor(Color.Yellow)
Flex().width(300).height(200).backgroundColor(Color.Grey)
}
}
}
上述代码可将Flex替换为Column、Row,在保证实现的页面布局效果相同的前提下避免Flex二次布局带来的负面影响。
scss
@Entry
@Component
struct MyComponent {
build() {
Column() {
Row().width(300).height(200).backgroundColor(Color.Pink)
Row().width(300).height(200).backgroundColor(Color.Yellow)
Row().width(300).height(200).backgroundColor(Color.Grey)
}
}
}
2.4 -> 设置List组件的宽高
在使用Scroll容器组件嵌套List子组件时,若不指定List的宽高尺寸,则默认全部加载,如下所示:
scss
@Entry
@Component
struct MyComponent {
private arr: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
build() {
Scroll() {
List() {
ForEach(this.arr, (item) => {
ListItem() {
Text(`item value: ${item}`).fontSize(30).margin({ left: 10 })
}.height(100)
}, (item) => item.toString())
}
}.backgroundColor(Color.Pink)
}
}
因此,在这种场景下建议设置List子组件的宽高,如下所示:
scss
@Entry
@Component
struct MyComponent {
private arr: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
build() {
Scroll() {
List() {
ForEach(this.arr, (item) => {
ListItem() {
Text(`item value: ${item}`).fontSize(30).margin({ left: 10 })
}.height(100)
}, (item) => item.toString())
}.width('100%').height(500)
}.backgroundColor(Color.Pink)
}
}
2.5 -> 减少应用滑动白块
应用通过增大List/Grid控件的cachedCount参数,调整UI的加载范围。cachedCount表示屏幕外List/Grid预加载item的个数。
如果需要请求网络图片,可以在item滑动到屏幕显示之前,提前下载好内容,从而减少滑动白块。
如下是使用cachedCount参数的例子:
typescript
@Entry
@Component
struct MyComponent {
private source: MyDataSource = new MyDataSource();
build() {
List() {
LazyForEach (this.source, item => {
ListItem() {
Text("Hello" + item)
.fontSize(100)
.onAppear(()=>{
console.log("appear:" + item)
})
}
})
}.cachedCount(3) // 扩大数值appear日志范围会变大
}
}
class MyDataSource implements IDataSource {
data: number[] = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];
public totalCount(): number {
return this.data.length
}
public getData(index: number): any {
return this.data[index]
}
registerDataChangeListener(listener: DataChangeListener): void {
}
unregisterDataChangeListener(listener: DataChangeListener): void {
}
}
注意:
cachedCount的增加会增大UI的cpu、内存开销。使用时需要根据实际情况,综合性能和用户体验进行调整。
感谢各位大佬支持!!!
互三啦!!!