huge_listview.dart 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. import 'dart:math' show max;
  2. import 'package:flutter/material.dart';
  3. import 'package:photos/ui/huge_listview/draggable_scrollbar.dart';
  4. import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
  5. typedef HugeListViewItemBuilder<T> = Widget Function(
  6. BuildContext context,
  7. int index,
  8. );
  9. typedef HugeListViewErrorBuilder = Widget Function(
  10. BuildContext context,
  11. dynamic error,
  12. );
  13. class HugeListView<T> extends StatefulWidget {
  14. /// A [ScrollablePositionedList] controller for jumping or scrolling to an item.
  15. final ItemScrollController controller;
  16. /// Index of an item to initially align within the viewport.
  17. final int startIndex;
  18. /// Total number of items in the list.
  19. final int totalCount;
  20. /// Called to build the thumb. One of [DraggableScrollbarThumbs.RoundedRectThumb], [DraggableScrollbarThumbs.ArrowThumb]
  21. /// or [DraggableScrollbarThumbs.SemicircleThumb], or build your own.
  22. final String Function(int) labelTextBuilder;
  23. /// Background color of scroll thumb, defaults to white.
  24. final Color thumbBackgroundColor;
  25. /// Drawing color of scroll thumb, defaults to gray.
  26. final Color thumbDrawColor;
  27. /// Height of scroll thumb, defaults to 48.
  28. final double thumbHeight;
  29. /// Called to build an individual item with the specified [index].
  30. final HugeListViewItemBuilder<T> itemBuilder;
  31. /// Called to build a progress widget while the whole list is initialized.
  32. final WidgetBuilder waitBuilder;
  33. /// Called to build a widget when the list is empty.
  34. final WidgetBuilder emptyResultBuilder;
  35. /// Called to build a widget when there is an error.
  36. final HugeListViewErrorBuilder errorBuilder;
  37. /// Event to call with the index of the topmost visible item in the viewport while scrolling.
  38. /// Can be used to display the current letter of an alphabetically sorted list, for instance.
  39. final ValueChanged<int> firstShown;
  40. final bool isDraggableScrollbarEnabled;
  41. const HugeListView({
  42. Key key,
  43. this.controller,
  44. @required this.startIndex,
  45. @required this.totalCount,
  46. @required this.labelTextBuilder,
  47. @required this.itemBuilder,
  48. this.waitBuilder,
  49. this.emptyResultBuilder,
  50. this.errorBuilder,
  51. this.firstShown,
  52. this.thumbBackgroundColor = Colors.red, // Colors.white,
  53. this.thumbDrawColor = Colors.yellow, //Colors.grey,
  54. this.thumbHeight = 48.0,
  55. this.isDraggableScrollbarEnabled = true,
  56. }) : super(key: key);
  57. @override
  58. HugeListViewState<T> createState() => HugeListViewState<T>();
  59. }
  60. class HugeListViewState<T> extends State<HugeListView<T>> {
  61. final scrollKey = GlobalKey<DraggableScrollbarState>();
  62. final listener = ItemPositionsListener.create();
  63. dynamic error;
  64. @override
  65. void initState() {
  66. super.initState();
  67. listener.itemPositions.addListener(_sendScroll);
  68. }
  69. @override
  70. void dispose() {
  71. listener.itemPositions.removeListener(_sendScroll);
  72. super.dispose();
  73. }
  74. void _sendScroll() {
  75. int current = _currentFirst();
  76. widget.firstShown?.call(current);
  77. scrollKey.currentState?.setPosition(current / widget.totalCount, current);
  78. }
  79. int _currentFirst() {
  80. try {
  81. return listener.itemPositions.value.first.index;
  82. } catch (e) {
  83. return 0;
  84. }
  85. }
  86. @override
  87. Widget build(BuildContext context) {
  88. if (error != null && widget.errorBuilder != null) {
  89. return widget.errorBuilder(context, error);
  90. }
  91. if (widget.totalCount == -1 && widget.waitBuilder != null) {
  92. return widget.waitBuilder(context);
  93. }
  94. if (widget.totalCount == 0 && widget.emptyResultBuilder != null) {
  95. return widget.emptyResultBuilder(context);
  96. }
  97. return LayoutBuilder(
  98. builder: (context, constraints) {
  99. return DraggableScrollbar(
  100. key: scrollKey,
  101. totalCount: widget.totalCount,
  102. initialScrollIndex: widget.startIndex,
  103. onChange: (position) {
  104. widget.controller
  105. ?.jumpTo(index: (position * widget.totalCount).floor());
  106. },
  107. labelTextBuilder: widget.labelTextBuilder,
  108. backgroundColor: widget.thumbBackgroundColor,
  109. drawColor: widget.thumbDrawColor,
  110. heightScrollThumb: widget.thumbHeight,
  111. currentFirstIndex: _currentFirst(),
  112. isEnabled: widget.isDraggableScrollbarEnabled,
  113. child: ScrollablePositionedList.builder(
  114. itemScrollController: widget.controller,
  115. itemPositionsListener: listener,
  116. initialScrollIndex: widget.startIndex,
  117. itemCount: max(widget.totalCount, 0),
  118. itemBuilder: (context, index) {
  119. return widget.itemBuilder(context, index);
  120. },
  121. ),
  122. );
  123. },
  124. );
  125. }
  126. /// Jump to the [position] in the list. [position] is between 0.0 (first item) and 1.0 (last item), practically currentIndex / totalCount.
  127. /// To jump to a specific item, use [ItemScrollController.jumpTo] or [ItemScrollController.scrollTo].
  128. void setPosition(double position) {
  129. scrollKey.currentState?.setPosition(position, _currentFirst());
  130. }
  131. }