粘性无限列表

带有粘性标题的无限列表。

此包旨在实现
双向渲染无限列表,带有粘性页眉,不同于大多数
Dart Pub上的软件包。

支持各种页眉定位。还支持垂直和
水平滚动列表

它高度可定制,并且没有任何第三方依赖项或原生(Android/iOS)代码。

除了默认用法外,此包还公开了一些类,这些类
可以在需要时被覆盖。另外一些类可以在
Scrollable小部件中独立于InfiniteList容器使用。

此包使用CustomScrollView来执行滚动,并具有所有
Flutter提供的性能优势。

功能

  • 无限列表中的粘性页眉
  • 多向无限列表
  • 粘性页眉位置的自定义
  • 水平粘性列表支持
  • 动态页眉构建于内容滚动之上
  • 动态最小偏移量在内容滚动时计算

演示

入门

安装并导入包


import 'package:sticky_infinite_list/sticky_infinite_list.dart';

包公开InfiniteListInfiniteListItemStickyListItem
StickyListItemRenderObject

示例

简单示例

要开始使用带有粘性页眉的无限列表,
您需要创建一个带有指定构建器的InfiniteList实例。

无需指定任何附加配置即可使其正常工作


import 'package:sticky_infinite_list/sticky_infinite_list.dart';

class Example extends StatelessWidget {
  
  @override
  Widget build(BuildContext context) {
    return InfiniteList(
      builder: (BuildContext context, int index) {
        /// Builder requires [InfiniteList] to be returned
        return InfiniteListItem(
          /// Header builder
          headerBuilder: (BuildContext context) {
            return Container(
              ///...
            );
          },
          /// Content builder
          contentBuilder: (BuildContext context) {
            return Container(
              ///...
            );
          },
        );
      }
    );
  }
}

状态

当调用最小偏移量回调或调用页眉构建器时,
StickyState对象将作为参数传递

此对象描述粘性页眉的当前状态。

class StickyState<I> {
  /// Position, that header already passed
  ///
  /// Value can be between 0.0 and 1.0
  ///
  /// If it's `0.0` - sticky in max start position
  ///
  /// `1.0` - max end position
  ///
  /// If [InfiniteListItem.initialHeaderBuild] is true, initial
  /// header render will be with position = 0
  final double position;

  /// Number of pixels, that outside of viewport
  ///
  /// If [InfiniteListItem.initialHeaderBuild] is true, initial
  /// header render will be with offset = 0
  /// 
  /// For header bottom positions (or right positions for horizontal)
  /// offset value also will be amount of pixels that was scrolled away
  final double offset;

  /// Item index
  final I index;

  /// If header is in sticky state
  ///
  /// If [InfiniteListItem.minOffsetProvider] is defined,
  /// it could be that header builder will be emitted with new state
  /// on scroll, but [sticky] will be false, if offset already passed
  /// min offset value
  ///
  /// WHen [InfiniteListItem.minOffsetProvider] is called, [sticky]
  /// will always be `false`. Since for min offset calculation
  /// offset itself not defined yet
  final bool sticky;

  /// Scroll item height.
  ///
  /// If [InfiniteListItem.initialHeaderBuild] is true, initial
  /// header render will be called without this value
  final double contentSize;
}

扩展配置

可用配置

除了开始使用的最少配置外。

InfiniteList允许您定义滚动列表渲染的配置

InfiniteList(
  /// Optional parameter to pass ScrollController instance
  controller: ScrollController(),
  
  /// Optional parameter
  /// to specify scroll direction
  /// 
  /// By default scroll will be rendered with just positive
  /// direction `InfiniteListDirection.forward`
  /// 
  /// If you need infinite list in both directions use `InfiniteListDirection.multi`
  direction: InfiniteListDirection.multi,
  
  /// Min child count.
  /// 
  /// Will be used only when `direction: InfiniteListDirection.multi`
  /// 
  /// Accepts negative values only
  /// 
  /// If it's not provided, scroll will be infinite in negative direction
  minChildCount: -100,
  
  /// Max child count
  /// 
  /// Specifies number of elements for forward list
  /// 
  /// If it's not provided, scroll will be infinite in positive direction
  maxChildCount: 100,
  
  /// ScrollView anchor value.
  anchor: 0.0,

  /// Item builder
  /// 
  /// Should return `InfiniteListItem`
  builder: (BuildContext context, int index) {
    return InfiniteListItem(
      //...
    )
  }
)

InfiniteListItem允许您为您的自定义指定更多选项。

InfiniteListItem(
  /// See class description for more info
  /// 
  /// Forces initial header render when [headerStateBuilder]
  /// is specified.
  initialHeaderBuild: false,

  /// Simple Header builder
  /// that will be called once during List item render
  headerBuilder: (BuildContext context) {},
  
  /// Header builder, that will be invoked each time
  /// when header should change it's position
  /// 
  /// Unlike prev method, it also provides `state` of header
  /// position
  /// 
  /// This callback has higher priority than [headerBuilder],
  /// so if both header builders will be provided,
  /// [headerBuilder] will be ignored
  headerStateBuilder: (BuildContext context, StickyState<int> state) {},
  
  /// Content builder
  contentBuilder: (BuildContext context) {},
  
  /// Min offset invoker
  /// 
  /// This callback is called on each header position change,
  /// to define when header should be stick to the bottom of
  /// content.
  /// 
  /// If this method not provided or it returns `0`,
  /// header will be in sticky state until list item
  /// will be visible inside view port
  minOffsetProvider: (StickyState<int> state) {},
  
  /// Header alignment
  /// 
  /// Use [HeaderAlignment] to align header to left,
  /// right, top or bottom side
  /// 
  /// Optional. Default value [HeaderAlignment.topLeft]
  headerAlignment: HeaderAlignment.topLeft,
  
  /// Scroll direction
  ///
  /// Can be vertical or horizontal (see [Axis] class)
  ///
  /// This value also affects how bottom or top
  /// edge header positioned headers behave
  scrollDirection: Axis.vertical,
);

演示

页眉对齐演示

水平滚动演示

反向无限滚动

当前包不支持CustomScrollView.reverse选项。

但是,通过定义anchor = 1可以获得相同的结果,
maxChildCount = 0。这样,视口中心将固定
在底部,正面列表将不渲染任何内容。

此外,您可以将headerAlignment指定到任何一侧。

import 'package:sticky_infinite_list/sticky_infinite_list.dart';

class Example extends StatelessWidget {
  
  @override
  Widget build(BuildContext context) {
    return InfiniteList(
      anchor: 1.0,
      
      direction: InfiniteListDirection.multi,
      
      maxChildCount: 0,
      
      builder: (BuildContext context, int index) {
        /// Builder requires [InfiniteList] to be returned
        return InfiniteListItem(
        
          headerAlignment: HeaderAlignment.bottomLeft,
          
          /// Header builder
          headerBuilder: (BuildContext context) {
            return Container(
              ///...
            );
          },
          /// Content builder
          contentBuilder: (BuildContext context) {
            return Container(
              ///...
            );
          },
        );
      }
    );
  }
}

演示

有关更多信息,请参阅
示例项目

可覆盖

在大多数情况下,仅使用InfiniteListItem就足够了

但在某些情况下,您可能需要为
每个项目添加额外功能。

幸运的是,您可以扩展和覆盖基类InfiniteListItem

/// Generic `I` is index type, by default list item uses `int`
class SomeCustomListItem extends InfiniteListItem<I> {
  /// Header alignment
  /// 
  /// Supports all sides alignment, see [HeaderAlignment] for more info
  /// 
  /// By default [HeaderAlignment.topLeft]
  final HeaderAlignment headerAlignment;
  
  /// Let item builder know if it should watch
  /// header position changes
  /// 
  /// If this value is `true` - it will invoke [buildHeader]
  /// each time header position changes
  @override
  bool get watchStickyState => true;
  
  /// Let item builder know that this class
  /// provides header
  /// 
  /// If it returns `false` - [buildHeader] will be ignored 
  /// and never called
  @override
  bool get hasStickyHeader => true;
  
  /// This methods builds header
  /// 
  /// If [watchStickyState] is `true`,
  /// it will be invoked on each header position change
  /// and `state` option will be provided
  /// 
  /// Otherwise it will be called only once on initial render
  /// and each header position change won't invoke this method.
  /// 
  /// Also in that case `state` will be `null`
  @override
  Widget buildHeader(BuildContext context, [StickyState<I> state]) {}
  
  /// Content item builder
  /// 
  /// This method invoked only once
  @override
  Widget buildContent(BuildContext context) => {}

  /// Called during init state (see Statefull widget [State.initState])
  /// 
  /// For additional information about Statefull widget `initState`
  /// lifecycle - see Flutter docs
  @protected
  @mustCallSuper
  void initState() {}

  /// Called during item dispose (see Statefull widget [State.dispose])
  /// 
  /// For additional information about Statefull widget `dispose`
  /// lifecycle - see Flutter docs
  @protected
  @mustCallSuper
  void dispose() {}
}

需要更多覆盖?..

如果您在此类覆盖方面遇到任何问题,
请创建一个问题

除了列表项覆盖之外,还要在InfiniteList构建器中使用,
您也可以独立使用此包公开的StickyListItem

此类使用Stream来通知其父级关于页眉位置的变化

它还需要在Scrollable小部件和Viewport中渲染,
因为它订阅了滚动事件并计算位置
相对于Viewport坐标(请参阅StickyListItemRenderObject
了解更多信息)

例如

Widget build(BuildContext context) {
  return SingleChildScrollView(
    child: Column(
      children: <Widget>[
        Container(
          height: height,
          color: Colors.lightBlueAccent,
          child: Placeholder(),
        ),
        StickyListItem<String>(
          header: Container(
            height: 30,
            width: double.infinity,
            color: Colors.orange,
            child: Center(
              child: Text('Sticky Header')
            ),
          ),
          content: Container(
            height: height,
            color: Colors.blueAccent,
            child: Placeholder(),
          ),
          itemIndex: 'single-child-index',
        ),
        Container(
          height: height,
          color: Colors.cyan,
          child: Placeholder(),
        ),
      ],
    ),
  );
}

此代码将渲染单个子滚动
带有3个小部件。中间一个 - 带有粘性页眉的项目。

演示

有关更复杂的示例,请查看“单个示例”页面
示例项目

GitHub

https://github.com/TatsuUkraine/flutter_sticky_infinite_list