Flutter : How to add a Header Row to a ListView
Asked Answered
T

12

121

Very new to Flutter. I've been able to utilize HTTP requests for data, build a ListView, edit a Row in that List and other basics. Excellent environment.

I've managed to cobble together a badly constructed Header for a ListView but I know this isn't right. I can't get the Header text to line up properly.

I see that the Drawer Class has a DrawerHeader Class, but can't see that ListView has a ListViewHeader.

@override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('Contacts'),
          actions: <Widget>[
            IconButton(icon: Icon(Icons.add_circle),
                onPressed: getCustData
            ),
          ],
        ),
        //body:
        body: Column(
            children: <Widget>[
              Row(
                  children: <Widget>[
                    Expanded(child: Text('', style: TextStyle(height: 3.0, fontSize: 15.2, fontWeight: FontWeight.bold,))),
                    Expanded(child: Text('First Name', style:  TextStyle(height: 3.0, fontSize: 15.2, fontWeight: FontWeight.bold,))),
                    Expanded(child: Text('Last Name', style:  TextStyle(height: 3.0, fontSize: 15.2, fontWeight: FontWeight.bold,))),
                    Expanded(child: Text('City', style: TextStyle(height: 3.0, fontSize: 15.2, fontWeight: FontWeight.bold,))),
                    Expanded(child: Text('Customer Id', style: TextStyle(height: 3.0, fontSize: 15.2, fontWeight: FontWeight.bold,))),
                    Expanded(child: Text('', style: TextStyle(height: 3.0, fontSize: 15.2, fontWeight: FontWeight.bold,))),
                  ]
              ),

              Expanded(child:Container(
                child: ListView.builder(

                  itemCount: data == null ? 0 : data.length,
                  itemBuilder: (BuildContext context, int index) {

                    return InkWell(
                      onTap: () {
                        Navigator.push(
                          context,
                          MaterialPageRoute(
                              builder: (context) => APIDetailView(data[index])),
                        );
                      },

                      child: ListTile(                //return new ListTile(
                          onTap: null,
                          leading: CircleAvatar(
                            backgroundColor: Colors.blue,
                            child: Text(data[index]["FirstName"][0]),
                          ),
                          title: Row(
                              children: <Widget>[
                                Expanded(child: Text(data[index]["FirstName"])),
                                Expanded(child: Text(data[index]["LastName"])),
                                Expanded(child: Text(data[index]["Bill_City"])),
                                Expanded(child: Text(data[index]["Customer_Id"])),
                              ]
                          )
                      ),

                    );
                  }, //itemBuilder

                ),
              ),
            ),
          ]
        )
    );
  }
}

Thanks.

Output Screenshot

Trinomial answered 23/4, 2018 at 16:59 Comment(2)
Consider using DataTable classRamadan
According to the code provided, your header has 6 child elements (column headers); the first and last of which are empty. The first empty header element is represented by your leading property in the ListTile, but there is no correlating trailing property to match the 6th empty header column. Ergo, the header shows 6 elements, but your list only consumes 5 columns (1 leading and a title w/ 4 children). So adding the trailing will help line them up, but making the header a ListItem with leading, trailing and a title w/ 4 elements makes it perfect; as you did in your answer.Bung
T
45

Here's how I solved this. Thanks @najeira for getting me thinking about other solutions.

In the first body Column I used the same layout for my Header that I used for the ListTile.

Because my data ListTile, in this case, includes a CircleAvatar, all the horizontal spacing is off a bit... 5 columns where the CircleAvatar is rendered... then 4 evenly spaced columns.

So... I added a ListTile to the first body Column, a CircleAvatar with a backgroundColor of transparent, and then a Row of my 4 Headings.

        ListTile(
        onTap: null,
        leading: CircleAvatar(
          backgroundColor: Colors.transparent,
        ),
        title: Row(
            children: <Widget>[
              Expanded(child: Text("First Name")),
              Expanded(child: Text("Last Name")),
              Expanded(child: Text("City")),
              Expanded(child: Text("Id")),
            ]
        ),
      ),

enter image description here

Trinomial answered 25/4, 2018 at 21:13 Comment(4)
Would you by any chance know how to make the whole thing scrollable horrizontally? Was facing your same issue with header except that my list items are WAY bigger, i have like 10 columns in the table and want to make the whole thing scrollable sideways as wellHassett
You could wrap the whole thing in a ListView with the scroll direction set to horizontal.Solvent
@Hassett wrap the title: Row(...) with ```SingleChildScrollView```` , it will scrollMassive
@Hassett sir you can use Flexible widget for scrolling wrap the whole thing in itNarco
R
134

Return the header as first row by itemBuilder:

ListView.builder(
    itemCount: data == null ? 1 : data.length + 1,
    itemBuilder: (BuildContext context, int index) {
        if (index == 0) {
            // return the header
            return new Column(...);
        }
        index -= 1;

        // return row
        var row = data[index];
        return new InkWell(... with row ...);
    },
);
Retail answered 24/4, 2018 at 1:42 Comment(7)
Thanks... I feel this is much cleaner than what I had. I'm still struggling to get the Header titles to line up with the ListView columns.Trinomial
Can you make each column a fixed width?Retail
I wouldn't like to. It's a mobile app... never know what screen width I'm dealing with.Trinomial
You can get the width of the screen from MediaQuery. How about calculating the width with a percentage?Retail
Nice. I'll give that a try and post back.Trinomial
This approach should be avoided as it completely ignores the first or the last element depending on the list size.Transude
shows first and last elements because it uses data.length + 1 and index -= 1;, if you miss that out it won't workCivilize
T
45

Here's how I solved this. Thanks @najeira for getting me thinking about other solutions.

In the first body Column I used the same layout for my Header that I used for the ListTile.

Because my data ListTile, in this case, includes a CircleAvatar, all the horizontal spacing is off a bit... 5 columns where the CircleAvatar is rendered... then 4 evenly spaced columns.

So... I added a ListTile to the first body Column, a CircleAvatar with a backgroundColor of transparent, and then a Row of my 4 Headings.

        ListTile(
        onTap: null,
        leading: CircleAvatar(
          backgroundColor: Colors.transparent,
        ),
        title: Row(
            children: <Widget>[
              Expanded(child: Text("First Name")),
              Expanded(child: Text("Last Name")),
              Expanded(child: Text("City")),
              Expanded(child: Text("Id")),
            ]
        ),
      ),

enter image description here

Trinomial answered 25/4, 2018 at 21:13 Comment(4)
Would you by any chance know how to make the whole thing scrollable horrizontally? Was facing your same issue with header except that my list items are WAY bigger, i have like 10 columns in the table and want to make the whole thing scrollable sideways as wellHassett
You could wrap the whole thing in a ListView with the scroll direction set to horizontal.Solvent
@Hassett wrap the title: Row(...) with ```SingleChildScrollView```` , it will scrollMassive
@Hassett sir you can use Flexible widget for scrolling wrap the whole thing in itNarco
L
25

You can add Container and ListView in Column.

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  void initState() {
    // TODO: implement initState
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        appBar: AppBar(
          title: Text("Demo App1"),
        ),
        body: Column(
          children: <Widget>[
            Container(
              height: 40.0,
              child: Row(
                children: <Widget>[
                  Container(
                      padding: EdgeInsets.all(4.0),
                      width: 100.0,
                      child: Text(
                        "Name",
                        style: TextStyle(fontSize: 18),
                      )),
                  Container(
                      padding: EdgeInsets.all(4.0),
                      width: 100.0,
                      child: Text(
                        "Age",
                        style: TextStyle(fontSize: 18),
                      )),
                ],
              ),
            ),
            Expanded(
              child: ListView.builder(
                itemCount: 100,
                itemBuilder: (BuildContext context, int index) {
                  return Row(
                    children: <Widget>[
                      Container(
                          padding: EdgeInsets.all(4.0),
                          width: 100.0,
                          child: Text(
                            "Name $index",
                            style: TextStyle(fontSize: 18),
                          )),
                      Container(
                        padding: EdgeInsets.all(4.0),
                        width: 100.0,
                        child: Text(
                          "Age $index",
                          style: TextStyle(fontSize: 18),
                        ),
                      )
                    ],
                  );
                },
              ),
            ),
          ],
        ),
      ),
    );
  }
}
Langan answered 9/10, 2019 at 9:38 Comment(1)
I got an issue with "A RenderFlex overflowed by 69 pixels on the bottom" when using ListView and this one helped me. The trick here is to wrap ListView in Expanded widget. Didn't work when I used SingleChildScrollView, because I wanted non-scrollable header and scrollable list.Glossography
A
22

You can add a column to the first item in the item list like this

  new ListView.builder(
    itemCount: litems.length,
    itemBuilder: (BuildContext ctxt, int index) {
      if (index == 0) {
        return Column(
          children: <Widget>[
            Header(),
            rowContent(index),
          ],
        );
      } else {
        return rowContent(index);
      }
    },
  )
Arjan answered 9/10, 2019 at 4:42 Comment(3)
Yea, this is very simple and straight forward. I will say though, now this has me thinking of trying to make it a sticky header instead. :-)Burgett
Good idea. Simple and easy to use.Pledget
you can not display items [0] index itemShaun
F
12

najeira's solution is easy and simple, but you can get the same and more flexible result without touching index.

Instead of using listView, you could use CustomScrollView & SliverList which is functionally the same as listView.

   return Scaffold(
      body: CustomScrollView(
        slivers: <Widget>[
          SliverToBoxAdapter(
            // you could add any widget
            child: ListTile(
              leading: CircleAvatar(
                backgroundColor: Colors.transparent,
              ),
              title: Row(
                children: <Widget>[
                  Expanded(child: Text("First Name")),
                  Expanded(child: Text("Last Name")),
                  Expanded(child: Text("City")),
                  Expanded(child: Text("Id")),
                ],
              ),
            ),
          ),
          SliverList(
            delegate: SliverChildBuilderDelegate(
              (context, index) {
                return InkWell(
                  onTap: () {
                    Navigator.push(
                      context,
                      MaterialPageRoute(
                          builder: (context) => APIDetailView(data[index])),
                    );
                  },
                  child: ListTile(
                    //return  ListTile(
                    leading: CircleAvatar(
                      backgroundColor: Colors.blue,
                      child: Text(data[index]["FirstName"][0]),
                    ),
                    title: Row(
                      children: <Widget>[
                        Expanded(child: Text(data[index]["FirstName"])),
                        Expanded(child: Text(data[index]["LastName"])),
                        Expanded(child: Text(data[index]["Bill_City"])),
                        Expanded(child: Text(data[index]["Customer_Id"])),
                      ],
                    ),
                  ),
                );
              },
              childCount: data == null ? 0 : data.length,
            ),
          ),
        ],
      ),
    );
Florist answered 7/4, 2020 at 6:23 Comment(1)
If list items have a fixed height I would suggest using SliverFixedExtentList instead of SliverList. Just specify itemExtent and scrolling will be better.Barcot
T
11

Use DataTable widget !
That widget allows you to build a table. Code : DataTable(columns: [], rows: [],)

Example :

  DataTable(
   columns: [
     DataColumn(label: Text('Lang.')),
     DataColumn(label: Text('Year')),
   ],
   rows: [
     DataRow(cells: [DataCell(Text('Dart')), DataCell(Text('2010'))]),
     DataRow(cells: [DataCell(Text('Go')), DataCell(Text('2009'))]),
     DataRow(cells: [DataCell(Text('PHP')), DataCell(Text('1994'))]),
     DataRow(cells: [DataCell(Text('Java')), DataCell(Text('1995'))]),
   ],
)

Output:

enter image description here

You can learn more about DataTable by watching this official video or by visiting flutter.dev

Trichinopoly answered 5/2, 2021 at 17:3 Comment(1)
This solution will not iterate over data. The DataTable header can't be separated from the rows.Vipul
D
7

It seems what you are really looking for is the DataTable widget instead of a ListView. It has a customizable Header including sorting options.

Read the documentation including some great examples on api.flutter.dev: Data Table CLass enter image description here

Dolly answered 8/12, 2020 at 17:12 Comment(0)
C
5

I have created listview_utils package to reduce boilerplate code needed to build header and footer list items. Here's an example code using the package:

import 'package:listview_utils/listview_utils.dart';

CustomListView(
  header: Container(
    child: Text('Header'),
  ),
  itemCount: items.length,
  itemBuilder: (BuildContext context, int index, _) {
    return ListTile(
      title: Text(item['title']),
    );
  },
);

Disclaimer: I am maintainer of the package.

Centroid answered 2/3, 2020 at 18:13 Comment(0)
S
3

I use this:

body: Column(
      children: [
        Container(
          // The header will be here
        ),
        Expanded(
          // The ListView
          child: ListView.builder(
              itemCount: // The length,
              itemBuilder: (_, index) {
                return //List Item Widget Here
              }),
        ),
      ],

)

Spirula answered 15/9, 2021 at 8:37 Comment(1)
Don't understand who put '-1', but this is working by fact and also exactly what I was looking for. Thank you!Repro
E
3

Looking for dynamic section headers according to your api data. Add this class to your project.

class _FlutterSectionListViewState extends State<FlutterSectionListView> {
  /// List of total number of rows and section in each group
  var itemList = [];
  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 = [];
    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) as int?;
        section = i;
        break;
      }
    }
    return IndexPath(section: section, row: row!);
  }
}

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

  int section = 0;
  int row = 0;
}

create your list according to your api data

 List<List<Operator>> ops = [];
        List<String> sections = [];
        if(c.operatorStatuses.value!.availableOperators.length>0){
           ops.add(c.operatorStatuses.value!.availableOperators);
           sections.add("Müsait Operatörler");
        }

         if(c.operatorStatuses.value!.busyOperators.length>0){
           ops.add(c.operatorStatuses.value!.busyOperators);
           sections.add("Meşgul Operatörler");
        }
         if(c.operatorStatuses.value!.breakOperators.length>0){
             ops.add(c.operatorStatuses.value!.breakOperators);
           sections.add("Moladaki Operatörler");
        }
         if(c.operatorStatuses.value!.closedOperators.length>0){
             ops.add(c.operatorStatuses.value!.closedOperators);
           sections.add("Kapalı Operatörler");
        }
       

show it in ui;

   FlutterSectionListView(
          numberOfSection: () => ops.length,
          numberOfRowsInSection: (section) {
            return ops[section].length;
          },
          sectionWidget: (section) {
            if(section<ops.length){
               return Container(
              child: Padding(
                padding: const EdgeInsets.all(8),
                child: Text(sections[section]),
              ),
              color: Colors.grey,
            );
            }else{
              return SizedBox();
            }
           
          },
          rowWidget: (section, row) {

            if(row < ops[section].length){
Operator? op = ops[section][row];
          
          
            return card(op);
            }else{
              return SizedBox();
            }
           
             
          },
        )

thanks to [this article][1].

NOTE : code block produces error some time according to updated data.. [1]: https://medium.com/@dharmendra_yadav/ios-like-sectioned-listview-widget-in-flutter-7cf9dab2dd1a

Ensheathe answered 9/5, 2022 at 7:47 Comment(0)
L
0

Here I've created flat_list widget which has similar specifications as in React Native's FlatList.

FlatList(
+  listHeaderWidget: const Header(),
  data: items.value,
  buildItem: (item, index) {
    var person = items.value[index];

    return ListItemView(person: person);
  },
),
Loggins answered 12/11, 2022 at 15:0 Comment(0)
T
0

One option is to use bottom property of AppBar. For example the below code would show two headers above the two far end columns.

bottom: PreferredSize(
        preferredSize: const Size.fromHeight(16),
        child: Row(
          children: const [
            Spacer(),
            Text('Column1', style: TextStyle(fontSize: 8)),
            SizedBox(width: 24),
            Text('Column2', style: TextStyle(fontSize: 8)),
            SizedBox(width: 24),
          ],
        )),
Treulich answered 17/11, 2023 at 20:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.