Flutter 使用 LongPressDraggable + DragTarget 实现长按编辑拖动交换位置效果

需要实现的效果
1、可滑动列表(GridView)
2、对列表项目进行拖动交换位置(Draggable + DragTarget)
3、拖动时列表根据拖动位置进行上下自动滑动控制(ScrollController)

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

class DragDemoPage extends StatefulWidget {
  @override
  _DragDemoPageState createState() => _DragDemoPageState();
}

class _DragDemoPageState extends State {
  List<String> _dataList = [
    "1", "2", "3", "4", "5", "6",
    "7", "8", "9", "10", "11", "12",
    "13", "14", "15", "16", "17", "18",
    "19", "20"
  ];
  String _top = "-A-";
  ScrollController _scrollController = new ScrollController();
  int _lastTargetIndex = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Drag"),
      ),
      body: Container(
        color: Colors.white,
        width: double.infinity,
        height: double.infinity,
        padding: EdgeInsets.all(10),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            Text("长按图标拖动调整排序"),
            Expanded(child: _buildContent()),
          ],
        ),
      ),
    );
  }

  Widget _buildContent() {
    return Center(
      child: Column(
        children: [
          _buildTop(),
          Expanded(
            child: Container(
              padding: EdgeInsets.all(20),
              child: GridView.count(
                crossAxisCount: 3,
                crossAxisSpacing: 10.0,
                mainAxisSpacing: 10.0,
                controller: _scrollController,
                children: _dataList
                    .asMap()
                    .keys
                    .map((index) => _buildDraggableItem(index))
                    .toList(),
              ),
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildTop() {
    return DragTarget<int>(
      builder: (context, data, rejects) {
        return Column(
          children: [
            Image(
              image: AssetImage("assets/images/icon_switch_open.png"),
              width: 140,
              height: 140,
              fit: BoxFit.contain,
            ),
            Text("冠军:$_top"),
          ],
        );
      },
      onLeave: (data) {},
      onWillAccept: (data) => true,
      onAccept: (data) {
        setState(() {
          final temp = _dataList[data];
          _dataList.remove(temp);
          _dataList.insert(data, _top);
          _top = temp;
        });
      },
    );
  }

  Widget _buildDraggableItem(int index) {
    // TODO LongPressDraggable 继承自 Draggable,因此用法和 Draggable 完全一样,
    // TODO 唯一的区别就是 LongPressDraggable 触发拖动的方式是长按,而 Draggable 触发拖动的方式是按下
    return LongPressDraggable(
      // TODO 传递给 DragTarget 的数据
      data: index,
      // TODO 拖动时跟随移动的 widget
      feedback: _buildItem(_dataList[index], isDragging: true),
      // TODO 用 DragTarget 包裹,表示可作为拖动的最终目标,<int>表示传递数据 data 的类型
      child: DragTarget<int>(
        builder: (context, data, rejects) {
          return _buildItem(_dataList[index]);
        },
        // 手指拖着一个widget从另一个widget头上滑走时会调用
        onLeave: (data) {
          // print('---$data is Leaving item $index---');
        },
        // 松手时,是否需要将数据给这个widget,因为需要在拖动时改变UI,所以在这里直接修改数据源
        onWillAccept: (data) {
          // print('---(target)$index will accept item (drag)$data---');
          // TODO 跨度超过一行数量,就是判定可以上/下滑动
          if ((index - _lastTargetIndex).abs() >= 3) {
            _scrollController.jumpTo(((index / 3).ceil() - 1) * 80.0); // 80为item高度
            setState(() {
              _lastTargetIndex = index;
            });
          }
          return true;
        },
        // 松手时,如果onWillAccept返回true,那么就会调用
        onAccept: (data) {
          // TODO 松手时交换数据并刷新 UI
          setState(() {
            final dragTemp = _dataList[index];
            final targetTemp = _dataList[data];
            _dataList.replaceRange(data, data + 1, [dragTemp]);
            _dataList.replaceRange(index, index + 1, [targetTemp]);
          });
        },
      ),
      /*// 开始拖动时回调
      onDragStarted: () {print('---1---onDragStarted');},
      // 拖动结束时回调
      onDragEnd: (DraggableDetails details) {print('---2---onDragEnd:$details');},
      // 未拖动到DragTarget控件上时回调
      onDraggableCanceled: (Velocity velocity, Offset offset) {print('---3---onDraggableCanceled velocity:$velocity,offset:$offset');},
      // 拖动到DragTarget控件上时回调
      onDragCompleted: () {print('---4---onDragCompleted');},*/
    );
  }

  Widget _buildItem(String item, {bool isDragging = false}) {
    return Container(
      color: Colors.transparent,
      child: Column(
        children: [
          Image(
            image: AssetImage("assets/images/icon_switch_open.png"),
            width: isDragging ? 88 : 55,
            height: isDragging ? 88 : 55,
            color: isDragging ? Colors.yellow : Colors.transparent,
            colorBlendMode: BlendMode.color,
            fit: BoxFit.contain,
          ),
          isDragging
              ? Container()
              : Text(
            "第$item位",
            maxLines: 2,
            overflow: TextOverflow.ellipsis,
            style: TextStyle(
              color: Color(0xa6000000),
              fontSize: 12,
            ),
          ),
        ],
      ),
    );
  }

  @override
  void dispose() {
    _scrollController.dispose();
    super.dispose();
  }
}


参考文章
1、https://www.cnblogs.com/mengqd/p/12458642.html
2、https://www.jianshu.com/p/924428a31b6b
3、https://www.keppel.fun/articles/2019/04/26/1556245200876.html

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Flutter 是一种跨平台的移动应用开发框架,它提供了丰富的组件和功能,可以帮助开发者快速构建漂亮、流畅的用户界面。对于可滑动自动滚动折线图,在 Flutter 中可以使用 `ListView` 和 `AnimatedContainer` 组件来实现。 首先,我们可以使用 `ListView` 组件来创建一个可以滑动的容器。使用 `ListView.builder` 构建一个动态列表,将折线图中的数据作为列表项进行展示。在 `ListView` 内部添加一个 `ScrollController`,用来控制列表的滚动。 当需要自动滚动时,我们可以通过动画来实现使用 `AnimatedContainer` 组件来包裹折线图,通过修改它的宽度来实现滚动效果。可以在需要的时候,通过调用 `setState` 方法,来更新 `AnimatedContainer` 的属性值,从而触发动画效果。 在滚动时,可以监听滚动的位置,根据当前滚动的位置来判断是否需要自动滚动。通过 `ScrollController` 的 `addListener` 方法监听滚动事件,计算滚动的位置,并进行相应的判断,如果需要自动滚动,就通过修改 `AnimatedContainer` 的属性值来触发动画。 同时,可以为 `AnimatedContainer` 设置合适的动画时和曲线,来使滚动效果更加顺滑。 总结来说,要实现可滑动自动滚动折线图,可以使用 `ListView` 和 `AnimatedContainer` 组件。通过监听滚动事件,根据滚动的位置进行判断,并通过修改 `AnimatedContainer` 的属性值来触发动画效果,从而实现自动滚动的效果
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值