如何解决拖拽功能和长按功能的冲突问题
==================

#### 问题现象

在List组件中，单个ListItem在同时设置拖拽功能与长按功能时，实际运行之后会产生冲突，如何解决单个ListItem拖拽功能和长按功能的冲突问题？

#### 效果预览

![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/67/v3/JaJqfeo3QrWTYWDvpj6z-g/zh-cn_image_0000002628398638.gif?HW-CC-KV=V1&HW-CC-Date=20260709T134036Z&HW-CC-Expire=86400&HW-CC-Sign=AC7356CACFDB8D291584B4E906F87BB1B89EA976F47BB6D444F210674AFB1AF7 "点击放大")

#### 背景知识

*   [支持统一拖拽](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-common-events-drag-event)提供了一种通过鼠标或手势触屏传递数据的机制，即从一个组件位置拖出数据并将其拖入到另一个组件位置，以触发响应。
*   [长按手势](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-gesture-events-single-gesture#长按手势longpressgesture)通过长按屏幕触发长按手势事件。
*   由于拖拽事件和长按手势都需要通过长按来触发，因此这种[组合手势](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-gesture-events-combined-gestures)类型会出现冲突，例如[List](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-container-list)组件中每个[ListItem](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-container-listitem)都设置单独的长按事件时，拖拽功能和长按手势功能就会出现冲突。
*   [Grid](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-container-grid)网格容器，由“行”和“列”分割的单元格所组成，通过指定“项目”所在的单元格做出各种各样的布局。
*   滑动手势[PanGesture](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-basic-gestures-pangesture)，能够提供自定义拖拽事件的功能。

#### 解决方案

由于在ListItem中设置单独的长按事件会与拖拽事件产生冲突，因此建议放弃List本身的拖拽事件，使用滑动手势PanGesture，自行实现拖拽过程的逻辑，然后再通过组合手势的功能，去实现其他的长按事件。

import curves from '@ohos.curves';

@Entry
@Component
struct Page {
  _//_ _元素数组_
  @State numbers: number\[\] \= \[\];
  _//_ _多列_
  private str: string \= '';
  row: number \= 4;
  _//_ _元素数组中最后一个元素的索引_
  @State lastIndex: number \= 0;
  @State dragItem: number \= -1;
  @State scaleItem: number \= -1;
  item: number \= -1;
  private dragRefOffsetX: number \= 0;
  private dragRefOffsetY: number \= 0;
  @State offsetX: number \= 0;
  @State offsetY: number \= 0;
  private FIX\_VP\_X: number \= 108;
  private FIX\_VP\_Y: number \= 120;

  aboutToAppear() {
    for (let i \= 1; i <\= 36; i++) {
      this.numbers.push(i);
    }
    this.lastIndex \= this.numbers.length \- 1;
   _ // 多列_
    for (let i \= 0; i < this.row; i++) {
      this.str \= this.str \+ '1fr ';
    }
  }

  itemMove(index: number, newIndex: number): void {
    console.info('index:' \+ index \+ ' newIndex:' \+ newIndex);
    if (!this.isDraggable(newIndex)) {
      return;
    }
    let tmp \= this.numbers.splice(index, 1);
    this.numbers.splice(newIndex, 0, tmp\[0\]);
  }

  _// 向下滑_
  down(index: number): void {
    if (!this.isDraggable(index \+ this.row)) {
      return;
    }
    this.offsetY \-= this.FIX\_VP\_Y;
    this.dragRefOffsetY += this.FIX\_VP\_Y;
   _ // 多列_
    this.itemMove(index, index \+ this.row);
  }

  _// 向下滑(右下角为空)_
  down2(index: number): void {
    if (!this.isDraggable(index \+ 3)) {
      return;
    }
    this.offsetY \-= this.FIX\_VP\_Y;
    this.dragRefOffsetY += this.FIX\_VP\_Y;
    this.itemMove(index, index \+ 3);
  }

  _// 向上滑_
  up(index: number): void {
    if (!this.isDraggable(index \- this.row)) {
      return;
    }
    this.offsetY += this.FIX\_VP\_Y;
    this.dragRefOffsetY \-= this.FIX\_VP\_Y;
    this.itemMove(index, index \- this.row);
  }

 _ // 向左滑_
  left(index: number): void {
    if (!this.isDraggable(index \- 1)) {
      return;
    }
    this.offsetX += this.FIX\_VP\_X;
    this.dragRefOffsetX \-= this.FIX\_VP\_X;
    this.itemMove(index, index \- 1);
  }

 _ // 向右滑_
  right(index: number): void {
    if (!this.isDraggable(index \+ 1)) {
      return;
    }
    this.offsetX \-= this.FIX\_VP\_X;
    this.dragRefOffsetX += this.FIX\_VP\_X;
    this.itemMove(index, index \+ 1);
  }

  _// 向右下滑_
  lowerRight(index: number): void {
    if (!this.isDraggable(index \+ this.row \+ 1)) {
      return;
    }
    this.offsetX \-= this.FIX\_VP\_X;
    this.dragRefOffsetX += this.FIX\_VP\_X;
    this.offsetY \-= this.FIX\_VP\_Y;
    this.dragRefOffsetY += this.FIX\_VP\_Y;
    this.itemMove(index, index \+ this.row \+ 1);
  }

  _// 向右上滑_
  upperRight(index: number): void {
    if (!this.isDraggable(index \- (this.row \- 1))) {
      return;
    }
    this.offsetX \-= this.FIX\_VP\_X;
    this.dragRefOffsetX += this.FIX\_VP\_X;
    this.offsetY += this.FIX\_VP\_Y;
    this.dragRefOffsetY \-= this.FIX\_VP\_Y;
    this.itemMove(index, index \- (this.row \- 1));
  }

 _ // 向左下滑_
  lowerLeft(index: number): void {
    if (!this.isDraggable(index \+ (this.row \- 1))) {
      return;
    }
    this.offsetX += this.FIX\_VP\_X;
    this.dragRefOffsetX \-= this.FIX\_VP\_X;
    this.offsetY \-= this.FIX\_VP\_Y;
    this.dragRefOffsetY += this.FIX\_VP\_Y;
    this.itemMove(index, index \+ (this.row \- 1));
  }

  _// 向左上滑_
  upperLeft(index: number): void {
    if (!this.isDraggable(index \- (this.row \+ 1))) {
      return;
    }
    this.offsetX += this.FIX\_VP\_X;
    this.dragRefOffsetX \-= this.FIX\_VP\_X;
    this.offsetY += this.FIX\_VP\_Y;
    this.dragRefOffsetY \-= this.FIX\_VP\_Y;
    this.itemMove(index, index \- (this.row \+ 1));
  }

 _ // 通过元素的索引，控制对应元素是否能移动排序_
  isDraggable(index: number): boolean {
    console.info(\`index: ${index}\`);
    return index \> -1; _//_ _恒成立，所有元素均可移动排序_
  }

  build() {
    Column() {
      Grid() {
        ForEach(this.numbers, (item: number) \=\> {
          GridItem() {
            Text(item \+ '')
              .fontSize(16)
              .width('100%')
              .textAlign(TextAlign.Center)
              .height(100)
              .borderRadius(10)
              .backgroundColor(0xFFFFFF)
              .shadow(this.scaleItem \== item ? {
                radius: 70,
                color: '#15000000',
                offsetX: 0,
                offsetY: 0
              } :
                {
                  radius: 0,
                  color: '#15000000',
                  offsetX: 0,
                  offsetY: 0
                })
              .animation({ curve: Curve.Sharp, duration: 300 });
          }
        _  // 添加震动_
          .onTouch(() \=\> {
          })
          .onAreaChange((oldVal, newVal) \=\> {
          _  // 多列_
            this.FIX\_VP\_X \= Math.round(newVal.width as number);
            this.FIX\_VP\_Y \= Math.round(newVal.height as number);
            console.info(\`oldVal:${JSON.stringify(oldVal)}\`);
          })
         _ // 指定固定GridItem不响应事件_
          .hitTestBehavior(this.isDraggable(this.numbers.indexOf(item)) ? HitTestMode.Default : HitTestMode.None)
          .scale({ x: this.scaleItem \== item ? 1.05 : 1, y: this.scaleItem \== item ? 1.05 : 1 })
          .zIndex(this.dragItem \== item ? 1 : 0)
          .translate(this.dragItem \== item ? { x: this.offsetX, y: this.offsetY } : { x: 0, y: 0 })
          .padding(10)
          .gesture(
            GestureGroup(GestureMode.Sequence,
              LongPressGesture({ repeat: true, duration: 50 })
                .onAction((event?: GestureEvent) \=\> {
                  console.info(\`event: ${event}\`);
                  this.getUIContext().animateTo({ curve: Curve.Friction, duration: 300 }, () \=\> {
                    this.scaleItem \= item;
                  });
                })
                .onActionEnd(() \=\> {
                  this.getUIContext().animateTo({ curve: Curve.Friction, duration: 300 }, () \=\> {
                    this.scaleItem \= -1;
                  });
                }),
              PanGesture({ fingers: 1, direction: null, distance: 0 })
                .onActionStart(() \=\> {
                  this.dragItem \= item;
                  this.dragRefOffsetX \= 0;
                  this.dragRefOffsetY \= 0;
                })
                .onActionUpdate((event: GestureEvent) \=\> {
                  this.offsetY \= event.offsetY \- this.dragRefOffsetY;
                  this.offsetX \= event.offsetX \- this.dragRefOffsetX;
                  console.info(\`移动过程中event.offsetY：${event.offsetY}\`,
                    \` this.dragRefOffsetY: ${this.dragRefOffsetY}\`, \`this.offsetY: ${this.offsetY}\`);
                  console.info(\`移动过程中event.offsetX：${event.offsetX}\`,
                    \` this.dragRefOffsetX: ${this.dragRefOffsetX}\`, \`this.offsetX: ${this.offsetX}\`);
                  this.getUIContext().animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () \=\> {
                    let index \= this.numbers.indexOf(this.dragItem);
                    console.info('index= ', index);
                    if (this.offsetY \>\= this.FIX\_VP\_Y / 2 &&
                      (this.offsetX <\= this.FIX\_VP\_X / 2 && this.offsetX \>\= -this.FIX\_VP\_X / 2)
                      && (index \+ this.row <\= this.lastIndex)) {
                      _//_ _向下滑_
                      this.down(index);
                    } else if (this.offsetY <\= -this.FIX\_VP\_Y / 2 &&
                      (this.offsetX <\= this.FIX\_VP\_X / 2 && this.offsetX \>\= -this.FIX\_VP\_X / 2)
                      && index \- this.row \>\= 0) {
                      _//_ _向上滑_
                      this.up(index);
                    } else if (this.offsetX \>\= this.FIX\_VP\_X / 2 &&
                      (this.offsetY <\= this.FIX\_VP\_Y / 2 && this.offsetY \>\= -this.FIX\_VP\_Y / 2)
                      && !(((index \- (this.row \- 1)) % this.row \== 0) || index \== this.lastIndex)) {
                      _//_ _向右滑_
                      this.right(index);
                    } else if (this.offsetX <\= -this.FIX\_VP\_X / 2 &&
                      (this.offsetY <\= this.FIX\_VP\_Y / 2 && this.offsetY \>\= -this.FIX\_VP\_Y / 2)
                      && !(index % this.row \== 0)) {
                    _  // 向左滑_
                      this.left(index);
                    } else if (this.offsetX \>\= this.FIX\_VP\_X / 2 && this.offsetY \>\= this.FIX\_VP\_Y / 2
                      && ((index \+ this.row \+ 1 <\= this.lastIndex && !((index \- (this.row \- 1)) % this.row \== 0)) ||
                        !((index \- (this.row \- 1)) % this.row \== 0))) {
                      _//_ _向右下滑_
                      this.lowerRight(index);
                    } else if (this.offsetX \>\= this.FIX\_VP\_X / 2 && this.offsetY <\= -this.FIX\_VP\_Y / 2
                      && !((index \- this.row < 0) || ((index \- (this.row \- 1)) % this.row \== 0))) {
                     _ // 向右上滑_
                      this.upperRight(index);
                    } else if (this.offsetX <\= -this.FIX\_VP\_X / 2 && this.offsetY \>\= this.FIX\_VP\_Y / 2
                      && (!(index % this.row \== 0) && (index \+ (this.row \- 1) <\= this.lastIndex))) {
                    _  // 向左下滑_
                      this.lowerLeft(index);
                    } else if (this.offsetX <\= -this.FIX\_VP\_X / 2 && this.offsetY <\= -this.FIX\_VP\_Y / 2
                      && !((index <\= this.row \- 1) || (index % this.row \== 0))) {
                      _//_ _向左上滑_
                      this.upperLeft(index);
                    } else if (this.offsetX \>\= this.FIX\_VP\_X / 2 && this.offsetY \>\= this.FIX\_VP\_Y / 2
                      && (index \== this.lastIndex)) {
                     _ // 向右下滑(右下角为空)_
                      this.down2(index);
                    }
                  });
                })
                .onActionEnd(() \=\> {
                  this.getUIContext().animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () \=\> {
                    this.dragItem \= -1;
                  });
                  this.getUIContext().animateTo({
                    curve: curves.interpolatingSpring(14, 1, 170, 17), delay: 150
                  }, () \=\> {
                    this.scaleItem \= -1;
                  });
                })
            )
              .onCancel(() \=\> {
                this.getUIContext().animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () \=\> {
                  this.dragItem \= -1;
                });
                this.getUIContext().animateTo({
                  curve: curves.interpolatingSpring(14, 1, 170, 17)
                }, () \=\> {
                  this.scaleItem \= -1;
                });
              })
          );
        }, (item: number) \=\> item.toString());
      }
      .width('90%')
      .editMode(true)
      .scrollBar(BarState.Off)
      _//_ _多列_
      .columnsTemplate(this.str);
    }
    .expandSafeArea(\[SafeAreaType.SYSTEM\], \[SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM\])
    .width('100%').height('100%').backgroundColor('#f1f3f5').padding({ top: 5 });
  }
}

#### 常见FAQ

Q：Grid上下左右拖拽动画如何实现？

A：在跨行或跨列场景下，可以通过自定义Grid布局、自定义手势和显式动画来实现拖拽交换的效果。