{"id":13465574,"url":"https://github.com/TatsuUkraine/flutter_sticky_infinite_list","last_synced_at":"2025-03-25T16:32:22.188Z","repository":{"id":52804277,"uuid":"195885600","full_name":"TatsuUkraine/flutter_sticky_infinite_list","owner":"TatsuUkraine","description":"Multi directional infinite list with Sticky headers for Flutter applications","archived":false,"fork":false,"pushed_at":"2021-04-18T21:25:02.000Z","size":28857,"stargazers_count":341,"open_issues_count":2,"forks_count":31,"subscribers_count":5,"default_branch":"master","last_synced_at":"2024-10-13T08:45:45.970Z","etag":null,"topics":["flutter","flutter-package","infinite-lists","infinite-scroll","multidirectionscroll","sticky","sticky-headers","stickyheader"],"latest_commit_sha":null,"homepage":"","language":"Dart","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-2-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/TatsuUkraine.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-07-08T21:01:14.000Z","updated_at":"2024-08-23T14:52:27.000Z","dependencies_parsed_at":"2022-08-22T19:31:50.845Z","dependency_job_id":null,"html_url":"https://github.com/TatsuUkraine/flutter_sticky_infinite_list","commit_stats":null,"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TatsuUkraine%2Fflutter_sticky_infinite_list","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TatsuUkraine%2Fflutter_sticky_infinite_list/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TatsuUkraine%2Fflutter_sticky_infinite_list/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TatsuUkraine%2Fflutter_sticky_infinite_list/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/TatsuUkraine","download_url":"https://codeload.github.com/TatsuUkraine/flutter_sticky_infinite_list/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":221577872,"owners_count":16846527,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["flutter","flutter-package","infinite-lists","infinite-scroll","multidirectionscroll","sticky","sticky-headers","stickyheader"],"created_at":"2024-07-31T15:00:32.070Z","updated_at":"2024-10-29T17:31:13.062Z","avatar_url":"https://github.com/TatsuUkraine.png","language":"Dart","funding_links":[],"categories":["Components","组件","Dart","UI [🔝](#readme)"],"sub_categories":["UI"],"readme":"# Sticky Infinite List\n\n[![pub package](https://img.shields.io/pub/v/sticky_infinite_list.svg)](https://pub.dartlang.org/packages/sticky_infinite_list)\n\u003ca href=\"https://github.com/Solido/awesome-flutter\"\u003e\n   \u003cimg alt=\"Awesome Flutter\" src=\"https://img.shields.io/badge/Awesome-Flutter-blue.svg?longCache=true\u0026style=flat-square\" /\u003e\n\u003c/a\u003e\n\nInfinite list with sticky headers.\n\nThis package was made in order to make possible\nrender infinite list in both directions with sticky headers, unlike most\npackages in Dart Pub.\n\nSupports various header positioning. Also supports Vertical and\nHorizontal scroll list\n\nIt highly customizable and doesn't have any third party dependencies or native(Android/iOS) code.\n\nIn addition to default usage, this package exposes some classes, that\ncan be overridden if needed. Also some classes it can be used inside\nScrollable widgets independently from `InfiniteList` container.\n\nThis package uses `CustomScrollView` to perform scroll with all\nbenefits for performance that Flutter provides.\n\n## Features\n\n- sticky headers within infinite list\n- multi directional infinite list\n- customization for sticky header position\n- horizontal sticky list support\n- dynamic header build on content scroll\n- dynamic min offset calculation on content scroll\n\n## Flutter before 1.20\n\nIf you're using Flutter version lower than 1.20 consider using v2.x.x.\n\n## Migration guide\n\nIf you using older MAJOR versions, please\n[visit this migration guide](https://github.com/TatsuUkraine/flutter_sticky_infinite_list/blob/master/MIGRATION.md)\n\n### Property not defined\n\nIf you get similar error message during the build please read [this\nsection](#flutter-version-related-errors) first.\n\n## Demo\n\n\u003cimg src=\"https://github.com/TatsuUkraine/flutter_sticky_infinite_list_example/raw/2277e153e7c723bca0a746b2730c5abef4cfe25e/doc/images/base_scroll.gif\" width=\"50%\" /\u003e\n\n## Getting Started\n\nInstall package and import\n\n```dart\n\nimport 'package:sticky_infinite_list/sticky_infinite_list.dart';\n```\n\nPackage exposes `InfiniteList`, `InfiniteListItem`, `StickyListItem`,\n`StickyListItemRenderObject` classes\n\n## Examples\n\n### Simple example\n\nTo start using Infinite list with sticky headers,\nyou need to create instance `InfiniteList` with builder specified.\n\nNo need to specify any additional config to make it work\n\n```dart\n\nimport 'package:sticky_infinite_list/sticky_infinite_list.dart';\n\nclass Example extends StatelessWidget {\n  \n  @override\n  Widget build(BuildContext context) {\n    return InfiniteList(\n      builder: (BuildContext context, int index) {\n        /// Builder requires [InfiniteList] to be returned\n        return InfiniteListItem(\n          /// Header builder\n          headerBuilder: (BuildContext context) {\n            return Container(\n              ///...\n            );\n          },\n          /// Content builder\n          contentBuilder: (BuildContext context) {\n            return Container(\n              ///...\n            );\n          },\n        );\n      }\n    );\n  }\n}\n```\n\nOr with header overlay content\n```dart\n\nimport 'package:sticky_infinite_list/sticky_infinite_list.dart';\n\nclass Example extends StatelessWidget {\n  \n  @override\n  Widget build(BuildContext context) {\n    return InfiniteList(\n      builder: (BuildContext context, int index) {\n        /// Builder requires [InfiniteList] to be returned\n        return InfiniteListItem.overlay(\n          /// Header builder\n          headerBuilder: (BuildContext context) {\n            return Container(\n              ///...\n            );\n          },\n          /// Content builder\n          contentBuilder: (BuildContext context) {\n            return Container(\n              ///...\n            );\n          },\n        );\n      }\n    );\n  }\n}\n```\n\n### State\n\nWhen min offset callback invoked or header builder is invoked\nobject `StickyState` is passed as parameter\n\nThis object describes current state for sticky header.\n\n```dart\nclass StickyState\u003cI\u003e {\n  /// Position, that header already passed\n  ///\n  /// Value can be between 0.0 and 1.0\n  ///\n  /// If it's `0.0` - sticky in max start position\n  ///\n  /// `1.0` - max end position\n  ///\n  /// If [InfiniteListItem.initialHeaderBuild] is true, initial\n  /// header render will be with position = 0\n  final double position;\n\n  /// Number of pixels, that outside of viewport\n  ///\n  /// If [InfiniteListItem.initialHeaderBuild] is true, initial\n  /// header render will be with offset = 0\n  /// \n  /// For header bottom positions (or right positions for horizontal)\n  /// offset value also will be amount of pixels that was scrolled away\n  final double offset;\n\n  /// Item index\n  final I index;\n\n  /// If header is in sticky state\n  ///\n  /// If [InfiniteListItem.minOffsetProvider] is defined,\n  /// it could be that header builder will be emitted with new state\n  /// on scroll, but [sticky] will be false, if offset already passed\n  /// min offset value\n  ///\n  /// WHen [InfiniteListItem.minOffsetProvider] is called, [sticky]\n  /// will always be `false`. Since for min offset calculation\n  /// offset itself not defined yet\n  final bool sticky;\n\n  /// Scroll item height.\n  ///\n  /// If [InfiniteListItem.initialHeaderBuild] is true, initial\n  /// header render will be called without this value\n  final double contentSize;\n}\n```\n\n### Extended configuration\n\n#### Available configuration\n\nAlongside with minimal config to start using.\n\n`InfiniteList` allows you to define config for scroll list rendering\n\n```dart\nInfiniteList(\n  /// Optional parameter to pass ScrollController instance\n  controller: ScrollController(),\n  \n  /// Optional parameter\n  /// to specify scroll direction\n  /// \n  /// By default scroll will be rendered with just positive\n  /// direction `InfiniteListDirection.forward`\n  /// \n  /// If you need infinite list in both directions use `InfiniteListDirection.multi`\n  direction: InfiniteListDirection.multi,\n  \n  /// Negative max child count.\n  /// \n  /// Will be used only when `direction: InfiniteListDirection.multi`\n  /// \n  /// If it's not provided, scroll will be infinite in negative direction\n  negChildCount: 100,\n  \n  /// Positive max child count\n  /// \n  /// Specifies number of elements for forward list\n  /// \n  /// If it's not provided, scroll will be infinite in positive direction\n  posChildCount: 100,\n  \n  /// ScrollView anchor value.\n  anchor: 0.0,\n  \n  /// ScrollView physics value.\n  physics: null,\n\n  /// Item builder\n  /// \n  /// Should return `InfiniteListItem`\n  builder: (BuildContext context, int index) {\n    return InfiniteListItem(\n      //...\n    )\n  }\n)\n```\n \n`InfiniteListItem` allows you to specify more options for you customization.\n\n```dart\nInfiniteListItem(\n  /// See class description for more info\n  /// \n  /// Forces initial header render when [headerStateBuilder]\n  /// is specified.\n  initialHeaderBuild: false,\n\n  /// Simple Header builder\n  /// that will be called once during List item render\n  headerBuilder: (BuildContext context) {},\n  \n  /// Header builder, that will be invoked each time\n  /// when header should change it's position\n  /// \n  /// Unlike prev method, it also provides `state` of header\n  /// position\n  /// \n  /// This callback has higher priority than [headerBuilder],\n  /// so if both header builders will be provided,\n  /// [headerBuilder] will be ignored\n  headerStateBuilder: (BuildContext context, StickyState\u003cint\u003e state) {},\n  \n  /// Content builder\n  contentBuilder: (BuildContext context) {},\n  \n  /// Min offset invoker\n  /// \n  /// This callback is called on each header position change,\n  /// to define when header should be stick to the bottom of\n  /// content.\n  /// \n  /// If this method returns `0`,\n  /// header will be in sticky state until list item\n  /// will be visible inside view port\n  /// \n  /// If this method not provided or it returns null, header\n  /// will be sticky until offset equals to \n  /// header size\n  minOffsetProvider: (StickyState\u003cint\u003e state) {},\n  \n  /// Header alignment against main axis direction\n  ///\n  /// See [HeaderMainAxisAlignment] for more info\n  HeaderMainAxisAlignment mainAxisAlignment: HeaderMainAxisAlignment.start,\n\n  /// Header alignment against cross axis direction\n  ///\n  /// See [HeaderCrossAxisAlignment] for more info\n  HeaderCrossAxisAlignment crossAxisAlignment: HeaderCrossAxisAlignment.start,\n\n  /// Header position against scroll axis for relative positioned headers\n  ///\n  /// Only for relative header positioning\n  HeaderPositionAxis positionAxis: HeaderPositionAxis.mainAxis,\n\n  /// List item padding, see [EdgeInsets] for more info\n  EdgeInsets padding: const EdgeInsets.all(8.0),\n  \n  /// Scroll direction\n  ///\n  /// Can be vertical or horizontal (see [Axis] class)\n  ///\n  /// This value also affects how bottom or top\n  /// edge header positioned headers behave\n  scrollDirection: Axis.vertical,\n);\n```\n\n### Demos\n\n#### Header alignment demo\n\nRelative positioning\n\n\u003cimg src=\"https://github.com/TatsuUkraine/flutter_sticky_infinite_list_example/raw/28c70054b5a11a4ba22e9a1a2b6b76b892c441b3/doc/images/header_position.gif\" width=\"50%\" /\u003e\n\nRelative cross axis positioning\n\n\u003cimg src=\"https://github.com/TatsuUkraine/flutter_sticky_infinite_list_example/raw/28c70054b5a11a4ba22e9a1a2b6b76b892c441b3/doc/images/header_cross_position.gif\" width=\"50%\" /\u003e\n\nOverlay positioning\n\n\u003cimg src=\"https://github.com/TatsuUkraine/flutter_sticky_infinite_list_example/raw/28c70054b5a11a4ba22e9a1a2b6b76b892c441b3/doc/images/header_position_overlay.gif\" width=\"50%\" /\u003e\n\n#### Horizontal scroll demo\n\n\u003cimg src=\"https://github.com/TatsuUkraine/flutter_sticky_infinite_list_example/raw/ab9ec55223c2a60192559fed553634a7f358b002/doc/images/horizontal_scroll.gif\" width=\"50%\" /\u003e\n\n### Reverse infinite scroll\n\nCurrently package doesn't support `CustomScrollView.reverse` option.\n\nBut same result can be achieved with defining `anchor = 1` and\n`posChildCount = 0`. In that way viewport center will be stick to the\nbottom and positive list won't render anything.\n\nAdditionally you can specify header alignment to any side.\n\n```dart\nimport 'package:sticky_infinite_list/sticky_infinite_list.dart';\n\nclass Example extends StatelessWidget {\n  \n  @override\n  Widget build(BuildContext context) {\n    return InfiniteList(\n      anchor: 1.0,\n      \n      direction: InfiniteListDirection.multi,\n      \n      posChildCount: 0,\n      \n      builder: (BuildContext context, int index) {\n        /// Builder requires [InfiniteList] to be returned\n        return InfiniteListItem(\n        \n          mainAxisAlignment: HeaderMainAxisAlignment.end,\n          crossAxisAlignment: HeaderCrossAxisAlignment.start,\n          \n          /// Header builder\n          headerBuilder: (BuildContext context) {\n            return Container(\n              ///...\n            );\n          },\n          /// Content builder\n          contentBuilder: (BuildContext context) {\n            return Container(\n              ///...\n            );\n          },\n        );\n      }\n    );\n  }\n}\n``` \n\n#### Demo\n\n\u003cimg src=\"https://github.com/TatsuUkraine/flutter_sticky_infinite_list_example/raw/e7a35fba60c9f4da97e84131a8c342424819aae7/doc/images/reverse_scroll.gif\" width=\"50%\" /\u003e\n\nFor more info take a look at\n[Example](https://github.com/TatsuUkraine/flutter_sticky_infinite_list_example) project\n\n### Available for override\n\nIn most cases it will be enough to just use `InfiniteListItem`\n\nBut in some cases you may need to add additional functionality to\neach item.\n\nLuckily you can extend and override base `InfiniteListItem` class\n\n```dart\n/// Generic `I` is index type, by default list item uses `int`\n\nclass SomeCustomListItem\u003cI\u003e extends InfiniteListItem\u003cI\u003e {\n  /// Header alignment against main axis direction\n  ///\n  /// See [HeaderMainAxisAlignment] for more info\n  @override\n  final HeaderMainAxisAlignment mainAxisAlignment = HeaderMainAxisAlignment.start;\n\n  /// Header alignment against cross axis direction\n  ///\n  /// See [HeaderCrossAxisAlignment] for more info\n  @override\n  final HeaderCrossAxisAlignment crossAxisAlignment = HeaderCrossAxisAlignment.start;\n\n  /// Header position against scroll axis for relative positioned headers\n  ///\n  /// See [HeaderPositionAxis] for more info\n  @override\n  final HeaderPositionAxis positionAxis = HeaderPositionAxis.mainAxis;\n\n  /// If header should overlay content or not\n  @override\n  final bool overlayContent = false;\n\n  /// Let item builder know if it should watch\n  /// header position changes\n  /// \n  /// If this value is `true` - it will invoke [buildHeader]\n  /// each time header position changes\n  @override\n  bool get watchStickyState =\u003e true;\n\n  /// Let item builder know that this class\n  /// provides header\n  /// \n  /// If it returns `false` - [buildHeader] will be ignored \n  /// and never called\n  @override\n  bool get hasStickyHeader =\u003e true;\n\n  /// This methods builds header\n  /// \n  /// If [watchStickyState] is `true`,\n  /// it will be invoked on each header position change\n  /// and `state` option will be provided\n  /// \n  /// Otherwise it will be called only once on initial render\n  /// and each header position change won't invoke this method.\n  /// \n  /// Also in that case `state` will be `null`\n  @override\n  Widget buildHeader(BuildContext context, [StickyState\u003cI\u003e state]) {}\n\n  /// Content item builder\n  /// \n  /// This method invoked only once\n  @override\n  Widget buildContent(BuildContext context) {}\n\n  /// Called during init state (see [Statefull] widget [State.initState])\n  /// \n  /// For additional information about [Statefull] widget `initState`\n  /// lifecycle - see Flutter docs\n  @protected\n  @mustCallSuper\n  void initState() {}\n\n  /// Called during item dispose (see [Statefull] widget [State.dispose])\n  /// \n  /// For additional information about [Statefull] widget `dispose`\n  /// lifecycle - see Flutter docs\n  @protected\n  @mustCallSuper\n  void dispose() {}\n}\n```\n\n#### Need more override?..\n\nAlongside with list item override, to use inside `InfiniteList` builder,\nyou can also use or extend `StickyListItem`, that exposed by this\npackage too, independently.\n\nThis class uses `Stream` to inform it's parent about header position changes\n\nAlso it requires to be rendered inside `Scrollable` widget and `Viewport`,\nsince it subscribes to scroll event and calculates position\nagainst `Viewport` coordinates (see `StickyListItemRenderObject` class\nfor more information)\n\nFor example \n\n```dart\nWidget build(BuildContext context) {\n  return SingleChildScrollView(\n    child: Column(\n      children: \u003cWidget\u003e[\n        Container(\n          height: height,\n          color: Colors.lightBlueAccent,\n          child: Placeholder(),\n        ),\n        StickyListItem\u003cString\u003e(\n          streamSink: _headerStream.sink, /// stream to update header during scroll\n          header: Container(\n            height: _headerHeight,\n            width: double.infinity,\n            color: Colors.orange,\n            child: Center(\n              child: StreamBuilder\u003cStickyState\u003cString\u003e\u003e(\n                stream: _headerStream.stream, /// stream to update header during scroll\n                builder: (_, snapshot) {\n                  if (!snapshot.hasData) {\n                    return Container();\n                  }\n\n                  final position = (snapshot.data.position * 100).round();\n\n                  return Text('Positioned relative. Position: $position%');\n                },\n              ),\n            ),\n          ),\n          content: Container(\n            height: height,\n            color: Colors.blueAccent,\n            child: Placeholder(),\n          ),\n          itemIndex: \"single-child\",\n        ),\n        StickyListItem\u003cString\u003e.overlay(\n          streamSink: _headerOverlayStream.sink, /// stream to update header during scroll\n          header: Container(\n            height: _headerHeight,\n            width: double.infinity,\n            color: Colors.orange,\n            child: Center(\n              child: StreamBuilder\u003cStickyState\u003cString\u003e\u003e(\n                stream: _headerOverlayStream.stream, /// stream to update header during scroll\n                builder: (_, snapshot) {\n                  if (!snapshot.hasData) {\n                    return Container();\n                  }\n\n                  final position = (snapshot.data.position * 100).round();\n\n                  return Text('Positioned overlay. Position: $position%');\n                },\n              ),\n            ),\n          ),\n          content: Container(\n            height: height,\n            color: Colors.lightBlueAccent,\n            child: Placeholder(),\n          ),\n          itemIndex: \"single-overlayed-child\",\n        ),\n        Container(\n          height: height,\n          color: Colors.cyan,\n          child: Placeholder(),\n        ),\n      ],\n    ),\n  );\n}\n```\n\nThis code will render single child scroll with 4 widgets. Two middle\nitems - items with sticky header.\n\n**Demo**\n\n\u003cimg src=\"https://github.com/TatsuUkraine/flutter_sticky_infinite_list_example/raw/450dffae4a4e3a10496aa5dacf267e1511fe7cee/doc/images/single-scroll.gif\" width=\"50%\" /\u003e\n\nFor more complex example please take a look at \"Single Example\" page\nin [Example project](https://github.com/TatsuUkraine/flutter_sticky_infinite_list_example)\n\n## Changelog\n\nPlease see the [Changelog](https://github.com/TatsuUkraine/flutter_sticky_infinite_list/blob/master/CHANGELOG.md) page to know what's recently changed.\n\n## Bugs/Requests\n\nIf you encounter any problems feel free to open an [issue](https://github.com/TatsuUkraine/flutter_sticky_infinite_list/issues).\nIf you feel the library is missing a feature,\nplease raise a ticket on Github and I'll look into it.\nPull request are also welcome.\n\n## Known issues\n\nCurrently this package can't work with reverse scroll. For some reason\nflutter calculates coordinate for negative list items in a\ndifferent way in reverse mode, comparing to regular scroll direction.\n\nBut there is an workaround can be used, described\nin [Reverse infinite scroll](#reverse-infinite-scroll)\n\n## Flutter version related errors\n\n### Named parameter clipBehavior isn't defined error\n\nIf you get this kind of error, most likely you are using Flutter 1.17.\nIf so, please ensure that you're using 2.x.x version.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FTatsuUkraine%2Fflutter_sticky_infinite_list","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FTatsuUkraine%2Fflutter_sticky_infinite_list","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FTatsuUkraine%2Fflutter_sticky_infinite_list/lists"}