1## 如何在网格Grid中通过拖拽交换子组件位置
2
3### 场景说明
4
5在使用网格Grid的应用中,可以通过拖拽子组件GridItem的方式,交换子组件的显示位置。
6
7### 效果呈现
8
9本示例在模拟器中显示的最终效果如下(预览器中显示效果略有差异):
10
11![listitem-slide](figures/griditem-drag.gif)
12
13### 运行环境
14
15- IDE:DevEco Studio 3.1 Beta2
16- SDK:Ohos_sdk_public 3.2.11.9 (API Version 9 Release)
17
18### 实现原理
19
201. 设置Grid的editMode属性为true,使Grid进入编辑模式,从而可以拖拽Grid组件内部GridItem。
21
222. 在Grid的相关拖拽事件中进行拖拽逻辑处理:
23
24   1. 在onItemDragStart事件中显示拖拽过程中的图片,即被拖拽的GridItem。
25
26   2. 在onItemDrop事件中根据拖拽前后的位置,完成两个GridItem位置交换的逻辑。
27
28### 开发步骤
29
301. 构建Grid组件及子组件GridItem,开启Grid组件的editMode属性。
31
32   ```ts
33   build() {
34     Column({ space: 5 }) {
35       Grid(this.scroller) {
36         ForEach(this.numbers, (day: string) => {
37           GridItem() {
38             ...
39           }
40         })
41       }
42       .editMode(true)
43       ...
44
45     }.width('100%').margin({ top: 5 })
46   }
47   ```
48
492. 当长按GridItem时触发onItemDragStart事件,在该事件中提供被拖拽GridItem的显示逻辑。
50
51   ```ts
52   .onItemDragStart((event: ItemDragInfo, itemIndex: number) => {
53     return this.pixelMapBuilder()
54   })
55   ```
56
57   其中,pixelMapBuilder构造拖拽过程中显示的图片,即被拖拽的GridItem。
58
59   ```ts
60   @State text: string = 'drag'
61
62   @Builder pixelMapBuilder() {
63     Column() {
64       Text(this.text)
65         .fontSize(16)
66         .backgroundColor(0xF9CF93)
67         .width(80)
68         .height(80)
69         .textAlign(TextAlign.Center)
70     }
71   }
72   ```
73
74   拖拽过程中GridItem显示的内容,在触摸事件发生时进行传递。
75
76   ```ts
77   ForEach(this.numbers, (day: string) => {
78     GridItem() {
79       Text(day)
80         ...
81         .onTouch((event: TouchEvent) => {
82           if (event.type === TouchType.Down) {
83             this.text = day
84           }
85         })
86     }
87   })
88   ```
89
903. 停止拖拽时触发onItemDrop事件,在该事件中完成两个GridItem位置交换的逻辑。
91
92   为了防止GridItem被拖拽到空白的区域,在交换之前判断拖拽插入的位置是否超出当前已有内容的范围:
93
94   ```ts
95   .onItemDrop((event: ItemDragInfo, itemIndex: number, insertIndex: number, isSuccess: boolean) => {
96     if(insertIndex < this.numbers.length){
97       this.changeIndex(itemIndex, insertIndex)
98     }
99   })
100   ```
101
102   其中,changeIndex为具体交换数组元素位置的逻辑:
103
104   ```ts
105   changeIndex(index1: number, index2: number) {
106     [this.numbers[index1], this.numbers[index2]] = [this.numbers[index2], this.numbers[index1]];
107   }
108   ```
109
110### 完整代码
111
112通过上述步骤可以完成整个示例的开发,完整代码如下:
113
114```ts
115@Entry
116@Component
117struct Index {
118  @State numbers: String[] = []
119  scroller: Scroller = new Scroller()
120  @State text: string = 'drag'
121
122  //拖拽过程中展示的样式
123  @Builder pixelMapBuilder() {
124    Column() {
125      Text(this.text)
126        .fontSize(16)
127        .backgroundColor(0xF9CF93)
128        .width(80)
129        .height(80)
130        .textAlign(TextAlign.Center)
131    }
132  }
133
134  aboutToAppear() {
135    for (let i = 1;i <= 15; i++) {
136      this.numbers.push(i + '')
137    }
138  }
139
140  //交换数组中元素位置
141  changeIndex(index1: number, index2: number) {
142    [this.numbers[index1], this.numbers[index2]] = [this.numbers[index2], this.numbers[index1]];
143  }
144
145  build() {
146    Column({ space: 5 }) {
147      Grid(this.scroller) {
148        ForEach(this.numbers, (day: string) => {
149          GridItem() {
150            Text(day)
151              .fontSize(16)
152              .backgroundColor(0xF9CF93)
153              .width(80)
154              .height(80)
155              .textAlign(TextAlign.Center)
156              .onTouch((event: TouchEvent) => {
157                if (event.type === TouchType.Down) {
158                  this.text = day
159                }
160              })
161          }
162        })
163      }
164      .columnsTemplate('1fr 1fr 1fr')
165      .columnsGap(10)
166      .rowsGap(10)
167      .onScrollIndex((first: number) => {
168        console.info(first.toString())
169      })
170      .width('90%')
171      .backgroundColor(0xFAEEE0)
172      .height('100%')
173      //设置Grid是否进入编辑模式,进入编辑模式可以拖拽Grid组件内部GridItem
174      .editMode(true)
175      //第一次拖拽此事件绑定的组件时,触发回调
176      .onItemDragStart((event: ItemDragInfo, itemIndex: number) => {
177        //设置拖拽过程中显示的图片
178        return this.pixelMapBuilder()
179      })
180      //绑定此事件的组件可作为拖拽释放目标,当在本组件范围内停止拖拽行为时,触发回调
181      //itemIndex为拖拽起始位置,insertIndex为拖拽插入位置
182      .onItemDrop((event: ItemDragInfo, itemIndex: number, insertIndex: number, isSuccess: boolean) => {
183        //不支持拖拽到已有内容以外的位置
184        if(insertIndex < this.numbers.length){
185          this.changeIndex(itemIndex, insertIndex)
186        }
187      })
188    }.width('100%').margin({ top: 5 })
189  }
190}
191```