文章目录
- 前言
- 一、如何实现?
- 1、使用GestureDetector响应拖动事件
- 2、使用Transform变换控件位置
- 3、计算拖动区域
- 二、完整代码
- 三、使用示例
- 1、基本用法
- 总结
前言
使用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获取控件大小以及获取控件大小的方法,很容易就实现拖动功能了。