背景:项目中需要做一个可拖拽悬浮控件,松手后悬停到对应的两边。
效果:似乎不能放视频,我放两张图片。红色的是可以拖动的控件。
image.png
image.png
主要思路:
- 通过创建OverlayManagerMixin对Overlay进行创建和销毁管理
- DragHoverBothSidesWidget中通过GestureDetector的拖拽回调实时更新控件的x和y轴的距离。
代码实现
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
///统一管理唯一Overlay
mixin OverlayManagerMixin<T extends StatefulWidget> on State<T> {
bool _overlayEntryInserted = false;
OverlayState? _overlayState;
OverlayEntry _overlayEntry = OverlayEntry(
builder: (_) => const SizedBox.shrink(),
);
@override
void initState() {
super.initState();
_overlayState = Overlay.of(context);
}
void injectOverlay(Widget widget, double width, double height) {
if (_overlayEntryInserted) {
debugPrint("overlay 没有销毁,不允许重复插入");
return;
}
_overlayEntry = OverlayEntry(builder: (_) {
return Material(
type: MaterialType.transparency,
child: DragHoverBothSidesWidget(widget, width, height),
);
});
_overlayState?.insert(_overlayEntry);
_overlayEntryInserted = true;
}
void removeOverlay() {
// Call `remove` only when the entry has been inserted.
if (_overlayEntryInserted) {
_overlayEntry.remove();
_overlayEntryInserted = false;
}
}
}
///创建一个可以拖拽悬停两边控件
class DragHoverBothSidesWidget extends StatefulWidget {
Widget child;
///可拖拽控件的宽高
double height;
double width;
double bottomDistance;
DragHoverBothSidesWidget(this.child, this.width, this.height, {Key? key, this.bottomDistance = 68}) : super(key: key);
@override
State<StatefulWidget> createState() {
return DragHoverBothSidesState();
}
}
final double ratio = WidgetsBinding.instance.window.devicePixelRatio;
class DragHoverBothSidesState extends State<DragHoverBothSidesWidget> {
double _dx = 0;
double _dy = 0;
final Size _windowSize = WidgetsBinding.instance.window.physicalSize / ratio;
@override
void initState() {
super.initState();
_dx = _windowSize.width - widget.width;
_dy = _windowSize.height - widget.height - widget.bottomDistance;
}
void dragEnd(DragEndDetails details) {
if (_dx + widget.width / 2 < _windowSize.width / 2) {
_dx = 0;
} else {
_dx = _windowSize.width - widget.width;
}
if (_dy + widget.height > _windowSize.height) {
_dy = _windowSize.height - widget.height - widget.bottomDistance;
} else if (_dy < 0) {
_dy = 0;
}
setState(() {});
}
void dragEvent(DragUpdateDetails details) {
_dx = details.globalPosition.dx - widget.width / 2;
_dy = details.globalPosition.dy - widget.height / 2;
setState(() {});
}
@override
Widget build(BuildContext context) {
return Container(
width: _windowSize.width,
height: _windowSize.height,
// color: Colors.green,
child: Stack(
alignment: Alignment.center,
children: [Positioned(left: _dx, top: _dy, child: GestureDetector(onVerticalDragEnd: dragEnd, onHorizontalDragEnd: dragEnd, onHorizontalDragUpdate: dragEvent, onVerticalDragUpdate: dragEvent, child: widget.child))],
),
);
}
}
网友评论