Flutter中scrollController.animateTo及Scrollable.ensureVisible失效解决
解决Flutter横向滚动按钮点击后滚动至完全显示的问题
你的代码里有两个核心问题导致功能失效:
- 所有按钮共用同一个
itemKey,Scrollable.ensureVisible无法定位到正确的按钮上下文 scrollController.animateTo(0)固定滚动到最左侧,不管点击的是哪个按钮,完全不符合需求
修复步骤
给每个按钮分配独立的GlobalKey
创建一个存储每个按钮key的列表,确保每个按钮有唯一的标识,这样才能准确获取对应按钮的渲染上下文。修改点击事件逻辑
点击按钮时,用当前按钮的key调用Scrollable.ensureVisible,或者通过渲染盒子计算滚动偏移量,让控制器滚动到对应位置。
修改后的完整代码
import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:sizer/sizer.dart'; class ProjectFloorPlan extends StatefulWidget { ProjectFloorPlan( {Key? key, required this.selectedFloorPlan, required this.onChangeFloorplan}) : super(key: key); final FloorPlan1? selectedFloorPlan; final Function onChangeFloorplan; @override State<ProjectFloorPlan> createState() => _ProjectFloorPlanState(); } class _ProjectFloorPlanState extends State<ProjectFloorPlan> { // 给每个按钮创建独立的GlobalKey final List<GlobalKey> itemKeys = [ GlobalKey(), GlobalKey(), GlobalKey(), ]; final scrollController = ScrollController(); @override void initState() { super.initState(); } @override void dispose() { scrollController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return SingleChildScrollView( key: const PageStorageKey<String>('controllerA'), scrollDirection: Axis.horizontal, controller: scrollController, child: Row( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ GoldenRoundedBox( key: itemKeys[0], // 绑定第一个key onTap: () { // 使用当前按钮的key确保可见 Scrollable.ensureVisible( itemKeys[0].currentContext!, duration: const Duration(milliseconds: 700), curve: Curves.easeIn, alignment: 0.5, // 让按钮居中显示,可根据需求调整 ); widget.onChangeFloorplan(); }, text: 'Floor plan 1BHK', ), GoldenRoundedBox( key: itemKeys[1], // 绑定第二个key onTap: () { Scrollable.ensureVisible( itemKeys[1].currentContext!, duration: const Duration(milliseconds: 700), curve: Curves.easeIn, alignment: 0.5, ); widget.onChangeFloorplan(); }, text: 'Floor plan 2BHK', ), GoldenRoundedBox( key: itemKeys[2], // 绑定第三个key onTap: () { Scrollable.ensureVisible( itemKeys[2].currentContext!, duration: const Duration(milliseconds: 700), curve: Curves.easeIn, alignment: 0.5, ); widget.onChangeFloorplan(); }, text: 'Floor plan 3BHK', ), ], ), ); } } // 补充GoldenRoundedBox的定义(假设你有这个自定义组件) class GoldenRoundedBox extends StatelessWidget { const GoldenRoundedBox({Key? key, required this.onTap, required this.text}) : super(key: key); final VoidCallback onTap; final String text; @override Widget build(BuildContext context) { return GestureDetector( onTap: onTap, child: Container( margin: const EdgeInsets.symmetric(horizontal: 8), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: BoxDecoration( color: Colors.amber, borderRadius: BorderRadius.circular(8), ), child: Text(text), ), ); } } // 补充FloorPlan1的定义(避免编译错误) class FloorPlan1 {}
关键说明
- 独立GlobalKey:每个按钮绑定自己的key,确保
Scrollable.ensureVisible能找到正确的组件上下文。 - Scrollable.ensureVisible参数:
duration和curve控制滚动动画的时长和曲线,保持和你原来的设置一致。alignment设置为0.5可以让按钮滚动到视图中心,如果你想让按钮对齐左侧,设置为0即可。
- 如果你更倾向于用
scrollController.animateTo实现,也可以通过RenderBox计算偏移量:
// 替代Scrollable.ensureVisible的写法 final renderBox = itemKeys[index].currentContext!.findRenderObject() as RenderBox; final offset = renderBox.localToGlobal(Offset.zero); final scrollOffset = scrollController.offset + offset.dx - MediaQuery.of(context).size.width / 2 + renderBox.size.width / 2; await scrollController.animateTo( scrollOffset, duration: const Duration(milliseconds: 700), curve: Curves.easeIn, );
内容的提问来源于stack exchange,提问作者Nithyashree B L
相关产品推荐
相关产品推荐

