Flutter Scrollbar in vertical and horizontal axis with multiple tables in screen

99 Views Asked by At

I am a junior developer and I'm trying to learn flutter while doing a project, I'm using the Tables and sometime encountering problems, a problem i had is to show always the scrollbar also if not at the end of the table but for that i found a solution fortunately, but now I'm using a double table model to make the first column stay fixed when i scroll horizontally but the problem I'm encountering is that with this solution I can't make the horizontal scrollbar be always visible also if in the middle of the table.

Here the body of the screen I'm creating with this problem:

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

class Body extends StatefulWidget {
  final int getColumnWidth;

  Body({super.key, required this.getColumnWidth});

  @override
  State<Body> createState() => _BodyState();
}

class _BodyState extends State<Body> {
  Timer? _incrementTimer;
  Timer? _decrementTimer;
  final List<String> rowTexts = [
    "Manomissione",
    "Batteria Scarica",
    "Assenza rete",
    "Rapina",
    "Coercizione",
    "Avaria generale (fusibili, batteria sensori radio)",
    "Ritorno rete",
    "Anomalia GSM"
  ];
  final List<String> columnTexts = [
    "Evento fonia",
    "Evento SMS",
    "Attiva sirene",
    "Attiva bip",
    "Ritardo segnalazione"
  ];

  final Map<String, Map<String, bool>> checkboxControllers = {
    "1-1": {"value": true, "visible": true},
    "1-2": {"value": true, "visible": true},
    "1-3": {"value": true, "visible": true},
    "1-4": {"value": false, "visible": false},
    "2-1": {"value": true, "visible": true},
    "2-2": {"value": true, "visible": true},
    "2-3": {"value": false, "visible": false},
    "2-4": {"value": true, "visible": true},
    "3-1": {"value": true, "visible": true},
    "3-2": {"value": true, "visible": true},
    "3-3": {"value": false, "visible": false},
    "3-4": {"value": true, "visible": true},
    "4-1": {"value": true, "visible": true},
    "4-2": {"value": true, "visible": true},
    "4-3": {"value": false, "visible": false},
    "4-4": {"value": false, "visible": false},
    "5-1": {"value": true, "visible": true},
    "5-2": {"value": true, "visible": true},
    "5-3": {"value": false, "visible": false},
    "5-4": {"value": false, "visible": false},
    "6-1": {"value": true, "visible": true},
    "6-2": {"value": true, "visible": true},
    "6-3": {"value": false, "visible": false},
    "6-4": {"value": false, "visible": false},
    "7-1": {"value": true, "visible": true},
    "7-2": {"value": true, "visible": true},
    "7-3": {"value": false, "visible": false},
    "7-4": {"value": false, "visible": false},
    "8-1": {"value": true, "visible": true},
    "8-2": {"value": false, "visible": false},
    "8-3": {"value": false, "visible": false},
    "8-4": {"value": true, "visible": true},
    // Add more entries as needed
  };

  @override
  void dispose() {
    _incrementTimer?.cancel();
    _decrementTimer?.cancel();
    super.dispose();
  }

  void _startIncrementTimer(TextEditingController textController, int tipo) {
    // Start a timer that increments the value every 100 milliseconds
    _incrementTimer = Timer.periodic(Duration(milliseconds: 100), (_) {
      int currentValue = int.parse(textController.text);
      setState(() {
        switch (tipo) {
          case 1:
            currentValue++;
            textController.text =
                (currentValue < 255 ? currentValue : 255).toString();
            break;
        }
      });
    });
  }

  void _startDecrementTimer(TextEditingController textController, int tipo) {
    // Start a timer that decrements the value every 100 milliseconds
    _decrementTimer = Timer.periodic(Duration(milliseconds: 100), (_) {
      int currentValue = int.parse(textController.text);
      setState(() {
        switch (tipo) {
          case 1:
            currentValue--;
            textController.text =
                (currentValue > 0 ? currentValue : 0).toString();
            break;
        }
      });
    });
  }

  void _stopTimer() {
    // Cancel both increment and decrement timers
    _incrementTimer?.cancel();
    _decrementTimer?.cancel();
  }

  final Map<String, TextEditingController> ritardiControllers = {
    "3-5": TextEditingController(text: "20"),
    "4-5": TextEditingController(text: "20"),
    "5-5": TextEditingController(text: "20"),
    "7-5": TextEditingController(text: "20"),
    "8-5": TextEditingController(text: "10"),
    // Aggiungi altri label text e controller di testo
  };

  ScrollController _vertical = new ScrollController(),
      _horizontal = new ScrollController();

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scrollbar(
      controller: _vertical,
      thumbVisibility: true,
      trackVisibility: true,
      child: SingleChildScrollView(
        controller: _vertical,
        padding: EdgeInsets.all(1.0),
        child: SizedBox(
          width: MediaQuery.of(context).size.width -
                      (48 + 17 + widget.getColumnWidth) <
                  750
              ? 750
              : MediaQuery.of(context).size.width -
                  (48 + 17 + widget.getColumnWidth),
          child: Row(
            children: [
              Table(
                columnWidths: {
                  0: FixedColumnWidth(
                      320), // Adjust the width for the first column
                },
                border: TableBorder.all(),
                children: List.generate(
                  9,
                  (rowIndex) => TableRow(
                    children: List.generate(
                      1,
                      (colIndex) => TableCell(
                        child: SizedBox(
                          height: 53,
                          child: Container(
                            color: Colors.grey.shade600,
                            child: Align(
                              alignment: Alignment.centerLeft,
                              child: buildCellElement(
                                rowIndex,
                                colIndex,
                              ),
                            ),
                          ),
                        ),
                      ),
                    ),
                  ),
                ),
              ),
              Expanded(
                child: Scrollbar(
                  controller: _horizontal,
                  thumbVisibility: true,
                  child: SingleChildScrollView(
                    padding: EdgeInsets.symmetric(
                      vertical: 4,
                    ),
                    controller: _horizontal,
                    scrollDirection: Axis.horizontal,
                    child: Table(
                      columnWidths: {
                        0: IntrinsicColumnWidth(),
                        1: IntrinsicColumnWidth(),
                        2: IntrinsicColumnWidth(),
                        3: IntrinsicColumnWidth(),
                        4: IntrinsicColumnWidth(),
                        5: IntrinsicColumnWidth(),
                      },
                      border: TableBorder.all(),
                      children: List.generate(
                        
                        9,
                        (rowIndex) => TableRow(
                          children: List.generate(
                            5,
                            (colIndex) => TableCell(
                              child: SizedBox(
                                height: 53,
                                child: Container(
                                  color: rowIndex == 0
                                      ? Colors.grey.shade600
                                      : null,
                                  width:
                                      colIndex == 4 && rowIndex != 0 ? 150 : 80,
                                  child: buildCellElement(
                                    rowIndex,
                                    colIndex + 1,
                                  ),
                                ),
                              ),
                            ),
                          ),
                        ),
                      ),
                    ),
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }

  Widget buildCellElement(int rowIndex, int colIndex) {
    final cellLabel = "$rowIndex-$colIndex";

    if (colIndex != 0 && rowIndex != 0 && colIndex != 5) {
      final checkboxData = checkboxControllers[cellLabel];
      if (checkboxData != null) {
        return Visibility(
          visible: checkboxData["visible"] ?? false,
          child: Align(
            alignment: Alignment.center,
            child: Transform.scale(
              scale: 0.8,
              child: Checkbox(
                value: checkboxData["value"] ?? false,
                onChanged: (bool? value) {
                  setState(() {
                    checkboxData["value"] = value ?? false;
                  });
                  // Handle checkbox state changes
                  // You can use checkboxData["value"] and checkboxData["visible"] here
                  // to update the state as needed.
                },
              ),
            ),
          ),
        );
      } else {
        return Container();
      }
    } else {
      if (colIndex == 0 && rowIndex == 0) {
        return Container();
      } else if (colIndex == 0) {
        return Text(
          rowTexts[rowIndex - 1],
          style: TextStyle(fontSize: 12),
        );
      } else if (rowIndex == 0) {
        return Center(
            child: Text(
          columnTexts[colIndex - 1],
          style: TextStyle(fontSize: 12),
        ));
      } else if (colIndex == 5) {
        final ritardoData = ritardiControllers[cellLabel];
        if (ritardoData != null) {
          return Row(
            children: [
              Text(
                rowIndex + colIndex == 8 || rowIndex + colIndex == 13
                    ? "minuti"
                    : "secondi",
                style: TextStyle(fontSize: 12),
              ),
              Spacer(),
              Container(
                decoration: BoxDecoration(
                  border: Border.all(
                      color: Colors.black), // Set the color of the border
                  borderRadius: BorderRadius.circular(8),
                ),
                height: 28,
                width: 52,
                child: TextFormField(
                  style: TextStyle(fontSize: 12),
                  maxLength: 3,
                  textAlign: TextAlign.right,
                  decoration: InputDecoration(
                    counterText: "",
                    contentPadding: EdgeInsets.all(8.0),
                    border: OutlineInputBorder(
                      borderRadius: BorderRadius.circular(5.0),
                    ),
                  ),
                  controller: ritardoData,
                  keyboardType: TextInputType.numberWithOptions(
                    decimal: false,
                    signed: true,
                  ),
                  inputFormatters: <TextInputFormatter>[
                    FilteringTextInputFormatter.digitsOnly,
                  ],
                  onEditingComplete: () {
                    if (int.parse(ritardoData.text) > 255) {
                      setState(() {
                        ritardoData.text = "255";
                      });
                    } else if (int.parse(ritardoData.text) < 0) {
                      setState(() {
                        ritardoData.text = "0";
                      });
                    }
                  },
                ),
              ),
              numberArrowsContainer(ritardoData, 1),
            ],
          );
        } else {
          return Container();
        }
      }
      return Text(
        cellLabel,
        style: TextStyle(fontSize: 12),
      );
    }
  }

  Column numberArrowsContainer(TextEditingController textController, int tipo) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.center,
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        GestureDetector(
          onTapDown: (_) {
            // When the button is pressed, start incrementing
            _startIncrementTimer(textController, tipo);
          },
          onTapUp: (_) {
            // When the button is released, stop incrementing
            _stopTimer();
          },
          onTap: () {
            // When the button is tapped (not held), increment once
            int currentValue = int.parse(textController.text);
            setState(() {
              switch (tipo) {
                case 1:
                  currentValue++;
                  textController.text =
                      (currentValue < 255 ? currentValue : 255).toString();
                  break;
              }
            });
          },
          child: Container(
            decoration: BoxDecoration(
              border: Border(
                bottom: BorderSide(
                  width: 0.5,
                ),
              ),
            ),
            child: Icon(
              Icons.arrow_drop_up,
              size: 18.0,
            ),
          ),
        ),
        GestureDetector(
          onTapDown: (_) {
            // When the button is pressed, start decrementing
            _startDecrementTimer(textController, tipo);
          },
          onTapUp: (_) {
            // When the button is released, stop decrementing
            _stopTimer();
          },
          onTap: () {
            // When the button is tapped (not held), decrement once
            int currentValue = int.parse(textController.text);
            setState(() {
              switch (tipo) {
                case 1:
                  currentValue--;
                  textController.text =
                      (currentValue > 0 ? currentValue : 0).toString();
                  break;
              }
            });
          },
          child: InkWell(
            child: Icon(
              Icons.arrow_drop_down,
              size: 18.0,
            ),
          ),
        ),
      ],
    );
  }
}

Here's the solution I found for the first case, so only table in the screen:

import 'dart:async';
import 'dart:math';

import 'package:flutter/material.dart';

class Body extends StatefulWidget {
  final int getColumnWidth;

  Body({super.key, required this.getColumnWidth});

  @override
  State<Body> createState() => _BodyState();
}

class _BodyState extends State<Body> {
  Timer? _incrementTimer;
  Timer? _decrementTimer;

  final List<String> columnTexts = [
    "n zona",
    "Collocazione",
    "Zona software",
    "Descrizione zona",
    "Allarme",
    "Ripristino",
    "Manomissione",
    "Ripristino"
  ];

  final Map<String, bool> checkboxControllers = {
    "1-4": false,
    "1-5": false,
    "1-6": false,
    "1-7": false,
    "2-5": false,
    "2-6": false,
    "2-7": false,
    "2-4": false,
    "3-5": false,
    "3-6": false,
    "3-7": false,
    "3-4": false,
    "4-5": false,
    "4-6": false,
    "4-7": false,
    "4-4": false,
    "5-5": false,
    "5-6": false,
    "5-7": false,
    "5-4": false,
  };

  @override
  void dispose() {
    _incrementTimer?.cancel();
    _decrementTimer?.cancel();
    super.dispose();
  }

  void _startIncrementTimer(TextEditingController textController, int tipo) {
    // Start a timer that increments the value every 100 milliseconds
    _incrementTimer = Timer.periodic(Duration(milliseconds: 100), (_) {
      int currentValue = int.parse(textController.text);
      setState(() {
        switch (tipo) {
          case 1:
            currentValue++;
            textController.text =
                (currentValue < 255 ? currentValue : 255).toString();
            break;
        }
      });
    });
  }

  void _startDecrementTimer(TextEditingController textController, int tipo) {
    // Start a timer that decrements the value every 100 milliseconds
    _decrementTimer = Timer.periodic(Duration(milliseconds: 100), (_) {
      int currentValue = int.parse(textController.text);
      setState(() {
        switch (tipo) {
          case 1:
            currentValue--;
            textController.text =
                (currentValue > 0 ? currentValue : 0).toString();
            break;
        }
      });
    });
  }

  void _stopTimer() {
    // Cancel both increment and decrement timers
    _incrementTimer?.cancel();
    _decrementTimer?.cancel();
  }

  ScrollController _vertical = new ScrollController(),
      _horizontal = new ScrollController();

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Column(
      children: [
        Card(
          elevation: 5,
          child: Padding(
            padding: const EdgeInsets.all(8.0),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text("TRASMISSIONE DIGITALE - REPORT ZONE"),
              ],
            ),
          ),
        ),
        Expanded(
          child: Scrollbar(
            controller: _horizontal,
            thumbVisibility: true,
            child: Scrollbar(
              controller: _vertical,
              thumbVisibility: true,
              notificationPredicate: (notif) => notif.depth == 1,
              child: SingleChildScrollView(
                controller: _horizontal,
                scrollDirection: Axis.horizontal,
                padding: EdgeInsets.all(1.0),
                child: SingleChildScrollView(
                  padding: EdgeInsets.symmetric(
                    vertical: 4,
                  ),
                  controller: _vertical,
                  child: SizedBox(
                    width: MediaQuery.of(context).size.width -
                                (48 + 17 + widget.getColumnWidth) <
                            750
                        ? 750
                        : MediaQuery.of(context).size.width -
                            (48 + 17 + widget.getColumnWidth),
                    child: Table(
                      columnWidths: {
                        0: FixedColumnWidth(60),
                        1: FixedColumnWidth(160),
                        2: FixedColumnWidth(90),
                        3: FlexColumnWidth(),
                        4: FlexColumnWidth(),
                        5: FlexColumnWidth(),
                        6: FlexColumnWidth(),
                        7: FlexColumnWidth(),
                      },
                      border: TableBorder.all(),
                      children: List.generate(
                        5,
                        (rowIndex) => TableRow(
                          children: List.generate(
                            8,
                            (colIndex) => TableCell(
                              child: SizedBox(
                                height: 53,
                                child: Container(
                                  color: rowIndex == 0 || colIndex == 0
                                      ? Colors.grey.shade600
                                      : null,
                                  child: buildCellElement(
                                    rowIndex,
                                    colIndex,
                                  ),
                                ),
                              ),
                            ),
                          ),
                        ),
                      ),
                    ),
                  ),
                ),
              ),
            ),
          ),
        ),
      ],
    );
  }

  Widget buildCellElement(int rowIndex, int colIndex) {
    final cellLabel = "$rowIndex-$colIndex";

    if (colIndex >= 4 && rowIndex >= 1) {
      final checkboxData = checkboxControllers[cellLabel];
      if (checkboxData != null) {
        return Row(
          children: [
            Align(
              alignment: Alignment.center,
              child: Transform.scale(
                scale: 0.8,
                child: Checkbox(
                  value: checkboxData,
                  onChanged: (bool? value) {
                    setState(() {
                      checkboxControllers[cellLabel] = value ?? false;
                    });
                    // Handle checkbox state changes
                    // You can use checkboxData["value"] and checkboxData["visible"] here
                    // to update the state as needed.
                  },
                ),
              ),
            ),
            Text(checkboxData ? "Si" : "No"),
          ],
        );
      } else {
        return Container();
      }
    } else {
      if (rowIndex == 0) {
        return Padding(
          padding: const EdgeInsets.all(8.0),
          child: Center(
              child: Text(
            columnTexts[colIndex],
            style: TextStyle(fontSize: 12),
          )),
        );
      } else if (colIndex == 0) {
        return Align(
          alignment: Alignment.centerRight,
          child: Text(
            "${rowIndex.toString()}  ",
            style: TextStyle(fontSize: 12),
          ),
        );
      } else if (colIndex == 1) {
        return Align(
          alignment: Alignment.centerLeft,
          child: Text("Collocazione di prova"),
        );
      } else if (colIndex == 2) {
        return Center(
            child: Text(
          (rowIndex + Random().nextInt(56)).toString(),
        ));
      } else if (colIndex == 3) {
        return Center(
            child: Text(
          ("dddddddddddddddd"),
        ));
      }
      return Text(
        cellLabel,
        style: TextStyle(fontSize: 12),
      );
    }
  }
}

EDIT: SOLVED

I used the Stack widget to create the page I was expecting, it is really useful in this situations, thanks for the help.

0

There are 0 best solutions below