Creating a Horizontal Scrollable Table Layout in Flutter

A horizontal scrollable table layout is an effective way to display tabular data that exceeds the screen’s width. The following article provides a detailed explanation of how to build such a layout using Flutter, with an emphasis on the key properties and their roles in achieving the desired functionality.

Code Overview

The provided code demonstrates the creation of a table with:

  • Rows and columns for structured data presentation.
  • A horizontal scrolling mechanism to navigate content.
  • Custom styling applied to headers, cells, and borders.

Each section below explains the components and properties used in the implementation.

Widget Structure

ScrollableTable

ScrollableTable is a StatefulWidget responsible for building the user interface of the table. The use of StatefulWidget facilitates state management, though the example does not include dynamic state updates.

Scaffold and SafeArea

return Scaffold(
  body: SafeArea(
    child: Container(
      alignment: Alignment.center,
      padding: const EdgeInsets.symmetric(horizontal: 12),
      child: SingleChildScrollView(
        scrollDirection: Axis.horizontal,
        child: ...
      ),
    ),
  ),
);
  • Scaffold provides the basic layout structure, supporting components like navigation bars and the main content area.

  • SafeArea ensures that the content avoids overlapping system UI elements such as notches and status bars.
  • Container centers the table layout and applies padding for aesthetic spacing.

Enabling Horizontal Scrolling

SingleChildScrollView

SingleChildScrollView(
  scrollDirection: Axis.horizontal,
  child: ...
)

The SingleChildScrollView widget wraps the table and enables horizontal scrolling. The scrollDirection property is set to Axis.horizontal, specifying the horizontal scroll direction.

Table Styling

Outer Container

Container(
  decoration: BoxDecoration(
    borderRadius: BorderRadius.circular(12),
    border: Border.all(
      color: Colors.grey.withOpacity(0.5),
      width: 0.5,
    ),
  ),
  child: Table(...)
)

The outer container adds visual styling around the table:

  • BoxDecoration configures rounded corners and borders.
    • BorderRadius.circular(12) defines a uniform curve for all corners.
    • Border.all(…) applies a light grey border with a thin width for a subtle effect.

Configuring the Table Layout

Table Widget

Table(
  border: TableBorder(
    verticalInside: BorderSide(...),
    horizontalInside: BorderSide(...),
  ),
  defaultColumnWidth: FixedColumnWidth(80.0),
  children: [...],
)

The Table widget arranges content in a grid-like layout. Key properties include:

  • TableBorder adds dividers between rows and columns for better readability.
    • verticalInside specifies the appearance of vertical dividers.
    • horizontalInside specifies the appearance of horizontal dividers.
  • defaultColumnWidth sets a fixed width of 80.0 for all columns.

Adding Table Content

Header Row

TableRow(
  children: [
    TableCell(
      child: Container(
        height: 50,
        decoration: const BoxDecoration(
          color: Color(0xff28282B),
          borderRadius: BorderRadius.only(
            topLeft: Radius.circular(12),
          ),
        ),
        padding: const EdgeInsets.all(8.0),
        alignment: Alignment.center,
        child: const Text(
          'Score',
          style: TextStyle(...),
        ),
      ),
    ),
    for (var month in [...]) 
      TableCell(...),
  ],
),

The header row consists of multiple TableCell widgets. Key points include:

  • First Cell: The “Score” column features a dark background (Color(0xff28282B)) and rounded top-left corners.
  • Dynamic Content: The header dynamically generates cells for each month using a for loop. The last column, corresponding to December, has rounded top-right corners applied conditionally.

Data Rows

TableRow(
  children: [
    TableCell(
      child: Container(
        alignment: Alignment.center,
        padding: const EdgeInsets.symmetric(horizontal: 5),
        child: const Text(
          'A',
          style: TextStyle(...),
        ),
      ),
    ),
    for (int i = 0; i < 12; i++)
      TableCell(
        child: Container(
          alignment: Alignment.center,
          child: Text('$i', style: const TextStyle(...)),
        ),
      ),
  ],
),

Each data row displays:

  • Row Identifiers: The first column of each row identifies the row with labels such as “A”, “B”, “C”, and “D”.
  • Dynamic Data Cells: A for loop generates 12 cells for each month, displaying numerical values.

All cells are vertically and horizontally centered, with consistent styling for text alignment and appearance.

Customizing Borders

The TableBorder property defines thin dividers between cells:

TableBorder(
  verticalInside: BorderSide(
    color: const Color(0xff48A14D).withOpacity(0.2),
    width: 0.5,
  ),
  horizontalInside: BorderSide(
    color: const Color(0xff48A14D).withOpacity(0.3),
    width: 0.5,
  ),
),

Subtle green hues with transparency ensure that borders enhance readability without being overly distracting.

Header Styling Logic

Rounded corners are applied to the first and last header cells using conditional logic:

borderRadius: month.startsWith('Dec')
  ? const BorderRadius.only(
      topRight: Radius.circular(12),
    )
  : BorderRadius.zero,

This ensures that only the last header cell has a rounded top-right corner, while other cells remain square.

Resulting Layout

The completed implementation creates a visually appealing, horizontally scrollable table layout with:

  • A fixed header row styled with bold text and rounded corners.
  • Rows containing dynamically generated data cells.
  • Borders between rows and columns for clear separation of content.

This layout can be extended further by adding more rows, integrating real-time data, or modifying styling to match application themes. The flexibility of Flutter’s Table widget and related components makes it ideal for such use cases.

Full Code:

import 'package:flutter/material.dart';

class ScrollableTable extends StatefulWidget {
  const ScrollableTable({super.key});

  @override
  State<ScrollableTable> createState() => _ScrollableTableState();
}

class _ScrollableTableState extends State<ScrollableTable> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Container(
          alignment: Alignment.center,
          padding: const EdgeInsets.symmetric(horizontal: 12),
          child: SingleChildScrollView(
            scrollDirection: Axis.horizontal,
            child: Container(
              decoration: BoxDecoration(
                  borderRadius: BorderRadius.circular(12),
                  border: Border.all(
                      color: Colors.grey.withOpacity(0.5), width: 0.5)),
              child: Table(
                border: TableBorder(
                    verticalInside: BorderSide(
                        color: const Color(0xff48A14D).withOpacity(0.2),
                        width: 0.5),
                    horizontalInside: BorderSide(
                        color: const Color(0xff48A14D).withOpacity(0.3),
                        width: 0.5)),
                defaultColumnWidth: const FixedColumnWidth(80.0),
                children: [
                  TableRow(
                    children: [
                      TableCell(
                          child: Container(
                        height: 50,
                        decoration: const BoxDecoration(
                            color: Color(0xff28282B),
                            borderRadius: BorderRadius.only(
                                topLeft: Radius.circular(12))),
                        padding: const EdgeInsets.all(8.0),
                        alignment: Alignment.center,
                        child: const Text(
                          'Score',
                          textAlign: TextAlign.center,
                          style: TextStyle(
                              color: Colors.white, fontWeight: FontWeight.bold),
                        ),
                      )),
                      for (var month in [
                        'Jan',
                        'Feb',
                        'Mar',
                        'Apr',
                        'May',
                        'Jun',
                        'Jul',
                        'Aug',
                        'Sep',
                        'Oct',
                        'Nov',
                        'Dec'
                      ])
                        TableCell(
                          child: Container(
                            height: 50,
                            alignment: Alignment.center,
                            decoration: BoxDecoration(
                              color: const Color(0xff28282B),
                              borderRadius: month.startsWith('Dec')
                                  ? const BorderRadius.only(
                                      topRight: Radius.circular(12))
                                  : BorderRadius.zero,
                            ),
                            child: Text(
                              month,
                              textAlign: TextAlign.center,
                              style: const TextStyle(
                                  color: Colors.white,
                                  fontWeight: FontWeight.bold),
                            ),
                          ),
                        ),
                    ],
                  ),
                  TableRow(
                    children: [
                      TableCell(
                        child: Container(
                          height: 50,
                          alignment: Alignment.center,
                          padding: const EdgeInsets.symmetric(horizontal: 5),
                          child: const Text(
                            'A',
                            textAlign: TextAlign.start,
                            style: TextStyle(fontWeight: FontWeight.bold),
                          ),
                        ),
                      ),
                      for (int i = 0; i < 12; i++)
                        TableCell(
                          child: Container(
                            alignment: Alignment.center,
                            height: 50,
                            child: Text(
                              '$i',
                              textAlign: TextAlign.center,
                              style:
                                  const TextStyle(fontWeight: FontWeight.bold),
                            ),
                          ),
                        ),
                    ],
                  ),
                  TableRow(
                    children: [
                      TableCell(
                        child: Container(
                          alignment: Alignment.center,
                          height: 50,
                          padding: const EdgeInsets.symmetric(horizontal: 5),
                          child: const Text(
                            'B',
                            textAlign: TextAlign.start,
                            style: TextStyle(fontWeight: FontWeight.bold),
                          ),
                        ),
                      ),
                      for (int i = 0; i < 12; i++)
                        TableCell(
                          child: Container(
                            height: 50,
                            alignment: Alignment.center,
                            child: Text(
                              '$i',
                              textAlign: TextAlign.center,
                              style:
                                  const TextStyle(fontWeight: FontWeight.bold),
                            ),
                          ),
                        ),
                    ],
                  ),
                  TableRow(
                    children: [
                      TableCell(
                        child: Container(
                          height: 50,
                          alignment: Alignment.center,
                          padding: const EdgeInsets.symmetric(horizontal: 5),
                          child: const Text(
                            'C',
                            textAlign: TextAlign.start,
                            style: TextStyle(fontWeight: FontWeight.bold),
                          ),
                        ),
                      ),
                      for (int i = 0; i < 12; i++)
                        TableCell(
                          child: Container(
                            height: 50,
                            alignment: Alignment.center,
                            child: Text(
                              '$i',
                              textAlign: TextAlign.center,
                              style:
                                  const TextStyle(fontWeight: FontWeight.bold),
                            ), // Placeholder for data cells
                          ),
                        ),
                    ],
                  ),
                  TableRow(
                    children: [
                      TableCell(
                        child: Container(
                          height: 50,
                          alignment: Alignment.center,
                          padding: const EdgeInsets.symmetric(horizontal: 5),
                          child: const Text(
                            'D',
                            textAlign: TextAlign.start,
                            style: TextStyle(fontWeight: FontWeight.bold),
                          ),
                        ),
                      ),
                      for (int i = 0; i < 12; i++)
                        TableCell(
                          child: Container(
                            height: 50,
                            alignment: Alignment.center,
                            child: Text(
                              '$i',
                              textAlign: TextAlign.center,
                              style:
                                  const TextStyle(fontWeight: FontWeight.bold),
                            ), // Placeholder for data cells
                          ),
                        ),
                    ],
                  ),
                ],
              ),
            ),
          ),
        ),
      ),
    );
  }
}
Dart

Output:

Conclusion:

This implementation highlights the process of creating a horizontally scrollable table layout in Flutter with a blend of dynamic content and custom styling. The approach ensures both functionality and a visually appealing design, making it well-suited for structured data presentation.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top