Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
.pub/
packages
pubspec.lock
.dart_tool/
137 changes: 64 additions & 73 deletions lib/side_header_list_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ library side_header_list_view;
import 'package:flutter/material.dart';
import 'package:meta/meta.dart';


/**
* SideHeaderListView for Flutter
*
Expand All @@ -12,7 +11,6 @@ import 'package:meta/meta.dart';
* Released under BSD License.
*/


typedef bool HasSameHeader(int a, int b);

class SideHeaderListView extends StatefulWidget {
Expand All @@ -21,97 +19,90 @@ class SideHeaderListView extends StatefulWidget {
final IndexedWidgetBuilder itemBuilder;
final EdgeInsets padding;
final HasSameHeader hasSameHeader;
final itemExtend;
final double itemExtent;

SideHeaderListView({
Key key,
this.itemCount,
@required this.itemExtend,
@required this.itemExtent,
@required this.headerBuilder,
@required this.itemBuilder,
@required this.hasSameHeader,
this.padding,
})
: super(key: key);
}) : super(key: key);

@override
_SideHeaderListViewState createState() => new _SideHeaderListViewState();
_SideHeaderListViewState createState() => _SideHeaderListViewState();
}

class _SideHeaderListViewState extends State<SideHeaderListView> {
int currentPosition = 0;
final _controller = ScrollController();

@override
Widget build(BuildContext context) {
return new Stack(
children: <Widget>[
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: <Widget>[
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
_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
Comment on lines +50 to +51
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we do this in the build method? We might need to set some property if the position is set or not.

I'm also think if we need to do a reposition in didChangeDependencies

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the problem is that if you try to do this anywhere before the first build (for example in initState or didChangeDependencies) it complains with ScrollController not attached to any scroll view.

The only workaround I could find was to wait until the build is done, so the controller is attached to a scroll view, and then update the offset. There might a better solution but not sure how exactly it can be done in build or didChangeDependencies

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() {
_controller?.removeListener(_reposition);
super.dispose();
}

if (
position != widget.itemCount -1 &&
!widget.hasSameHeader(position, position + 1) &&
position == currentPosition) {
return true;
}
return false;
void _reposition() {
print('new position = ${(_controller.offset / widget.itemExtent).floor()}');
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this print is a good idea.

Suggested change
print('new position = ${(_controller.offset / widget.itemExtent).floor()}');

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep that was an old commit (I push another one since then)

setState(() =>
currentPosition = (_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: <Widget>[
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: _controller,
itemBuilder: (BuildContext context, int index) => Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
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);
}