DEV Community

ZHZL-m
ZHZL-m

Posted on

【Journey of HarmonyOS Next】Developing with ArkTS (2) - > UI Development 4

Image description

1 -> Web component development

1.1 -> Create a component

Create a web component in the pages directory. In the Web component, the reference web page path is specified by src, the controller is the controller of the component, and the web component is bound by the controller to invoke the methods of the web component.

// test.ets
@Entry
@Component
struct WebComponent {
  controller: WebController = new WebController();
  build() {
    Column() {
      Web({ src: 'https://www.example.com', controller: this.controller })
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

1.2 -> Set styles and properties

The use of web components requires the addition of rich page styles and functional attributes. Set height and padding styles to add height and padding to the web component, set the fileAccess property to add file access permissions to the web component, and set the javaScriptAccess property to true to enable the web component to execute JavaScript code.

// 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)
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

1.3 -> Add events and methods

Add an onProgressChange event to the web component, which calls back the progress value of the page loaded by the web engine. Assign the progress value to the value of the Progress component to control the status of the Progress component. When the progress is 100%, the Progress component is hidden, and the web page load is complete.

// 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)
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Add the runJavaScript method to the onPageEnd event. The onPageEnd event is a callback when the web page is loaded, and the runJavaScript method can execute the JavaScript script in the HTML. When the page is loaded, the onPageEnd event is triggered, and the test method in the HTML file is called to print the information in the console.

// 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)
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Create an HTML file in the main/resources/rawfile directory.

<!-- 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>
Enter fullscreen mode Exit fullscreen mode

1.4 -> Scenario Example

In this scenario, the video in the web component is dynamically played. First, embed the video resource in the HTML page, and then use the web component's controller to call the onActive and onInactive methods to activate and pause the page rendering. Click the onInactive button, the web page will stop rendering, and the video will be paused. Click the onActive button to activate the web component and the video continues to play.

// 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)
    }
  }
}
Enter fullscreen mode Exit fullscreen mode
<!-- 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>
Enter fullscreen mode Exit fullscreen mode

2 -> Recommended methods for performance improvement

2.1 -> It is recommended to use lazy loading of data

When using a long list, if you directly use the loop rendering method, as shown below, all list elements will be loaded at once, which will cause the page to start for too long on the one hand, which will affect the user experience, and on the other hand, it will also increase the pressure and traffic on the server, and increase the burden on the system.

@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())
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

The above code will load all 100 list elements on page load, which is not what we need, we want to iteratively load data from the data source and create the corresponding components on demand, so we need to use data lazy loading, as shown below:

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)
  }
}
Enter fullscreen mode Exit fullscreen mode
@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)
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

The above code initializes the loading of only three list elements on page load, and then adds one list element for each click on the list element.

2.2 -> Use conditional rendering instead of vis/hers control

As shown below, when the visibility generic property is used to control the visible and hidden state of a component, there is still a process of recreating the component, resulting in performance loss.

@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%')
  }
}
Enter fullscreen mode Exit fullscreen mode

To avoid this problem, you can use if conditional rendering instead of the visibility property transform, as shown below:

@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%')
  }
}
Enter fullscreen mode Exit fullscreen mode

2.3 -> Use Column/Row instead of Flex

Since the Flex container component has a shrink by default, it causes a secondary layout, which will cause performance degradation on page rendering to a certain extent.

@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)
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

The preceding code can replace Flex with Column and Row to avoid the negative impact of Flex secondary layout on the premise of ensuring that the page layout effect is the same.

@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)
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

2.4 -> Set the width and height of the List component

If you use the Scroll container component to nest List subcomponents, if you do not specify the width and height of the List, all of them are loaded by default, as shown below:

@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)
  }
}
Enter fullscreen mode Exit fullscreen mode

Therefore, in this scenario, it is recommended to set the width and height of the List subcomponent as follows:

@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)
  }
}
Enter fullscreen mode Exit fullscreen mode

2.5 -> Reduce the application of sliding white blocks

The application adjusts the loading range of the UI by increasing the cachedCount parameter of the List/Grid control. cachedCount indicates the number of items that are preloaded in the off-screen List/Grid.

If you need to request a web image, you can download the content in advance before the item slides to the screen, thus reducing the sliding white block.

Here's an example of using the cachedCount parameter:

@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 {
  }
}
Enter fullscreen mode Exit fullscreen mode

Note:

The increase of cachedCount increases the CPU and memory overhead of the UI. When you use it, you need to adjust it according to the actual situation, comprehensive performance and user experience.

Top comments (0)