116

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

Daniel
  • 8,655
  • 5
  • 60
  • 87
davidk
  • 1,641
  • 2
  • 11
  • 10
  • 4
    Consider using [DataTable](https://api.flutter.dev/flutter/material/DataTable-class.html) class – Alex Semeniuk May 08 '20 at 09:57
  • 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 Jan 20 '21 at 21:06

11 Answers11

130

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 ...);
    },
);
najeira
  • 3,133
  • 2
  • 19
  • 21
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

davidk
  • 1,641
  • 2
  • 11
  • 10
  • 1
    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 – Abbas.M Jul 06 '19 at 06:51
  • You could wrap the whole thing in a ListView with the scroll direction set to horizontal. – Jonas Dec 06 '19 at 18:00
  • @Abbas.M wrap the title: Row(...) with ```SingleChildScrollView```` , it will scroll – M.M.Hasibuzzaman Nov 17 '20 at 10:15
  • @Abbas.M sir you can use Flexible widget for scrolling wrap the whole thing in it – Shiv Shrivas Mar 29 '23 at 13:04
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),
                        ),
                      )
                    ],
                  );
                },
              ),
            ),
          ],
        ),
      ),
    );
  }
}
Mizuki
  • 2,153
  • 1
  • 17
  • 29
Sonu Saini
  • 1,814
  • 9
  • 16
  • 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. – dorsz Jan 03 '20 at 13:39
21

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);
      }
    },
  )
Mizuki
  • 2,153
  • 1
  • 17
  • 29
Nikhil Biju
  • 645
  • 7
  • 8
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,
            ),
          ),
        ],
      ),
    );
user12208004
  • 1,704
  • 3
  • 15
  • 37
  • If list items have a fixed height I would suggest using `SliverFixedExtentList` instead of `SliverList`. Just specify `itemExtent` and scrolling will be better. – Maksym Letiushov Jan 31 '22 at 15:38
10

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

Kab Agouda
  • 6,309
  • 38
  • 32
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

derZorn
  • 186
  • 2
  • 5
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.

TheMisir
  • 4,083
  • 1
  • 27
  • 37
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
              }),
        ),
      ],

)

Shadros
  • 728
  • 2
  • 10
  • 19
  • 2
    Don't understand who put '-1', but this is working by fact and also exactly what I was looking for. Thank you! – rgolovakha Feb 13 '23 at 08:04
2

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

Bilal Şimşek
  • 5,453
  • 2
  • 19
  • 33
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);
  },
),
Hyo
  • 1
  • 2
  • 13