1# 导航栏的使用编辑 2## 场景描述 3在大部分应用中导航栏的使用是非常普遍的,用户可以通过拖拽导航栏里的页签标题变换位置来调整导航栏的顺序。本文即为大家介绍导航栏的使用编辑。 4 5## 效果呈现 6本例最终效果图如下: 7 8 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)