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

嵌套SizeTransition的SliverFillRemaining占用空间超出视口问题

问题现象

嵌套在SliverFillRemaining中的SizeTransition无法动态调整父级空间:即使SizeTransition的sizeFactor变为0,SliverFillRemaining仍会占据sizeFactor=1时的最大空间,导致视口出现多余空白;而相同逻辑在SingleChildScrollView中能正常工作。

错误展示图

问题原因

SliverFillRemaining的布局逻辑依赖子组件的固有尺寸(intrinsic size)计算自身空间,而SizeTransition的固有尺寸是它在sizeFactor=1时的最大高度——即使动画将组件缩小,SliverFillRemaining依然会保留这个最大尺寸的空间。反观SingleChildScrollView是基于子组件的实际渲染尺寸布局,因此不会出现这个问题。

解决方案

方案1:用SliverLayoutBuilder动态适配剩余空间

通过SliverLayoutBuilder获取视口剩余空间,结合AnimatedBuilder直接控制组件高度,让父级布局能感知到尺寸变化:

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(),
        body: CustomScrollView(
          slivers: [
            SliverLayoutBuilder(
              builder: (context, constraints) {
                // 获取视口剩余可用高度
                final remainingHeight = constraints.remainingPaintExtent;
                return SliverToBoxAdapter(
                  child: SizedBox(
                    height: remainingHeight,
                    child: Column(
                      children: [
                        SizeTransitionExample(maxHeight: remainingHeight / 3),
                        SizeTransitionExample(maxHeight: remainingHeight / 3),
                        SizeTransitionExample(maxHeight: remainingHeight / 3),
                        const SafeArea(child: Text("Text")),
                      ],
                    ),
                  ),
                );
              },
            ),
          ],
        ),
      ),
    );
  }
}

class SizeTransitionExample extends StatefulWidget {
  final double maxHeight;
  const SizeTransitionExample({super.key, required this.maxHeight});

  @override
  State<SizeTransitionExample> createState() => _SizeTransitionExampleState();
}

class _SizeTransitionExampleState extends State<SizeTransitionExample> with TickerProviderStateMixin {
  late final AnimationController _controller = AnimationController(
    duration: const Duration(seconds: 3),
    vsync: this,
  )..repeat(reverse: true);
  late final Animation<double> _animation = CurvedAnimation(
    parent: _controller,
    curve: Curves.fastOutSlowIn,
  );

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _animation,
      builder: (context, child) {
        return SizedBox(
          height: widget.maxHeight * _animation.value,
          child: Center(
            child: FlutterLogo(size: widget.maxHeight),
          ),
        );
      },
    );
  }
}

方案2:用SliverList替代SliverFillRemaining

如果不需要强制填充视口剩余空间,直接用SliverList包裹子组件,它会根据子组件的实际渲染尺寸自适应布局,效果和SingleChildScrollView一致:

// 修改后的CustomScrollView部分
CustomScrollView(
  slivers: [
    SliverList(
      delegate: SliverChildListDelegate(
        const [
          SizeTransitionExample(),
          SizeTransitionExample(),
          SizeTransitionExample(),
          SafeArea(child: Text("Text")),
        ],
      ),
    ),
  ],
)

方案说明

  • 方案1保留了SliverFillRemaining的“填充剩余空间”特性,同时让动画能动态调整空间占用;
  • 方案2更简洁,适合不需要强制填充剩余空间的场景,直接让内容自适应高度。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 23:05:00