82
votes

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

9
Consider using DataTable classAlex Semeniuk
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.Keith DC

9 Answers

98
votes

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

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

17
votes

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

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

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

You could use listview_utils package to append header and footer to ListView easily, like that:

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

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

4
votes

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

3
votes

In this code example, we are doing how to add section header row in the listview.

class HeaderRowListView extends StatelessWidget {
  final List<int> _listData = List<int>.generate(100, (i) => i);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('ListView Header Row'),
      ),
      body: ListView(
        padding: EdgeInsets.all(8.0),
        children: _listData.map((i) {
          return i % 10 == 0
              ? Container(
            color: Colors.grey.withOpacity(.5),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              mainAxisSize: MainAxisSize.max,
              children: <Widget>[
                Text("Header",
                    style: TextStyle(
                      fontSize: 20.0,
                      fontWeight: FontWeight.bold,
                    )),
                Text("Feb 26th, 2019",
                    style: TextStyle(
                      fontSize: 14.0,
                      color: Colors.black45,
                    )),
              ],
            ),
            padding: EdgeInsets.all(10.0),
          )
              : ListTile(
            title: Text("Item $i"),
          );
        }).toList(),
      ),
    );
  }
}

enter image description here