Flutter实现水平滚动ListView hover边缘显示箭头并点击滚动
实现亚马逊式水平ListView边缘悬停箭头滚动效果
要实现你需要的效果,核心是通过ScrollController控制滚动,结合MouseRegion监听鼠标位置来显示/隐藏箭头按钮,点击按钮触发滚动。以下是修改后的完整代码:
import 'package:flutter/material.dart'; import 'package:frontend_fst/utilities/constants.dart'; import 'package:frontend_fst/components/cards.dart'; class MainScreen extends StatefulWidget { static const String id = 'main_screen_web'; @override _MainScreenState createState() => _MainScreenState(); } class _MainScreenState extends State<MainScreen> { late FSTCards cards = const FSTCards(); // 初始化滚动控制器 final ScrollController _scrollController = ScrollController(); // 控制左右箭头的显示状态 bool _showLeftArrow = false; bool _showRightArrow = false; // 边缘触发区域宽度(可根据需求调整) final double _edgeTriggerWidth = 40; @override void initState() { super.initState(); // 监听滚动位置,更新箭头显示状态 _scrollController.addListener(_updateArrowVisibility); } @override void dispose() { // 释放控制器,避免内存泄漏 _scrollController.dispose(); super.dispose(); } // 更新箭头显示状态:判断是否滚到最左/最右端 void _updateArrowVisibility() { setState(() { _showLeftArrow = _scrollController.offset > 0; _showRightArrow = _scrollController.offset < _scrollController.position.maxScrollExtent; }); } // 滚动到左侧 void _scrollLeft() { // 计算滚动距离:item宽度 + 分隔符宽度 + 左右padding(根据你的布局调整) final double scrollDistance = cards.preferredSize.width + 100 + 24; _scrollController.animateTo( _scrollController.offset - scrollDistance, duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, ); } // 滚动到右侧 void _scrollRight() { final double scrollDistance = cards.preferredSize.width + 100 + 24; _scrollController.animateTo( _scrollController.offset + scrollDistance, duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, ); } // 监听鼠标位置,判断是否在边缘区域 void _onMouseMove(PointerMoveEvent event) { final RenderBox renderBox = context.findRenderObject() as RenderBox; final Offset localPosition = renderBox.globalToLocal(event.position); setState(() { // 鼠标在左侧边缘 _showLeftArrow = localPosition.dx <= _edgeTriggerWidth && _scrollController.offset > 0; // 鼠标在右侧边缘 _showRightArrow = localPosition.dx >= (renderBox.size.width - _edgeTriggerWidth) && _scrollController.offset < _scrollController.position.maxScrollExtent; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: PreferredSize( preferredSize: const Size.fromHeight(70), child: AppBar( centerTitle: true, title: Image.asset(companyLogo, height: 80, width: 85), ), ), body: SingleChildScrollView( padding: const EdgeInsets.all(30), child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ // 用Stack包裹ListView和箭头按钮 SizedBox( height: 210, child: MouseRegion( onHover: _onMouseMove, child: Stack( children: [ ListView.separated( controller: _scrollController, scrollDirection: Axis.horizontal, padding: const EdgeInsets.all(12), itemBuilder: (context, index) { return cards; }, separatorBuilder: (context, index) { return const SizedBox(width: 100); }, itemCount: 20, ), // 左侧箭头按钮 if (_showLeftArrow) Positioned( left: 0, top: 0, bottom: 0, child: GestureDetector( onTap: _scrollLeft, child: Container( width: _edgeTriggerWidth, color: Colors.black.withOpacity(0.1), child: const Icon(Icons.arrow_left, size: 40), ), ), ), // 右侧箭头按钮 if (_showRightArrow) Positioned( right: 0, top: 0, bottom: 0, child: GestureDetector( onTap: _scrollRight, child: Container( width: _edgeTriggerWidth, color: Colors.black.withOpacity(0.1), child: const Icon(Icons.arrow_right, size: 40), ), ), ), ], ), ), ), ], ), ), ), ); } }
关键细节说明
- ScrollController:绑定到ListView,用来获取当前滚动位置,以及触发平滑滚动操作。记得在组件销毁时调用
dispose释放资源。 - MouseRegion:监听鼠标在整个ListView区域内的移动,当鼠标进入左右边缘区域(宽度可通过
_edgeTriggerWidth调整)时,显示对应的箭头按钮。 - 箭头显示逻辑:除了鼠标悬停边缘,还会判断当前滚动位置——如果已经滚到最左端,左箭头会隐藏;滚到最右端,右箭头会隐藏,避免无效操作。
- 滚动距离计算:
_scrollLeft和_scrollRight里的滚动距离,是你的卡片宽度+分隔符宽度+ListView的padding总和,确保每次滚动刚好切换一组item,你可以根据实际布局调整这个数值。
内容的提问来源于stack exchange,提问作者Eder Savillon
相关产品推荐
相关产品推荐

