HarmonyOS鸿蒙Next中list嵌套grid,grid的item有手势拖拽,如何设置可以拖拽超出grid的大小

HarmonyOS鸿蒙Next中list嵌套grid,grid的item有手势拖拽,如何设置可以拖拽超出grid的大小

代码如下,如何让子组件的拖拽范围可以超出父组件的边界大小,由于业务需求,网格大小不固定,所以不能使用官方提供的定onItemDragStartonItemDrop

只能使用通用手势拖拽。

import { GridPage } from './GridPage'

@ComponentV2
struct ListViewPage {
  @Local listData: string[] = []

  aboutToAppear(): void {
    for (let index = 0; index < 9; index++) {
      this.listData.push('itemIndex=' + index)
    }
  }

  build() {
    List() {
      Repeat(this.listData)
        .each((obj: RepeatItem<string>) => {
          ListItem() {
            Column() {
              Text(obj.item).padding(10)
              GridPage()
            }
          }
        })
        .virtualScroll()
        .key((item: string) => item)
    }
    .height('100%')
    .width('100%')
  }
}
import { curves } from '@kit.ArkUI';

@Component
export struct GridPage {
  //元素数组
  @State numbers: number[] = [];
  @State row: number = 4;
  //元素数组中最后一个元素的索引
  @State lastIndex: number = 0;
  @State dragItem: number = -1;
  @State scaleItem: number = -1;
  @State item: number = -1;
  @State offsetX: number = 0;
  @State offsetY: number = 0;
  // row 设置网格列数
  private str: string = '';
  private dragRefOffsetx: number = 0;
  private dragRefOffsety: number = 0;
  private FIX_VP_X: number = 108;
  private FIX_VP_Y: number = 120;

  aboutToAppear() {
    for (let i = 1; i <= 8; 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 {
    if (!this.isDraggable(newIndex)) {
      return;
    }
    let tmp = this.numbers.splice(index, 1);
    this.numbers.splice(newIndex, 0, tmp[0]);
  }

  //向下滑
  down(index: number): void {
    // 指定固定GridItem不响应事件
    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 {
    return index > -1; //恒成立,所有元素均可移动排序
  }

  build() {
    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
            })
        }
        .onAreaChange((_oldVal, newVal) => {
          // 多列
          this.FIX_VP_X = Math.round(newVal.width as number);
          this.FIX_VP_Y = Math.round(newVal.height as number);
        })
        // 指定固定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
            })//控制触发拖动的长按事件的时间,默认500毫秒,设置小于0为默认值,这里设置为50毫秒
              .onAction((_event?: GestureEvent) => {
                animateTo({
                  curve: Curve.Friction,
                  duration: 300
                }, () => {
                  this.scaleItem = item;
                });
              })
              .onActionEnd(() => {
                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;

                animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => {
                  let index = this.numbers.indexOf(this.dragItem);
                  //  44  宽度一半  减间距
                  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(() => {
                animateTo({
                  curve: curves.interpolatingSpring(0, 1, 400, 38)
                }, () => {
                  this.dragItem = -1;
                });
                animateTo({
                  curve: curves.interpolatingSpring(14, 1, 170, 17),
                  delay: 150
                }, () => {
                  this.scaleItem = -1;
                });
              })
          )
            .onCancel(() => {
              animateTo({
                curve: curves.interpolatingSpring(0, 1, 400, 38)
              }, () => {
                this.dragItem = -1;
              });
              animateTo({
                curve: curves.interpolatingSpring(14, 1, 170, 17)
              }, () => {
                this.scaleItem = -1;
              });
            })
        )
      }, (item: number) => item.toString())
    }
    .editMode(true)
    .scrollBar(BarState.Off)
    // 多列
    .columnsTemplate(this.str)
    .width('100%')
    .backgroundColor('#0D182431')
    .padding({ top: 5 })
  }
}

更多关于HarmonyOS鸿蒙Next中list嵌套grid,grid的item有手势拖拽,如何设置可以拖拽超出grid的大小的实战教程也可以访问 https://www.itying.com/category-93-b0.html

5 回复

【背景知识】
Grid组件的默认值为true,不允许GridItem子组件超Grid范围。

【解决方案】
Grid组件的默认值为true,为Grid组件设置clip属性值为false值即可。

示例代码:

import { curves } from '@kit.ArkUI'

@ComponentV2
struct ListViewPage {
  @Local listData: string[] = []

  aboutToAppear(): void {
    for (let index = 0; index < 9; index++) {
      this.listData.push('itemIndex=' + index)
    }
  }

  build() {
    List() {
      Repeat(this.listData)
        .each((obj: RepeatItem<string>) => {
          ListItem() {
            Column() {
              Text(obj.item).padding(10)
              GridPage()
            }
          }
        })
        .virtualScroll()
        .key((item: string) => item)
    }
    .height('100%')
    .width('100%')
  }
}

@Component
export struct GridPage {
  //元素数组
  @State numbers: number[] = [];
  @State row: number = 4;
  //元素数组中最后一个元素的索引
  @State lastIndex: number = 0;
  @State dragItem: number = -1;
  @State scaleItem: number = -1;
  @State item: number = -1;
  @State offsetX: number = 0;
  @State offsetY: number = 0;
  // row 设置网格列数
  private str: string = '';
  private dragRefOffsetx: number = 0;
  private dragRefOffsety: number = 0;
  private FIX_VP_X: number = 108;
  private FIX_VP_Y: number = 120;

  aboutToAppear() {
    for (let i = 1; i <= 8; 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 {
    if (!this.isDraggable(newIndex)) {
      return;
    }
    let tmp = this.numbers.splice(index, 1);
    this.numbers.splice(newIndex, 0, tmp[0]);
  }

  //向下滑
  down(index: number): void {
    // 指定固定GridItem不响应事件
    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 {
    return index > -1; //恒成立,所有元素均可移动排序
  }

  build() {
    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
            })
        }
        .onAreaChange((_oldVal, newVal) => {
          // 多列
          this.FIX_VP_X = Math.round(newVal.width as number);
          this.FIX_VP_Y = Math.round(newVal.height as number);
        })
        // 指定固定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
            })//控制触发拖动的长按事件的时间,默认500毫秒,设置小于0为默认值,这里设置为50毫秒
              .onAction((_event?: GestureEvent) => {
                animateTo({
                  curve: Curve.Friction,
                  duration: 300
                }, () => {
                  this.scaleItem = item;
                });
              })
              .onActionEnd(() => {
                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;

                animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => {
                  let index = this.numbers.indexOf(this.dragItem);
                  //  44  宽度一半  减间距
                  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(() => {
                animateTo({
                  curve: curves.interpolatingSpring(0, 1, 400, 38)
                }, () => {
                  this.dragItem = -1;
                });
                animateTo({
                  curve: curves.interpolatingSpring(14, 1, 170, 17),
                  delay: 150
                }, () => {
                  this.scaleItem = -1;
                });
              })
          )
            .onCancel(() => {
              animateTo({
                curve: curves.interpolatingSpring(0, 1, 400, 38)
              }, () => {
                this.dragItem = -1;
              });
              animateTo({
                curve: curves.interpolatingSpring(14, 1, 170, 17)
              }, () => {
                this.scaleItem = -1;
              });
            })
        )
      }, (item: number) => item.toString())
    }
    .editMode(true)
    .scrollBar(BarState.Off)
    // 多列
    .columnsTemplate(this.str)
    .width('100%')
    // 设置clip属性,允许子组件超出范围
    .clip(false)
    .backgroundColor('#0D182431')
    .padding({ top: 5 })
  }
}

更多关于HarmonyOS鸿蒙Next中list嵌套grid,grid的item有手势拖拽,如何设置可以拖拽超出grid的大小的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


标题

这是段落一。

这是段落二。

在HarmonyOS Next中实现grid item拖拽超出grid范围,需要使用GridonDragStartonDragMove回调配合绝对定位。将拖拽元素设置为Positioned组件,父容器需设置足够大的clip区域。通过onDragMove事件更新item位置时,需动态计算偏移量并突破grid边界限制。关键代码示例:

Grid() {
  // grid items
}
.onDragStart((event) => {
  // 记录初始位置
})
.onDragMove((event) => {
  // 计算新坐标,可超出grid范围
})
.clip(false) // 允许内容溢出

在HarmonyOS Next中实现GridItem拖拽超出Grid边界的效果,可以通过以下方式实现:

  1. 使用通用手势方案时,需要为GridItem设置clip(false)属性来取消父容器的裁剪限制:
GridItem() {
  // 内容
}
.clip(false)  // 关键设置
.gesture(
  // 拖拽手势代码
)
  1. 在PanGesture的onActionUpdate回调中,直接更新offsetX/offsetY而不做边界检查:
.onActionUpdate((event: GestureEvent) => {
  this.offsetY = event.offsetY - this.dragRefOffsety;
  this.offsetX = event.offsetX - this.dragRefOffsetx;
  // 移除原有的位置判断逻辑
})
  1. 确保父容器Grid也有足够的空间:
Grid()
.width('100%')
.height('100%')  // 确保Grid有足够高度
.layoutWeight(1)  // 在List中占满剩余空间

这种方案通过禁用裁剪+自由位移的方式,可以实现拖拽元素突破Grid边界的效果。注意要处理好手势结束时元素的归位逻辑。

回到顶部