You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter中scrollController.animateTo及Scrollable.ensureVisible失效解决

解决Flutter横向滚动按钮点击后滚动至完全显示的问题

你的代码里有两个核心问题导致功能失效:

  • 所有按钮共用同一个itemKey,Scrollable.ensureVisible无法定位到正确的按钮上下文
  • scrollController.animateTo(0)固定滚动到最左侧,不管点击的是哪个按钮,完全不符合需求

修复步骤

  1. 给每个按钮分配独立的GlobalKey
    创建一个存储每个按钮key的列表,确保每个按钮有唯一的标识,这样才能准确获取对应按钮的渲染上下文。

  2. 修改点击事件逻辑
    点击按钮时,用当前按钮的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 13:35:31