前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >【HarmonyOS之旅】基于ArkTS开发(二) -> UI开发四

【HarmonyOS之旅】基于ArkTS开发(二) -> UI开发四

作者头像
枫叶丹
发布2025-01-23 08:45:49
发布2025-01-23 08:45:49
9100
代码可运行
举报
文章被收录于专栏:C++C++
运行总次数:0
代码可运行

1 -> Web组件开发

1.1 -> 创建组件

在pages目录下创建一个Web组件。在Web组件中通过src指定引用的网页路径,controller为组件的控制器,通过controller绑定Web组件,用于调用Web组件的方法。

代码语言:javascript
代码运行次数:0
复制
// 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代码的能力。

代码语言:javascript
代码运行次数:0
复制
// 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页面加载完成。

代码语言:javascript
代码运行次数:0
复制
// 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方法,在控制台打印信息。

代码语言:javascript
代码运行次数:0
复制
// 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文件。

代码语言:javascript
代码运行次数:0
复制
<!-- 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组件,视频继续播放。

代码语言:javascript
代码运行次数:0
复制
// 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)
    }
  }
}
代码语言:javascript
代码运行次数:0
复制
<!-- 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 -> 推荐使用数据懒加载

在使用长列表时,如果直接采用循环渲染方式,如下所示,会一次性加载所有的列表元素,一方面会导致页面启动时间过长,影响用户体验,另一方面也会增加服务器的压力和流量,加重系统负担。

代码语言:javascript
代码运行次数:0
复制
@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个列表元素全部加载,这并非我们需要的,我们希望从数据源中按需迭代加载数据并创建相应组件,因此需要使用数据懒加载,如下所示:

代码语言:javascript
代码运行次数:0
复制
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通用属性控制组件的显隐状态时,仍存在组件的重新创建过程,造成性能上的损耗。

代码语言:javascript
代码运行次数:0
复制
@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属性变换,如下所示:

代码语言:javascript
代码运行次数:0
复制
@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导致二次布局,这会在一定程度上造成页面渲染上的性能劣化。

代码语言:javascript
代码运行次数:0
复制
@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二次布局带来的负面影响。

代码语言:javascript
代码运行次数:0
复制
@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的宽高尺寸,则默认全部加载,如下所示:

代码语言:javascript
代码运行次数:0
复制
@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子组件的宽高,如下所示:

代码语言:javascript
代码运行次数:0
复制
@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参数的例子:

代码语言:javascript
代码运行次数:0
复制
@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、内存开销。使用时需要根据实际情况,综合性能和用户体验进行调整。

感谢各位大佬支持!!!

互三啦!!!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-01-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 -> Web组件开发
    • 1.1 -> 创建组件
    • 1.2 -> 设置样式和属性
    • 1.3 -> 添加事件和方法
    • 1.4 -> 场景示例
  • 2 -> 性能提升的推荐方法
    • 2.1 -> 推荐使用数据懒加载
    • 2.2 -> 使用条件渲染替代显隐控制
    • 2.3 -> 使用Column/Row替代Flex
    • 2.4 -> 设置List组件的宽高
    • 2.5 -> 减少应用滑动白块
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档