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

Flutter中CustomScrollView实现类似Android AppBarLayout的滚动行为

Flutter 实现类似Android AppBarLayout的滚动行为(CustomScrollView版)

可以直接用Flutter内置的Sliver组件实现需求,核心是通过SliverPersistentHeader自定义Delegate来匹配原生的滚动标记行为,无需依赖第三方库。

1. 实现scroll|snap|enterAlways效果的组件

这个组件需要满足两个核心特性:

  • 吸附(snap):无中间滚动状态,要么完全显示要么完全隐藏
  • enterAlways:无论当前滚动位置,向下滚动立即显示自身

通过SliverPersistentHeader的floating: true实现enterAlways效果,snap: true配合自定义Delegate完成吸附逻辑:

class EnterAlwaysSnapHeaderDelegate extends SliverPersistentHeaderDelegate {
  final double height;
  final Widget child;

  EnterAlwaysSnapHeaderDelegate({required this.height, required this.child});

  @override
  double get maxExtent => height;

  @override
  double get minExtent => 0;

  @override
  Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
    return SizedBox(height: maxExtent - shrinkOffset, child: child);
  }

  @override
  bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) {
    return oldDelegate.maxExtent != height || oldDelegate != this;
  }

  @override
  FloatingHeaderSnapConfiguration? get snapConfiguration => const FloatingHeaderSnapConfiguration();
}

在CustomScrollView中使用:

SliverPersistentHeader(
  delegate: EnterAlwaysSnapHeaderDelegate(
    height: 60,
    child: Container(
      color: Colors.blue,
      alignment: Alignment.center,
      child: const Text(
        "Scroll|Snap|EnterAlways",
        style: TextStyle(color: Colors.white, fontSize: 16),
      ),
    ),
  ),
  floating: true, // 实现enterAlways:向下滚动立即显示
  snap: true, // 实现吸附效果
)

2. 实现noScroll粘性头部

这个组件需要固定在顶部,不随滚动隐藏,同样用SliverPersistentHeader实现,通过设置pinned: true并让Delegate的最小/最大高度一致:

class StickyHeaderDelegate extends SliverPersistentHeaderDelegate {
  final double height;
  final Widget child;

  StickyHeaderDelegate({required this.height, required this.child});

  @override
  double get maxExtent => height;

  @override
  double get minExtent => height; // 最小高度等于最大高度,禁止收缩

  @override
  Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
    return child;
  }

  @override
  bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) {
    return oldDelegate.maxExtent != height || oldDelegate != this;
  }
}

在CustomScrollView中使用:

SliverPersistentHeader(
  delegate: StickyHeaderDelegate(
    height: 50,
    child: Container(
      color: Colors.green,
      alignment: Alignment.center,
      child: const Text(
        "NoScroll (Sticky)",
        style: TextStyle(color: Colors.white, fontSize: 16),
      ),
    ),
  ),
  pinned: true, // 固定在顶部,不随滚动隐藏
)

完整示例代码

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(
        body: CustomScrollView(
          slivers: [
            // scroll|snap|enterAlways 组件
            SliverPersistentHeader(
              delegate: EnterAlwaysSnapHeaderDelegate(
                height: 60,
                child: Container(
                  color: Colors.blue,
                  alignment: Alignment.center,
                  child: const Text(
                    "Scroll|Snap|EnterAlways",
                    style: TextStyle(color: Colors.white, fontSize: 16),
                  ),
                ),
              ),
              floating: true,
              snap: true,
            ),
            // noScroll 粘性头部
            SliverPersistentHeader(
              delegate: StickyHeaderDelegate(
                height: 50,
                child: Container(
                  color: Colors.green,
                  alignment: Alignment.center,
                  child: const Text(
                    "NoScroll (Sticky)",
                    style: TextStyle(color: Colors.white, fontSize: 16),
                  ),
                ),
              ),
              pinned: true,
            ),
            // 滚动列表内容
            SliverList(
              delegate: SliverChildBuilderDelegate(
                (context, index) => ListTile(title: Text("Item $index")),
                childCount: 50,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class EnterAlwaysSnapHeaderDelegate extends SliverPersistentHeaderDelegate {
  final double height;
  final Widget child;

  EnterAlwaysSnapHeaderDelegate({required this.height, required this.child});

  @override
  double get maxExtent => height;

  @override
  double get minExtent => 0;

  @override
  Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
    return SizedBox(height: maxExtent - shrinkOffset, child: child);
  }

  @override
  bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) {
    return oldDelegate.maxExtent != height || oldDelegate != this;
  }

  @override
  FloatingHeaderSnapConfiguration? get snapConfiguration => const FloatingHeaderSnapConfiguration();
}

class StickyHeaderDelegate extends SliverPersistentHeaderDelegate {
  final double height;
  final Widget child;

  StickyHeaderDelegate({required this.height, required this.child});

  @override
  double get maxExtent => height;

  @override
  double get minExtent => height;

  @override
  Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
    return child;
  }

  @override
  bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) {
    return oldDelegate.maxExtent != height || oldDelegate != this;
  }
}

补充说明

关于你提到的SliverAppBar:它本质是基于SliverPersistentHeader封装的应用栏组件,虽然自带工具栏、TabBar等功能,但如果你的场景不需要这些元素,直接用SliverPersistentHeader自定义Delegate是更轻量、灵活的选择,完全可以替代原生AppBarLayout的滚动行为。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 10:15:04