Flutter 实现任意控件拖动

news2024/9/20 22:28:16

文章目录

  • 前言
  • 一、如何实现?
    • 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获取控件大小以及获取控件大小的方法,很容易就实现拖动功能了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/708991.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

chatgpt赋能python:用Python进行数据挖掘来优化SEO排名

用Python进行数据挖掘来优化SEO排名 您是否一直在为如何在搜索引擎排名上优化自己的网站而苦恼&#xff1f;事实上&#xff0c;Python可以成为您的得力助手&#xff0c;来简化您的分析和优化过程&#xff0c;并帮助您在搜索引擎排名上获得更好的结果。 什么是SEO&#xff1f;…

tomcat与自定义类加载器

类加载器与类的”相同“判断 类加载器除了用于加载类外&#xff0c;还可用于确定类在Java虚拟机中的唯一性。 不同类加载器加载的类在 JVM 看来是两个不同的类&#xff0c;因为在 JVM 中一个类的唯一标识是 类加载器类名(包括包名)。 类加载器种类 启动类加载器&#xff0c;…

phpstorm左边变黄,页面内跳转追踪等功能不好使了

第一种方法&#xff1a;删除项目根目录中.idea文件夹(确保删除成功)&#xff0c;然后再重新打开项目即可&#xff08;这个方法对我无效&#xff09; 第二种方法&#xff1a;点击File(文件)->Repair IDE(修复IDE)&#xff0c;按照右下角的指示一步步操作即可&#xff08;这种…

华为OD机试真题 Python 实现【静态代码扫描服务】【2023Q1 100分】

目录 一、题目描述二、输入描述三、输出描述四、解题思路五、Python算法源码六、效果展示1、输入2、输出 一、题目描述 静态扫描快速识别源代码的缺陷&#xff0c;静态扫描的结果以扫描报告作为输出&#xff1a; 文件扫描的成本和文件大小相关&#xff0c;如果文件大小为N&am…

echarts折线图背景空白太大

1、没有调间距的效果&#xff1a; 2、更改间距后的效果&#xff1a; 3、其实只要更改grid定位值就行了&#xff0c;上代码&#xff1a; grid: {top: 20px,left: 20px,right: 20px,bottom: 20px,containLabel: true},4、将代码copy到option里面就行了

【中间件】Ngnix入门

文章目录 下载地址相关概念*反向代理&#xff1a;*负载均衡&#xff1a;轮询权重轮询iphash*动静分离 文件解释/conf/nginx.conf 基本使用双击运行&#xff08;不建议&#xff09;使用命令行 常用命令&#xff08;Linux下&#xff09; 下载地址 Nginx官网下载页 相关概念 *反…

IMX8QXP SPI CS片选连续传输模式

问题背景 在开发项目中&#xff0c;换了一个spi安全芯片&#xff0c;发现不能进行通信&#xff0c;查看spi信号波形&#xff0c;时钟&#xff0c;发送数据&#xff0c;片选都是正常的&#xff0c;但没有回复数据。安全芯片技术正常通过查看CS片选波形信号&#xff0c;提出CS片选…

Appium: Windows系统桌面应用自动化测试(一)

Appium: Windows系统桌面应用自动化测试 一、方案调研二、环境搭建1、WinAppDriver环境搭建&#xff08;1&#xff09;开启开发者选项中的“开发人员模式”&#xff08;2&#xff09;windows sdk下载安装&#xff08;3&#xff09;WinAppDriver下载安装 2、appium环境搭建&…

2018年全国硕士研究生入学统一考试管理类专业学位联考逻辑试题——纯享题目版

&#x1f3e0;个人主页&#xff1a;fo安方的博客✨ &#x1f482;个人简历&#xff1a;大家好&#xff0c;我是fo安方&#xff0c;考取过HCIE Cloud Computing、CCIE Security、CISP等证书。&#x1f433; &#x1f495;兴趣爱好&#xff1a;b站天天刷&#xff0c;题目常常看&a…

第十二章 原理篇:vision transformer

参考教程&#xff1a; https://arxiv.org/pdf/2010.11929.pdf https://zhuanlan.zhihu.com/p/340149804 【大佬总结的非常好&#xff0c;他的好多篇文章都很值得学习】 文章目录 为什么会使用transformerVIT详解method获得patchpatch embeddingposition embedding 代码实现eino…

数据结构--队列的顺序实现

数据结构–队列的顺序实现 队列的顺序存储代码定义 #define MaxSize 10 //定义队列中元素最大个数 typedef struct {ElemType data[MaxSize]; //静态数组存放队列元素int front, rear; //对头指针 & 队尾指针 } SqQueue; 初始化操作 void InitQueue(SqQueue &Q) {Q.r…

第九十三天学习记录:C++核心:类和对象Ⅱ(五星重要)

对象的初始化和清理 构造函数和析构函数 对象的初始化和清理也是两个非常重要的安全问题 一个对象或者变量没有初始化状态&#xff0c;对其使用后果是未知 同样的使用完一个对象或变量&#xff0c;没有及时清理&#xff0c;也会造成一定的安全问题 c利用了构造函数和析构函数…

搜索封装+crud+重点细节优化

新建GSearchQuery.vue <template> <el-row :span"24><el-input type"text" v-model"query"></el-input><slot></slot> </el-row> </template>data() {return{query: }}index.vue 引入组件 <GSe…

每周学点数学 1:数学建模计划篇

用GPT3.5生成了一段数学建模学习的计划&#xff0c;我准备在视觉学习的同时&#xff0c;穿插一些数学理论方面的学习&#xff0c;以下是近三个月的计划。 7月2日-7月8日&#xff1a;了解数学建模的基本概念和方法&#xff0c;阅读相关教材和论文&#xff0c;了解数学建模的应…

【如何用大语言模型快速深度学习系列】开篇+文本匹配系列

开篇 很久没有更新啦&#xff01;这个系列其实是自己的一个学习笔记啦&#xff01;这个系列的特色就是我们不再通过看视频的方式入门深度学习。 开篇的时候&#xff08;2023.07.01&#xff09;&#xff0c;我想的是围绕自然语言处理的一些基础方法&#xff08;代码方面&#…

带你用Python制作一个经典小游戏:扫雷

名字&#xff1a;阿玥的小东东 学习&#xff1a;Python、C/C 主页链接&#xff1a;阿玥的小东东的博客_CSDN博客-python&&c高级知识,过年必备,C/C知识讲解领域博主 目录 游戏界面尺寸 方块尺寸 雷的数量 游戏状态 最后&#xff0c;我们定义一个函数run()&#xff…

软件项目成本的计算

在《架构思维的六要素》中&#xff0c;列出的第一个要素就是成本&#xff0c;成本对项目设计和决策起着至关重要的作用。今天咱们就来看看直接成本、间接成本和总成本的关系。 直接成本 工作于服务的开发者工作于系统测试的测试者设计数据库的DBA设计界面和致力于优化用户体验的…

ROS学习篇之远程控制(七)-局域网内的控制

文章目录 一.pc与pc端二.手机与pc端&#xff08;1&#xff09;pc端&#xff1a;**步骤1&#xff1a;** 运行 roscore**步骤2&#xff1a;** 新开一个终端运行&#xff0c;运行 rosrun turtlesim turtlesim_node **步骤3&#xff1a;** 新建一个终端查看ip&#xff0c;运行ifcon…

基于matlab根据特征进行全景图像拼接(附源码)

一、前言 此示例演示如何使用基于特征的图像配准技术自动创建全景图。 特征检测和匹配是许多计算机视觉应用&#xff08;如图像配准、跟踪和对象检测&#xff09;中使用的强大技术。在此示例中&#xff0c;基于特征的技术用于自动拼接一组图像。图像拼接过程是基于特征的图像…

火车头采集:高效数据采集工具的介绍

火车头采集是一款基于Python语言开发的网络爬虫工具&#xff0c;用于快速高效地从互联网上采集数据并存储到本地或远程数据库。它简单易用且功能强大&#xff0c;在各行各业广泛应用。 1、设置chatgpt自定义key 添加网站 通过关键词批量生成原创文章 设置发布网站 发布成功 相…