diff --git a/.gitignore b/.gitignore index 75e9658..139c6a9 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ .pub/ packages pubspec.lock +.dart_tool/ diff --git a/lib/side_header_list_view.dart b/lib/side_header_list_view.dart index be93aff..f6ac383 100644 --- a/lib/side_header_list_view.dart +++ b/lib/side_header_list_view.dart @@ -3,7 +3,6 @@ library side_header_list_view; import 'package:flutter/material.dart'; import 'package:meta/meta.dart'; - /** * SideHeaderListView for Flutter * @@ -12,7 +11,6 @@ import 'package:meta/meta.dart'; * Released under BSD License. */ - typedef bool HasSameHeader(int a, int b); class SideHeaderListView extends StatefulWidget { @@ -21,97 +19,90 @@ class SideHeaderListView extends StatefulWidget { final IndexedWidgetBuilder itemBuilder; final EdgeInsets padding; final HasSameHeader hasSameHeader; - final itemExtend; + final double itemExtent; + final ScrollController controller; SideHeaderListView({ Key key, this.itemCount, - @required this.itemExtend, + @required this.itemExtent, @required this.headerBuilder, @required this.itemBuilder, @required this.hasSameHeader, + @required this.controller, this.padding, - }) - : super(key: key); + }) : super(key: key); @override - _SideHeaderListViewState createState() => new _SideHeaderListViewState(); + _SideHeaderListViewState createState() => _SideHeaderListViewState(); } class _SideHeaderListViewState extends State { int currentPosition = 0; @override - Widget build(BuildContext context) { - return new Stack( - children: [ - new Positioned( - child: new Opacity( - opacity: _shouldShowHeader(currentPosition) ? 0.0 : 1.0, - child: widget.headerBuilder(context, currentPosition >= 0 ? currentPosition : 0), - ), - top: 0.0 + (widget.padding?.top ?? 0), - left: 0.0 + (widget.padding?.left ?? 0), - ), - new ListView.builder( - padding: widget.padding, - itemCount: widget.itemCount, - itemExtent: widget.itemExtend, - controller: _getScrollController(), - itemBuilder: (BuildContext context, int index) { - return new Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - new FittedBox( - child: new Opacity( - opacity: _shouldShowHeader(index) ? 1.0 : 0.0, - child: widget.headerBuilder(context, index), - ), - ), - new Expanded(child: widget.itemBuilder(context, index)) - ], - ); - }), - ], - ); + void initState() { + super.initState(); + // Reposition side view each time the main list view is scrolled + widget.controller.addListener(_reposition); + // In case the initial offset is not 0, we reposition the side view to the + // correct offset after first build + // TODO: this produces a flickering effect -- we should try to position it + // correctly before the first build + WidgetsBinding.instance.addPostFrameCallback((_) => _reposition()); } - bool _shouldShowHeader(int position) { - if(position < 0){ - return true; - } - if (position == 0 && currentPosition < 0) { - return true; - } - - if ( - position != 0 && - position != currentPosition && - !widget.hasSameHeader(position, position - 1)) { - return true; - } + @override + void dispose() { + widget.controller.removeListener(_reposition); + super.dispose(); + } - if ( - position != widget.itemCount -1 && - !widget.hasSameHeader(position, position + 1) && - position == currentPosition) { - return true; - } - return false; + void _reposition() { + setState(() => currentPosition = + (widget.controller.offset / widget.itemExtent).floor()); } - ScrollController _getScrollController() { - var controller = new ScrollController(); - controller.addListener(() { - var pixels = controller.offset; - var newPosition = (pixels / widget.itemExtend).floor(); + @override + Widget build(BuildContext context) => Stack( + children: [ + Positioned( + child: Opacity( + opacity: _shouldShowHeader(currentPosition) ? 0.0 : 1.0, + child: widget.headerBuilder( + context, currentPosition >= 0 ? currentPosition : 0), + ), + top: 0.0 + (widget.padding?.top ?? 0), + left: 0.0 + (widget.padding?.left ?? 0), + ), + ListView.builder( + padding: widget.padding, + itemCount: widget.itemCount, + itemExtent: widget.itemExtent, + controller: widget.controller, + itemBuilder: (BuildContext context, int index) => Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + FittedBox( + child: Opacity( + opacity: _shouldShowHeader(index) ? 1.0 : 0.0, + child: widget.headerBuilder(context, index), + ), + ), + Expanded(child: widget.itemBuilder(context, index)) + ], + ), + ), + ], + ); - if (newPosition != currentPosition) { - setState(() { - currentPosition = newPosition; - }); - } - }); - return controller; - } + bool _shouldShowHeader(int position) => + (position < 0) || + (position == 0 && currentPosition < 0) || + (position != 0 && + position != currentPosition && + !widget.hasSameHeader(position, position - 1)) || + (position != widget.itemCount - 1 && + !widget.hasSameHeader(position, position + 1) && + position == currentPosition); }