如何更改 Sliver 持久标头的固定

问题描述 投票:0回答:3

我在 GridView 上方使用 2 个 Sliver Headers ,在 CustomScrollView 上使用 ListView 。当我向下滚动时,我只希望固定 1 个标题(我正在滚动的标题)。我希望能够向下滚动,并且当我经过 Gridview 时,只有一个标题被固定。

编辑: 添加了 _SliverAppBarDelegate

Scaffold(
body: SafeArea(
        child: DefaultTabController(
          length: 2,
          child: CustomScrollView(
            slivers: [              
              makeHeader('Categories', false),
              SliverGrid(
                gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                  crossAxisCount: 2,
                  childAspectRatio: 1.5,
                ),
                delegate: SliverChildBuilderDelegate(
                    (context, index) => Container(
                          margin: EdgeInsets.all(5.0),
                          color: Colors.blue,
                        ),
                    childCount: 10),
              ),
              makeHeader('Watchlist', false),
              SliverGrid(
                gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                  crossAxisCount: 2,
                  childAspectRatio: 1.5,
                ),
                delegate: SliverChildBuilderDelegate(
                    (context, index) => Container(
                          margin: EdgeInsets.all(5.0),
                          color: Colors.red,
                        ),
                    childCount: 10),
              ),
            ],
          ),
        ),
  ),
)


SliverPersistentHeader makeHeader(String headerText, bool pinned) {
  return SliverPersistentHeader(
    pinned: pinned,
     floating: true,
    delegate: _SliverAppBarDelegate(
      minHeight: 40.0,
      maxHeight: 60.0,
      child: Container(
          child: Text(
            headerText,
            style: TextStyle(fontSize: 24, color: Colors.green,fontWeight: FontWeight.bold),
          )),
    ),
  );
}

///////////////////////////EDIT

class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
  _SliverAppBarDelegate({
    @required this.minHeight,
    @required this.maxHeight,
    this.child,
  });
  final double minHeight;
  final double maxHeight;
  final Widget child;
  @override
  double get minExtent => minHeight;
  @override
  double get maxExtent => math.max(maxHeight, minHeight);
  @override
  Widget build(
      BuildContext context, double shrinkOffset, bool overlapsContent) {
    return new SizedBox.expand(child: child);
  }

  @override
  bool shouldRebuild(_SliverAppBarDelegate oldDelegate) {
    return maxHeight != oldDelegate.maxHeight ||
        minHeight != oldDelegate.minHeight ||
        child != oldDelegate.child;
  }
}
flutter
3个回答
4
投票

这是一个老问题,但我将我的解决方案放在这里,以防其他人需要无需插件的粘性标题效果。

我的解决方案是在映射中包含 sliver 标头的 minExtent 值,其中键是标头上方的 sliverList 项目数。

final _headersMinExtent = <int, double>{};

当需要将 header 推出视图时,我们可以减少 minExtent。为了实现这一点,我们监听scrollController。请注意,我们还可以更新固定状态,但我们不会获得转换。

我们从以下位置计算 minExtent:

  • 标题上方的项目数量:键。
  • 已经推出视图的标题数量:n。
_scrollListener() {
    var n = 0;
    setState(() {
      _headersMinExtent.forEach((key, value) {
        _headersMinExtent[key] = (key * 30 + n * 40 + 190 - _scrollController.offset).clamp(0, 40);
        n++;
      });
    });
  }

当我们构建小部件列表时,我们必须将 minExtent 参数传递给 SliverPersistentHeaderDelegate :

List<Widget> _constructList() {
    var widgetList = <Widget>[];

    for (var i = 0; i < itemList.length; i++) {
      // We want a header every 5th item
      if (i % 5 == 0) {
        // Don't forget to init the minExtent value.
        _headersMinExtent[i] = _headersMinExtent[i] ?? 40;
        // We pass the minExtent as a parameter.
        widgetList.add(SliverPersistentHeader(pinned: true, delegate: HeaderDelegate(_headersMinExtent[i]!)));
      }
      widgetList.add(SliverList(
          delegate: SliverChildBuilderDelegate(
        (context, index) => Container(
          decoration: BoxDecoration(color: Colors.yellow, border: Border.all(width: 0.5)),
          height: 30,
        ),
        childCount: 1,
      )));
    }
    return widgetList;
}

这就是结果:

screen capture

完整的应用程序代码:

import 'package:flutter/material.dart';

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

class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final _scrollController = ScrollController();
  final _headersMinExtent = <int, double>{};
  final itemList = List.filled(40, "item");

  @override
  void initState() {
    super.initState();
    _scrollController.addListener(() {
      _scrollListener();
    });
  }

  _scrollListener() {
    var n = 0;
    setState(() {
      _headersMinExtent.forEach((key, value) {
        _headersMinExtent[key] = (key * 30 + n * 40 + 190 - _scrollController.offset).clamp(0, 40);
        n++;
      });
    });
  }

  List<Widget> _constructList() {
    var widgetList = <Widget>[];

    for (var i = 0; i < itemList.length; i++) {
      // We want a header every 5th item
      if (i % 5 == 0) {
        // Don't forget to init the minExtent value.
        _headersMinExtent[i] = _headersMinExtent[i] ?? 40;
        // We pass the minExtent as a parameter.
        widgetList.add(SliverPersistentHeader(pinned: true, delegate: HeaderDelegate(_headersMinExtent[i]!)));
      }
      widgetList.add(SliverList(
          delegate: SliverChildBuilderDelegate(
        (context, index) => Container(
          decoration: BoxDecoration(color: Colors.yellow, border: Border.all(width: 0.5)),
          height: 30,
        ),
        childCount: 1,
      )));
    }
    return widgetList;
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(),
        body: CustomScrollView(
          controller: _scrollController,
          slivers: _constructList(),
        ),
      ),
    );
  }
}

class HeaderDelegate extends SliverPersistentHeaderDelegate {
  final double _minExtent;
  HeaderDelegate(this._minExtent);

  @override
  Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
    return Container(
      decoration: BoxDecoration(color: Colors.green, border: Border.all(width: 0.5)),
    );
  }

  @override
  double get minExtent => _minExtent;

  @override
  double get maxExtent => 40;

  @override
  bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) => true;
}

class ListChildDelegate extends SliverChildDelegate {
  @override
  Widget? build(BuildContext context, int index) {
    // TODO: implement build
    throw UnimplementedError();
  }

  @override
  bool shouldRebuild(covariant SliverChildDelegate oldDelegate) => true;
}

2
投票

@diegovoper 我找到了这个插件。 https://pub.dev/packages/flutter_sticky_header 我希望还有一种更简单的方法可以在没有插件的情况下完成它。然而,这个插件完全解决了我所描述的问题。


0
投票

对于现在想知道同样事情的人,有一组新的小部件,

SliverMainAxisGroup
SliverCrossAxisGroup
SliverCrossAxisExpanded
,让您无需插件即可轻松完成此操作。

https://github.com/flutter/flutter/pull/126596

https://api.flutter.dev/flutter/widgets/SliverMainAxisGroup-class.html

您可以将此代码示例复制粘贴到链接的文档内的代码游乐场中以查看其行为:

import 'package:flutter/material.dart';

void main() => runApp(const SliverMainAxisGroupExampleApp());

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('SliverMainAxisGroup Sample')),
        body: const SliverMainAxisGroupExample(),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return CustomScrollView(
      slivers: <Widget>[
        SliverMainAxisGroup(
          slivers: <Widget>[
            const SliverAppBar(
              title: Text('Section Title 1'),
              expandedHeight: 70.0,
              pinned: true,
            ),
            SliverList.builder(
              itemBuilder: (BuildContext context, int index) {
                return Container(
                  color: index.isEven ? Colors.amber[300] : Colors.blue[300],
                  height: 100.0,
                  child: Center(
                    child: Text(
                      'Item $index',
                      style: const TextStyle(fontSize: 24),
                    ),
                  ),
                );
              },
              itemCount: 5,
            ),
            SliverToBoxAdapter(
              child: Container(
                color: Colors.cyan,
                height: 100,
                child: const Center(
                  child: Text('Another sliver child',
                      style: TextStyle(fontSize: 24)),
                ),
              ),
            ),
          ],
        ),
        SliverMainAxisGroup(
          slivers: <Widget>[
            const SliverAppBar(
              title: Text('Section Title 2'),
              expandedHeight: 70.0,
              pinned: true,
            ),
            SliverList.builder(
              itemBuilder: (BuildContext context, int index) {
                return Container(
                  color: index.isEven ? Colors.amber[300] : Colors.blue[300],
                  height: 100.0,
                  child: Center(
                    child: Text(
                      'Item $index',
                      style: const TextStyle(fontSize: 24),
                    ),
                  ),
                );
              },
              itemCount: 5,
            ),
            SliverToBoxAdapter(
              child: Container(
                color: Colors.cyan,
                height: 100,
                child: const Center(
                  child: Text('Another sliver child',
                      style: TextStyle(fontSize: 24)),
                ),
              ),
            ),
          ],
        ),
        SliverToBoxAdapter(
          child: Container(
            height: 1000,
            decoration: const BoxDecoration(color: Colors.greenAccent),
            child: const Center(
              child: Text('Hello World!', style: TextStyle(fontSize: 24)),
            ),
          ),
        ),
      ],
    );
  }
}
© www.soinside.com 2019 - 2024. All rights reserved.