在这里插入图片描述

知识点概述

除了基础的布局和展示功能,GridView还提供了许多高级特性,包括拖拽排序、滑动删除、多选操作、嵌套滚动、自定义动画等。这些高级功能可以极大地扩展GridView的应用场景,提供更丰富的交互体验。本章将深入探讨这些高级功能的实现方法和最佳实践。

高级功能的核心价值:

功能维度用户体验技术实现适用场景
拖拽排序直观的手势操作ReorderableGridView列表重排、相册排序
滑动删除快速删除操作Dismissible邮件列表、任务管理
多选操作批量处理能力状态管理文件管理、相册批量操作
嵌套滚动复杂界面布局CustomScrollView社交应用、电商应用

1. 拖拽排序

拖拽排序是GridView中非常实用的交互功能,允许用户通过拖动来重新排列item的位置。这种功能常见于应用首页、相册排序、任务列表等场景。

1.1 拖拽实现方式对比

实现方式难度性能灵活性推荐场景代码复杂度
ReorderableGridView⭐ 低⭐⭐⭐⭐⭐ 高⭐⭐⭐ 中简单排序
自定义拖拽⭐⭐⭐ 中⭐⭐⭐⭐ 中⭐⭐⭐⭐⭐ 高特殊需求
第三方库⭐ 低⭐⭐⭐⭐⭐ 高⭐⭐⭐⭐ 高快速实现

选择建议:

  • 快速实现 → ReorderableGridView
  • 特殊动画需求 → 自定义拖拽
  • 快速开发 → 第三方库 (flutter_reorderable_list)

1.2 拖拽核心概念

关键组件:

  • ReorderableGridView: 可拖拽的网格视图
  • ReorderableDelayedDragStartListener: 延迟启动拖拽,避免误触
  • onReorder: 拖拽完成后的回调

拖拽流程:

用户长按item

DelayedDragStartListener触发

创建拖拽代理

显示拖拽反馈

用户拖动

实时更新位置

用户释放?

onReorder回调

计算新位置

更新数据源

刷新UI

1.3 拖拽实现要点

技术要点实现方式注意事项
延迟启动ReorderableDelayedDragStartListener默认延迟300ms,可调整
Key管理ValueKey确保唯一性使用数据唯一标识
索引处理newIndex > oldIndex时需要-1避免索引越界
动画优化使用Transform而非手动计算确保流畅度

1.4 拖拽性能优化

优化点方法效果实现难度
延迟启动DelayedDragStartListener避免误触,提升体验⭐ 简单
轻量代理简化拖拽反馈widget减少渲染压力⭐⭐ 中等
节流更新控制刷新频率降低CPU占用⭐⭐⭐ 较高
动画优化使用Transform组件提升流畅度⭐⭐ 中等
虚拟化使用ListView.builder减少内存占用⭐⭐⭐ 较高

1.5 拖拽常见问题

问题原因解决方案
误触延迟时间太短增加delay参数
卡顿item太复杂简化item UI,使用RepaintBoundary
位置错误索引计算错误正确处理newIndex > oldIndex情况
无反馈缺少视觉提示添加阴影、透明度变化

2. 滑动删除

滑动删除是一种流行的交互方式,用户可以通过左右滑动item来触发删除或其他操作。这种交互方式在移动设备上非常直观和高效。

2.1 滑动操作类型对比

操作类型手势方向常见用途实现难度用户友好度
左滑删除向左滑动删除item⭐ 低⭐⭐⭐⭐⭐ 高
右滑删除向右滑动删除item⭐ 低⭐⭐⭐⭐⭐ 高
双向操作左右滑动删除/编辑⭐⭐ 中等⭐⭐⭐⭐ 高
多级操作不同距离不同操作⭐⭐⭐ 较高⭐⭐⭐ 中等

2.2 Dismissible核心组件

Dismissible属性:

属性类型说明必需
keyKeyitem的唯一标识✅ 是
childWidgetitem的内容✅ 是
onDismissedFunction删除回调✅ 是
directionDismissDirection滑动方向❌ 否
backgroundWidget背景widget❌ 否
secondaryBackgroundWidget次要背景❌ 否

2.3 滑动方向选择

DismissDirection值说明适用场景
endToStart从右向左iOS风格删除
startToEnd从左向右阅读类应用
horizontal水平双向双操作场景
vertical垂直方向特殊需求

2.4 滑动操作实现要点

视觉反馈:

  • 背景颜色提示操作类型 (红色=删除,橙色=归档)
  • 图标指示操作 (垃圾桶=删除,文件夹=归档)
  • 文字说明操作内容

交互优化:

  • 提供撤销功能 (SnackBar + SnackBarAction)
  • 确认对话框 (删除重要数据时)
  • 震动反馈 (HapticFeedback)

2.5 滑动删除性能对比

实现方式性能灵活性维护性推荐场景
Dismissible⭐⭐⭐⭐⭐ 高⭐⭐⭐ 中等⭐⭐⭐⭐⭐ 高标准删除操作
自定义手势⭐⭐⭐ 中等⭐⭐⭐⭐⭐ 高⭐⭐ 低特殊需求
第三方库⭐⭐⭐⭐ 高⭐⭐⭐⭐ 高⭐⭐⭐ 中等快速开发

2.6 滑动删除最佳实践

最佳实践说明实现方式
提供撤销避免误删损失SnackBarAction
视觉提示清晰显示操作结果背景颜色+图标
确认删除重要数据需确认AlertDialog
批量操作多选删除更高效结合多选模式
动画流畅使用系统动画AnimatedContainer

3. 多选操作

多选操作允许用户同时选中多个item,然后对它们执行批量操作,如删除、分享、移动等。这种功能在文件管理器、相册、邮件应用等场景中非常常见。

3.1 多选交互模式对比

交互方式触发方式优点缺点推荐场景
Checkbox模式点击切换直观明了占用空间空间充裕的场景
长按进入长按开始多选空间省略需要提示空间受限场景
编辑按钮点击按钮进入明确提示需要额外操作批量操作频繁

3.2 多选状态管理

状态变量类型用途更新时机
_isSelectionModebool是否处于多选模式长按item/点击编辑按钮
_selectedIndicesSet<int>选中的索引集合点击item/全选操作
_itemsList<T>数据源删除/添加操作

3.3 多选操作类型

操作类型功能实现复杂度用户价值
全选/取消全选选中所有/取消所有⭐ 低⭐⭐⭐⭐⭐ 高
批量删除删除选中项⭐⭐ 中等⭐⭐⭐⭐⭐ 高
批量分享分享选中项⭐⭐⭐ 较高⭐⭐⭐⭐ 高
批量移动移动到其他分类⭐⭐⭐ 较高⭐⭐⭐ 中等
批量下载下载选中项⭐⭐⭐ 较高⭐⭐⭐⭐ 高

3.4 多选交互流程

选中

取消选中

删除

全选

取消

用户长按item

进入多选模式

选中长按的item

显示多选UI

用户点击其他item

添加到选中集合

从选中集合移除

更新选中状态

选中集合为空?

退出多选模式

继续多选

恢复常规模式

用户执行操作

确认对话框

删除选中item

选中所有item

刷新列表

更新UI

3.5 多选UI设计要点

UI元素设计要点实现建议
选中标记清晰可见Checkbox或圆形勾选图标
选中状态颜色/边框区分使用主题色高亮
操作按钮放置在AppBarIconButton或TextButton
数量提示显示选中数量AppBar标题动态更新
确认对话框重要操作需确认AlertDialog

3.6 多选性能优化

优化项方法效果实现难度
状态隔离使用Set而非List快速查找⭐ 简单
局部刷新只更新改变的item减少重建⭐⭐ 中等
虚拟化大数据量时使用分页降低内存⭐⭐⭐ 较高
异步操作批量操作使用Future不阻塞UI⭐⭐ 中等

4. 嵌套滚动

嵌套滚动是指在GridView中嵌套其他可滚动组件,如ListView、PageView等。这种场景常见于复杂的应用界面,如社交媒体的时间线、新闻应用的分类列表等。

4.1 嵌套滚动常见问题

问题类型表现影响解决方案难度
滚动冲突多个滚动区同时响应操作不流畅使用NestedScrollView⭐⭐ 中等
性能下降渲染多个滚动区滚动卡顿优化布局层次⭐⭐⭐ 较高
手势冲突手势识别混乱操作失败明确手势响应⭐⭐⭐ 较高
状态丢失滚动位置不保存体验差AutomaticKeepAlive⭐ 简单

4.2 嵌套滚动方案对比

方案优点缺点适用场景复杂度
CustomScrollView滚动协调好布局复杂标准嵌套滚动⭐⭐ 中等
NestedScrollView简单易用性能一般简单嵌套场景⭐ 简单
PageView页面切换自然滚动受限标签页切换⭐ 简单
自定义方案完全可控实现复杂特殊需求⭐⭐⭐⭐⭐ 复杂

4.3 Sliver组件体系

Sliver组件用途常用属性
SliverGrid网格布局gridDelegate, delegate
SliverList列表布局delegate
SliverPadding添加内边距padding
SliverToBoxAdapter包装任意widgetchild
SliverAppBar可折叠的AppBarexpandedHeight, pinned

4.4 嵌套滚动优化策略

优化项方法参数建议效果
缓存范围cacheExtent500.0~1000.0提升流畅度
重绘隔离addRepaintBoundariestrue减少GPU负载
保持状态AutomaticKeepAlivetrue保持滚动位置
懒加载按需加载分页加载减少初始化时间

4.5 TabBar嵌套滚动设计

结构设计:

Scaffold
└── AppBar (TabBar)
└── TabBarView
    ├── Tab 1: CustomScrollView + SliverGrid
    ├── Tab 2: CustomScrollView + SliverGrid
    └── Tab 3: CustomScrollView + SliverGrid

最佳实践:

  • 每个Tab使用独立的ScrollController
  • 避免在Tab切换时重建所有内容
  • 使用AutomaticKeepAlive保持滚动位置

5. 自定义ScrollPhysics

ScrollPhysics控制滚动物理效果,通过自定义ScrollPhysics可以实现各种有趣的滚动效果,如弹性滚动、回弹效果、滚动阻尼等。

5.1 常用Physics类型对比

Physics类型平台效果特点推荐场景
ClampingScrollPhysicsAndroid无弹性滚动到边界停止列表、网格
BouncingScrollPhysicsiOS弹性回弹滚动到边界回弹相册、图片
AlwaysScrollable通用总是可滚动内容少时也能滚动下拉刷新
RangeMaintaining通用保持范围滚动在指定范围内分页器

5.2 Physics类层次结构

ScrollPhysics (基类)
├── ClampingScrollPhysics
├── BouncingScrollPhysics
├── AlwaysScrollableScrollPhysics
├── RangeMaintainingScrollPhysics
└── CustomScrollPhysics (自定义)

5.3 自定义Physics核心方法

方法说明返回值用途
applyTo构建Physics链ScrollPhysics链式组合多个Physics
applyBoundaryConditions计算边界条件double控制边界行为
createBallisticSimulation创建弹道模拟Simulation惯性滚动
shouldAcceptUserOffset是否接受用户偏移bool判断是否处理滚动

5.4 滚动效果参数调优

参数范围默认值效果调整建议
阻尼系数0.5~1.00.95弹性强度值越小弹性越强
边界弹性0.0~1.00.5边界回弹值越大回弹越明显
摩擦系数0.01~0.10.02滚动阻力值越小越滑
滚动阈值0.0~10.00.0触发滚动最小距离避免误触

5.5 滚动效果应用场景

效果适用场景实现方式性能影响
弹性滚动相册、图片BouncingScrollPhysics
无弹性列表、菜单ClampingScrollPhysics
自定义弹性特殊UI自定义Physics
惯性滚动长列表默认Physics
阻尼效果游戏UI自定义Physics

6. 数据同步与状态管理

在复杂的应用中,GridView往往需要与多个数据源和状态管理方案集成,实现数据的实时同步和状态共享。

6.1 状态管理方案对比

方案学习曲线性能适用规模推荐指数生态成熟度
setState⭐ 低⭐⭐⭐ 中等小型⭐⭐⭐⭐⭐⭐⭐⭐
InheritedWidget⭐⭐⭐ 中等⭐⭐⭐⭐⭐ 高中型⭐⭐⭐⭐⭐⭐⭐⭐
Provider⭐⭐ 中等⭐⭐⭐⭐⭐ 高中大型⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
Riverpod⭐⭐⭐ 中等⭐⭐⭐⭐⭐ 高大型⭐⭐⭐⭐⭐⭐⭐⭐
Bloc⭐⭐⭐⭐ 较高⭐⭐⭐⭐ 高大型⭐⭐⭐⭐⭐⭐⭐⭐⭐
GetX⭐ 低⭐⭐⭐⭐ 高中大型⭐⭐⭐⭐⭐⭐⭐

选择建议:

  • 简单页面 → setState
  • 中型应用 → Provider
  • 大型应用 → Riverpod/Bloc
  • 快速开发 → GetX

6.2 Provider核心概念

组件用途使用场景
ChangeNotifier状态基类可变数据源
ChangeNotifierProvider提供状态创建Provider
Consumer消费状态局部更新UI
context.watch监听状态简化代码
context.read调用方法不触发更新

6.3 数据同步架构

┌─────────────┐
│  用户操作   │
└──────┬──────┘
       │
       ▼
┌─────────────┐
│  UI事件     │
└──────┬──────┘
       │
       ▼
┌─────────────┐
│  Provider   │ ◄──────┐
│  更新状态   │        │
└──────┬──────┘        │
       │               │
       ▼               │
┌─────────────┐        │
│ notifyList  │        │
│   eners()   │        │
└──────┬──────┘        │
       │               │
       ▼               │
┌─────────────┐        │
│  Consumer   │────────┘
│  重建UI     │
└──────┬──────┘
       │
       ▼
┌─────────────┐
│  UI更新     │
└─────────────┘

┌─────────────┐
│  网络请求   │
└──────┬──────┘
       │
       ▼ (更新状态)

6.4 状态管理最佳实践

实践说明实现方式
单一数据源状态集中管理Provider统一管理
不可变数据避免副作用使用copyWith
最小化重建只更新需要部分Consumer精准定位
异步处理网络请求异步Future + async/await
错误处理友好错误提示try-catch + SnackBar

6.5 数据持久化方案

方案类型读写速度适用数据复杂度
SharedPreferences键值对简单配置
HiveNoSQL极快复杂对象⭐⭐
SQLite关系数据库中等结构化数据⭐⭐⭐
IsarNoSQL极快大数据量⭐⭐⭐

7. 高级手势处理

除了基本的点击和滚动,GridView还可以支持更复杂的手势操作,如双击、捏合缩放、拖拽移动等,为用户提供更丰富的交互体验。

7.1 手势类型对比

手势类型触发方式常见用途实现难度性能影响
单击点击一次选择/打开⭐ 低
双击快速点击两次缩放/点赞⭐ 低
长按按住不松多选/菜单⭐ 低
捏合两指收放缩放⭐⭐ 中等
拖拽按住移动排序⭐⭐ 中等
滑动手势滑动方向翻页/删除⭐⭐ 中等

7.2 GestureDetector常用回调

回调触发时机参数用途
onTap点击完成简单点击
onDoubleTap双击完成双击操作
onLongPress长按完成长按菜单
onScaleStart缩放开始ScaleStartDetails缩放初始化
onScaleUpdate缩放更新ScaleUpdateDetails缩放过程
onScaleEnd缩放结束ScaleEndDetails缩放完成
onPanStart拖拽开始DragStartDetails拖拽初始化
onPanUpdate拖拽更新DragUpdateDetails拖拽过程
onPanEnd拖拽结束DragEndDetails拖拽完成

7.3 手势冲突处理

冲突场景解决方案实现方式
点击vs长按时间阈值区分GestureDetector自动处理
滚动vs拖拽方向判断设置horizontal/vertical
捏合vs点击手势数量区分onScaleStart检测
滑动vs滚动禁用滚动physics: NeverScrollable

7.4 手势性能优化

优化项方法效果实现难度
减少回调合并手势操作降低CPU占用⭐⭐ 中等
节流处理控制回调频率减少更新次数⭐⭐⭐ 较高
使用TransformGPU加速提升流畅度⭐ 简单
避免重建使用setState智能减少Widget重建⭐⭐ 中等

8. 自定义布局算法

对于特殊的布局需求,可以自定义GridDelegate来实现完全自定义的布局算法,如圆形布局、六边形布局、螺旋布局等。

8.1 GridDelegate核心方法

方法说明返回值用途
getLayout计算布局SliverGridLayout返回布局计算器
shouldRelayout判断是否重建bool是否需要重新布局

8.2 SliverGridLayout核心方法

方法说明返回值用途
getGeometryForChildIndex获取item几何信息SliverGridGeometry计算item位置和大小
getMinScrollChild获取最小滚动索引int滚动边界计算
getMaxScrollChild获取最大滚动索引int滚动边界计算
getMinChildIndexForScrollOffset滚动偏移转索引int可见性计算
getMaxChildIndexForScrollOffset滚动偏移转索引int可见性计算

8.3 常见自定义布局

布局类型复杂度视觉效果适用场景性能
犬牙布局⭐⭐ 中等错落有致瀑布流
圆形布局⭐⭐⭐⭐ 较高圆形排列特殊展示
螺旋布局⭐⭐⭐⭐⭐ 复杂螺旋排列创意展示
六边形布局⭐⭐⭐ 较高蜂窝效果游戏、创意
不规则布局⭐⭐⭐⭐⭐ 复杂自由排列艺术展示

8.4 自定义布局实现要点

要点说明实现建议
数学计算精确计算位置使用三角函数
性能优化缓存计算结果避免重复计算
边界处理处理异常情况null检查和默认值
动画支持支持布局动画使用shouldRelayout

8.5 布局算法性能对比

算法时间复杂度空间复杂度适合数据量
标准网格O(1)O(1)任意
犬牙布局O(1)O(1)任意
螺旋布局O(n)O(1)<1000
圆形布局O(n)O(1)<500
自定义复杂布局O(n)~O(n²)O(n)<200

知识点总结

本章深入探讨了GridView的各种高级功能,从拖拽排序到自定义布局,涵盖了实际开发中的常见需求。

核心功能回顾

功能推荐方案难度实用性
拖拽排序ReorderableGridView⭐ 低⭐⭐⭐⭐⭐
滑动删除Dismissible⭐ 低⭐⭐⭐⭐⭐
多选操作状态管理 + Set⭐⭐ 中等⭐⭐⭐⭐⭐
嵌套滚动CustomScrollView + Sliver⭐⭐ 中等⭐⭐⭐⭐
自定义Physics继承ScrollPhysics⭐⭐⭐ 较高⭐⭐⭐
状态管理Provider⭐⭐ 中等⭐⭐⭐⭐⭐
高级手势GestureDetector⭐⭐ 中等⭐⭐⭐⭐
自定义布局自定义GridDelegate⭐⭐⭐⭐ 较高⭐⭐⭐

学习路径建议

基础阶段

进阶阶段

高级阶段

精通阶段

拖拽排序

滑动删除

多选操作

嵌套滚动

自定义Physics

状态管理

高级手势

自定义布局

综合应用

性能优化

最佳实践总结

类别最佳实践
性能优化使用Builder/Consumer最小化重建范围,使用RepaintBoundary隔离重绘
用户体验提供清晰视觉反馈,支持撤销操作,合理使用动画
代码质量遵循单一职责原则,合理拆分组件,注重可维护性
状态管理单一数据源,不可变数据,异步操作使用Future
错误处理使用try-catch捕获异常,提供友好的错误提示

9. 完整可运行代码示例

9.1 主应用入口

import 'package:flutter/material.dart';

void main() {
  runApp(const GridViewAdvancedApp());
}

class GridViewAdvancedApp extends StatelessWidget {
  const GridViewAdvancedApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'GridView高级功能',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
        useMaterial3: true,
      ),
      home: const HomePage(),
      debugShowCheckedModeBanner: false,
    );
  }
}

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('GridView高级功能演示'),
        backgroundColor: Colors.blue,
        foregroundColor: Colors.white,
      ),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          _buildDemoCard(
            context,
            '拖拽排序',
            Icons.swap_vert,
            Colors.blue,
            () => Navigator.push(
              context,
              MaterialPageRoute(
                builder: (context) => const DraggableGrid(),
              ),
            ),
          ),
          const SizedBox(height: 12),
          _buildDemoCard(
            context,
            '滑动删除',
            Icons.swipe,
            Colors.red,
            () => Navigator.push(
              context,
              MaterialPageRoute(
                builder: (context) => const DismissibleGrid(),
              ),
            ),
          ),
          const SizedBox(height: 12),
          _buildDemoCard(
            context,
            '双向滑动',
            Icons.compare_arrows,
            Colors.orange,
            () => Navigator.push(
              context,
              MaterialPageRoute(
                builder: (context) => const BidirectionalSwipeGrid(),
              ),
            ),
          ),
          const SizedBox(height: 12),
          _buildDemoCard(
            context,
            '多选操作',
            Icons.checklist,
            Colors.green,
            () => Navigator.push(
              context,
              MaterialPageRoute(
                builder: (context) => const MultiSelectGrid(),
              ),
            ),
          ),
          const SizedBox(height: 12),
          _buildDemoCard(
            context,
            '嵌套滚动',
            Icons.view_column,
            Colors.purple,
            () => Navigator.push(
              context,
              MaterialPageRoute(
                builder: (context) => const NestedScrollGrid(),
              ),
            ),
          ),
          const SizedBox(height: 12),
          _buildDemoCard(
            context,
            'TabBar嵌套',
            Icons.tab,
            Colors.teal,
            () => Navigator.push(
              context,
              MaterialPageRoute(
                builder: (context) => const TabBarNestedGrid(),
              ),
            ),
          ),
          const SizedBox(height: 12),
          _buildDemoCard(
            context,
            '自定义Physics',
            Icons.animation,
            Colors.indigo,
            () => Navigator.push(
              context,
              MaterialPageRoute(
                builder: (context) => const CustomPhysicsGrid(),
              ),
            ),
          ),
          const SizedBox(height: 12),
          _buildDemoCard(
            context,
            '高级手势',
            Icons.gesture,
            Colors.pink,
            () => Navigator.push(
              context,
              MaterialPageRoute(
                builder: (context) => const GestureGrid(),
              ),
            ),
          ),
          const SizedBox(height: 12),
          _buildDemoCard(
            context,
            '自定义布局',
            Icons.grid_on,
            Colors.amber,
            () => Navigator.push(
              context,
              MaterialPageRoute(
                builder: (context) => const CustomLayoutGrid(),
              ),
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildDemoCard(
    BuildContext context,
    String title,
    IconData icon,
    Color color,
    VoidCallback onTap,
  ) {
    return Card(
      elevation: 2,
      child: ListTile(
        leading: CircleAvatar(
          backgroundColor: color,
          child: Icon(icon, color: Colors.white),
        ),
        title: Text(
          title,
          style: const TextStyle(
            fontSize: 16,
            fontWeight: FontWeight.w500,
          ),
        ),
        trailing: const Icon(Icons.arrow_forward_ios, size: 16),
        onTap: onTap,
      ),
    );
  }
}

9.2 完整拖拽排序实现

class DraggableGrid extends StatefulWidget {
  const DraggableGrid({super.key});

  
  State<DraggableGrid> createState() => _DraggableGridState();
}

class _DraggableGridState extends State<DraggableGrid> {
  final List<String> _items = List.generate(20, (index) => 'Item $index');

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('拖拽排序'),
        backgroundColor: Colors.blue,
        foregroundColor: Colors.white,
        actions: [
          IconButton(
            icon: const Icon(Icons.undo),
            onPressed: () {
              setState(() {
                _items.sort((a, b) {
                  final aIndex = int.parse(a.split(' ')[1]);
                  final bIndex = int.parse(b.split(' ')[1]);
                  return aIndex.compareTo(bIndex);
                });
              });
              ScaffoldMessenger.of(context).showSnackBar(
                const SnackBar(content: Text('已恢复默认顺序')),
              );
            },
          ),
        ],
      ),
      body: ReorderableGridView.count(
        crossAxisCount: 3,
        mainAxisSpacing: 8,
        crossAxisSpacing: 8,
        padding: const EdgeInsets.all(8),
        children: [
          for (int i = 0; i < _items.length; i++)
            ReorderableDelayedDragStartListener(
              key: ValueKey(_items[i]),
              index: i,
              child: _buildDraggableItem(_items[i], i),
            ),
        ],
        onReorder: (oldIndex, newIndex) {
          setState(() {
            if (newIndex > oldIndex) {
              newIndex -= 1;
            }
            final item = _items.removeAt(oldIndex);
            _items.insert(newIndex, item);
          });
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(content: Text('Item $oldIndex 移动到位置 $newIndex')),
          );
        },
      ),
    );
  }

  Widget _buildDraggableItem(String item, int index) {
    return Container(
      decoration: BoxDecoration(
        color: Colors.primaries[index % Colors.primaries.length],
        borderRadius: BorderRadius.circular(12),
        boxShadow: [
          BoxShadow(
            color: Colors.black.withOpacity(0.2),
            blurRadius: 4,
            offset: const Offset(2, 2),
          ),
        ],
      ),
      child: Stack(
        children: [
          Center(
            child: Text(
              item,
              style: const TextStyle(color: Colors.white, fontSize: 16),
            ),
          ),
          Positioned(
            top: 4,
            right: 4,
            child: Icon(
              Icons.drag_handle,
              color: Colors.white70,
              size: 20,
            ),
          ),
        ],
      ),
    );
  }
}

9.3 完整滑动删除实现

class DismissibleGrid extends StatefulWidget {
  const DismissibleGrid({super.key});

  
  State<DismissibleGrid> createState() => _DismissibleGridState();
}

class _DismissibleGridState extends State<DismissibleGrid> {
  final List<String> _items = List.generate(20, (index) => 'Item $index');
  final List<String> _deletedItems = [];

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('滑动删除'),
        backgroundColor: Colors.red,
        foregroundColor: Colors.white,
      ),
      body: GridView.builder(
        padding: const EdgeInsets.all(8),
        gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 3,
          childAspectRatio: 1,
          crossAxisSpacing: 8,
          mainAxisSpacing: 8,
        ),
        itemCount: _items.length,
        itemBuilder: (context, index) {
          return Dismissible(
            key: ValueKey(_items[index]),
            direction: DismissDirection.endToStart,
            onDismissed: (direction) {
              _showSnackBar(_items[index], index);
              setState(() {
                _deletedItems.add(_items[index]);
                _items.removeAt(index);
              });
            },
            background: Container(
              decoration: BoxDecoration(
                color: Colors.red,
                borderRadius: BorderRadius.circular(8),
              ),
              alignment: Alignment.centerRight,
              padding: const EdgeInsets.only(right: 20),
              child: const Icon(Icons.delete, color: Colors.white, size: 36),
            ),
            child: _buildItem(index),
          );
        },
      ),
    );
  }

  Widget _buildItem(int index) {
    return Container(
      decoration: BoxDecoration(
        color: Colors.primaries[index % Colors.primaries.length],
        borderRadius: BorderRadius.circular(8),
      ),
      child: Center(
        child: Text(
          _items[index],
          style: const TextStyle(color: Colors.white, fontSize: 16),
        ),
      ),
    );
  }

  void _showSnackBar(String item, int originalIndex) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text('已删除 $item'),
        action: SnackBarAction(
          label: '撤销',
          textColor: Colors.yellow,
          onPressed: () {
            setState(() {
              _items.insert(originalIndex, item);
              _deletedItems.remove(item);
            });
          },
        ),
        duration: const Duration(seconds: 3),
      ),
    );
  }
}

9.4 完整双向滑动实现

class BidirectionalSwipeGrid extends StatefulWidget {
  const BidirectionalSwipeGrid({super.key});

  
  State<BidirectionalSwipeGrid> createState() => _BidirectionalSwipeGridState();
}

class _BidirectionalSwipeGridState extends State<BidirectionalSwipeGrid> {
  final List<String> _items = List.generate(20, (index) => 'Item $index');

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('双向滑动'),
        backgroundColor: Colors.orange,
        foregroundColor: Colors.white,
      ),
      body: GridView.builder(
        padding: const EdgeInsets.all(8),
        gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 3,
          childAspectRatio: 1,
          crossAxisSpacing: 8,
          mainAxisSpacing: 8,
        ),
        itemCount: _items.length,
        itemBuilder: (context, index) {
          return Dismissible(
            key: ValueKey(_items[index]),
            onDismissed: (direction) {
              if (direction == DismissDirection.startToEnd) {
                _handleArchive(index);
              } else {
                _handleDelete(index);
              }
            },
            background: _buildBackground(Colors.orange, Icons.archive),
            secondaryBackground: _buildBackground(Colors.red, Icons.delete),
            child: _buildItem(index),
          );
        },
      ),
    );
  }

  Widget _buildBackground(Color color, IconData icon) {
    return Container(
      decoration: BoxDecoration(
        color: color,
        borderRadius: BorderRadius.circular(8),
      ),
      alignment: Alignment.center,
      child: Icon(icon, color: Colors.white, size: 36),
    );
  }

  Widget _buildItem(int index) {
    return Container(
      decoration: BoxDecoration(
        color: Colors.primaries[index % Colors.primaries.length],
        borderRadius: BorderRadius.circular(8),
      ),
      child: Center(
        child: Text(
          _items[index],
          style: const TextStyle(color: Colors.white, fontSize: 16),
        ),
      ),
    );
  }

  void _handleDelete(int index) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text('已删除 ${_items[index]}')),
    );
    setState(() => _items.removeAt(index));
  }

  void _handleArchive(int index) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text('已归档 ${_items[index]}')),
    );
  }
}

9.5 完整多选操作实现

class MultiSelectGrid extends StatefulWidget {
  const MultiSelectGrid({super.key});

  
  State<MultiSelectGrid> createState() => _MultiSelectGridState();
}

class _MultiSelectGridState extends State<MultiSelectGrid> {
  final List<String> _items = List.generate(20, (index) => 'Item $index');
  final Set<int> _selectedIndices = {};
  bool _isSelectionMode = false;

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(_isSelectionMode ? '已选 ${_selectedIndices.length}' : '多选操作'),
        backgroundColor: Colors.green,
        foregroundColor: Colors.white,
        actions: [
          if (_isSelectionMode) ...[
            IconButton(
              icon: const Icon(Icons.select_all),
              onPressed: _selectAll,
              tooltip: '全选',
            ),
            IconButton(
              icon: const Icon(Icons.delete),
              onPressed: _selectedIndices.isEmpty ? null : _deleteSelected,
              tooltip: '删除',
            ),
            IconButton(
              icon: const Icon(Icons.cancel),
              onPressed: _exitSelectionMode,
              tooltip: '取消',
            ),
          ] else ...[
            IconButton(
              icon: const Icon(Icons.checklist),
              onPressed: _enterSelectionMode,
              tooltip: '多选',
            ),
          ],
        ],
      ),
      body: GridView.builder(
        padding: const EdgeInsets.all(8),
        gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 3,
          childAspectRatio: 1,
          crossAxisSpacing: 8,
          mainAxisSpacing: 8,
        ),
        itemCount: _items.length,
        itemBuilder: (context, index) {
          return _buildItem(index);
        },
      ),
    );
  }

  Widget _buildItem(int index) {
    final isSelected = _selectedIndices.contains(index);

    return GestureDetector(
      onTap: () {
        if (_isSelectionMode) {
          setState(() {
            if (isSelected) {
              _selectedIndices.remove(index);
              if (_selectedIndices.isEmpty) {
                _exitSelectionMode();
              }
            } else {
              _selectedIndices.add(index);
            }
          });
        } else {
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(content: Text('点击了 ${_items[index]}')),
          );
        }
      },
      onLongPress: () {
        setState(() {
          if (!_isSelectionMode) {
            _isSelectionMode = true;
            _selectedIndices.add(index);
            HapticFeedback.lightImpact();
          }
        });
      },
      child: AnimatedContainer(
        duration: const Duration(milliseconds: 200),
        decoration: BoxDecoration(
          color: _isSelectionMode
              ? (isSelected ? Colors.green : Colors.grey[300])
              : Colors.primaries[index % Colors.primaries.length],
          borderRadius: BorderRadius.circular(8),
          border: _isSelectedModeBorder(isSelected),
          boxShadow: isSelected
              ? [
                  BoxShadow(
                    color: Colors.green.withOpacity(0.4),
                    blurRadius: 10,
                    spreadRadius: 2,
                  ),
                ]
              : null,
        ),
        child: Stack(
          children: [
            Center(
              child: Text(
                _items[index],
                style: TextStyle(
                  color: _isSelectionMode && !isSelected ? Colors.black54 : Colors.white,
                  fontSize: 16,
                ),
              ),
            ),
            if (_isSelectionMode)
              Positioned(
                top: 4,
                right: 4,
                child: Container(
                  decoration: BoxDecoration(
                    color: isSelected ? Colors.green : Colors.white,
                    shape: BoxShape.circle,
                    border: Border.all(color: Colors.grey),
                  ),
                  child: Icon(
                    isSelected ? Icons.check : null,
                    color: Colors.white,
                    size: 20,
                  ),
                ),
              ),
          ],
        ),
      ),
    );
  }

  BoxBorder? _isSelectedModeBorder(bool isSelected) {
    if (!_isSelectionMode) return null;
    return Border.all(
      color: isSelected ? Colors.green : Colors.grey,
      width: 3,
    );
  }

  void _enterSelectionMode() {
    setState(() => _isSelectionMode = true);
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(content: Text('已进入多选模式,长按item可取消多选模式')),
    );
  }

  void _exitSelectionMode() {
    setState(() {
      _isSelectionMode = false;
      _selectedIndices.clear();
    });
  }

  void _selectAll() {
    setState(() {
      if (_selectedIndices.length == _items.length) {
        _selectedIndices.clear();
      } else {
        _selectedIndices.addAll(List.generate(_items.length, (i) => i));
      }
    });
  }

  void _deleteSelected() {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('确认删除'),
        content: Text('确定要删除选中的 ${_selectedIndices.length} 个项目吗?'),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: const Text('取消'),
          ),
          TextButton(
            onPressed: () {
              setState(() {
                _items.removeWhere((_, index) => _selectedIndices.contains(index));
                _exitSelectionMode();
              });
              Navigator.pop(context);
              ScaffoldMessenger.of(context).showSnackBar(
                const SnackBar(content: Text('删除成功')),
              );
            },
            child: const Text('删除', style: TextStyle(color: Colors.red)),
          ),
        ],
      ),
    );
  }
}

9.6 完整嵌套滚动实现

class NestedScrollGrid extends StatelessWidget {
  const NestedScrollGrid({super.key});

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('嵌套滚动'),
        backgroundColor: Colors.purple,
        foregroundColor: Colors.white,
      ),
      body: CustomScrollView(
        slivers: [
          SliverToBoxAdapter(
            child: Container(
              height: 200,
              decoration: const BoxDecoration(
                gradient: LinearGradient(
                  colors: [Colors.purple, Colors.deepPurple],
                  begin: Alignment.topLeft,
                  end: Alignment.bottomRight,
                ),
              ),
              child: const Center(
                child: Text(
                  '顶部固定区域',
                  style: TextStyle(color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold),
                ),
              ),
            ),
          ),
          SliverPadding(
            padding: const EdgeInsets.all(16),
            sliver: SliverGrid(
              gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 3,
                childAspectRatio: 1,
                crossAxisSpacing: 10,
                mainAxisSpacing: 10,
              ),
              delegate: SliverChildBuilderDelegate(
                (context, index) {
                  return Container(
                    decoration: BoxDecoration(
                      color: Colors.primaries[index % Colors.primaries.length],
                      borderRadius: BorderRadius.circular(8),
                    ),
                    child: Center(
                      child: Text(
                        '$index',
                        style: const TextStyle(color: Colors.white, fontSize: 20),
                      ),
                    ),
                  );
                },
                childCount: 50,
              ),
            ),
          ),
          SliverToBoxAdapter(
            child: Container(
              height: 100,
              color: Colors.green,
              child: const Center(
                child: Text(
                  '底部固定区域',
                  style: TextStyle(color: Colors.white, fontSize: 18),
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

9.7 完整TabBar嵌套滚动实现

class TabBarNestedGrid extends StatelessWidget {
  const TabBarNestedGrid({super.key});

  
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: 3,
      child: Scaffold(
        appBar: AppBar(
          title: const Text('TabBar嵌套滚动'),
          backgroundColor: Colors.teal,
          foregroundColor: Colors.white,
          bottom: const TabBar(
            indicatorColor: Colors.white,
            labelColor: Colors.white,
            unselectedLabelColor: Colors.white70,
            tabs: [
              Tab(text: '推荐'),
              Tab(text: '热门'),
              Tab(text: '最新'),
            ],
          ),
        ),
        body: const TabBarView(
          children: [
            _NestedGridPage('推荐'),
            _NestedGridPage('热门'),
            _NestedGridPage('最新'),
          ],
        ),
      ),
    );
  }
}

class _NestedGridPage extends StatelessWidget {
  final String title;

  const _NestedGridPage(this.title);

  
  Widget build(BuildContext context) {
    return CustomScrollView(
      slivers: [
        SliverToBoxAdapter(
          child: Padding(
            padding: const EdgeInsets.all(16),
            child: Text(
              '$title 分类',
              style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
            ),
          ),
        ),
        SliverGrid(
          gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
            crossAxisCount: 3,
            childAspectRatio: 1,
            crossAxisSpacing: 8,
            mainAxisSpacing: 8,
          ),
          delegate: SliverChildBuilderDelegate(
            (context, index) {
              return Container(
                decoration: BoxDecoration(
                  color: Colors.primaries[index % Colors.primaries.length],
                  borderRadius: BorderRadius.circular(8),
                ),
                child: Center(
                  child: Text(
                    '$title $index',
                    style: const TextStyle(color: Colors.white, fontSize: 12),
                  ),
                ),
              );
            },
            childCount: 30,
          ),
        ),
      ],
    );
  }
}

9.8 完整自定义Physics实现

class CustomBouncingPhysics extends BouncingScrollPhysics {
  final double damping;

  const CustomBouncingPhysics({ScrollPhysics? parent, this.damping = 0.95})
      : super(parent: parent);

  
  CustomBouncingPhysics applyTo(ScrollPhysics? ancestor) {
    return CustomBouncingPhysics(parent: buildParent(ancestor), damping: damping);
  }

  
  double applyBoundaryConditions(ScrollMetrics position, double value) {
    if (value < position.minScrollExtent) {
      return position.minScrollExtent - value;
    }
    if (value > position.maxScrollExtent) {
      return value - position.maxScrollExtent;
    }
    return 0.0;
  }
}

class CustomPhysicsGrid extends StatelessWidget {
  const CustomPhysicsGrid({super.key});

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('自定义滚动效果'),
        backgroundColor: Colors.indigo,
        foregroundColor: Colors.white,
      ),
      body: DefaultTabController(
        length: 3,
        child: Column(
          children: [
            const TabBar(
              indicatorColor: Colors.white,
              labelColor: Colors.white,
              unselectedLabelColor: Colors.white70,
              tabs: [
                Tab(text: '弹性'),
                Tab(text: '阻尼'),
                Tab(text: '无弹性'),
              ],
            ),
            Expanded(
              child: TabBarView(
                children: [
                  _buildGrid(const BouncingScrollPhysics(), '弹性滚动'),
                  _buildGrid(const CustomBouncingPhysics(damping: 0.85), '自定义阻尼'),
                  _buildGrid(const ClampingScrollPhysics(), '无弹性'),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildGrid(ScrollPhysics physics, String title) {
    return Column(
      children: [
        Padding(
          padding: const EdgeInsets.all(16),
          child: Text(
            title,
            style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
          ),
        ),
        Expanded(
          child: GridView.builder(
            physics: physics,
            padding: const EdgeInsets.all(8),
            gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
              crossAxisCount: 3,
              childAspectRatio: 1,
              crossAxisSpacing: 8,
              mainAxisSpacing: 8,
            ),
            itemCount: 20,
            itemBuilder: (context, index) {
              return Container(
                decoration: BoxDecoration(
                  color: Colors.primaries[index % Colors.primaries.length],
                  borderRadius: BorderRadius.circular(8),
                ),
                child: Center(
                  child: Text(
                    '$index',
                    style: const TextStyle(color: Colors.white, fontSize: 20),
                  ),
                ),
              );
            },
          ),
        ),
      ],
    );
  }
}

9.9 完整高级手势实现

class GestureGrid extends StatelessWidget {
  const GestureGrid({super.key});

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('高级手势'),
        backgroundColor: Colors.pink,
        foregroundColor: Colors.white,
      ),
      body: GridView.builder(
        padding: const EdgeInsets.all(8),
        gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 3,
          childAspectRatio: 1,
          crossAxisSpacing: 8,
          mainAxisSpacing: 8,
        ),
        itemCount: 20,
        itemBuilder: (context, index) {
          return _GestureItem(index: index);
        },
      ),
    );
  }
}

class _GestureItem extends StatefulWidget {
  final int index;

  const _GestureItem({required this.index});

  
  State<_GestureItem> createState() => _GestureItemState();
}

class _GestureItemState extends State<_GestureItem> {
  double _scale = 1.0;
  int _tapCount = 0;
  DateTime? _lastTapTime;

  
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        final now = DateTime.now();
        if (_lastTapTime != null &&
            now.difference(_lastTapTime!) < const Duration(milliseconds: 300)) {
          _handleDoubleTap();
        } else {
          _handleTap();
        }
        _lastTapTime = now;
      },
      onLongPress: () {
        HapticFeedback.mediumImpact();
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('长按了 item ${widget.index}')),
        );
      },
      onScaleStart: (details) {
        setState(() => _scale = 1.0);
      },
      onScaleUpdate: (details) {
        setState(() => _scale = details.scale.clamp(0.8, 1.5));
      },
      onScaleEnd: (details) {
        setState(() => _scale = 1.0);
      },
      child: Transform.scale(
        scale: _scale,
        child: Container(
          decoration: BoxDecoration(
            color: Colors.primaries[widget.index % Colors.primaries.length],
            borderRadius: BorderRadius.circular(8),
            boxShadow: [
              BoxShadow(
                color: Colors.black.withOpacity(0.2),
                blurRadius: 4,
                offset: const Offset(2, 2),
              ),
            ],
          ),
          child: Center(
            child: Text(
              '${widget.index}',
              style: const TextStyle(color: Colors.white, fontSize: 20),
            ),
          ),
        ),
      ),
    );
  }

  void _handleTap() {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text('单击了 item ${widget.index}')),
    );
  }

  void _handleDoubleTap() {
    HapticFeedback.lightImpact();
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text('双击了 item ${widget.index}')),
    );
  }
}

9.10 完整自定义布局实现

class CustomGridDelegate extends SliverGridDelegate {
  final int crossAxisCount;
  final double childAspectRatio;
  final double mainAxisSpacing;
  final double crossAxisSpacing;
  final int pattern;

  const CustomGridDelegate({
    required this.crossAxisCount,
    required this.childAspectRatio,
    this.mainAxisSpacing = 0,
    this.crossAxisSpacing = 0,
    this.pattern = 0,
  });

  
  SliverGridLayout getLayout(SliverConstraints constraints) {
    final crossAxisCount = this.crossAxisCount;
    final usableCrossAxisExtent =
        constraints.crossAxisExtent - (crossAxisCount - 1) * crossAxisSpacing;
    final childCrossAxisExtent = usableCrossAxisExtent / crossAxisCount;
    final childMainAxisExtent = childCrossAxisExtent / childAspectRatio;

    return _CustomGridLayout(
      crossAxisCount: crossAxisCount,
      childCrossAxisExtent: childCrossAxisExtent,
      childMainAxisExtent: childMainAxisExtent,
      mainAxisSpacing: mainAxisSpacing,
      crossAxisSpacing: crossAxisSpacing,
      pattern: pattern,
    );
  }

  
  bool shouldRelayout(CustomGridDelegate oldDelegate) {
    return oldDelegate.crossAxisCount != crossAxisCount ||
        oldDelegate.childAspectRatio != childAspectRatio ||
        oldDelegate.mainAxisSpacing != mainAxisSpacing ||
        oldDelegate.crossAxisSpacing != crossAxisSpacing ||
        oldDelegate.pattern != pattern;
  }
}

class _CustomGridLayout extends SliverGridLayout {
  final int crossAxisCount;
  final double childCrossAxisExtent;
  final double childMainAxisExtent;
  final double mainAxisSpacing;
  final double crossAxisSpacing;
  final int pattern;

  _CustomGridLayout({
    required this.crossAxisCount,
    required this.childCrossAxisExtent,
    required this.childMainAxisExtent,
    required this.mainAxisSpacing,
    required this.crossAxisSpacing,
    required this.pattern,
  });

  
  double getMinScrollChild(int index) {
    final row = index ~/ crossAxisCount;
    return row * (childMainAxisExtent + mainAxisSpacing);
  }

  
  double getMaxScrollChild(int index) {
    final row = index ~/ crossAxisCount;
    return (row + 1) * (childMainAxisExtent + mainAxisSpacing) - mainAxisSpacing;
  }

  
  SliverGridGeometry getGeometryForChildIndex(int index) {
    if (pattern == 1 && index % (crossAxisCount * 2) >= crossAxisCount) {
      final row = index ~/ crossAxisCount;
      final col = index % crossAxisCount;
      return SliverGridGeometry(
        scrollOffset: row * (childMainAxisExtent + mainAxisSpacing),
        crossAxisOffset: (col + 1) * childCrossAxisExtent + col * crossAxisSpacing,
        mainAxisExtent: childMainAxisExtent,
        crossAxisExtent: childCrossAxisExtent,
      );
    }

    final row = index ~/ crossAxisCount;
    final col = index % crossAxisCount;
    return SliverGridGeometry(
      scrollOffset: row * (childMainAxisExtent + mainAxisSpacing),
      crossAxisOffset: col * (childCrossAxisExtent + crossAxisSpacing),
      mainAxisExtent: childMainAxisExtent,
      crossAxisExtent: childCrossAxisExtent,
    );
  }

  
  int getMinChildIndexForScrollOffset(double scrollOffset) {
    return (scrollOffset / (childMainAxisExtent + mainAxisSpacing)).floor() * crossAxisCount;
  }

  
  int getMaxChildIndexForScrollOffset(double scrollOffset) {
    return ((scrollOffset + 1) / (childMainAxisExtent + mainAxisSpacing)).ceil() * crossAxisCount - 1;
  }
}

class CustomLayoutGrid extends StatelessWidget {
  const CustomLayoutGrid({super.key});

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('自定义布局'),
        backgroundColor: Colors.amber,
        foregroundColor: Colors.white,
      ),
      body: DefaultTabController(
        length: 2,
        child: Column(
          children: [
            const TabBar(
              indicatorColor: Colors.white,
              labelColor: Colors.white,
              unselectedLabelColor: Colors.white70,
              tabs: [
                Tab(text: '标准布局'),
                Tab(text: '犬牙布局'),
              ],
            ),
            Expanded(
              child: TabBarView(
                children: [
                  _buildGrid(pattern: 0, title: '标准布局'),
                  _buildGrid(pattern: 1, title: '犬牙布局'),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildGrid({required int pattern, required String title}) {
    return Column(
      children: [
        Padding(
          padding: const EdgeInsets.all(16),
          child: Text(
            title,
            style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
          ),
        ),
        Expanded(
          child: GridView.builder(
            padding: const EdgeInsets.all(8),
            gridDelegate: CustomGridDelegate(
              crossAxisCount: 3,
              childAspectRatio: 1,
              mainAxisSpacing: 8,
              crossAxisSpacing: 8,
              pattern: pattern,
            ),
            itemCount: 30,
            itemBuilder: (context, index) {
              return Container(
                decoration: BoxDecoration(
                  color: Colors.primaries[index % Colors.primaries.length],
                  borderRadius: BorderRadius.circular(8),
                ),
                child: Center(
                  child: Text(
                    '$index',
                    style: const TextStyle(color: Colors.white, fontSize: 18),
                  ),
                ),
              );
            },
          ),
        ),
      ],
    );
  }
}

10. 总结

本文全面介绍了GridView的高级功能实现,从拖拽排序到自定义布局,涵盖了实际开发中的各种需求。

功能掌握度评估

功能类别掌握程度实用性学习优先级
拖拽排序⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐🔥 高
滑动删除⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐🔥 高
多选操作⭐⭐⭐⭐⭐⭐⭐⭐⭐🔥 高
嵌套滚动⭐⭐⭐⭐⭐⭐⭐⚡ 中
自定义Physics⭐⭐⭐⭐⭐💡 低
状态管理⭐⭐⭐⭐⭐⭐⭐⭐⭐🔥 高
高级手势⭐⭐⭐⭐⭐⭐💡 低
自定义布局⭐⭐⭐⭐💡 低

技术选型建议

场景推荐方案备选方案
简单拖拽ReorderableGridView自定义拖拽
删除操作Dismissible自定义手势
多选功能Provider + SetsetState + Set
嵌套滚动CustomScrollViewNestedScrollView
状态管理ProviderRiverpod/Bloc

实践建议

  1. 循序渐进: 先掌握简单的拖拽和滑动,再学习复杂的状态管理
  2. 注重体验: 始终关注用户体验,提供清晰的视觉反馈
  3. 性能优先: 合理使用Builder、Consumer,避免不必要的重建
  4. 代码复用: 提取通用组件,避免重复代码
  5. 持续优化: 根据用户反馈不断改进交互细节

通过学习和实践这些高级功能,您可以创建出功能丰富、体验优秀的GridView应用。

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐