Flutter如何检测设备旋转(方向变化) 适配可拖动悬浮按钮位置
Flutter可拖动悬浮按钮旋转越界问题解决方案
不需要单独获取旋转前的屏幕方向,直接监听父容器尺寸变化(屏幕旋转、分屏、折叠屏状态切换都会触发该变化),每次尺寸更新后重新计算按钮可移动边界,同时将当前按钮偏移量钳位到新的合法坐标区间即可。该方案比单独监听方向事件覆盖更多适配场景,也不需要为横屏单独实现独立组件。
具体修改点
- 移除组件中冗余的
deviceWidth、deviceHeight入参,所有尺寸直接通过父容器的RenderBox实时获取,避免外部传入的静态尺寸和实际渲染尺寸不一致 - 新增尺寸变化校验逻辑:每次帧渲染完成后检查父容器尺寸是否变化,若变化则重新计算可移动边界
- 边界更新完成后自动修正按钮坐标,确保按钮始终在屏幕可视范围内,不会出现越界无法点击的问题
修改后完整代码
import 'package:flutter/material.dart'; class DraggableFloatingActionButton extends StatefulWidget { final Widget child; final Function onPressed; final GlobalKey parentKey; DraggableFloatingActionButton({ required this.child, required this.onPressed, required this.parentKey, }); @override State<StatefulWidget> createState() => _DraggableFloatingActionButtonState(); } class _DraggableFloatingActionButtonState extends State<DraggableFloatingActionButton> { final GlobalKey _key = GlobalKey(); bool _isDragging = false; late Offset _offset; late Offset _minOffset; late Offset _maxOffset; // 缓存上一次父容器尺寸,用于判断是否需要更新边界 Size _lastParentSize = Size.zero; @override void initState() { super.initState(); // 首帧渲染完成后初始化边界和初始位置 WidgetsBinding.instance.addPostFrameCallback((_) { _updateBoundary(); // 初始位置保持原逻辑,放在屏幕右下区域 final initSize = _lastParentSize; _offset = Offset(initSize.width * 0.8, initSize.height * 0.75); // 钳位初始位置避免越界 _clampOffset(); if (mounted) setState(() {}); }); // 注册帧回调,持续监听尺寸变化 WidgetsBinding.instance.addPersistentFrameCallback((_) { _updateBoundary(); }); } // 更新按钮可移动边界 void _updateBoundary() { final RenderBox? parentRenderBox = widget.parentKey.currentContext?.findRenderObject() as RenderBox?; final RenderBox? renderBox = _key.currentContext?.findRenderObject() as RenderBox?; if (parentRenderBox == null || renderBox == null) return; final Size parentSize = parentRenderBox.size; // 父容器尺寸无变化时直接返回,不做多余更新 if (parentSize == _lastParentSize) return; _lastParentSize = parentSize; final Size btnSize = renderBox.size; _minOffset = const Offset(0, 0); _maxOffset = Offset( parentSize.width - btnSize.width, parentSize.height - btnSize.height ); // 边界更新后自动修正按钮位置,防止越界 _clampOffset(); if (mounted) setState(() {}); } // 将偏移量钳位到合法坐标区间 void _clampOffset() { double newX = _offset.dx.clamp(_minOffset.dx, _maxOffset.dx); double newY = _offset.dy.clamp(_minOffset.dy, _maxOffset.dy); _offset = Offset(newX, newY); } void _updatePosition(PointerMoveEvent pointerMoveEvent) { double newOffsetX = _offset.dx + pointerMoveEvent.delta.dx; double newOffsetY = _offset.dy + pointerMoveEvent.delta.dy; newOffsetX = newOffsetX.clamp(_minOffset.dx, _maxOffset.dx); newOffsetY = newOffsetY.clamp(_minOffset.dy, _maxOffset.dy); setState(() { _offset = Offset(newOffsetX, newOffsetY); }); } @override Widget build(BuildContext context) { return Positioned( left: _offset.dx, top: _offset.dy, child: Listener( onPointerMove: (PointerMoveEvent pointerMoveEvent) { _updatePosition(pointerMoveEvent); setState(() { _isDragging = true; }); }, onPointerUp: (PointerUpEvent pointerUpEvent) { if (_isDragging) { setState(() { _isDragging = false; }); } else { widget.onPressed(); } }, child: Container( key: _key, child: widget.child, ), ), ); } }
使用说明
- 调用组件时无需再传入
deviceWidth、deviceHeight参数,仅需传入child、onPressed、parentKey三个必传参数即可 - 实现自动适配竖屏/横屏切换、分屏模式、折叠屏展开/折叠等所有导致父容器尺寸变化的场景,按钮位置会在布局变化后自动修正到可视区域内,不会出现越界无法点击的问题
- 原有拖动、点击交互逻辑完全保留,无使用层面的改动
内容的提问来源于stack exchange,提问作者Captain Mockery
相关产品推荐
相关产品推荐

