Flutter : How to add a Header Row to a ListView

78,403

Solution 1

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

Solution 2

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 ...);
    },
);

Solution 3

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),
                        ),
                      )
                    ],
                  );
                },
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Solution 4

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);
      }
    },
  )

Solution 5

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,
            ),
          ),
        ],
      ),
    );
Share:
78,403
Admin
Author by

Admin

Updated on October 04, 2021

Comments

  • Admin
    Admin over 2 years

    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: [
                IconButton(icon: Icon(Icons.add_circle),
                    onPressed: getCustData
                ),
              ],
            ),
            //body:
            body: Column(
                children: [
                  Row(
                      children: [
                        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.

    enter image description here

  • Admin
    Admin about 6 years
    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.
  • najeira
    najeira about 6 years
    Can you make each column a fixed width?
  • Admin
    Admin about 6 years
    I wouldn't like to. It's a mobile app... never know what screen width I'm dealing with.
  • najeira
    najeira about 6 years
    You can get the width of the screen from MediaQuery. How about calculating the width with a percentage?
  • Admin
    Admin about 6 years
    Nice. I'll give that a try and post back.
  • Marco Altran
    Marco Altran about 5 years
    This approach should be avoided as it completely ignores the first or the last element depending on the list size.
  • Abbas.M
    Abbas.M almost 5 years
    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 well
  • F-1
    F-1 over 4 years
    shows first and last elements because it uses data.length + 1 and index -= 1;, if you miss that out it won't work
  • Jonas
    Jonas over 4 years
    You could wrap the whole thing in a ListView with the scroll direction set to horizontal.
  • dorsz
    dorsz over 4 years
    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.
  • M.M.Hasibuzzaman
    M.M.Hasibuzzaman over 3 years
    @Abbas.M wrap the title: Row(...) with ```SingleChildScrollView```` , it will scroll
  • Eradicatore
    Eradicatore about 3 years
    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. :-)
  • 无夜之星辰
    无夜之星辰 almost 3 years
    Good idea. Simple and easy to use.
  • Maksym Letiushov
    Maksym Letiushov over 2 years
    If list items have a fixed height I would suggest using SliverFixedExtentList instead of SliverList. Just specify itemExtent and scrolling will be better.