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

如何修复Widget:让内容固定抽屉顶部并消除底部间隙

双部分布局Widget问题修复请求

布局需求

  • 顶部部分:固定在顶部,用户下拉抽屉时会轻微拉伸;
  • 抽屉部分:占据屏幕剩余区域,为可滚动部分。

状态展示

静止状态

Widget静止状态

下拉时状态

下拉Widget时的状态

存在的问题

  1. 下拉抽屉时,内容被拉伸(标题远离抽屉顶部),希望内容保持原相对位置;
  2. Widget底部会形成间隙,下拉时间隙扩大,希望消除该间隙。

现有代码

/// A Scrolling widget that have a sticky widget at the top and a scrolling drawer below
class ScaffoldPinnedWidgetAndAd extends Stateful Widget {
  final Widget topChild;
  final List<Widget> children;
  final double imageCoverRate;
  final bool withAd;

  /// The scrolling container background color
  final Color? color;
  const ScaffoldPinnedWidgetAndAd({
    super.key,
    required this.topChild,
    required this.children,
    this.imageCoverRate = 0.9,
    this.color,
    this.withAd = true,
  });

  @override
  State<ScaffoldPinnedWidgetAndAd> createState() =>
      _ScaffoldPinnedWidgetAndAdState();
}

class _ScaffoldPinnedWidgetAndAdState extends State<ScaffoldPinnedWidgetAndAd> {
  late final size = MediaQuery.of(context).size;

  late final normalImageHeight = size.height / 4 * widget.imageCoverRate;
  late double imageHeight = size.height / 4;
  final _scrollController = ScrollController();
  double? scrollDelta;
  double _roundingAttenuation = 1;
  static const double _kRoundingRadius = 32.0;

  Widget get _body => SizedBox(
        width: size.width,
        height: size.height,
        child: Stack(
          children: [
            SizedBox(
              height: imageHeight,
              width: double.infinity,
              child: widget.topChild,
            ),
            // The scrolling content
            NotificationListener<ScrollNotification>(
              // Listen for scroll events
              onNotification: (notification) {
                if (notification is ScrollUpdateNotification) {
                  final rate =
                      _scrollController.position.pixels < 0 ? 4.0 : 1.0;
                  _roundingAttenuation = 1;
                  setState(() {
                    scrollDelta = notification.scrollDelta;
                    imageHeight = normalImageHeight -
                        _scrollController.position.pixels * rate;
                    // Clamp imageHeight to prevent negative values
                    imageHeight = imageHeight.clamp(0.0, size.height * .9);
                    if (imageHeight == 0) _roundingAttenuation = 0;
                  });
                }
                return true; // Allow further notification propagation
              },
              child: Positioned(
                top: imageHeight * widget.imageCoverRate,
                child: SizedBox(
                  width: size.width,
                  height: size.height * 0.9 - imageHeight,
                  child: Container(
                    padding: EdgeInsets.fromLTRB(
                      _kRoundingRadius / 2,
                      0,
                      _kRoundingRadius / 2,
                      _kRoundingRadius / 2,
                    ),
                    decoration: BoxDecoration(
                      color: 
                          widget.color ??
                              Theme.of(context)
                                  .scaffoldBackgroundColor,
                      borderRadius: BorderRadius.only(
                        topLeft: Radius.circular(
                            _kRoundingRadius * _roundingAttenuation),
                        topRight: Radius.circular(
                            _kRoundingRadius * _roundingAttenuation),
                      ),
                      boxShadow: [
                        BoxShadow(
                          color: Colors.grey.withOpacity(0.5),
                          spreadRadius: 5,
                          blurRadius: 7,
                          offset: const Offset(0, 3),
                        ),
                      ],
                    ),
                    child: ListView.builder(
                      padding: EdgeInsets.only(top: _kRoundingRadius),
                      controller: _scrollController,
                      physics: const BouncingScrollPhysics(),
                      itemCount: widget.children.length,
                      itemBuilder: (context, index) => widget.children[index],
                    ),
                  ),
                ),
              ),
            ),
          ],
        ),
      );

  @override
  Widget build(BuildContext context) => Scaffold(body: _body);

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

内容的提问来源于stack exchange,提问作者Stéphane de Luca

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 09:05:55