laitimes

How OpenHarmony implements second-level linkage?

author:Not bald programmer
How OpenHarmony implements second-level linkage?
Cascading List is the option to update one list (Level 1 list) based on the results of another list (Level 2 list). This linkage allows users to quickly locate the desired option according to their actual needs and improve the interactive experience. For example, if you choose the shooting style in a short video or the scene when editing a photo, this article will introduce you how to develop a secondary linkage.

Effect rendering

The final result of this example is as follows:

How OpenHarmony implements second-level linkage?

Operating environment

This example is developed based on the following environment, and developers can also develop based on other adapted versions:

  • IDE: DevEco Studio 3.1 Beta2
  • SDK: Ohos_sdk_public 3.2.11.9 (API Version 9 Release)

Implement ideas

  • Number titles and the following number list (contents) are displayed in groups: two list components are used to carry the number titles and number items respectively.
  • Scroll through the list of numbers, and the title of the upper number will also change: obtain the index of the current scrolling number through the onScrollIndex event of the List component, calculate the index of the corresponding title number according to the index, and then use the scrollToIndex method of the Roller to jump to the corresponding number title, and add an underscore to the selected title through the Line component.
  • Click on the number title, and the number list below will also change: first, get the index of the number title clicked, calculate the starting item index of the corresponding number below through the index, and then jump to the number item of the corresponding index through the scroller's scrollToIndex method.

Development steps

According to the implementation idea, the specific implementation steps are as follows:

1. First, build the list data, and record the first index value of each number in the number list in the records, the specific code block is as follows:

...
@State typeIndex: number = 0
private tmp: number = 0
private titles: Array<string> = ["1", "2", "3", "4", "5", "6", "7", "8", "9"]
private contents: Array<string> = ["1", "1", "1", "1", "1", "1", "1", "1", "1", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "3"
    , "3", "3", "3", "3", "4", "4", "4", "5", "5", "5", "5", "5", "6", "7", "7", "7", "7", "7", "7", "7", "7", "7", "7", "7", "7",
    "8", "8", "8", "8", "8", "9", "9", "9", "9", "9", "9", "9", "9", "9", "9", "9"]
private records: Array<number> = [0, 9, 21, 26, 29, 34, 35, 47, 52, 63]
private classifyScroller: Scroller = new Scroller();
private scroller: Scroller = new Scroller();
...           

List of numeric headers: The specific code blocks are as follows:

...
build() {
  Column({ space: 0 }) {
    List  ({ space: 50, scroller: this.classifyScroller, initialIndex: 0 }) {
      ForEach(this.titles, (item, index) => {
        ListItem() {
          Column() {
            Text(item)
              .fontSize(14)
            ...  
          }
        }
      }
      ...
    }
    .listDirection(Axis.Horizontal)
    .height(50)
  }
}           

A list of numbers, with the following code blocks:

List({ space: 20, scroller: this.scroller }) {
  ForEach(this.contents, (item, index) => {
    ListItem() {
      Column({ space: 5 }) {
        Image($r("app.media.app_icon"))
          .width(40)
          .height(40)
        Text(item)
          .fontSize(12)
      }
      ...
    }
  }
}
.listDirection(Axis.Horizontal) //列表排列方向水平
.edgeEffect(EdgeEffect.None) //不支持滑动效果           

2. Judgment of the index value of the number title, the index of the number title is calculated according to the first index value of the current scrolling number, and the specific code block is as follows:

...
findClassIndex(ind: number) { // 当前界面最左边图的索引值ind
  let ans = 0
  // 定义一个i 并进行遍历 this.records.length = 10
  for (let i = 0; i < this.records.length; i++) { 
    // 判断ind在this.records中那两个临近索引值之间
    if (ind >= this.records[i] && ind < this.records[i + 1]) {
      ans = i
      break
    }
  }
  return ans
}
findItemIndex(ind: number) { 
  // 将ind重新赋值给类型标题列表的索引值
  return this.records[ind]
}
...           

The Line component is used to form a header slideline, and the specific code block is as follows:

...
if (this.typeIndex == index) {
  Line()
    //根据长短判断下划线
    .width(item.length === 2 ? 25 : item.length === 3 ? 35 : 50)
    .height(3)
    .strokeWidth(20)
    .strokeLineCap(LineCapStyle.Round)
    .backgroundColor('#ffcf9861')
}
...           

3. Click on the number title, and the number list will slide accordingly: first, get the index of the number title clicked, calculate the starting item index of the corresponding number below through the index, and then jump to the number item of the corresponding index through the scrollToIndex method of scroller, the specific code block is as follows:

...
.onClick(() => {
  this.typeIndex = index
  this.classifyScroller.scrollToIndex(index)
  let itemIndex = this.findItemIndex(index)
  console.log("移动元素:" + itemIndex)
  this.scroller.scrollToIndex(itemIndex)
})
...           

4. Sliding or clicking on the number list causes the number title to change: start the index value of the leftmost number on the screen obtained through the onScrollIndex event in the List component, and then calculate the index of the corresponding number title currentClassIndex through the index value, and then control the number title to jump to the corresponding index through scrollToIndex, the specific code block is as follows:

...
.onScrollIndex((start) => {
  let currentClassIndex = this.findClassIndex(start)
  console.log("找到的类索引为: " + currentClassIndex)
  if (currentClassIndex != this.tmp) {
    this.tmp = currentClassIndex
    console.log("类别移动到索引: " + currentClassIndex)
    this.typeIndex = currentClassIndex
    this.classifyScroller.scrollToIndex(currentClassIndex)
  }
})
...           

Full code

The full sample code is as follows:

@Entry
@Component
struct TwoLevelLink {
  @State typeIndex: number = 0
  private tmp: number = 0
  private titles: Array<string> = ["1", "2", "3", "4", "5", "6", "7", "8", "9"]
  private contents: Array<string> = ["1", "1", "1", "1", "1", "1", "1", "1", "1", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "3"
    , "3", "3", "3", "3", "4", "4", "4", "5", "5", "5", "5", "5", "6", "7", "7", "7", "7", "7", "7", "7", "7", "7", "7", "7", "7",
    "8", "8", "8", "8", "8", "9", "9", "9", "9", "9", "9", "9", "9", "9", "9", "9"]
  private colors: Array<string> = ["#18183C", "#E8A027", "#D4C3B3", "#A4AE77", "#A55D51", "#1F3B54", "#002EA6", "#FFE78F", "#FF770F"]
  private records: Array<number> = [0, 9, 21, 26, 29, 34, 35, 47, 52, 63]
  private classifyScroller: Scroller = new Scroller();
  private scroller: Scroller = new Scroller();
  // 根据数字列表索引计算对应数字标题的索引
  findClassIndex(ind: number) {
    let ans = 0
    for (let i = 0; i < this.records.length; i++) {
      if (ind >= this.records[i] && ind < this.records[i + 1]) {
        ans = i
        break
      }
    }
    return ans
  }
  // 根据数字标题索引计算对应数字列表的索引
  findItemIndex(ind: number) {
    return this.records[ind]
  }
  build() {
    Column({ space: 0 }) {
      List  ({ space: 50, scroller: this.classifyScroller, initialIndex: 0 }) {
        ForEach(this.titles, (item, index) => {
          ListItem() {
            Column() {
              Text(item)
                .fontSize(24)
              if (this.typeIndex == index) {
                Line()
                  .width(item.length === 2 ? 25 : item.length === 3 ? 35 : 50)
                  .height(3)
                  .strokeWidth(20)
                  .strokeLineCap(LineCapStyle.Round)
                  .backgroundColor('#ffcf9861')
              }
            }
            .onClick(() => {
              this.typeIndex = index
              this.classifyScroller.scrollToIndex(index)
              let itemIndex = this.findItemIndex(index)
              console.log("移动元素:" + itemIndex)
              this.scroller.scrollToIndex(itemIndex)
            })
          }
        })
      }
      .listDirection(Axis.Horizontal)
      .height(50)
      List({ space: 20, scroller: this.scroller }) {
        ForEach(this.contents, (item, index) => {
          ListItem() {
            Column({ space: 5 }) {
              Text(item)
                .fontSize(30)
                .fontColor(Color.White)
            }
            .width(60)
            .height(60)
            .backgroundColor(this.colors[item-1])
            .justifyContent(FlexAlign.Center)
            .onClick(() => {
              this.scroller.scrollToIndex(index)
            })
          }
        })
      }
      .listDirection(Axis.Horizontal) //列表排列方向水平
      .edgeEffect(EdgeEffect.None) //不支持滑动效果
      .onScrollIndex((start) => {
        let currentClassIndex = this.findClassIndex(start)
        console.log("找到的类索引为: " + currentClassIndex)
        if (currentClassIndex != this.tmp) {
          this.tmp = currentClassIndex
          console.log("类别移动到索引: " + currentClassIndex)
          this.typeIndex = currentClassIndex
          this.classifyScroller.scrollToIndex(currentClassIndex)
        }
      })
    }.width('100%').height('100%').backgroundColor(0xDCDCDC).padding({ top: 5 })
  }
}