I want to add a sticky header to a list view without using third-party libraries.
I already created a class for grouped list view, I want to know how can I add a sticky header to it, is it possible to have a stack widget contain list view and another widget for the sticky header and rebuild header widget by listening to list view scroll controller (like below code)? is there a better way to doing this?
class GroupedListView<E, G extends Comparable<Object>> extends StatefulWidget {
const GroupedListView({
@required this.groupBy,
@required this.groupBuilder,
@required this.itemBuilder,
Key key,
this.elements,
}) : super(key: key);
final G Function(E element) groupBy;
final Widget Function(G group) groupBuilder;
final Widget Function(BuildContext context, E element) itemBuilder;
final List<E> elements;
@override
_GroupedListViewState<E, G> createState() => _GroupedListViewState<E, G>();
}
class _GroupedListViewState<E, G extends Comparable<Object>>
extends State<GroupedListView<E, G>> {
List<E> _elements;
@override
void initState() {
super.initState();
_elements = widget.elements;
}
@override
Widget build(BuildContext context) => Stack(
children: <Widget>[
ListView.builder(
itemCount: _elements.length * 2,
itemBuilder: (BuildContext context, int index) {
final int actualIndex = index ~/ 2;
if (index.isEven) {
final G currentGroup = widget.groupBy(_elements[actualIndex]);
final G previousGroup = actualIndex - 1 < 0
? null
: widget.groupBy(_elements[actualIndex - 1]);
if (previousGroup != currentGroup) {
return widget.groupBuilder(currentGroup);
}
return Container();
}
return widget.itemBuilder(context, _elements[actualIndex]);
},
),
// THIS WIDGET SHOULD REBUILD BY LISTENING TO SCROLL VIEW CONTROLLER
Container(
color: Colors.black,
child: ListTile(
title: Text(
'STICKY HEADER',
style: TextStyle(color: Colors.white),
),
),
),
],
);
}
sample code:
class HomePage extends StatefulWidget {
HomePage({
Key key,
this.title,
}) : super(key: key);
final String title;
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: GroupedListView<dynamic, String>(
elements: listItems,
groupBy: (dynamic element) => element['group'],
groupBuilder: (String value) => Container(
color: Colors.grey,
child: ListTile(
title: Text(
value,
style: TextStyle(color: Colors.white),
),
),
),
itemBuilder: (BuildContext context, dynamic element) => ListTile(
title: Text(element['name']),
),
),
);
}
}
final List<Map<String, String>> listItems = <Map<String, String>>[
<String, String>{'name': 'user_01', 'group': 'group_01'},
<String, String>{'name': 'user_02', 'group': 'group_01'},
<String, String>{'name': 'user_03', 'group': 'group_02'},
<String, String>{'name': 'user_04', 'group': 'group_02'},
<String, String>{'name': 'user_05', 'group': 'group_02'},
<String, String>{'name': 'user_06', 'group': 'group_03'},
<String, String>{'name': 'user_07', 'group': 'group_04'},
<String, String>{'name': 'user_08', 'group': 'group_04'},
<String, String>{'name': 'user_09', 'group': 'group_05'},
<String, String>{'name': 'user_10', 'group': 'group_05'},
<String, String>{'name': 'user_11', 'group': 'group_06'},
<String, String>{'name': 'user_12', 'group': 'group_06'},
];
Any help would be greatly appreciated.
