iOS like Sectioned ListView Widget in Flutter

import ‘package:flutter/material.dart’;void main() => runApp(App());
import 'package:flutter/material.dart';
import 'package:sectioned_list_view/home_screen.dart';

class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomeScreen(
title: 'Sectioned List',
));
}
}
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

class HomeScreen extends StatefulWidget {
HomeScreen({this.title});
final String title;

@override
_HomeScreenState createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Text('Section List'));
}
}
import 'package:flutter/material.dart';

/// ----#1
typedef int NumberOfRowsCallBack(int section);
typedef int NumberOfSectionCallBack();
typedef Widget SectionWidgetCallBack(int section);
typedef Widget RowsWidgetCallBack(int section, int row);

/// ----#2
class FlutterSectionListView extends StatefulWidget {
FlutterSectionListView({
this.numberOfSection,
@required this.numberOfRowsInSection,
this.sectionWidget,
@required this.rowWidget,
}) : assert(!(numberOfRowsInSection == null || rowWidget == null),
'numberOfRowsInSection and rowWidget are mandatory');

/// ----#3
/// Defines the total number of sections
final NumberOfSectionCallBack numberOfSection;

/// Mandatory callback method to get the rows count in each section
final NumberOfRowsCallBack numberOfRowsInSection;

/// Callback method to get the section widget
final SectionWidgetCallBack sectionWidget;

/// Mandatory callback method to get the row widget
final RowsWidgetCallBack rowWidget;

@override
_FlutterSectionListViewState createState() => _FlutterSectionListViewState();
}

class _FlutterSectionListViewState extends State<FlutterSectionListView> {
/// List of total number of rows and section in each group
var itemList = new List<int>();
int itemCount = 0;
int sectionCount = 0;

@override
void initState() {
/// ----#4
sectionCount = widget.numberOfSection();

/// ----#5
itemCount = listItemCount();
super.initState();
}

/// ----#6
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: itemCount,
itemBuilder: (context, index) {
return buildItemWidget(index);
},
key: widget.key,
);
}

/// Get the total count of items in list(including both row and sections)
int listItemCount() {
itemList = new List<int>();
int rowCount = 0;

for (int i = 0; i < sectionCount; i++) {
/// Get the number of rows in each section using callback
int rows = widget.numberOfRowsInSection(i);

/// Here 1 is added for each section in one group
rowCount += rows + 1;
itemList.insert(i, rowCount);
}
return rowCount;
}

/// ----#7
/// Get the widget for each item in list
Widget buildItemWidget(int index) {
/// ----#8
IndexPath indexPath = sectionModel(index);

/// ----#9
/// If the row number is -1 of any indexPath it will represent a section else row
if (indexPath.row < 0) {
/// ----#10
return widget.sectionWidget != null
? widget.sectionWidget(indexPath.section)
: SizedBox(
height: 0,
);
} else {
return widget.rowWidget(indexPath.section, indexPath.row);
}
}

/// Calculate/Map the indexPath for an item Index
IndexPath sectionModel(int index) {
int row = 0;
int section = 0;
for (int i = 0; i < sectionCount; i++) {
int item = itemList[i];
if (index < item) {
row = index - (i > 0 ? itemList[i - 1] : 0) - 1;
section = i;
break;
}
}
return IndexPath(section: section, row: row);
}
}

/// Helper class for indexPath of each item in list
class IndexPath {
IndexPath({this.section, this.row});

int section = 0;
int row = 0;
}
class _HomeScreenState extends State<HomeScreen> {
/// ----#1
List<List<String>> items = [
['Item 1', 'Item 2', 'Item 3'],
['Item 4', 'Item 5'],
['Item 6', 'Item 7', 'Item 8', 'Item 9'],
['Item 10', 'Item 11'],
['Item 12', 'Item 13'],
['Item 14', 'Item 15', 'Item 16'],
['Item 17', 'Item 18']
];

@override
Widget build(BuildContext context) {
/// ----#2
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: FlutterSectionListView(
numberOfSection: () => items.length,
numberOfRowsInSection: (section) {
return items[section].length;
},
sectionWidget: (section) {
return Container(
child: Padding(
padding: const EdgeInsets.all(8),
child: Text('Section $section'),
),
color: Colors.grey,
);
},
rowWidget: (section, row) {
return Container(
child: Padding(
padding: EdgeInsets.all(8),
child: Text(items[section][row]),
),
);
},
));
}
}
  1. We have created a static List data consisting of multiple List to replicate the data having many Sections and Rows. Like [[section-1 Rows], [section-2 Rows], [section-3 Rows]]
  2. Returned the FlutterSectionListView Widget with all its call-back implemented using the items list created in the last step.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store