文章目录
前言
使用flutter开发是需要控件能拖动,比如画板中的元素,或者工具条,搜索框,每个都单独去实现拖动还是比较麻烦的,将拖动功能封装成一个控件,需要的时候直接使用拖动控件作为父控件这样就方便很多了。
一、如何实现?
1、使用GestureDetector响应拖动事件
//总位移var _unlimtedOffset = Offset.zero;//当前位移(有活动区域限制时,鼠标超过边界后当前位移不等于总位移,此时总位移可以确保回到边界内鼠标与控件的相对位置不变)final _offset = ValueNotifier<Offset>(Offset.zero);GestureDetector( child: this.widget.child, onPanUpdate: (detail) { //累加拖动距离 _unlimtedOffset += detail.delta; })
2、使用Transform变换控件位置
使用translate变换位置即可
//ValueListenableBuilder监听_offset 改变,此处略Transform.translate( offset: offset, child:GestureDetector()//上一步的child:GestureDetector)
3、计算拖动区域
这一步不是必须的,但是如果需要限制控件活动范围则需要这一步。
通过GlobalKey获取控件大小,在GestureDetector的onPanUpdate事件中:
onPanUpdate: (detail) { //拖动区域为父控件,去掉则不受限制,但拖出父控件会被遮挡无法点击。 //获取父控件大小 RenderBox ? parentRenderBox = _mykey.currentContext ? .findAncestorRenderObjectOfType<RenderObject>() as RenderBox ? ; final screenSize = parentRenderBox ? .size; //获取控件大小 final mySize = _mykey.currentContext ? .size; final renderBox = _mykey.currentContext ? .findRenderObject() as RenderBox ? ; //获取控件当前位置 var originOffset = renderBox ? .localToGlobal(Offset.zero); if (originOffset != null) { originOffset = parentRenderBox ? .globalToLocal(originOffset); } if (screenSize == null || mySize == null || originOffset == null) { return; } //计算不超出父控件区域 if (off.dx < -originOffset.dx) { off = Offset(-originOffset.dx, off.dy); } else if (off.dx > screenSize.width - mySize.width - originOffset.dx) { off = Offset( screenSize.width - mySize.width - originOffset.dx, off.dy, ); } if (off.dy < -originOffset.dy) { off = Offset(off.dx, -originOffset.dy); } else if (off.dy > screenSize.height - mySize.height - originOffset.dy) { off = Offset( off.dx, screenSize.height - mySize.height - originOffset.dy, ); } //现在活动区域为父控件 --end}
二、完整代码
drag_move_box.dart
import 'package:flutter/material.dart';/// 可拖动容器/// 拖动范围是父控件class DragMoveBox extends StatefulWidget { final Widget child; const DragMoveBox({ super.key, required this.child, }); State<DragMoveBox> createState() => _DragMoveBoxState();}class _DragMoveBoxState extends State<DragMoveBox> { final GlobalKey _mykey = GlobalKey(); //当前位移(有活动区域限制时,鼠标超过边界后当前位移不等于总位移,此时总位移可以确保回到边界内鼠标与控件的相对位置不变) final _offset = ValueNotifier<Offset>(Offset.zero); //总位移 var _unlimtedOffset = Offset.zero; Widget build(BuildContext context) { return ValueListenableBuilder( valueListenable: _offset, builder: //采用transform变换实现拖动 (context, offset, widget) => Transform.translate( key: _mykey, offset: offset, child: GestureDetector( child: this.widget.child, onPanUpdate: (detail) { var off = _unlimtedOffset = _unlimtedOffset + detail.delta; //拖动区域为父控件,去掉则不受限制,但拖出父控件会被遮挡无法点击。 //获取父控件大小 RenderBox? parentRenderBox = _mykey.currentContext ?.findAncestorRenderObjectOfType<RenderObject>() as RenderBox?; final screenSize = parentRenderBox?.size; //获取控件大小 final mySize = _mykey.currentContext?.size; final renderBox = _mykey.currentContext?.findRenderObject() as RenderBox?; //获取控件当前位置 var originOffset = renderBox?.localToGlobal(Offset.zero); if (originOffset != null) { originOffset = parentRenderBox?.globalToLocal(originOffset); } if (screenSize == null || mySize == null || originOffset == null) { return; } //计算不超出父控件区域 if (off.dx < -originOffset.dx) { off = Offset(-originOffset.dx, off.dy); } else if (off.dx > screenSize.width - mySize.width - originOffset.dx) { off = Offset( screenSize.width - mySize.width - originOffset.dx, off.dy, ); } if (off.dy < -originOffset.dy) { off = Offset(off.dx, -originOffset.dy); } else if (off.dy > screenSize.height - mySize.height - originOffset.dy) { off = Offset( off.dx, screenSize.height - mySize.height - originOffset.dy, ); } //现在活动区域为父控件 --end _offset.value = off; }, ), ), ); }}
三、使用示例
1、基本用法
DragMoveBox(child:Text("You have pushed the button this many times:") //需要拖动的控件)
效果预览
总结
以上就是今天要讲的内容,本文提供了一种简单的拖动控件实现,尤其是封装成容器后使用变得很简单,主要在于能想到translate变换可以改变位置,在了解通过GlobalKey获取控件大小以及获取控件大小的方法,很容易就实现拖动功能了。
来源地址:https://blog.csdn.net/u013113678/article/details/131489967