Android?Flutter實(shí)現(xiàn)任意拖動(dòng)的控件
前言
使用flutter開(kāi)發(fā)是需要控件能拖動(dòng),比如畫(huà)板中的元素,或者工具條,搜索框,每個(gè)都單獨(dú)去實(shí)現(xiàn)拖動(dòng)還是比較麻煩的,將拖動(dòng)功能封裝成一個(gè)控件,需要的時(shí)候直接使用拖動(dòng)控件作為父控件這樣就方便很多了。
一、如何實(shí)現(xiàn)
1、使用GestureDetector響應(yīng)拖動(dòng)事件
//總位移 var _unlimtedOffset = Offset.zero; //當(dāng)前位移(有活動(dòng)區(qū)域限制時(shí),鼠標(biāo)超過(guò)邊界后當(dāng)前位移不等于總位移,此時(shí)總位移可以確?;氐竭吔鐑?nèi)鼠標(biāo)與控件的相對(duì)位置不變) final _offset = ValueNotifier<Offset>(Offset.zero); GestureDetector( child: this.widget.child, onPanUpdate: (detail) { //累加拖動(dòng)距離 _unlimtedOffset += detail.delta; } )
2、使用Transform變換控件位置
使用translate變換位置即可
//ValueListenableBuilder監(jiān)聽(tīng)_offset 改變,此處略 Transform.translate( offset: offset, child:GestureDetector()//上一步的child:GestureDetector )
3、計(jì)算拖動(dòng)區(qū)域
這一步不是必須的,但是如果需要限制控件活動(dòng)范圍則需要這一步。
通過(guò)GlobalKey獲取控件大小,在GestureDetector的onPanUpdate事件中:
onPanUpdate: (detail) { //拖動(dòng)區(qū)域?yàn)楦缚丶?,去掉則不受限制,但拖出父控件會(huì)被遮擋無(wú)法點(diǎn)擊。 //獲取父控件大小 RenderBox ? parentRenderBox = _mykey.currentContext ? .findAncestorRenderObjectOfType<RenderObject>() as RenderBox ? ; final screenSize = parentRenderBox ? .size; //獲取控件大小 final mySize = _mykey.currentContext ? .size; final renderBox = _mykey.currentContext ? .findRenderObject() as RenderBox ? ; //獲取控件當(dāng)前位置 var originOffset = renderBox ? .localToGlobal(Offset.zero); if (originOffset != null) { originOffset = parentRenderBox ? .globalToLocal(originOffset); } if (screenSize == null || mySize == null || originOffset == null) { return; } //計(jì)算不超出父控件區(qū)域 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, ); } //現(xiàn)在活動(dòng)區(qū)域?yàn)楦缚丶?--end }
二、完整代碼
drag_move_box.dart
import 'package:flutter/material.dart'; /// 可拖動(dòng)容器 /// 拖動(dòng)范圍是父控件 class DragMoveBox extends StatefulWidget { final Widget child; const DragMoveBox({ super.key, required this.child, }); @override State<DragMoveBox> createState() => _DragMoveBoxState(); } class _DragMoveBoxState extends State<DragMoveBox> { final GlobalKey _mykey = GlobalKey(); //當(dāng)前位移(有活動(dòng)區(qū)域限制時(shí),鼠標(biāo)超過(guò)邊界后當(dāng)前位移不等于總位移,此時(shí)總位移可以確?;氐竭吔鐑?nèi)鼠標(biāo)與控件的相對(duì)位置不變) final _offset = ValueNotifier<Offset>(Offset.zero); //總位移 var _unlimtedOffset = Offset.zero; @override Widget build(BuildContext context) { return ValueListenableBuilder( valueListenable: _offset, builder: //采用transform變換實(shí)現(xiàn)拖動(dòng) (context, offset, widget) => Transform.translate( key: _mykey, offset: offset, child: GestureDetector( child: this.widget.child, onPanUpdate: (detail) { var off = _unlimtedOffset = _unlimtedOffset + detail.delta; //拖動(dòng)區(qū)域?yàn)楦缚丶サ魟t不受限制,但拖出父控件會(huì)被遮擋無(wú)法點(diǎn)擊。 //獲取父控件大小 RenderBox? parentRenderBox = _mykey.currentContext ?.findAncestorRenderObjectOfType<RenderObject>() as RenderBox?; final screenSize = parentRenderBox?.size; //獲取控件大小 final mySize = _mykey.currentContext?.size; final renderBox = _mykey.currentContext?.findRenderObject() as RenderBox?; //獲取控件當(dāng)前位置 var originOffset = renderBox?.localToGlobal(Offset.zero); if (originOffset != null) { originOffset = parentRenderBox?.globalToLocal(originOffset); } if (screenSize == null || mySize == null || originOffset == null) { return; } //計(jì)算不超出父控件區(qū)域 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, ); } //現(xiàn)在活動(dòng)區(qū)域?yàn)楦缚丶?--end _offset.value = off; }, ), ), ); } }
三、使用示例
1、基本用法
DragMoveBox( child:Text("You have pushed the button this many times:") //需要拖動(dòng)的控件 )
2.效果預(yù)覽
總結(jié)
本文提供了一種簡(jiǎn)單的拖動(dòng)控件實(shí)現(xiàn),尤其是封裝成容器后使用變得很簡(jiǎn)單,主要在于能想到translate變換可以改變位置,在了解通過(guò)GlobalKey獲取控件大小以及獲取控件大小的方法,很容易就實(shí)現(xiàn)拖動(dòng)功能了。
以上就是Android Flutter實(shí)現(xiàn)任意拖動(dòng)的控件的詳細(xì)內(nèi)容,更多關(guān)于Android Flutter任意拖動(dòng)控件的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android Studio 代理配置指南(小結(jié))
這篇文章主要介紹了Android Studio 代理配置指南(小結(jié)),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-01-01Kotlin擴(kuò)展函數(shù)與運(yùn)算符重載超詳細(xì)解析
Kotlin可以為一個(gè)不能修改的或來(lái)自第三方庫(kù)中的類(lèi)編寫(xiě)一個(gè)新的函數(shù)。這個(gè)新增的函數(shù)就像那個(gè)原始類(lèi)本來(lái)就有的函數(shù)一樣,可以用普通的方法調(diào)用,這種機(jī)制的函數(shù)稱(chēng)為擴(kuò)展函數(shù)2022-11-11Android 數(shù)據(jù)存儲(chǔ)方式有哪幾種
android為數(shù)據(jù)存儲(chǔ)提供了五種方式,有SharedPreferences、文件存儲(chǔ)、SQLite數(shù)據(jù)庫(kù)、ContentProvider、網(wǎng)絡(luò)存儲(chǔ),對(duì)android數(shù)據(jù)存儲(chǔ)方式感興趣的朋友可以通過(guò)本文學(xué)習(xí)一下2015-11-11Android布局技巧之include、merge與ViewStub標(biāo)簽的巧用
Android 官方提供了三個(gè)用來(lái)優(yōu)化布局的標(biāo)簽,分別是include、merge與ViewStub,下面這篇文章主要給大家介紹了關(guān)于Android布局技巧之include、merge與ViewStub標(biāo)簽巧用的相關(guān)資料,需要的朋友可以參考下2018-06-06android 之Spinner下拉菜單實(shí)現(xiàn)級(jí)聯(lián)
android 之Spinner下拉菜單實(shí)現(xiàn)級(jí)聯(lián),需要的朋友可以參考一下2013-02-02Android App實(shí)現(xiàn)監(jiān)聽(tīng)軟鍵盤(pán)按鍵的三種方式
本篇文章主要介紹Android App實(shí)現(xiàn)監(jiān)聽(tīng)軟鍵盤(pán)按鍵的三種方式,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-03-03代碼分析Android實(shí)現(xiàn)側(cè)滑菜單
現(xiàn)在app越來(lái)越注重用戶(hù)體驗(yàn),本文給大家分析android實(shí)現(xiàn)側(cè)滑菜單的代碼,代碼簡(jiǎn)單易懂,感興趣的朋友一起看看吧2015-11-11