Flutter MouseRegion滚动时Hover临时中断,Projects按钮停止追踪鼠标
滚动时MouseRegion的Hover效果中断问题解决方案
Flutter应用中,滚动开始时MouseRegion内的hover效果会中断,导致Projects按钮停止追踪鼠标移动。调整MouseRegion属性后问题仍存在。
问题代码片段
class MainButtonStarting extends StatefulWidget { const MainButtonStarting({super.key}); @override State<MainButtonStarting> createState() => _MainButtonStartingState(); } class _MainButtonStartingState extends State<MainButtonStarting> with SingleTickerProviderStateMixin { var containerPosition = const Offset( 10000, 680, ); double containerSize = 100; double containerOpacity = .5; @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { containerPosition = Offset( MediaQuery.of(context).size.width - 410, 670, ); }); } @override Widget build(BuildContext context) { return MouseRegion( onExit: (event) { containerOpacity = .5; containerPosition = Offset( MediaQuery.of(context).size.width - 410, 670, ); containerSize = 100; }, onHover: (event) { setState(() { containerOpacity = 1; containerSize = 156; double containerWidth = 100; // Adjust according to your container size double containerHeight = 100; // Adjust according to your container size double mouseX = event.localPosition.dx; double mouseY = event.localPosition.dy; double screenWidth = MediaQuery.of(context).size.width; double screenHeight = MediaQuery.of(context).size.height; // Calculate new position for the container double newLeft = mouseX - containerSize / 2; double newTop = mouseY - containerSize / 2; // Ensure the container stays within the bounds of the screen if (newLeft < 0) { newLeft = 0; } else if (newLeft + containerWidth > screenWidth) { newLeft = screenWidth - containerWidth; } if (newTop < 0) { newTop = 0; } else if (newTop + containerHeight > screenHeight) { newTop = screenHeight - containerHeight; } // Update container position containerPosition = Offset(newLeft, newTop); }); }, child: Stack( children: [ Container( height: 782, width: double.infinity, decoration: basicContainer, child: Stack( children: [ ClipRRect( borderRadius: BorderRadius.circular(12), child: Image.asset( 'assets/images/newImages/bg_container.png', fit: BoxFit.cover, height: 782, ), ), Positioned( left: 50, bottom: 40, child: Container( width: 875, decoration: basicContainer, child: Padding( padding: const EdgeInsets.all(46.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( width: 875, child: Text( 'Embracing the interplay of artistic freedom and strategic order.', textAlign: TextAlign.right, style: GoogleFonts.gfsDidot( color: Colors.white, fontSize: 40, fontWeight: FontWeight.w400, ), ), ), const Gap(74), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.end, children: [ SizedBox( width: 537, child: Text( 'We welcome the invigorating fusion of boundless creativity and steadfast structure. It’s within this captivating balance that we discover our inspiration and drive to deliver exceptional results.\n\nOur design philosophy is rooted in our commitment to creativity, collaboration, and attention to detail. \n\nWe are committed to delivering the best possible design solutions that works for you.', style: GoogleFonts.inter( color: Colors.white, fontSize: 16, fontWeight: FontWeight.w300, ), ), ), Container( width: 163, height: 55, clipBehavior: Clip.antiAlias, decoration: ShapeDecoration( shape: RoundedRectangleBorder( side: const BorderSide( width: 1, color: Color(0xFF6C6C6C)), borderRadius: BorderRadius.circular(6), ), ), child: const Center( child: Text( 'About us', style: TextStyle( color: Colors.white, fontSize: 16, fontFamily: 'Inter', fontWeight: FontWeight.w300, ), ), ), ) ], ) ], ), ), ), ), ], ), ), AnimatedPositioned( curve: Curves.fastEaseInToSlowEaseOut, duration: const Duration(milliseconds: 250), left: containerPosition.dx, top: containerPosition.dy, child: AnimatedOpacity( duration: const Duration(milliseconds: 250), opacity: containerOpacity, child: AnimatedContainer( duration: const Duration(milliseconds: 250), width: containerSize, height: containerSize, decoration: const ShapeDecoration( color: Color(0x7F6C6C6C), shape: OvalBorder( side: BorderSide(width: 1, color: Colors.white), ), ), child: Center( child: Text( 'Projects', style: GoogleFonts.inter( color: Colors.white, fontSize: 15, fontWeight: FontWeight.w600, ), ), ), ), ), ), ], ), ); } }
解决思路与方案
根源分析
滚动时Flutter事件分发会优先处理滚动事件,导致MouseRegion的onHover暂时停更,甚至误触发onExit。当前代码中onExit直接重置按钮状态,一旦滚动时触发该事件,按钮就会停止追踪鼠标。
具体解决方案
1. 结合ScrollController过滤滚动时的误触发
添加ScrollController监听滚动状态,滚动时忽略onExit的重置操作:
class _MainButtonStartingState extends State<MainButtonStarting> with SingleTickerProviderStateMixin { var containerPosition = const Offset(10000, 680); double containerSize = 100; double containerOpacity = .5; final ScrollController _scrollController = ScrollController(); bool _isScrolling = false; @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { containerPosition = Offset( MediaQuery.of(context).size.width - 410, 670, ); }); // 监听滚动状态 _scrollController.addListener(() { setState(() { _isScrolling = _scrollController.position.isScrollingNotifier.value; }); }); } @override void dispose() { _scrollController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return MouseRegion( onExit: (event) { // 滚动时不执行重置 if (!_isScrolling) { setState(() { containerOpacity = .5; containerPosition = Offset( MediaQuery.of(context).size.width - 410, 670, ); containerSize = 100; }); } }, onHover: (event) { setState(() { containerOpacity = 1; containerSize = 156; // 用当前containerSize替换固定值100,避免边界判断错误 double containerWidth = containerSize; double containerHeight = containerSize; double mouseX = event.localPosition.dx; double mouseY = event.localPosition.dy; double screenWidth = MediaQuery.of(context).size.width; double screenHeight = MediaQuery.of(context).size.height; double newLeft = mouseX - containerSize / 2; double newTop = mouseY - containerSize / 2; if (newLeft < 0) newLeft = 0; else if (newLeft + containerWidth > screenWidth) newLeft = screenWidth - containerWidth; if (newTop < 0) newTop = 0; else if (newTop + containerHeight > screenHeight) newTop = screenHeight - containerHeight; containerPosition = Offset(newLeft, newTop); }); }, // 给滚动组件绑定controller child: SingleChildScrollView( controller: _scrollController, child: Stack( // 原Stack内容保持不变 children: [...], ), ), ); } }
2. 使用Listener替代MouseRegion
Listener的指针事件优先级更高,不会被滚动事件轻易打断,适合持续追踪鼠标位置:
@override Widget build(BuildContext context) { return Listener( onPointerHover: (event) { setState(() { // 同原onHover逻辑 }); }, onPointerExit: (event) { // 同优化后的onExit逻辑 }, child: Stack([...]), ); }
3. 确保MouseRegion覆盖完整滚动区域
如果滚动内容超出固定高度的容器,MouseRegion的范围需要动态匹配滚动区域的实际尺寸,避免鼠标滚动出原始容器时触发onExit。可以用LayoutBuilder获取父级尺寸,或者将MouseRegion放在Scrollable的外层。
内容的提问来源于stack exchange,提问作者Ismail Mahmood
相关产品推荐
相关产品推荐

