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

为何我的SingleChildScrollView组件无法正常滚动?

问题:SingleChildScrollView无法滚动,嵌套GridView.builder引发滚动冲突

开发Flutter应用时,页面中的SingleChildScrollView完全无法滚动。页面结构为MasterPage组件通过SingleChildScrollView包裹多个FeatureCard组件,而FeatureCard内部使用了GridView.builder。尝试为SingleChildScrollView添加scrollDirection属性后问题仍未解决,请问哪里操作有误?

相关代码

MasterPage组件

class MasterPage extends Stateful Widget {

  static String id = 'master_page';

  const MasterPage({Key? key}) : super(key: key);

  @override
  State<MasterPage> createState() => _MasterPageState();
}

class _MasterPageState extends State<MasterPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFFffaa5b),
      body: SafeArea(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Container(
              child: Padding(
                padding: const EdgeInsets.only(left: 30, top: 35),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: const [
                    SizedBox(
                      height: 35,
                    ),
                    Text(
                      'Master Menu',
                      style: TextStyle(
                        fontSize: 40,
                        fontFamily: 'Comfortaa',
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                    SizedBox(
                      height: 50,
                    ),
                  ],
                ),
              ),
            ),

            //features container

            Expanded(
              child: Container(
                decoration: BoxDecoration(
                  borderRadius: const BorderRadius.only(
                    topLeft: Radius.circular(40),
                    topRight: Radius.circular(40),
                  ),
                  color: Colors.grey[100],
                ),
                child: SingleChildScrollView(
                  scrollDirection: Axis.vertical,
                  child: Column(
                    children: const [
                      FeatureCard(),
                      FeatureCard(),
                      FeatureCard(),
                      FeatureCard(),
                      FeatureCard(),
                      FeatureCard(),
                      FeatureCard(),
                      SizedBox(
                        height: 30,
                      ),
                    ],
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

FeatureCard组件

class FeatureCard extends Stateful Widget {
  const FeatureCard({
    Key? key,
  }) : super(key: key);

  @override
  State<FeatureCard> createState() => _FeatureCardState();
}

class _FeatureCardState extends State<FeatureCard> {
  final List<Map<String, dynamic>> masterMap = [
    {
      'image': 'assets/images/raw.png',
      'title': 'Input Raw',
      'destination': InputRaw.id
    },
    {
      'image': 'assets/images/supplier.png',
      'title': 'Input Supplier',
      'destination': InputRaw()
    },
    {
      'image': 'assets/images/cmt.png',
      'title': 'Input CMT',
      'destination': InputRaw()
    },
    {
      'image': 'assets/images/pola.png',
      'title': 'Input Pola',
      'destination': InputRaw()
    },
    {
      'image': 'assets/images/outlet.png',
      'title': 'Input Outlet',
      'destination': InputRaw()
    },
    {
      'image': 'assets/images/product.png',
      'title': 'Input Produk',
      'destination': InputRaw()
    },
    {
      'image': 'assets/images/material.png',
      'title': 'Input Jenis Material',
      'destination': InputRaw()
    },
    {
      'image': 'assets/images/warna.png',
      'title': 'Input Warna',
      'destination': InputRaw()
    },
    {
      'image': 'assets/images/qr.png',
      'title': 'Input by QR',
      'destination': InputRaw()
    },
    {
      'image': 'assets/images/acc.png',
      'title': 'Accessories',
      'destination': InputRaw()
    },
    {
      'image': 'assets/images/tools.png',
      'title': 'Tools & Equipment',
      'destination': InputRaw()
    },
    {
      'image': 'assets/images/adjustment.png',
      'title': 'Adjustments',
      'destination': InputRaw()
    },
    {
      'image': 'assets/images/stock.png',
      'title': 'Stock Opname',
      'destination': InputRaw()
    },
  ];

  @override
  Widget build(BuildContext context) {
    return GridView.builder(
      scrollDirection: Axis.vertical,
      shrinkWrap: true,
      gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 2,
      ),
      itemCount: masterMap.length,
      itemBuilder: (BuildContext context, index) {
        return GestureDetector(
          onTap: () {
            Navigator.pushNamed(
                context, masterMap.elementAt(index)['destination']);
          },
          child: Padding(
            padding: const EdgeInsets.symmetric(vertical: 25, horizontal: 25),
            child: Container(
              decoration: BoxDecoration(
                borderRadius: BorderRadius.circular(20),
                color: Colors.white,
              ),
              child: Column(
                mainAxisSize: MainAxisSize.max,
                children: [
                  Container(
                    height: 105,
                    padding: const EdgeInsets.all(20),
                    decoration: BoxDecoration(
                      borderRadius: BorderRadius.circular(15),
                    ),
                    child: Image.asset(
                      '${masterMap.elementAt(index)['image']}',
                    ),
                  ),
                  Text(
                    '${masterMap.elementAt(index)['title']}',
                    style: const TextStyle(
                      fontFamily: 'Quicksand',
                      fontSize: 20,
                    ),
                  ),
                ],
              ),
            ),
          ),
        );
      },
    );
  }
}

解决方案

核心问题是嵌套滚动组件的事件冲突:FeatureCard内的GridView默认拥有独立滚动能力,即便设置了shrinkWrap: true,它依然会优先捕获滚动事件,导致外层SingleChildScrollView无法响应滚动操作。

只需修改FeatureCard中的GridView配置:

  1. 添加physics: const NeverScrollableScrollPhysics(),禁用GridView自身的滚动功能,让滚动事件传递给外层的SingleChildScrollView。
  2. 保留shrinkWrap: true,确保GridView根据内容自适应高度,避免布局溢出。

修改后的FeatureCard build方法代码:

@override
Widget build(BuildContext context) {
  return GridView.builder(
    scrollDirection: Axis.vertical,
    shrinkWrap: true,
    // 禁用自身滚动,将控制权交给外层SingleChildScrollView
    physics: const NeverScrollableScrollPhysics(),
    gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
      crossAxisCount: 2,
    ),
    itemCount: masterMap.length,
    itemBuilder: (BuildContext context, index) {
      return GestureDetector(
        onTap: () {
          Navigator.pushNamed(
              context, masterMap.elementAt(index)['destination']);
        },
        child: Padding(
          padding: const EdgeInsets.symmetric(vertical: 25, horizontal: 25),
          child: Container(
            decoration: BoxDecoration(
              borderRadius: BorderRadius.circular(20),
              color: Colors.white,
            ),
            child: Column(
              mainAxisSize: MainAxisSize.max,
              children: [
                Container(
                  height: 105,
                  padding: const EdgeInsets.all(20),
                  decoration: BoxDecoration(
                    borderRadius: BorderRadius.circular(15),
                  ),
                  child: Image.asset(
                    '${masterMap.elementAt(index)['image']}',
                  ),
                ),
                Text(
                  '${masterMap.elementAt(index)['title']}',
                  style: const TextStyle(
                    fontFamily: 'Quicksand',
                    fontSize: 20,
                  ),
                ),
              ],
            ),
          ),
        ),
      );
    },
  );
}

另外,MasterPage中SingleChildScrollView的scrollDirection: Axis.vertical是默认值,可保留也可省略,不影响最终效果。

内容的提问来源于stack exchange,提问作者Bryant Anthony

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 09:50:43