1# 二级联动
2## 场景介绍
3列表的二级联动(Cascading List)是指根据一个列表(一级列表)的选择结果,来更新另一个列表(二级列表)的选项。这种联动可以使用户根据实际需求,快速定位到想要的选项,提高交互体验。例如,短视频中拍摄风格的选择、照片编辑时的场景的选择,本文即为大家介绍如何开发二级联动。
4## 效果呈现
5本例最终效果如下:
6
7![](figures/secondarylinkage.gif)
8
9## 运行环境
10本例基于以下环境开发,开发者也可以基于其他适配的版本进行开发:
11- IDE: DevEco Studio 3.1 Beta2
12- SDK: Ohos_sdk_public 3.2.11.9 (API Version 9 Release)
13## 实现思路
14- 数字标题(titles)以及下方的数字列表(contents)分组展示:通过两个List组件分别承载数字标题和数字项。
15- 滚动数字列表,上方数字标题也随之变动:通过List组件的onScrollIndex事件获取到当前滚动数字的索引,根据该索引计算出对应标题数字的索引,然后通过Scroller的scrollToIndex方法跳转到对应的数字标题,且通过Line组件为选中的标题添加下划线。
16- 点击数字标题,下方的数字列表也随之变化:首先获取到点击数字标题的索引,通过该索引计算出下方对应数字的起始项索引,然后通过scroller的scrollToIndex方法跳转到对应索引的数字项。
17
18## 开发步骤
19根据实现思路,具体实现步骤如下:
201. 首先构建列表数据,在records中记录数字列表中各个数字的首项索引值,具体代码块如下:
21    ```ts
22    ...
23    @State typeIndex: number = 0
24    private tmp: number = 0
25    private titles: Array<string> = ["1", "2", "3", "4", "5", "6", "7", "8", "9"]
26    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"
27        , "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",
28        "8", "8", "8", "8", "8", "9", "9", "9", "9", "9", "9", "9", "9", "9", "9", "9"]
29    private records: Array<number> = [0, 9, 21, 26, 29, 34, 35, 47, 52, 63]
30    private classifyScroller: Scroller = new Scroller();
31    private scroller: Scroller = new Scroller();
32    ...
33    ```
34    数字标题列表:具体代码块如下:
35    ```ts
36    ...
37    build() {
38      Column({ space: 0 }) {
39        List  ({ space: 50, scroller: this.classifyScroller, initialIndex: 0 }) {
40          ForEach(this.titles, (item, index) => {
41            ListItem() {
42              Column() {
43                Text(item)
44                  .fontSize(14)
45                ...
46              }
47            }
48          }
49          ...
50        }
51        .listDirection(Axis.Horizontal)
52        .height(50)
53      }
54    }
55    ```
56    数字列表,具体代码块如下:
57    ```ts
58    List({ space: 20, scroller: this.scroller }) {
59      ForEach(this.contents, (item, index) => {
60        ListItem() {
61          Column({ space: 5 }) {
62            Image($r("app.media.app_icon"))
63              .width(40)
64              .height(40)
65            Text(item)
66              .fontSize(12)
67          }
68          ...
69        }
70      }
71    }
72    .listDirection(Axis.Horizontal) //列表排列方向水平
73    .edgeEffect(EdgeEffect.None) //不支持滑动效果
74    ```
752. 数字标题的索引值判断,根据当前滚动数字的首项索引值计算出数字标题的索引,具体代码块如下:
76    ```ts
77    ...
78    findClassIndex(ind: number) { // 当前界面最左边图的索引值ind
79      let ans = 0
80      // 定义一个i 并进行遍历 this.records.length = 10
81      for (let i = 0; i < this.records.length; i++) {
82        // 判断ind在this.records中那两个临近索引值之间
83        if (ind >= this.records[i] && ind < this.records[i + 1]) {
84          ans = i
85          break
86        }
87      }
88      return ans
89    }
90    findItemIndex(ind: number) {
91      // 将ind重新赋值给类型标题列表的索引值
92      return this.records[ind]
93    }
94    ...
95    ```
96    通过Line组件构成标题下滑线,具体代码块如下:
97    ```ts
98    ...
99    if (this.typeIndex == index) {
100      Line()
101        //根据长短判断下划线
102        .width(item.length === 2 ? 25 : item.length === 3 ? 35 : 50)
103        .height(3)
104        .strokeWidth(20)
105        .strokeLineCap(LineCapStyle.Round)
106        .backgroundColor('#ffcf9861')
107    }
108    ...
109    ```
1103. 点击数字标题,数字列表随之滑动:首先获取到点击数字标题的索引,通过该索引计算出下方对应数字的起始项索引,然后通过scroller的scrollToIndex方法跳转到对应索引的数字项,具体代码块如下:
111    ```ts
112    ...
113    .onClick(() => {
114      this.typeIndex = index
115      this.classifyScroller.scrollToIndex(index)
116      let itemIndex = this.findItemIndex(index)
117      console.log("移动元素:" + itemIndex)
118      this.scroller.scrollToIndex(itemIndex)
119    })
120    ...
121    ```
1224. 数字列表的滑动或点击导致数字标题的变动:通过List组件中onScrollIndex事件获取的到屏幕中最左边数字的索引值start,然后通过该索引值计算出对应的数字标题的索引currentClassIndex,然后通过scrollToIndex控制数字标题跳转到对应索引处,具体代码块如下:
123    ```ts
124    ...
125    .onScrollIndex((start) => {
126      let currentClassIndex = this.findClassIndex(start)
127      console.log("找到的类索引为: " + currentClassIndex)
128      if (currentClassIndex != this.tmp) {
129        this.tmp = currentClassIndex
130        console.log("类别移动到索引: " + currentClassIndex)
131        this.typeIndex = currentClassIndex
132        this.classifyScroller.scrollToIndex(currentClassIndex)
133      }
134    })
135    ...
136    ```
137## 完整代码
138完整示例代码如下:
139```ts
140@Entry
141@Component
142struct TwoLevelLink {
143  @State typeIndex: number = 0
144  private tmp: number = 0
145  private titles: Array<string> = ["1", "2", "3", "4", "5", "6", "7", "8", "9"]
146  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"
147    , "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",
148    "8", "8", "8", "8", "8", "9", "9", "9", "9", "9", "9", "9", "9", "9", "9", "9"]
149  private colors: Array<string> = ["#18183C", "#E8A027", "#D4C3B3", "#A4AE77", "#A55D51", "#1F3B54", "#002EA6", "#FFE78F", "#FF770F"]
150  private records: Array<number> = [0, 9, 21, 26, 29, 34, 35, 47, 52, 63]
151  private classifyScroller: Scroller = new Scroller();
152  private scroller: Scroller = new Scroller();
153  // 根据数字列表索引计算对应数字标题的索引
154  findClassIndex(ind: number) {
155    let ans = 0
156    for (let i = 0; i < this.records.length; i++) {
157      if (ind >= this.records[i] && ind < this.records[i + 1]) {
158        ans = i
159        break
160      }
161    }
162    return ans
163  }
164  // 根据数字标题索引计算对应数字列表的索引
165  findItemIndex(ind: number) {
166    return this.records[ind]
167  }
168  build() {
169    Column({ space: 0 }) {
170      List  ({ space: 50, scroller: this.classifyScroller, initialIndex: 0 }) {
171        ForEach(this.titles, (item, index) => {
172          ListItem() {
173            Column() {
174              Text(item)
175                .fontSize(24)
176              if (this.typeIndex == index) {
177                Line()
178                  .width(item.length === 2 ? 25 : item.length === 3 ? 35 : 50)
179                  .height(3)
180                  .strokeWidth(20)
181                  .strokeLineCap(LineCapStyle.Round)
182                  .backgroundColor('#ffcf9861')
183              }
184            }
185            .onClick(() => {
186              this.typeIndex = index
187              this.classifyScroller.scrollToIndex(index)
188              let itemIndex = this.findItemIndex(index)
189              console.log("移动元素:" + itemIndex)
190              this.scroller.scrollToIndex(itemIndex)
191            })
192          }
193        })
194      }
195      .listDirection(Axis.Horizontal)
196      .height(50)
197      List({ space: 20, scroller: this.scroller }) {
198        ForEach(this.contents, (item, index) => {
199          ListItem() {
200            Column({ space: 5 }) {
201              Text(item)
202                .fontSize(30)
203                .fontColor(Color.White)
204            }
205            .width(60)
206            .height(60)
207            .backgroundColor(this.colors[item-1])
208            .justifyContent(FlexAlign.Center)
209            .onClick(() => {
210              this.scroller.scrollToIndex(index)
211            })
212          }
213        })
214      }
215      .listDirection(Axis.Horizontal) //列表排列方向水平
216      .edgeEffect(EdgeEffect.None) //不支持滑动效果
217      .onScrollIndex((start) => {
218        let currentClassIndex = this.findClassIndex(start)
219        console.log("找到的类索引为: " + currentClassIndex)
220        if (currentClassIndex != this.tmp) {
221          this.tmp = currentClassIndex
222          console.log("类别移动到索引: " + currentClassIndex)
223          this.typeIndex = currentClassIndex
224          this.classifyScroller.scrollToIndex(currentClassIndex)
225        }
226      })
227    }.width('100%').height('100%').backgroundColor(0xDCDCDC).padding({ top: 5 })
228  }
229}
230```
231## 参考
232[List](../application-dev/reference/apis-arkui/arkui-ts/ts-container-list.md)
233
234[Line](../application-dev/reference/apis-arkui/arkui-ts/ts-drawing-components-line.md)
235
236[Scroll](../application-dev/reference/apis-arkui/arkui-ts/ts-container-scroll.md)