项目作者: TatsuUkraine

项目描述 :
Multi directional infinite list with Sticky headers for Flutter applications
高级语言: Dart
项目地址: git://github.com/TatsuUkraine/flutter_sticky_infinite_list.git
创建时间: 2019-07-08T21:01:14Z
项目社区:https://github.com/TatsuUkraine/flutter_sticky_infinite_list

开源协议:BSD 2-Clause "Simplified" License

下载


Sticky Infinite List

pub package

Awesome Flutter

Infinite list with sticky headers.

This package was made in order to make possible
render infinite list in both directions with sticky headers, unlike most
packages in Dart Pub.

Supports various header positioning. Also supports Vertical and
Horizontal scroll list

It highly customizable and doesn’t have any third party dependencies or native(Android/iOS) code.

In addition to default usage, this package exposes some classes, that
can be overridden if needed. Also some classes it can be used inside
Scrollable widgets independently from InfiniteList container.

This package uses CustomScrollView to perform scroll with all
benefits for performance that Flutter provides.

Features

  • sticky headers within infinite list
  • multi directional infinite list
  • customization for sticky header position
  • horizontal sticky list support
  • dynamic header build on content scroll
  • dynamic min offset calculation on content scroll

Flutter before 1.20

If you’re using Flutter version lower than 1.20 consider using v2.x.x.

Migration guide

If you using older MAJOR versions, please
visit this migration guide

Property not defined

If you get similar error message during the build please read this
section
first.

Demo

Getting Started

Install package and import

  1. import 'package:sticky_infinite_list/sticky_infinite_list.dart';

Package exposes InfiniteList, InfiniteListItem, StickyListItem,
StickyListItemRenderObject classes

Examples

Simple example

To start using Infinite list with sticky headers,
you need to create instance InfiniteList with builder specified.

No need to specify any additional config to make it work

  1. import 'package:sticky_infinite_list/sticky_infinite_list.dart';
  2. class Example extends StatelessWidget {
  3. @override
  4. Widget build(BuildContext context) {
  5. return InfiniteList(
  6. builder: (BuildContext context, int index) {
  7. /// Builder requires [InfiniteList] to be returned
  8. return InfiniteListItem(
  9. /// Header builder
  10. headerBuilder: (BuildContext context) {
  11. return Container(
  12. ///...
  13. );
  14. },
  15. /// Content builder
  16. contentBuilder: (BuildContext context) {
  17. return Container(
  18. ///...
  19. );
  20. },
  21. );
  22. }
  23. );
  24. }
  25. }

Or with header overlay content

  1. import 'package:sticky_infinite_list/sticky_infinite_list.dart';
  2. class Example extends StatelessWidget {
  3. @override
  4. Widget build(BuildContext context) {
  5. return InfiniteList(
  6. builder: (BuildContext context, int index) {
  7. /// Builder requires [InfiniteList] to be returned
  8. return InfiniteListItem.overlay(
  9. /// Header builder
  10. headerBuilder: (BuildContext context) {
  11. return Container(
  12. ///...
  13. );
  14. },
  15. /// Content builder
  16. contentBuilder: (BuildContext context) {
  17. return Container(
  18. ///...
  19. );
  20. },
  21. );
  22. }
  23. );
  24. }
  25. }

State

When min offset callback invoked or header builder is invoked
object StickyState is passed as parameter

This object describes current state for sticky header.

  1. class StickyState<I> {
  2. /// Position, that header already passed
  3. ///
  4. /// Value can be between 0.0 and 1.0
  5. ///
  6. /// If it's `0.0` - sticky in max start position
  7. ///
  8. /// `1.0` - max end position
  9. ///
  10. /// If [InfiniteListItem.initialHeaderBuild] is true, initial
  11. /// header render will be with position = 0
  12. final double position;
  13. /// Number of pixels, that outside of viewport
  14. ///
  15. /// If [InfiniteListItem.initialHeaderBuild] is true, initial
  16. /// header render will be with offset = 0
  17. ///
  18. /// For header bottom positions (or right positions for horizontal)
  19. /// offset value also will be amount of pixels that was scrolled away
  20. final double offset;
  21. /// Item index
  22. final I index;
  23. /// If header is in sticky state
  24. ///
  25. /// If [InfiniteListItem.minOffsetProvider] is defined,
  26. /// it could be that header builder will be emitted with new state
  27. /// on scroll, but [sticky] will be false, if offset already passed
  28. /// min offset value
  29. ///
  30. /// WHen [InfiniteListItem.minOffsetProvider] is called, [sticky]
  31. /// will always be `false`. Since for min offset calculation
  32. /// offset itself not defined yet
  33. final bool sticky;
  34. /// Scroll item height.
  35. ///
  36. /// If [InfiniteListItem.initialHeaderBuild] is true, initial
  37. /// header render will be called without this value
  38. final double contentSize;
  39. }

Extended configuration

Available configuration

Alongside with minimal config to start using.

InfiniteList allows you to define config for scroll list rendering

  1. InfiniteList(
  2. /// Optional parameter to pass ScrollController instance
  3. controller: ScrollController(),
  4. /// Optional parameter
  5. /// to specify scroll direction
  6. ///
  7. /// By default scroll will be rendered with just positive
  8. /// direction `InfiniteListDirection.forward`
  9. ///
  10. /// If you need infinite list in both directions use `InfiniteListDirection.multi`
  11. direction: InfiniteListDirection.multi,
  12. /// Negative max child count.
  13. ///
  14. /// Will be used only when `direction: InfiniteListDirection.multi`
  15. ///
  16. /// If it's not provided, scroll will be infinite in negative direction
  17. negChildCount: 100,
  18. /// Positive max child count
  19. ///
  20. /// Specifies number of elements for forward list
  21. ///
  22. /// If it's not provided, scroll will be infinite in positive direction
  23. posChildCount: 100,
  24. /// ScrollView anchor value.
  25. anchor: 0.0,
  26. /// ScrollView physics value.
  27. physics: null,
  28. /// Item builder
  29. ///
  30. /// Should return `InfiniteListItem`
  31. builder: (BuildContext context, int index) {
  32. return InfiniteListItem(
  33. //...
  34. )
  35. }
  36. )

InfiniteListItem allows you to specify more options for you customization.

  1. InfiniteListItem(
  2. /// See class description for more info
  3. ///
  4. /// Forces initial header render when [headerStateBuilder]
  5. /// is specified.
  6. initialHeaderBuild: false,
  7. /// Simple Header builder
  8. /// that will be called once during List item render
  9. headerBuilder: (BuildContext context) {},
  10. /// Header builder, that will be invoked each time
  11. /// when header should change it's position
  12. ///
  13. /// Unlike prev method, it also provides `state` of header
  14. /// position
  15. ///
  16. /// This callback has higher priority than [headerBuilder],
  17. /// so if both header builders will be provided,
  18. /// [headerBuilder] will be ignored
  19. headerStateBuilder: (BuildContext context, StickyState<int> state) {},
  20. /// Content builder
  21. contentBuilder: (BuildContext context) {},
  22. /// Min offset invoker
  23. ///
  24. /// This callback is called on each header position change,
  25. /// to define when header should be stick to the bottom of
  26. /// content.
  27. ///
  28. /// If this method returns `0`,
  29. /// header will be in sticky state until list item
  30. /// will be visible inside view port
  31. ///
  32. /// If this method not provided or it returns null, header
  33. /// will be sticky until offset equals to
  34. /// header size
  35. minOffsetProvider: (StickyState<int> state) {},
  36. /// Header alignment against main axis direction
  37. ///
  38. /// See [HeaderMainAxisAlignment] for more info
  39. HeaderMainAxisAlignment mainAxisAlignment: HeaderMainAxisAlignment.start,
  40. /// Header alignment against cross axis direction
  41. ///
  42. /// See [HeaderCrossAxisAlignment] for more info
  43. HeaderCrossAxisAlignment crossAxisAlignment: HeaderCrossAxisAlignment.start,
  44. /// Header position against scroll axis for relative positioned headers
  45. ///
  46. /// Only for relative header positioning
  47. HeaderPositionAxis positionAxis: HeaderPositionAxis.mainAxis,
  48. /// List item padding, see [EdgeInsets] for more info
  49. EdgeInsets padding: const EdgeInsets.all(8.0),
  50. /// Scroll direction
  51. ///
  52. /// Can be vertical or horizontal (see [Axis] class)
  53. ///
  54. /// This value also affects how bottom or top
  55. /// edge header positioned headers behave
  56. scrollDirection: Axis.vertical,
  57. );

Demos

Header alignment demo

Relative positioning

Relative cross axis positioning

Overlay positioning

Horizontal scroll demo

Reverse infinite scroll

Currently package doesn’t support CustomScrollView.reverse option.

But same result can be achieved with defining anchor = 1 and
posChildCount = 0. In that way viewport center will be stick to the
bottom and positive list won’t render anything.

Additionally you can specify header alignment to any side.

  1. import 'package:sticky_infinite_list/sticky_infinite_list.dart';
  2. class Example extends StatelessWidget {
  3. @override
  4. Widget build(BuildContext context) {
  5. return InfiniteList(
  6. anchor: 1.0,
  7. direction: InfiniteListDirection.multi,
  8. posChildCount: 0,
  9. builder: (BuildContext context, int index) {
  10. /// Builder requires [InfiniteList] to be returned
  11. return InfiniteListItem(
  12. mainAxisAlignment: HeaderMainAxisAlignment.end,
  13. crossAxisAlignment: HeaderCrossAxisAlignment.start,
  14. /// Header builder
  15. headerBuilder: (BuildContext context) {
  16. return Container(
  17. ///...
  18. );
  19. },
  20. /// Content builder
  21. contentBuilder: (BuildContext context) {
  22. return Container(
  23. ///...
  24. );
  25. },
  26. );
  27. }
  28. );
  29. }
  30. }

Demo

For more info take a look at
Example project

Available for override

In most cases it will be enough to just use InfiniteListItem

But in some cases you may need to add additional functionality to
each item.

Luckily you can extend and override base InfiniteListItem class

  1. /// Generic `I` is index type, by default list item uses `int`
  2. class SomeCustomListItem<I> extends InfiniteListItem<I> {
  3. /// Header alignment against main axis direction
  4. ///
  5. /// See [HeaderMainAxisAlignment] for more info
  6. @override
  7. final HeaderMainAxisAlignment mainAxisAlignment = HeaderMainAxisAlignment.start;
  8. /// Header alignment against cross axis direction
  9. ///
  10. /// See [HeaderCrossAxisAlignment] for more info
  11. @override
  12. final HeaderCrossAxisAlignment crossAxisAlignment = HeaderCrossAxisAlignment.start;
  13. /// Header position against scroll axis for relative positioned headers
  14. ///
  15. /// See [HeaderPositionAxis] for more info
  16. @override
  17. final HeaderPositionAxis positionAxis = HeaderPositionAxis.mainAxis;
  18. /// If header should overlay content or not
  19. @override
  20. final bool overlayContent = false;
  21. /// Let item builder know if it should watch
  22. /// header position changes
  23. ///
  24. /// If this value is `true` - it will invoke [buildHeader]
  25. /// each time header position changes
  26. @override
  27. bool get watchStickyState => true;
  28. /// Let item builder know that this class
  29. /// provides header
  30. ///
  31. /// If it returns `false` - [buildHeader] will be ignored
  32. /// and never called
  33. @override
  34. bool get hasStickyHeader => true;
  35. /// This methods builds header
  36. ///
  37. /// If [watchStickyState] is `true`,
  38. /// it will be invoked on each header position change
  39. /// and `state` option will be provided
  40. ///
  41. /// Otherwise it will be called only once on initial render
  42. /// and each header position change won't invoke this method.
  43. ///
  44. /// Also in that case `state` will be `null`
  45. @override
  46. Widget buildHeader(BuildContext context, [StickyState<I> state]) {}
  47. /// Content item builder
  48. ///
  49. /// This method invoked only once
  50. @override
  51. Widget buildContent(BuildContext context) {}
  52. /// Called during init state (see [Statefull] widget [State.initState])
  53. ///
  54. /// For additional information about [Statefull] widget `initState`
  55. /// lifecycle - see Flutter docs
  56. @protected
  57. @mustCallSuper
  58. void initState() {}
  59. /// Called during item dispose (see [Statefull] widget [State.dispose])
  60. ///
  61. /// For additional information about [Statefull] widget `dispose`
  62. /// lifecycle - see Flutter docs
  63. @protected
  64. @mustCallSuper
  65. void dispose() {}
  66. }

Need more override?..

Alongside with list item override, to use inside InfiniteList builder,
you can also use or extend StickyListItem, that exposed by this
package too, independently.

This class uses Stream to inform it’s parent about header position changes

Also it requires to be rendered inside Scrollable widget and Viewport,
since it subscribes to scroll event and calculates position
against Viewport coordinates (see StickyListItemRenderObject class
for more information)

For example

  1. Widget build(BuildContext context) {
  2. return SingleChildScrollView(
  3. child: Column(
  4. children: <Widget>[
  5. Container(
  6. height: height,
  7. color: Colors.lightBlueAccent,
  8. child: Placeholder(),
  9. ),
  10. StickyListItem<String>(
  11. streamSink: _headerStream.sink, /// stream to update header during scroll
  12. header: Container(
  13. height: _headerHeight,
  14. width: double.infinity,
  15. color: Colors.orange,
  16. child: Center(
  17. child: StreamBuilder<StickyState<String>>(
  18. stream: _headerStream.stream, /// stream to update header during scroll
  19. builder: (_, snapshot) {
  20. if (!snapshot.hasData) {
  21. return Container();
  22. }
  23. final position = (snapshot.data.position * 100).round();
  24. return Text('Positioned relative. Position: $position%');
  25. },
  26. ),
  27. ),
  28. ),
  29. content: Container(
  30. height: height,
  31. color: Colors.blueAccent,
  32. child: Placeholder(),
  33. ),
  34. itemIndex: "single-child",
  35. ),
  36. StickyListItem<String>.overlay(
  37. streamSink: _headerOverlayStream.sink, /// stream to update header during scroll
  38. header: Container(
  39. height: _headerHeight,
  40. width: double.infinity,
  41. color: Colors.orange,
  42. child: Center(
  43. child: StreamBuilder<StickyState<String>>(
  44. stream: _headerOverlayStream.stream, /// stream to update header during scroll
  45. builder: (_, snapshot) {
  46. if (!snapshot.hasData) {
  47. return Container();
  48. }
  49. final position = (snapshot.data.position * 100).round();
  50. return Text('Positioned overlay. Position: $position%');
  51. },
  52. ),
  53. ),
  54. ),
  55. content: Container(
  56. height: height,
  57. color: Colors.lightBlueAccent,
  58. child: Placeholder(),
  59. ),
  60. itemIndex: "single-overlayed-child",
  61. ),
  62. Container(
  63. height: height,
  64. color: Colors.cyan,
  65. child: Placeholder(),
  66. ),
  67. ],
  68. ),
  69. );
  70. }

This code will render single child scroll with 4 widgets. Two middle
items - items with sticky header.

Demo

For more complex example please take a look at “Single Example” page
in Example project

Changelog

Please see the Changelog page to know what’s recently changed.

Bugs/Requests

If you encounter any problems feel free to open an issue.
If you feel the library is missing a feature,
please raise a ticket on Github and I’ll look into it.
Pull request are also welcome.

Known issues

Currently this package can’t work with reverse scroll. For some reason
flutter calculates coordinate for negative list items in a
different way in reverse mode, comparing to regular scroll direction.

But there is an workaround can be used, described
in Reverse infinite scroll

Named parameter clipBehavior isn’t defined error

If you get this kind of error, most likely you are using Flutter 1.17.
If so, please ensure that you’re using 2.x.x version.