鸿蒙特效教程06-可拖拽网格实现教程 原创

开门的鸿蒙
发布于 2025-3-26 19:08
浏览
1收藏

鸿蒙特效教程06-可拖拽网格实现教程

本教程适合 HarmonyOS Next 初学者,通过简单到复杂的步骤,一步步实现类似桌面APP中的可拖拽编辑效果。

开发环境准备

  • DevEco Studio 5.0.3
  • HarmonyOS Next API 15

效果预览

我们要实现的效果是一个 Grid 网格布局,用户可以通过长按并拖动来调整应用图标的位置顺序。拖拽完成后,底部会显示当前的排序结果。

鸿蒙特效教程06-可拖拽网格实现教程-鸿蒙开发者社区

实现步骤

步骤一:创建基本结构和数据模型

首先,我们需要创建一个基本的页面结构和数据模型。我们将定义一个应用名称数组和一个对应的颜色数组。

@Entry
@Component
struct DragGrid {
  // 应用名称数组
  @State apps: string[] = [
    '微信', '支付宝', 'QQ', '抖音',
    '快手', '微博', '头条', '网易云'
  ];
  
  build() {
    Column() {
      // 这里将放置我们的应用网格
      Text('应用网格示例')
        .fontSize(20)
        .fontColor(Color.White)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#121212')
  }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.

这个基本结构包含一个应用名称数组和一个简单的Column容器。在这个阶段,我们只是显示一个标题文本。

步骤二:使用Grid布局展示应用图标

接下来,我们将使用Grid组件来创建网格布局,并使用ForEach遍历应用数组,为每个应用创建一个网格项。

@Entry
@Component
struct DragGrid {
  @State apps: string[] = [
    '微信', '支付宝', 'QQ', '抖音',
    '快手', '微博', '头条', '网易云'
  ];
  
  build() {
    Column() {
      // 使用Grid组件创建网格布局
      Grid() {
        ForEach(this.apps, (item: string) => {
          GridItem() {
            Column() {
              // 应用图标(暂用占位图)
              Image($r('app.media.startIcon'))
                .width(60)
                .aspectRatio(1)
                
              // 应用名称
              Text(item)
                .fontSize(12)
                .fontColor(Color.White)
            }
            .padding(10)
          }
        })
      }
      .columnsTemplate('1fr 1fr 1fr 1fr') // 4列等宽布局
      .rowsGap(10) // 行间距
      .columnsGap(10) // 列间距
      .padding(20) // 内边距
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#121212')
  }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.

在这一步,我们添加了Grid组件,它具有以下关键属性:

  • columnsTemplate:定义网格的列模板,'1fr 1fr 1fr 1fr’表示四列等宽布局。
  • rowsGap:行间距,设置为10。
  • columnsGap:列间距,设置为10。
  • padding:内边距,设置为20。

每个GridItem包含一个Column布局,里面有一个Image(应用图标)和一个Text(应用名称)。

步骤三:优化图标布局和样式

现在我们有了基本的网格布局,接下来优化图标的样式和布局。我们将创建一个自定义的Builder函数来构建每个应用图标项,并添加一些颜色来区分不同应用。

@Entry
@Component
struct DragGrid {
  @State apps: string[] = [
    '微信', '支付宝', 'QQ', '抖音',
    '快手', '微博', '头条', '网易云',
    '腾讯视频', '爱奇艺', '优酷', 'B站'
  ];
  
  // 定义应用图标颜色
  private appColors: string[] = [
    '#34C759', '#007AFF', '#5856D6', '#FF2D55',
    '#FF9500', '#FF3B30', '#E73C39', '#D33A31',
    '#38B0DE', '#39A5DC', '#22C8BD', '#00A1D6'
  ];
  
  // 创建应用图标项的构建器
  @Builder
  itemBuilder(name: string, index: number) {
    Column({ space: 2 }) {
      // 应用图标
      Image($r('app.media.startIcon'))
        .width(80)
        .padding(10)
        .aspectRatio(1)
        .backgroundColor(this.appColors[index % this.appColors.length])
        .borderRadius(16)
        
      // 应用名称
      Text(name)
        .fontSize(12)
        .fontColor(Color.White)
    }
  }
  
  build() {
    Column() {
      Grid() {
        ForEach(this.apps, (item: string, index: number) => {
          GridItem() {
            this.itemBuilder(item, index)
          }
        })
      }
      .columnsTemplate('1fr 1fr 1fr 1fr')
      .rowsGap(20)
      .columnsGap(20)
      .padding(20)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#121212')
  }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.

在这一步,我们:

  1. 添加了appColors数组,定义了各个应用图标的背景颜色。
  2. 创建了@Builder itemBuilder函数,用于构建每个应用图标项,使代码更加模块化。
  3. 为图标添加了背景颜色和圆角边框,使其更加美观。
  4. 在ForEach中添加了index参数,用于获取当前项的索引,以便为不同应用使用不同的颜色。

步骤四:添加拖拽功能

现在我们有了美观的网格布局,下一步是添加拖拽功能。我们需要设置Grid的editMode属性为true,并添加相应的拖拽事件处理函数。

@Entry
@Component
struct DragGrid {
  @State apps: string[] = [
    '微信', '支付宝', 'QQ', '抖音',
    '快手', '微博', '头条', '网易云',
    '腾讯视频', '爱奇艺', '优酷', 'B站'
  ];
  
  private appColors: string[] = [
    '#34C759', '#007AFF', '#5856D6', '#FF2D55',
    '#FF9500', '#FF3B30', '#E73C39', '#D33A31',
    '#38B0DE', '#39A5DC', '#22C8BD', '#00A1D6'
  ];
  
  @Builder
  itemBuilder(name: string) {
    Column({ space: 2 }) {
      Image($r('app.media.startIcon'))
        .draggable(false) // 禁止图片本身被拖拽
        .width(80)
        .padding(10)
        .aspectRatio(1)
        
      Text(name)
        .fontSize(12)
        .fontColor(Color.White)
    }
  }
  
  // 交换两个应用的位置
  changeIndex(a: number, b: number) {
    let temp = this.apps[a];
    this.apps[a] = this.apps[b];
    this.apps[b] = temp;
  }
  
  build() {
    Column() {
      Grid() {
        ForEach(this.apps, (item: string) => {
          GridItem() {
            this.itemBuilder(item)
          }
        })
      }
      .columnsTemplate('1fr 1fr 1fr 1fr')
      .rowsGap(20)
      .columnsGap(20)
      .padding(20)
      .supportAnimation(true) // 启用动画
      .editMode(true) // 启用编辑模式
      // 拖拽开始事件
      .onItemDragStart((_event: ItemDragInfo, itemIndex: number) => {
        return this.itemBuilder(this.apps[itemIndex]);
      })
      // 拖拽放置事件
      .onItemDrop((_event: ItemDragInfo, itemIndex: number, insertIndex: number, isSuccess: boolean) => {
        if (!isSuccess || insertIndex >= this.apps.length) {
          return;
        }
        this.changeIndex(itemIndex, insertIndex);
      })
      .layoutWeight(1)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#121212')
  }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.

在这一步,我们添加了拖拽功能的关键部分:

  1. 设置supportAnimation(true)来启用动画效果。
  2. 设置editMode(true)来启用编辑模式,这是实现拖拽功能的必要设置。
  3. 添加onItemDragStart事件处理函数,当用户开始拖拽时触发,返回被拖拽项的UI表示。
  4. 添加onItemDrop事件处理函数,当用户放置拖拽项时触发,处理位置交换逻辑。
  5. 创建changeIndex方法,用于交换数组中两个元素的位置。
  6. 在Image上设置draggable(false),确保是整个GridItem被拖拽,而不是图片本身。

步骤五:添加排序结果展示

为了让用户更直观地看到排序结果,我们在网格下方添加一个区域,用于显示当前的应用排序结果。

@Entry
@Component
struct DragGrid {
  // 前面的代码保持不变...
  
  build() {
    Column() {
      // 应用网格部分保持不变...
      
      // 添加排序结果展示区域
      Column({ space: 10 }) {
        Text('应用排序结果')
          .fontSize(16)
          .fontColor(Color.White)
          
        Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.Center }) {
          ForEach(this.apps, (item: string, index: number) => {
            Text(item)
              .fontSize(12)
              .fontColor(Color.White)
              .backgroundColor(this.appColors[index % this.appColors.length])
              .borderRadius(12)
              .padding({
                left: 10,
                right: 10,
                top: 4,
                bottom: 4
              })
              .margin(4)
          })
        }
        .width('100%')
      }
      .width('100%')
      .padding(10)
      .backgroundColor('#0DFFFFFF') // 半透明背景
      .borderRadius({ topLeft: 20, topRight: 20 }) // 上方圆角
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#121212')
  }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.

在这一步,我们添加了一个展示排序结果的区域:

  1. 使用Column容器,顶部显示"应用排序结果"的标题。
  2. 使用Flex布局,设置wrap: FlexWrap.Wrap允许内容换行,justifyContent: FlexAlign.Center使内容居中对齐。
  3. 使用ForEach循环遍历应用数组,为每个应用创建一个带有背景色的文本标签。
  4. 为结果区域添加半透明背景和上方圆角,使其更加美观。

步骤六:美化界面

最后,我们美化整个界面,添加渐变背景和一些视觉改进。

@Entry
@Component
struct DragGrid {
  // 前面的代码保持不变...
  
  build() {
    Column() {
      // 网格和结果区域代码保持不变...
    }
    .width('100%')
    .height('100%')
    .expandSafeArea() // 扩展到安全区域
    .linearGradient({ // 渐变背景
      angle: 135,
      colors: [
        ['#121212', 0],
        ['#242424', 1]
      ]
    })
  }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.

在这一步,我们:

  1. 添加expandSafeArea()确保内容可以扩展到设备的安全区域。
  2. 使用linearGradient创建渐变背景,角度为135度,从深色(#121212)渐变到稍浅的色调(#242424)。

完整代码

以下是完整的实现代码:

@Entry
@Component
struct DragGrid {
  // 应用名称数组,用于显示和排序
  @State apps: string[] = [
    '微信', '支付宝', 'QQ', '抖音',
    '快手', '微博', '头条', '网易云',
    '腾讯视频', '爱奇艺', '优酷', 'B站',
    '小红书', '美团', '饿了么', '滴滴',
    '高德', '携程'
  ];
  
  // 定义应用图标对应的颜色数组
  private appColors: string[] = [
    '#34C759', '#007AFF', '#5856D6', '#FF2D55',
    '#FF9500', '#FF3B30', '#E73C39', '#D33A31',
    '#38B0DE', '#39A5DC', '#22C8BD', '#00A1D6',
    '#FF3A31', '#FFD800', '#4290F7', '#FF7700',
    '#4AB66B', '#2A9AF1'
  ];

  /**
   * 构建单个应用图标项
   * @param name 应用名称
   * @return 返回应用图标的UI组件
   */
  @Builder
  itemBuilder(name: string) {
    // 垂直布局,包含图标和文字
    Column({ space: 2 }) {
      // 应用图标图片
      Image($r('app.media.startIcon'))
        .draggable(false)// 禁止图片本身被拖拽,确保整个GridItem被拖拽
        .width(80)
        .aspectRatio(1)// 保持1:1的宽高比
        .padding(10)

      // 应用名称文本
      Text(name)
        .fontSize(12)
        .fontColor(Color.White)
    }
  }

  /**
   * 交换两个应用在数组中的位置
   * @param a 第一个索引
   * @param b 第二个索引
   */
  changeIndex(a: number, b: number) {
    // 使用临时变量交换两个元素位置
    let temp = this.apps[a];
    this.apps[a] = this.apps[b];
    this.apps[b] = temp;
  }

  /**
   * 构建组件的UI结构
   */
  build() {
    // 主容器,垂直布局
    Column() {
      // 应用网格区域
      Grid() {
        // 遍历所有应用,为每个应用创建一个网格项
        ForEach(this.apps, (item: string) => {
          GridItem() {
            // 使用自定义builder构建网格项内容
            this.itemBuilder(item)
          }
        })
      }
      // 网格样式和行为设置
      .columnsTemplate('1fr '.repeat(4)) // 设置4列等宽布局
      .columnsGap(20) // 列间距
      .rowsGap(20) // 行间距
      .padding(20) // 内边距
      .supportAnimation(true) // 启用动画支持
      .editMode(true) // 启用编辑模式,允许拖拽
      // 拖拽开始事件处理
      .onItemDragStart((_event: ItemDragInfo, itemIndex: number) => {
        // 返回被拖拽项的UI
        return this.itemBuilder(this.apps[itemIndex]);
      })
      // 拖拽放置事件处理
      .onItemDrop((_event: ItemDragInfo, itemIndex: number, insertIndex: number, isSuccess: boolean) => {
        // 如果拖拽失败或目标位置无效,则不执行操作
        if (!isSuccess || insertIndex >= this.apps.length) {
          return;
        }
        // 交换元素位置
        this.changeIndex(itemIndex, insertIndex);
      })
      .layoutWeight(1) // 使网格区域占用剩余空间

      // 结果显示区域
      Column({ space: 10 }) {
        // 标题文本
        Text('应用排序结果')
          .fontSize(16)
          .fontColor(Color.White)

        // 弹性布局,允许换行
        Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.Center }) {
          // 遍历应用数组,为每个应用创建一个彩色标签
          ForEach(this.apps, (item: string, index: number) => {
            Text(item)
              .fontSize(12)
              .fontColor(Color.White)
              .backgroundColor(this.appColors[index % this.appColors.length])
              .borderRadius(12)
              .padding({
                left: 10,
                right: 10,
                top: 4,
                bottom: 4
              })
              .margin(4)
          })
        }
        .width('100%')
      }
      .width('100%')
      .padding(10) // 内边距
      .backgroundColor('#0DFFFFFF') // 半透明背景
      .expandSafeArea() // 扩展到安全区域
      .borderRadius({ topLeft: 20, topRight: 20 }) // 上左右圆角
    }
    // 主容器样式设置
    .width('100%')
    .height('100%')
    .expandSafeArea() // 扩展到安全区域
    .linearGradient({
      angle: 135, // 渐变角度
      colors: [
        ['#121212', 0], // 起点色
        ['#242424', 1] // 终点色
      ]
    })
  }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.

Grid组件的关键属性详解

Grid是鸿蒙系统中用于创建网格布局的重要组件,它有以下关键属性:

  1. columnsTemplate: 定义网格的列模板。例如'1fr 1fr 1fr 1fr'表示四列等宽布局。'1fr’中的’fr’是fraction(分数)的缩写,表示按比例分配空间。

  2. rowsTemplate: 定义网格的行模板。如果不设置,行高将根据内容自动调整。

  3. columnsGap: 列之间的间距。

  4. rowsGap: 行之间的间距。

  5. editMode: 是否启用编辑模式。设置为true时启用拖拽功能。

  6. supportAnimation: 是否支持动画。设置为true时,拖拽过程中会有平滑的动画效果。

拖拽功能的关键事件详解

实现拖拽功能主要依赖以下事件:

  1. onItemDragStart: 当用户开始拖拽某个项时触发。

    • 参数:event(拖拽事件信息),itemIndex(被拖拽项的索引)
    • 返回值:被拖拽项的UI表示
  2. onItemDrop: 当用户放置拖拽项时触发。

    • 参数:event(拖拽事件信息),itemIndex(原始位置索引),insertIndex(目标位置索引),isSuccess(是否成功)
    • 功能:处理元素位置交换逻辑

此外,还有一些可选的事件可以用于增强拖拽体验:

  • onItemDragEnter: 当拖拽项进入某个位置时触发。
  • onItemDragMove: 当拖拽项在网格中移动时触发。
  • onItemDragLeave: 当拖拽项离开某个位置时触发。

小结与进阶提示

通过本教程,我们实现了一个功能完整的可拖拽应用网格界面。主要学习了以下内容:

  1. 使用Grid组件创建网格布局
  2. 使用@Builder创建可复用的UI构建函数
  3. 实现拖拽排序功能
  4. 优化UI和用户体验

进阶提示:

  1. 可以添加长按震动反馈,增强交互体验。
  2. 可以实现数据持久化,保存用户的排序结果。
  3. 可以添加编辑模式切换,只有在特定模式下才允许拖拽排序。
  4. 可以为拖拽过程添加更丰富的动画效果,如缩放、阴影等。

希望本教程对你有所帮助,让你掌握鸿蒙系统中Grid组件和拖拽功能的使用方法!

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2025-3-28 08:57:45修改
1
收藏 1
回复
举报
1
1


回复
    相关推荐