flutter 拖拽

本文最后更新于:2022年4月22日 上午

flutter 拖拽

拖拽

ReorderableListView, 所有的子 widget都需要key,长按可进行拖拽

body: const TestReorderable()

class TestReorderable extends StatefulWidget {
  const TestReorderable({Key? key}) : super(key: key);

  @override
  State<TestReorderable> createState() => _TestReorderableState();
}

class _TestReorderableState extends State<TestReorderable> {
  final boxes = [
    Box(color: Colors.blue[300]!, key: UniqueKey()),
    Box(color: Colors.blue[600]!, key: UniqueKey()),
    Box(color: Colors.blue[900]!, key: UniqueKey()),
  ];

  @override
  Widget build(BuildContext context) {
    return ReorderableListView(
      children: boxes,
      // 拖拽后触发的回调
      onReorder: (oldIndex, newIndex) {
        final widget = boxes.removeAt(oldIndex);
        if (newIndex > oldIndex) newIndex--; // 往后拖需要手动减
        boxes.insert(newIndex, widget);
        setState(() {});
      },
    );
  }
}

class Box extends StatelessWidget {
  final Color color;
  const Box({Key? key, required this.color}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Center(child: Container(color: color, width: 50, height: 50, margin: const EdgeInsets.all(8)));
  }
}

手动实现拖拽

Draggable可以把需要拖拽的子组件包裹住

Draggable(
  child:
  feedback: 拖拽时 手指的widget,
  childWhenDragging: 拖拽时 留在原地的widget
)

定位动画

AnimatedPositioned

AnimatedPositioned()

Listerner 监听

监听move事件

封装自定义拖拽

使用

final color = [
  Colors.blue[300]!,
  Colors.green[600]!,
  Colors.blue[600]!,
  Colors.blue[900]!,
  Colors.green[200]!,
];

Drag<Color>(
  childSize: const Size(76, 76),
  direction: Axis.vertical,
  list: color,
  buildItem: (index) => Container(margin: const EdgeInsets.all(8), color: color[index], width: 60, height: 60),
  buildKey: (index) => ValueKey(color[index]),
);
import 'package:flutter/material.dart';

enum _MoveActions {
  PREV, // 前一个
  NEXT // 后一个
}

class Drag<T> extends StatefulWidget {
  // final int itemCount;
  final List<T> list;
  final Widget Function(int) buildItem;
  final Key Function(int) buildKey;
  final Axis direction; // 拖拽方向
  final Size childSize; // 子元素尺寸

  const Drag({
    Key? key,
    required this.buildItem,
    required this.buildKey,
    required this.list,
    this.direction = Axis.horizontal,
    required this.childSize,
  }) : super(key: key);

  @override
  State<Drag> createState() => _DragState();
}

class _DragState extends State<Drag> {
  int? currentIndex; // 当前拖动的widget

  handleMove(PointerMoveEvent event) {
    // 垂直方向滚动需要 减去顶部 tabbar 高度,目前先写死
    final offset = widget.direction == Axis.horizontal ? event.position.dx : event.position.dy - 60;
    final targetOffset = (widget.direction == Axis.horizontal ? widget.childSize.width : widget.childSize.height);

    // 向后移动
    if (offset > (currentIndex! + 1) * targetOffset) {
      if (currentIndex! == widget.list.length - 1) return;
      _move(_MoveActions.NEXT);
      // 向前移动
    } else if (offset < currentIndex! * targetOffset) {
      if (currentIndex! == 0) return;
      _move(_MoveActions.PREV);
    }
  }

  // 移动
  _move(_MoveActions _moveActions) {
    setState(() {
      final currentItem = widget.list[currentIndex!];

      // 判断向前移动还是向后移动
      final targetIndex = _moveActions == _MoveActions.PREV ? currentIndex! - 1 : currentIndex! + 1;

      widget.list[currentIndex!] = widget.list[targetIndex];
      widget.list[targetIndex] = currentItem;

      // 更新当前索引
      currentIndex = _moveActions == _MoveActions.PREV ? currentIndex! - 1 : currentIndex! + 1;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Listener(
      onPointerMove: handleMove,
      child: Stack(
          children: List.generate(
        widget.list.length,
        (index) => _DragItem(
          key: widget.buildKey(index),
          child: widget.buildItem(index),
          x: widget.direction == Axis.horizontal ? index * widget.childSize.width : 0,
          y: widget.direction == Axis.horizontal ? 0 : index * widget.childSize.height,
          onDragStarted: () => setState(() => currentIndex = index),
          childSize: widget.childSize,
        ),
      )),
    );
  }
}

class _DragItem extends StatelessWidget {
  final Widget child; // 需要展示的内容
  final double x; // x 轴
  final double y; // y 轴
  void Function()? onDragStarted; // 开始拖拽事件
  final Size childSize; // 子元素尺寸

  _DragItem({
    Key? key,
    required this.x,
    required this.y,
    required this.child,
    required this.onDragStarted,
    required this.childSize,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return AnimatedPositioned(
      duration: const Duration(milliseconds: 300), // 动画特效
      left: x,
      top: y,
      child: Draggable(
        onDragStarted: onDragStarted,
        childWhenDragging: Container(width: childSize.width, height: childSize.height, color: Colors.transparent),
        feedback: Container(width: childSize.width, height: childSize.height, color: Colors.transparent, child: child),
        child: Container(width: childSize.width, height: childSize.height, color: Colors.transparent, child: child),
      ),
    );
  }
}

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议,转载请注明出处。