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

Flutter下拉显示子头部并固定+下拉刷新功能实现问题

Flutter 下拉显示子头部及下拉刷新功能实现问题

我在Flutter中实现了下拉显示子头部的功能,但运行效果不佳,还需添加更高级特性:

初始实现效果

需求细节:

  • 子头部完全显示后固定在列表顶部,用户上滑一段距离后取消固定
  • 用户继续下拉时,在子头部上方支持下拉刷新功能

我尝试了Stack+ListView的方案,但存在诸多问题:

  • 子头部不属于滚动容器,导致:
    • 执行下拉刷新时,子头部无法随列表项移动
    • 用户无法通过在子头部上滑动来隐藏它,只能通过滚动列表实现
  • 后续需要让AppBar中的红点通过动画转换为子头部,可能需要使用CustomScroll结合CustomPainter的方案,而非当前的Stack+ListView实现?

初始实现代码

class RevealSubheaderPage extends HookWidget {
  @override
  Widget build(BuildContext context) {
    const double HEIGHT = 96;
    final pinned = useState(false);
    final extend = useState(0.0);
    return Scaffold(
      appBar: AppBar(title: const Text('Test')),
      body: Stack(
        children: [
          NotificationListener<ScrollNotification>(
            onNotification: (n) {
              if (pinned.value) {
                print(n.metrics.pixels);
                if (n.metrics.pixels > 0 && n.metrics.pixels < HEIGHT) {
                  extend.value = n.metrics.pixels;
                }
              } else {
                if (n.metrics.pixels < 0) {
                  extend.value = n.metrics.pixels;
                }
                if (n.metrics.pixels < -HEIGHT) {
                  pinned.value = true;
                  extend.value = -HEIGHT;
                }
              }
              return false;
            },
            child: ListView.builder(
              itemCount: 42,
              itemBuilder: (context, index) {
                return Container(
                  color: Colors.green,
                  padding: const EdgeInsets.all(16),
                  alignment: Alignment.centerLeft,
                  child: Text('Item $index'),
                );
              },
            ),
          ),
          HideableWidget(height: HEIGHT, notifier: extend),
        ],
      ),
    );
  }
}

class HideableWidget extends StatelessWidget {
  final double height;
  final ValueNotifier<double> notifier;

  HideableWidget({required this.height, required this.notifier});

  @override
  Widget build(BuildContext context) {
    return ValueListenableBuilder<double>(
      valueListenable: notifier,
      builder: (context, value, child) {
        return Transform.translate(
          offset: Offset(0, -height - value),
          child: Container(
            height: height,
            width: double.infinity,
            color: Colors.red,
            alignment: Alignment.bottomCenter,
            child: Text("Subheader"),
          ),
        );
      },
    );
  }
}

更新:CustomScrollView实现尝试

我改用CustomScrollView实现了接近预期的效果,但如动图所示,子头部被移到了垂直居中位置。若要让它固定在顶部/底部,难以计算BouncingScrollPhysics的偏移量,无法让组件占据全部可用空间。虽然居中显示效果尚可,但我希望它能占满空间。此外,还未能集成RefreshIndicator,它应出现在子头部上方(需额外下拉100px的过滚动距离后渐显)。

CustomScrollView实现效果

更新后的实现代码

import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';

class Test3 extends StatelessWidget {

  const Test3({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: RevealSubheaderPage(
          revealHeight: 100,
          revealSnapHeight: 80,
        ),
      ),
    );
  }
}

class RevealSubheaderPage extends HookWidget {
  final double revealHeight;
  final double revealSnapHeight;

  const RevealSubheaderPage({
    super.key,
    required this.revealHeight,
    required this.revealSnapHeight,
  });

  @override
  Widget build(BuildContext context) {
    final snap = useState(false);
    final scrollExtend = useState(0.0);
    final scrollController = useScrollController();
    useEffect(() {
      l() {
        final o = scrollController.offset;
        scrollExtend.value = o;
        if (!snap.value && o < -revealSnapHeight) {
          snap.value = true;
        } else {
          if (snap.value && o > 0) {
            snap.value = false;
          }
        }
      }

      scrollController.addListener(l);
      return () => scrollController.removeListener(l);
    });

    return CustomScrollView(
      controller: scrollController,
      slivers: [
        const SliverAppBar(
          title: Text("Some Title"),
          pinned: true,
        ),
        SliverToBoxAdapter(
          child: Transform.translate(
            offset: Offset(
                0,
                snap.value
                    ? scrollExtend.value < 0
                        ? scrollExtend.value / 2
                        : 0
                    : scrollExtend.value < 0
                        ? scrollExtend.value / 2
                        : 0),
            child: AnimatedContainer(
              duration: const Duration(milliseconds: 150),
              height: snap.value
                  ? revealHeight
                  : scrollExtend.value < 0
                      ? -scrollExtend.value
                      : 0,
              child: Container(
                height: revealHeight,
                width: double.infinity,
                color: Colors.red,
                alignment: Alignment.centerLeft,
                child: Text("Subheader"),
              ),
            ),
          ),
        ),
        SliverList(
          delegate: SliverChildBuilderDelegate(
            childCount: 42,
            (context, index) {
              return Container(
                color: Colors.green,
                padding: const EdgeInsets.all(16),
                alignment: Alignment.centerLeft,
                child: Text('Item $index'),
              );
            },
          ),
        ),
      ],
    );
  }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 12:45:01