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)楦缚丶サ魟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
}二、完整代碼
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)楦缚丶?,去掉則不受限制,但拖出父控件會(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 中出現(xiàn)java.net.BindException: bind failed: EADDRINUSE 問(wèn)
這篇文章主要介紹了Android 中出現(xiàn)java.net.BindException: bind failed: EADDRINUSE 問(wèn)題解決辦法的相關(guān)資料,需要的朋友可以參考下2017-04-04
解決Android-RecyclerView列表倒計(jì)時(shí)錯(cuò)亂問(wèn)題
這篇文章主要介紹了解決Android-RecyclerView列表倒計(jì)時(shí)錯(cuò)亂問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-08-08
Android 登錄處理簡(jiǎn)單實(shí)例(源碼下載)
這篇文章主要介紹了Android 登錄處理簡(jiǎn)單實(shí)例(源碼下載)的相關(guān)資料,需要的朋友可以參考下2017-03-03
Android開(kāi)發(fā)之獲取短信驗(yàn)證碼后按鈕背景變化并且出現(xiàn)倒計(jì)時(shí)
在開(kāi)發(fā)是經(jīng)常會(huì)遇到獲取短信驗(yàn)證碼,然后獲取驗(yàn)證碼后需要等待n秒倒計(jì)時(shí),這時(shí)是不能再次發(fā)送短信請(qǐng)求的,這是需要一個(gè)倒計(jì)時(shí)程序,本文給大家分享了實(shí)現(xiàn)此功能的代碼,需要的朋友參考下2016-01-01
Flutter利用Hero組件實(shí)現(xiàn)自定義路徑效果的動(dòng)畫(huà)
本篇介紹了如何利用Hero動(dòng)畫(huà)組件的createRectTween屬性實(shí)現(xiàn)自定義路徑效果的動(dòng)畫(huà)。文中的示例代碼講解詳細(xì),感興趣的可以了解一下2022-06-06
Android Webview上的ssl warning的處理方式詳解及實(shí)例
這篇文章主要介紹了Android Webview上的ssl warning的處理方式詳解及實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-02-02

