在原 DraggableWidget 控件中新增左右吸边的方法;代码如下;

原DraggableWidget代码链接:draggable_widget | Flutter Package


import 'dart:math';import 'package:flutter/material.dart';import 'model/anchor_docker.dart';export 'model/anchor_docker.dart';class DraggableWidget extends StatefulWidget {/// The widget that will be displayed as dragging widgetfinal Widget child;/// The horizontal padding around the widgetfinal double horizontalSpace;/// The vertical padding around the widgetfinal double verticalSpace;/// Intial location of the widget, default to [AnchoringPosition.bottomRight]final AnchoringPosition initialPosition;/// Intially should the widget be visible or not, default to [true]final bool intialVisibility;/// The top bottom pargin to create the bottom boundary for the widget, for example if you have a [BottomNavigationBar],/// then you may need to set the bottom boundary so that the draggable button can't get on top of the [BottomNavigationBar]final double bottomMargin;/// The top bottom pargin to create the top boundary for the widget, for example if you have a [AppBar],/// then you may need to set the bottom boundary so that the draggable button can't get on top of the [AppBar]final double topMargin;/// Status bar's height, default to 24final double statusBarHeight;/// Shadow's border radius for the draggable widget, default to 10final double shadowBorderRadius;/// A drag controller to show/hide or move the widget around the screenfinal DragController? dragController;/// [BoxShadow] when the widget is not being dragged, default to/// ```Dart///const BoxShadow(///     color: Colors.black38,///    offset: Offset(0, 4),///    blurRadius: 2,///  ),/// ```final BoxShadow normalShadow;/// [BoxShadow] when the widget is being dragged///```Dart///const BoxShadow(///     color: Colors.black38,///    offset: Offset(0, 10),///    blurRadius: 10,///  ),/// ```final BoxShadow draggingShadow;/// How much should the [DraggableWidget] be scaled when it is being dragged, default to 1.1final double dragAnimationScale;/// Touch Delay Duration. Default value is zero. When set, drag operations will trigger after the duration.final Duration touchDelay;DraggableWidget({Key? key,required this.child,this.horizontalSpace = 0,this.verticalSpace = 0,this.initialPosition = AnchoringPosition.bottomRight,this.intialVisibility = true,this.bottomMargin = 0,this.topMargin = 0,this.statusBarHeight = 24,this.shadowBorderRadius = 10,this.dragController,this.dragAnimationScale = 1.1,this.touchDelay = Duration.zero,this.normalShadow = const BoxShadow(color: Colors.transparent,offset: Offset(0, 4),blurRadius: 2,),this.draggingShadow = const BoxShadow(color: Colors.black12,offset: Offset(0, 1),blurRadius: 10,),})  : assert(statusBarHeight >= 0),assert(horizontalSpace >= 0),assert(verticalSpace >= 0),assert(bottomMargin >= 0),super(key: key);@override_DraggableWidgetState createState() => _DraggableWidgetState();
}class _DraggableWidgetState extends State<DraggableWidget>with SingleTickerProviderStateMixin {double top = 0, left = 0;double boundary = 0;late AnimationController animationController;late Animation animation;double hardLeft = 0, hardTop = 0;bool offstage = true;AnchoringPosition? currentDocker;double widgetHeight = 18;double widgetWidth = 50;final key = GlobalKey();bool dragging = false;late AnchoringPosition currentlyDocked;bool? visible;bool get currentVisibilty => visible ?? widget.intialVisibility;bool isStillTouching = false;@overridevoid initState() {currentlyDocked = widget.initialPosition;hardTop = widget.topMargin;animationController = AnimationController(value: 1,vsync: this,duration: Duration(milliseconds: 150),)..addListener(() {if (currentDocker != null) {animateSideWidget(currentDocker!);}})..addStatusListener((status) {if (status == AnimationStatus.completed) {hardLeft = left;hardTop = top;}},);animation = Tween<double>(begin: 0,end: 1,).animate(CurvedAnimation(parent: animationController,curve: Curves.easeInOut,));widget.dragController?._addState(this);if (WidgetsBinding.instance != null) {WidgetsBinding.instance!.addPostFrameCallback((timeStamp) async {final widgetSize = getWidgetSize(key);if (widgetSize != null) {setState(() {widgetHeight = widgetSize.height;widgetWidth = widgetSize.width;});}await Future.delayed(Duration(milliseconds: 100,));setState(() {offstage = false;boundary = MediaQuery.of(context).size.height - widget.bottomMargin;if (widget.initialPosition == AnchoringPosition.bottomRight) {top = boundary - widgetHeight + widget.statusBarHeight;left = MediaQuery.of(context).size.width - widgetWidth;hardLeft = left;pointerUpX = left;} else if (widget.initialPosition == AnchoringPosition.bottomLeft) {top = boundary - widgetHeight + widget.statusBarHeight;left = 0;} else if (widget.initialPosition == AnchoringPosition.topRight) {top = widget.topMargin - widget.topMargin;left = MediaQuery.of(context).size.width - widgetWidth;hardLeft = left;pointerUpX = left;} else {top = widget.topMargin;left = 0;}});});}super.initState();}@overridevoid dispose() {animationController.dispose();super.dispose();}@overridevoid didUpdateWidget(DraggableWidget oldWidget) {if (offstage == false && WidgetsBinding.instance != null) {WidgetsBinding.instance!.addPostFrameCallback((timeStamp) {final widgetSize = getWidgetSize(key);if (widgetSize != null) {setState(() {widgetHeight = widgetSize.height;widgetWidth = widgetSize.width;});}setState(() {boundary = MediaQuery.of(context).size.height - widget.bottomMargin;animateSideWidget(currentlyDocked);});});}super.didUpdateWidget(oldWidget);}var pointerUpX = .0;@overrideWidget build(BuildContext context) {return Positioned(top: top,left: left,child: AnimatedSwitcher(duration: Duration(milliseconds: 150,),transitionBuilder: (child, animation) {return ScaleTransition(scale: animation,child: child,);},child: !currentVisibilty? Container(): Listener(onPointerUp: (v) {if (!isStillTouching) {return;}isStillTouching = false;final p = v.position;pointerUpX = p.dx;currentDocker = determineDocker(p.dx, p.dy);setState(() {dragging = false;});if (animationController.isAnimating) {animationController.stop();}animationController.reset();animationController.forward();},onPointerDown: (v) async {isStillTouching = false;await Future.delayed(widget.touchDelay);isStillTouching = true;},onPointerMove: (v) async {if (!isStillTouching) {return;}if (animationController.isAnimating) {animationController.stop();animationController.reset();}setState(() {dragging = true;if (v.position.dy < boundary &&v.position.dy > widget.topMargin) {top = max(v.position.dy - (widgetHeight) / 2, 0);}left = max(v.position.dx - (widgetWidth) / 2, 0);hardLeft = left;hardTop = top;});},child: Offstage(offstage: offstage,child: Container(key: key,padding: EdgeInsets.symmetric(horizontal: widget.horizontalSpace,vertical: widget.verticalSpace,),child: AnimatedContainer(duration: Duration(milliseconds: 150),decoration: BoxDecoration(borderRadius:BorderRadius.circular(widget.shadowBorderRadius),boxShadow: [dragging? widget.draggingShadow: widget.normalShadow// BoxShadow(//   color: Colors.black38,//   offset: dragging ? Offset(0, 10) : Offset(0, 4),//   blurRadius: dragging ? 10 : 2,// )],),child: Transform.scale(scale: dragging ? widget.dragAnimationScale : 1,child: widget.child)),),),),),);}AnchoringPosition determineDocker(double x, double y) {final double totalHeight = boundary;final double totalWidth = MediaQuery.of(context).size.width;if (x <= totalWidth / 2 && y <= totalHeight / 2) {return AnchoringPosition.topLeft;} else if (x < totalWidth / 2 && y > totalHeight / 2) {return AnchoringPosition.bottomLeft;} else if (x > totalWidth / 2 && y < totalHeight / 2) {return AnchoringPosition.topRight;} else {return AnchoringPosition.bottomRight;}}void animateSideWidget(AnchoringPosition docker) {final double totalWidth = MediaQuery.of(context).size.width;double remaingDistanceX = (totalWidth - widgetWidth - hardLeft);print("animateWidget - ${totalWidth / 2} -- $pointerUpX");if((totalWidth / 2) <= pointerUpX){setState(() {left = hardLeft + (animation.value) * remaingDistanceX;});}else {setState(() {left = (1 - animation.value) * hardLeft;});}}void animateWidget(AnchoringPosition docker) {final double totalHeight = boundary;final double totalWidth = MediaQuery.of(context).size.width;switch (docker) {case AnchoringPosition.topLeft:setState(() {left = (1 - animation.value) * hardLeft;if (animation.value == 0) {top = hardTop;} else {top = ((1 - animation.value) * hardTop +(widget.topMargin * (animation.value)));}currentlyDocked = AnchoringPosition.topLeft;});break;case AnchoringPosition.topRight:double remaingDistanceX = (totalWidth - widgetWidth - hardLeft);setState(() {left = hardLeft + (animation.value) * remaingDistanceX;if (animation.value == 0) {top = hardTop;} else {top = ((1 - animation.value) * hardTop +(widget.topMargin * (animation.value)));}currentlyDocked = AnchoringPosition.topRight;});break;case AnchoringPosition.bottomLeft:double remaingDistanceY = (totalHeight - widgetHeight - hardTop);setState(() {left = (1 - animation.value) * hardLeft;top = hardTop +(animation.value) * remaingDistanceY +(widget.statusBarHeight * animation.value);currentlyDocked = AnchoringPosition.bottomLeft;});break;case AnchoringPosition.bottomRight:double remaingDistanceX = (totalWidth - widgetWidth - hardLeft);double remaingDistanceY = (totalHeight - widgetHeight - hardTop);setState(() {left = hardLeft + (animation.value) * remaingDistanceX;top = hardTop +(animation.value) * remaingDistanceY +(widget.statusBarHeight * animation.value);currentlyDocked = AnchoringPosition.bottomRight;});break;case AnchoringPosition.center:double remaingDistanceX =(totalWidth / 2 - (widgetWidth / 2)) - hardLeft;double remaingDistanceY =(totalHeight / 2 - (widgetHeight / 2)) - hardTop;// double remaingDistanceX = (totalWidth - widgetWidth - hardLeft) / 2.0;// double remaingDistanceY = (totalHeight - widgetHeight - hardTop) / 2.0;setState(() {left = (animation.value) * remaingDistanceX + hardLeft;top = (animation.value) * remaingDistanceY + hardTop;currentlyDocked = AnchoringPosition.center;});break;default:}}Size? getWidgetSize(GlobalKey key) {final keyContext = key.currentContext;if (keyContext != null) {final box = keyContext.findRenderObject() as RenderBox;return box.size;} else {return null;}}void _showWidget() {setState(() {visible = true;});}void _hideWidget() {setState(() {visible = false;});}void _animateTo(AnchoringPosition anchoringPosition) {if (animationController.isAnimating) {animationController.stop();}animationController.reset();currentDocker = anchoringPosition;animationController.forward();}Offset _getCurrentPosition() {return Offset(left, top);}
}class DragController {_DraggableWidgetState? _widgetState;void _addState(_DraggableWidgetState _widgetState) {this._widgetState = _widgetState;}/// Jump to any [AnchoringPosition] programaticallyvoid jumpTo(AnchoringPosition anchoringPosition) {_widgetState?._animateTo(anchoringPosition);}/// Get the current screen [Offset] of the widgetOffset? getCurrentPosition() {return _widgetState?._getCurrentPosition();}/// Makes the widget visiblevoid showWidget() {_widgetState?._showWidget();}/// Hide the widgetvoid hideWidget() {_widgetState?._hideWidget();}
}
enum AnchoringPosition {topLeft,topRight,bottomLeft,bottomRight,center,
}

flutter 可拖拽吸边的悬浮按钮,悬浮布局;相关推荐

  1. 拖拽松开飞走触发按钮(飞屏按钮)

    using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using U ...

  2. 使用echarts实现类似股票k图可拖拽可悬浮十字线

    实现难点需要悬浮出现十字线,再次点击后可拖拽 点击屏幕后鼠标悬浮 实现思路点击折线图屏幕后开启或者开启可拖拽 chartDomBig.getZr().off("click");// ...

  3. Flutter全局悬浮按钮

    方法一 Offset _offset = Offset.zero;Scaffold(body: Stack(children: [_pageList[_currentIndex],Positioned ...

  4. vue 移动到图片浮动_基于Vue实现拖拽升级(九宫格拖拽)

    前言 在本文中将会用Vue完成九宫格拖拽效果,同时介绍一下网格布局.具体代码以及demo可以点以下超链接进入 效果实例 Demo 简单了解Grid布局(网格布局) 什么是网格布局 CSS网格布局(又称 ...

  5. [QT_015]Qt学习之基于条目控件的自定义特性(拖拽+右键菜单+样式)

    本文转自:<Qt编程指南>        作者:奇先生 Qt编程指南,Qt新手教程,Qt Programming Guide 本节介绍基于条目控件的定制特性,首先介绍条目的拖拽,列表控件. ...

  6. Vue实现拖拽升级(九宫格拖拽)

    前言 在本文中将会用Vue完成九宫格拖拽效果,同时介绍一下网格布局. 效果实例 简单了解Grid布局(网格布局) 什么是网格布局 CSS网格布局(又称"网格"),是一种二维网格布局 ...

  7. 纯CSS实现左右拖拽改变布局大小 使用CSS3中resize属性 水平,垂直拖动元素,改变大小

    利用浏览器非overflow:auto元素设置resize可以拉伸的特性实现无JavaScript的分栏宽度控制. webkit浏览器下滚动条可以自定义,其中resize区域大小就是scrollbar ...

  8. vue拖拽排序 组件

    vue拖拽排序 组件 npm install vuedraggable -S vue.draggable中文文档 组件代码 <template><div><div cla ...

  9. 干货分享!悬浮按钮设计规范和经典实践

    悬浮按钮(Floating Action Button,简称FAB)是APP应用中常见的UI元件.它轻盈.优雅.便捷又高效,是Google设计语言中一颗璀璨的明珠.悬浮按钮往往都会独立出现在界面之上, ...

最新文章

  1. Ubuntu su 认证失败
  2. sharepoint 2010 内容类型
  3. 【Linux】一步一步学Linux——wget命令(192)
  4. python异步_Python通过Thread实现异步
  5. qt ui指针和本类对象_您需要了解的有关UI设计的形状和对象的所有信息
  6. (转)如何保障微服务架构下的数据一致性?
  7. LeetCode题解
  8. 2013北理机试题——中缀算术表达式对应二叉树的先序遍历
  9. 末日前的唠叨:SEO之四大要不得
  10. 使用Adorner显示WPF控件的边界点
  11. 百度算法频频更新,草根站长的出路何在?
  12. html页面跳转传值原生,html页面跳转传递参数问题
  13. python鼠标监听_用Python监听鼠标和键盘事件
  14. 关于signed main()不报超时与int main()报超时
  15. 学JAVA编程用什么电脑配置_学习编程,你真正需要的是什么样配置的电脑?
  16. matlab 不允许函数定义,MATLAB中此上下文中不允许出现函数定义,急求~
  17. 毕业设计之“真心话大冒险”小程序
  18. Harbor容器安装以及相关特性部署与使用(SSL证书+AD域)
  19. 使用ICMP协议检测网络状态
  20. 怎样改证件照的背景颜色?两种方法教你换背景色

热门文章

  1. php警告注释,php程序(warning)警告
  2. 图灵 | 计算机器与智能
  3. Java程序员两年校招笔记总结分析——菜鸡求职
  4. python tutorial json_Python Tutorial - Parse JSON Objects with Python
  5. 2022年推土机司机(建筑特殊工种)考试题库及推土机司机(建筑特殊工种)考试技巧
  6. win10无线投屏_win10电脑投屏要无线投屏器吗?
  7. 2021十大手表品牌TOP排行榜
  8. p-sum结构解释+代码 二叉区间树
  9. caffe中各种cblas的函数使用总结
  10. 模仿微信更改头像,图片局部放大