1# 导航栏的使用编辑
2## 场景描述
3在大部分应用中导航栏的使用是非常普遍的,用户可以通过拖拽导航栏里的页签标题变换位置来调整导航栏的顺序。本文即为大家介绍导航栏的使用编辑。
4
5## 效果呈现
6本例最终效果图如下:
7
8![](figures/Navigation.gif)
9
10## 运行环境
11
12本例基于以下环境开发,开发者也可以基于其他适配的版本进行开发:
13- IDE: DevEco Studio 4.0 Release
14- SDK: Ohos_sdk_public 4.0.10.13 (API Version 10 Release)
15
16## 实现思路
17
18本例包含的关键操作及其实现方案如下:
191. 自定义弹窗的展示:通过点击分类实现自定义弹窗的弹出
202. 自定义弹窗内部元素的排列、拖拽和换位:通过Grid()和GridItem()显示页签标题的网格排列,通过onItemDragStart、onItemDrop、onDrag事件完成拖拽换位。
213. Tabs页签的顺序与网格元素顺序同步:通过@State与@Link父子组件双向同步。
22
23## 开发步骤
24本例详细开发步骤如下,开发步骤仅展示相关步骤代码,全量代码请参考完整代码章节的内容。
251. UI框架的构建:通过Stack、Tabs、Row等关键组件将UI框架搭建起来。具体代码如下:
26    ```ts
27    ...
28    Stack({ alignContent: Alignment.TopEnd }) {
29      Tabs() {
30        ForEach(this.tabBarArray, (item: string, index: number) => {
31          TabContent() {
32          }.tabBar(this.TabBuilder(index))
33        }, (item: string) => JSON.stringify(item))
34        TabContent() {
35        }
36        .tabBar(this.TabBuilder2(8))
37      }
38      .fadingEdge(true)
39      .barHeight('7.2%')
40      .barMode(BarMode.Scrollable)
41      .barWidth('100%')
42      .onChange((index: number) => {
43        this.currentIndex = index;
44      })
45      .vertical(false)
46
47      Row() {
48        Divider()
49          .vertical(true)
50          .width(1)
51          .height(18)
52          .color(Color.Black)
53        Image($r('app.media.ic_public_more'))
54          .width(16)
55          .height(16)
56          .objectFit(ImageFit.Contain)
57          .margin({ left: 4 })
58        Text('分类')
59          .fontSize(18)
60          .opacity(0.8)
61          .fontColor(Color.Black)
62          .margin({ left: 2 })
63      }.width('17%').height('7.2%').backgroundColor(Color.White)
64      ...
65    ```
662. 自定义弹窗的构建:首先将自定义弹窗样式设置为true,设置自定义弹窗的高度、显示位置、偏移量等。具体代码块如下:
67    ```ts
68    @CustomDialog
69    struct CustomDialogExample{
70      controller?:CustomDialogController
71      @Link tabBarArray:Array<string>
72      build(){
73        Column(){
74          Row({space:180}){
75            Text('导航')
76              .fontSize(30)
77              .fontColor(Color.Black)
78            Text('完成')
79              .fontSize(20)
80              .fontColor(Color.Green)
81              .onClick(()=>{
82                this.controller.close()
83              })
84          }
85          Divider()
86            .width('100%')
87            .height(5)
88            .color(Color.Black)
89          Text('我的频道')
90            .fontSize(20)
91            .fontColor(Color.Black)
92            .fontWeight(500)
93            .textAlign(TextAlign.Start)
94          Grid1({items:$tabBarArray})
95        }.width('80%').height('100%').backgroundColor(Color.White).padding({top:60})
96      }
97    }
98    ...
99    dialogController:CustomDialogController|null = new CustomDialogController({
100      builder:CustomDialogExample({tabBarArray:$tabBarArray}),
101      // 允许点击遮蔽层退出
102      autoCancel:true,
103      // 弹窗在竖直方向上的对齐方式:右中对齐
104      alignment:DialogAlignment.CenterEnd,
105      offset:{dx:35,dy:16},
106      // 弹窗容器样式允许自定义
107      customStyle:true,
108      backgroundColor:Color.White,
109      cornerRadius:0
110    })
111    ```
1123. 自定义弹窗内的拖拽和换位:设置属性editMode(true)设置Grid进入编辑模式,进入编辑模式可以进行拖拽Grid组件内部GridItem。在onItemDragStart回调中设置拖拽过程中显示的图片。在onItemDrop中获取拖拽起始位置,和拖入位置,在onDrag回调中完成交换数组位置逻辑。具体代码块如下:
113    ```ts
114    @Component
115    export struct Grid1{
116      @Link items:Array<string>
117      @State text:string= 'drag'
118
119      @Builder pixelMapBuilder(){
120        Column(){
121          Text(this.text)
122            .fontSize(16)
123            .backgroundColor(0xF9CF93)
124            .width(80)
125            .height(80)
126            .textAlign(TextAlign.Center)
127        }
128      }
129
130      changeIndex(index1:number,index2:number){
131        let temp :Resource|string;
132        temp = this.items[index1];
133        this.items[index1] = this.items[index2];
134        this.items[index2] = temp;
135      }
136      build(){
137        Column({space:5}){
138          Grid(){
139            ForEach(this.items,(name:string,index:number)=>{
140              GridItem(){
141                Text(name)
142                  .fontSize(16)
143                  .backgroundColor(0xF9CF93)
144                  .width(80)
145                  .height(80)
146                  .textAlign(TextAlign.Center)
147              }
148            })
149          }
150          .columnsTemplate('1fr 1fr 1fr')
151          .columnsGap(10)
152          .rowsGap(10)
153          .onScrollIndex((first:number)=>{
154            console.info(first.toString())
155          })
156          .width('90%')
157          .backgroundColor(Color.White)
158          .height('100%')
159          .editMode(true)
160          .onItemDragStart((event:ItemDragInfo,itemIndex:number)=>{
161            this.text = this.items[itemIndex]
162            return this.pixelMapBuilder()
163          })
164          .onItemDrop((event:ItemDragInfo,itemIndex:number,insertIndex:number,isSuccess:boolean)=>{
165            if(insertIndex < this.items.length){
166              this.changeIndex(itemIndex,insertIndex)
167            }
168          })
169        }.width('100%')
170        .margin({top:5})
171      }
172    }
173    ```
174
175## 完整代码
176完整示例代码如下:
177```ts
178// Index.ets
179import {Grid1} from './Grid'
180
181@CustomDialog
182struct CustomDialogExample{
183  controller:CustomDialogController
184  @Link tabBarArray:Array<string>
185  build(){
186    Column(){
187      Row({space:180}){
188        Text('导航')
189          .fontSize(30)
190          .fontColor(Color.Black)
191        Text('完成')
192          .fontSize(20)
193          .fontColor(Color.Green)
194          .onClick(()=>{
195            this.controller.close()
196          })
197      }
198      Divider()
199        .width('100%')
200        .height(5)
201        .color(Color.Black)
202      Text('我的频道')
203        .fontSize(20)
204        .fontColor(Color.Black)
205        .fontWeight(500)
206        .textAlign(TextAlign.Start)
207      Grid1({items:$tabBarArray})
208    }.width('80%').height('100%').backgroundColor(Color.White).padding({top:60})
209  }
210}
211
212@Entry
213@Component
214struct TabBar {
215  dialogController:CustomDialogController|null = new CustomDialogController({
216    builder:CustomDialogExample({tabBarArray:$tabBarArray}),
217    autoCancel:true,
218    offset:{dx:0,dy:0},
219    alignment:DialogAlignment.CenterEnd,
220    customStyle:true,
221    backgroundColor:Color.White,
222    cornerRadius:0
223  })
224  @State currentIndex:number = 0;
225  @State tabBarArray:Array<string> = ['关注','推荐','男生','女生','历史','传记','人性','动漫']
226
227  @Builder TabBuilder(index:number){
228    Column(){
229      Text(this.tabBarArray[index])
230        .height('100%')
231        .padding({left:'5%',right:'5%'})
232        .fontSize(this.currentIndex === index ? 24 : 18)
233        .fontWeight(this.currentIndex === index ? 700 : 400)
234        .fontColor(Color.Black)
235    }
236  }
237  @Builder TabBuilder2(index:number){
238    Column(){
239      Text('')
240        .height('100%')
241        .padding({left:'5%',right:'10%'})
242        .fontSize(this.currentIndex === index ? 24 : 18)
243        .fontWeight(this.currentIndex === index ? 700 : 400)
244        .fontColor(Color.Black)
245    }
246  }
247
248  build() {
249    Stack({ alignContent: Alignment.TopEnd }) {
250      Tabs() {
251        ForEach(this.tabBarArray, (item: string, index: number) => {
252          TabContent() {
253          }
254          .tabBar(this.TabBuilder(index))
255        }, (item: string) => JSON.stringify(item))
256        TabContent() {
257        }
258        .tabBar(this.TabBuilder2(8))
259      }
260      .fadingEdge(true)
261      .barHeight('7.2%')
262      .barMode(BarMode.Scrollable)
263      .barWidth('100%')
264      .onChange((index: number) => {
265        this.currentIndex = index;
266      })
267      .vertical(false)
268
269      Row() {
270        Divider()
271          .vertical(true)
272          .width(1)
273          .height(18)
274          .color(Color.Black)
275        Image($r('app.media.ic_public_more'))
276          .width(16)
277          .height(16)
278          .objectFit(ImageFit.Contain)
279          .margin({ left: 4 })
280        Text('分类')
281          .fontSize(18)
282          .opacity(0.8)
283          .fontColor(Color.Black)
284          .margin({ left: 2 })
285      }.width('17%').height('7.2%').backgroundColor(Color.White)
286      .onClick(() => {
287        if (this.dialogController != null) {
288          this.dialogController.open()
289        }
290      })
291    }.width('100%')
292  }
293}
294```
295```ts
296// Grid.ets
297@Component
298export struct Grid1{
299  @Link items:Array<string>
300  @State text:string= 'drag'
301
302  @Builder pixelMapBuilder(){
303    Column(){
304      Text(this.text)
305        .fontSize(16)
306        .backgroundColor(0xF9CF93)
307        .width(80)
308        .height(80)
309        .textAlign(TextAlign.Center)
310    }
311  }
312
313  changeIndex(index1:number,index2:number){
314    let temp :Resource|string;
315    temp = this.items[index1];
316    this.items[index1] = this.items[index2];
317    this.items[index2] = temp;
318  }
319  build(){
320    Column({space:5}){
321      Grid(){
322        ForEach(this.items,(name:string,index:number)=>{
323          GridItem(){
324            Text(name)
325              .fontSize(16)
326              .backgroundColor(0xF9CF93)
327              .width(80)
328              .height(80)
329              .textAlign(TextAlign.Center)
330          }
331        })
332      }
333      .columnsTemplate('1fr 1fr 1fr')
334      .columnsGap(10)
335      .rowsGap(10)
336      .onScrollIndex((first:number)=>{
337        console.info(first.toString())
338      })
339      .width('90%')
340      .backgroundColor(Color.White)
341      .height('100%')
342      .editMode(true)
343      .onItemDragStart((event:ItemDragInfo,itemIndex:number)=>{
344        this.text = this.items[itemIndex]
345        return this.pixelMapBuilder()
346      })
347      .onItemDrop((event:ItemDragInfo,itemIndex:number,insertIndex:number,isSuccess:boolean)=>{
348        if(insertIndex < this.items.length){
349          this.changeIndex(itemIndex,insertIndex)
350        }
351      })
352    }.width('100%')
353    .margin({top:5})
354  }
355}
356```
357## 参考
358[Tabs](../application-dev/reference/apis-arkui/arkui-ts/ts-container-tabs.md)
359
360[Grid](../application-dev/reference/apis-arkui/arkui-ts/ts-container-grid.md)
361
362[自定义弹窗](../application-dev/reference/apis-arkui/arkui-ts/ts-methods-custom-dialog-box.md)
363
364[@Link装饰器:父子双向同步](../application-dev/quick-start/arkts-link.md)