背景
由于活动Icon一直固定在一侧会产生视线遮挡,期望能由用户自行拖动并在松手之后吸附在屏幕的一侧
实现方式一
import 'package:flutter/material.dart';
class DragBoxWidget extends StatefulWidget {
/// 可拖动的Widget
final Widget child;
/// 可拖动的Widget宽度
final double width;
/// 可拖动的Widget高度
final double height;
/// 父级组件宽度
final double parentWidth;
/// 父级组件高度
final double parentHeight;
/// 初始化X轴
final double offsetX;
/// 初始化Y轴
final double offsetY;
/// 点击事件
final GestureTapCallback onTap;
const DragBoxWidget(
{Key? key,
required this.child,
required this.width,
required this.height,
required this.parentWidth,
required this.parentHeight,
required this.offsetX,
required this.offsetY,
required this.onTap})
: super(key: key);
@override
_DragBoxWidgetState createState() => _DragBoxWidgetState(
child, width, height, parentWidth, parentHeight, offsetX, offsetY, onTap);
}
class _DragBoxWidgetState extends State<DragBoxWidget> {
final Widget child;
final double width;
final double height;
final double parentWidth;
final double parentHeight;
final double offsetX;
final double offsetY;
final GestureTapCallback onTap;
Offset offset = const Offset(0, 0);
_DragBoxWidgetState(this.child, this.width, this.height, this.parentWidth,
this.parentHeight, this.offsetX, this.offsetY, this.onTap);
@override
void initState() {
offset = Offset(offsetX, offsetY);
super.initState();
}
@override
Widget build(BuildContext context) {
return Positioned(
left: offset.dx,
top: offset.dy,
child: GestureDetector(
onPanUpdate: (detail) {
setState(() {
offset = _calOffset(offset, detail.delta);
});
},
onTap: onTap,
onPanEnd: (detail) {
setState(() {
// 移动位置小于父组件宽度一半,吸附在左侧
if (offset.dx < parentWidth / 2 - width / 2) {
offset = Offset(0, offset.dy);
} else {
// 移动位置大于父组件宽度一半,吸附在右侧
offset = Offset(parentWidth - width, offset.dy);
}
});
},
child: child),
);
}
/// 计算偏移量
Offset _calOffset(Offset offset, Offset nextOffset) {
double dx = 0;
// 水平方向偏移量不能小于0不能大于父级组件宽度
if (offset.dx + nextOffset.dx <= 0) {
dx = 0;
} else if (offset.dx + nextOffset.dx >= (parentWidth - width)) {
dx = parentWidth - width;
} else {
dx = offset.dx + nextOffset.dx;
}
double dy = 0;
// 垂直方向偏移量不能小于0不能大于父级组件高度
if (offset.dy + nextOffset.dy >= (parentHeight - height)) {
dy = parentHeight - height;
} else if (offset.dy + nextOffset.dy <= 0) {
dy = 0;
} else {
dy = offset.dy + nextOffset.dy;
}
return Offset(
dx,
dy,
);
}
}
实现方式二
import 'package:flutter/material.dart';
/// 覆盖悬浮窗视图
class OverlayUtil {
// 用来保存OverlayEntry
static OverlayEntry? _holder;
// 要显示的视图
static Widget? _view;
// 左边距
static double _left = 0;
// 右边距
static double _right = 0;
// 左右分界线的X坐标
static double _borderlineX = 0;
// 视图高度
static double _viewHeight = 0;
// 当前顶部边距
static double _curTop = 0;
// 当前左边距
static double? _curLeft;
// 当前右边距
static double? _curRight = 0;
// 拖动开始回调
static VoidCallback? _onDragStarted;
// 拖动结束回调
static DragEndCallback? _onDragEnd;
// 是否初始化
static bool _isInit = true;
/// 显示悬浮窗
static void show(
{required BuildContext context,
required Widget view,
required double viewWidth,
required double viewHeight,
Offset initOffset = const Offset(800, 1200),
double left = 0,
double right = 0,
double? borderlineX,
VoidCallback? onDragStarted,
DragEndCallback? onDragEnd}) {
_left = left;
_right = right;
_borderlineX =
borderlineX ?? (MediaQuery.of(context).size.width - viewWidth) / 2;
_view = view;
_viewHeight = viewHeight;
_onDragStarted = onDragStarted;
_onDragEnd = onDragEnd;
if (_isInit) {
_isInit = false;
_calculatePosition(initOffset, context);
}
_remove();
// 创建一个OverlayEntry对象
OverlayEntry overlayEntry = OverlayEntry(
builder: (context) {
return Positioned(
top: _curTop,
left: _curLeft,
right: _curRight,
child: _buildDraggable(context),
);
},
);
// 往Overlay中插入插入OverlayEntry
Overlay.of(context).insert(overlayEntry);
_holder = overlayEntry;
}
/// 销毁悬浮窗
static void dispose() {
_isInit = true;
_remove();
}
/// 移除悬浮窗
static void _remove() {
_holder?.remove();
_holder = null;
_onDragStarted = null;
_onDragEnd = null;
}
/// 创建可拖动的视图
static _buildDraggable(BuildContext context) {
if (_view == null) {
return;
}
return Draggable(
feedback: _view!,
onDragStarted: () {
// 拖动开始
_onDragStarted?.call();
},
onDragEnd: (detail) {
// 拖动结束
_createDragTarget(offset: detail.offset, context: context);
_onDragEnd?.call(detail);
},
childWhenDragging: const SizedBox.shrink(),
child: _view!,
);
}
/// 计算位置
static void _calculatePosition(Offset offset, BuildContext context) {
bool isLeft = offset.dx < _borderlineX;
double maxY = MediaQuery.of(context).size.height - _viewHeight;
_curTop = offset.dy < 0 ? 0 : (offset.dy < maxY ? offset.dy : maxY);
_curLeft = isLeft ? _left : null;
_curRight = isLeft ? null : _right;
}
/// 创建拖动目标
static void _createDragTarget(
{required Offset offset, required BuildContext context}) {
_remove();
_holder = OverlayEntry(
builder: (context) {
_calculatePosition(offset, context);
return Positioned(
top: _curTop,
left: _curLeft,
right: _curRight,
child: DragTarget(
builder: (BuildContext context, List incoming, List rejected) {
return _buildDraggable(context);
},
),
);
},
);
Overlay.of(context).insert(_holder!);
}
}