skip to Main Content

I want to achieve tree hierarchy list view

i have tried few reference as i got dev.pub bellow are,

https://pub.dev/packages/parent_child_checkbox

https://pub.dev/packages/list_treeview

i have seen but i cannot see as per i need 3 level of sub tree with check box image state and selection according see below image i want to achieve,
Any one idea how to achieve or guild please share…
Thank you….

enter image description here

2

Answers


  1. Maybe you can create your own renderObject, I try my best for make the indent widget look like your image provided. Keep in mind this is not Sliver widget therefore this can cost some perform an issue.

    enter image description here

    also I suggest you take a look this video if youe interest about renderObject in flutter.
    https://www.youtube.com/watch?v=HqXNGawzSbY&t=7458s

    main.dart

    import 'package:flutter/material.dart';
    import 'indent_widget.dart';
    
    void main() {
      runApp(const MyApp());
    }
    
    class MyApp extends StatelessWidget {
      const MyApp({super.key});
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: const MyHomePage(title: 'Flutter Demo Home Page'),
        );
      }
    }
    
    class MyHomePage extends StatefulWidget {
      const MyHomePage({super.key, required this.title});
    
      final String title;
    
      @override
      State<MyHomePage> createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> {
      Widget _buildColumn() {
        return Row(
          children: [
            Checkbox(value: false, onChanged: (value) {}),
            const Expanded(child: Text('text'))
          ],
        );
      }
    
      @override
      Widget build(BuildContext context) {
        double tabSpace = 30;
        return Scaffold(
          body: SafeArea(
            child: SingleChildScrollView(
              child: IndentWidget(children: [
                _buildColumn(),
                IndentTab(
                    tabSpace: tabSpace,
                    child: IndentWidget(
                      children: [
                        IndentTab(
                            tabSpace: tabSpace,
                            child: IndentWidget(
                              children: [
                                _buildColumn(),
                                _buildColumn(),
                                IndentTab(
                                    tabSpace: tabSpace,
                                    child: IndentWidget(
                                      children: [
                                        _buildColumn(),
                                        _buildColumn(),
                                        IndentTab(
                                            tabSpace: tabSpace,
                                            child: IndentWidget(
                                              children: [
                                                _buildColumn(),
                                                _buildColumn(),
                                                _buildColumn(),
                                              ],
                                            )),
                                        _buildColumn(),
                                        IndentTab(
                                            tabSpace: tabSpace,
                                            child: IndentWidget(
                                              children: [
                                                _buildColumn(),
                                                _buildColumn(),
                                                _buildColumn(),
                                              ],
                                            )),
                                      ],
                                    )),
                                _buildColumn(),
                              ],
                            )),
                        _buildColumn(),
                        _buildColumn(),
                        _buildColumn(),
                      ],
                    )),
                _buildColumn(),
              ]),
            ),
          ),
        );
      }
    }
    

    indent_widget.dart

    import 'dart:math';
    
    import 'package:flutter/cupertino.dart';
    import 'package:flutter/rendering.dart';
    
    class IndentWidget extends MultiChildRenderObjectWidget {
      IndentWidget({super.key, super.children});
    
      @override
      RenderObject createRenderObject(BuildContext context) {
        /// 1. entry point.
        return RenderIndent();
      }
    }
    
    /// provide information to RenderIndent;
    class RenderIndentParentData extends ContainerBoxParentData<RenderBox> {
      double? tabSpace;
    }
    
    class IndentTab extends ParentDataWidget<RenderIndentParentData> {
      final double tabSpace;
    
      const IndentTab({super.key, required this.tabSpace, required super.child});
    
      @override
      void applyParentData(RenderObject renderObject) {
        final RenderIndentParentData parentData =
            renderObject.parentData! as RenderIndentParentData;
    
        if (parentData.tabSpace != tabSpace) {
          parentData.tabSpace = tabSpace;
          final targetObject = renderObject.parent;
          if (targetObject is RenderObject) {
            targetObject.markNeedsLayout();
          }
        }
      }
    
      @override
      Type get debugTypicalAncestorWidgetClass => RenderIndentParentData;
    }
    
    class RenderIndent extends RenderBox
        with
            ContainerRenderObjectMixin<RenderBox, RenderIndentParentData>,
            RenderBoxContainerDefaultsMixin<RenderBox, RenderIndentParentData> {
      @override
      void setupParentData(RenderBox child) {
        if (child.parentData is! RenderIndentParentData) {
          child.parentData = RenderIndentParentData();
        }
      }
    
      Size _performLayout(BoxConstraints constraints, bool dry) {
        RenderBox? child = firstChild;
        double width = 0, height = 0;
    
        while (child != null) {
          final RenderIndentParentData childParentData =
              child.parentData as RenderIndentParentData;
          final double leftShift = childParentData.tabSpace ?? 0;
    
          if (!dry) {
            childParentData.offset = Offset(leftShift, height);
            child.layout(BoxConstraints(maxWidth: constraints.maxWidth),
                parentUsesSize: true);
          }
          height += child.size.height;
          width = max(width, leftShift + child.size.width);
          child = childParentData.nextSibling;
        }
    
        if (width > constraints.maxWidth) {
          width = constraints.maxWidth;
        }
    
        return Size(width, height);
      }
    
      @override
      void performLayout() {
        size = _performLayout(constraints, false);
      }
    
      @override
      Size computeDryLayout(BoxConstraints constraints) {
        return _performLayout(constraints, true);
      }
    
      @override
      void paint(PaintingContext context, Offset offset) {
        defaultPaint(context, offset);
      }
    
      @override
      bool hitTestChildren(BoxHitTestResult result, {required Offset position}) {
        return defaultHitTestChildren(result, position: position);
      }
    }
    
    Login or Signup to reply.
  2. You should have a recursive data like this:

    final list = [
      {
        "title": "title1",
        "list": [
          {
            "title": "title11",
            "list": [
              {
                "title": "title111",
                "list": <Map<String, dynamic>>[],
              },
              {
                "title": "title112",
              },
            ],
          },
          {
            "title": "title12",
            "list": <Map<String, dynamic>>[],
          },
          {
            "title": "title13",
            "list": <Map<String, dynamic>>[],
          },
          {
            "title": "title14",
            "list": <Map<String, dynamic>>[],
          },
        ],
      },
      {
        "title": "title2",
        "list": [
          {
            "title": "title21",
            "list": <Map<String, dynamic>>[],
          },
          {
            "title": "title22",
            "list": <Map<String, dynamic>>[
              {
                "title": "title221",
                "list": [
                  {
                    "title": "title2211",
                    "list": <Map<String, dynamic>>[],
                  },
                  {
                    "title": "title2212",
                    "list": <Map<String, dynamic>>[],
                  },
                ],
              },
              {
                "title": "title222",
                "list": [
                  {
                    "title": "title2221",
                    "list": <Map<String, dynamic>>[],
                  },
                  {
                    "title": "title2222",
                    "list": <Map<String, dynamic>>[],
                  },
                ],
              },
            ],
          },
          {
            "title": "title23",
            "list": <Map<String, dynamic>>[],
          },
        ],
      },
    ];
    

    To Show this data as a recursive list in UI, you should build a Recursive Widget, firstly we build a CheckBoxTitle widget that its checkBox can be selected like this:

    class CheckBoxTitle extends StatefulWidget {
      const CheckBoxTitle({
        Key? key,
        required this.title,
        required this.level,
      }) : super(key: key);
    
      final String title;
      final int level;
    
      @override
      State<CheckBoxTitle> createState() => _CheckBoxTitleState();
    }
    
    class _CheckBoxTitleState extends State<CheckBoxTitle> {
      bool _checkBoxValue = false;
    
      @override
      Widget build(BuildContext context) {
        return Row(
          children: [
            SizedBox(
              width: widget.level * 16,
            ),
            Checkbox(
              value: _checkBoxValue,
              onChanged: (value) {
                setState(() {
                  _checkBoxValue = value!;
                });
              },
            ),
            Expanded(
              child: FittedBox(
                fit: BoxFit.scaleDown,
                alignment: AlignmentDirectional.centerStart,
                child: Text(
                  widget.title,
                ),
              ),
            ),
          ],
        );
      }
    }
    

    then we build our recursive Widget:

    
    class RecursiveListView extends StatelessWidget {
      const RecursiveListView({
        Key? key,
        required this.listOfMap,
        this.level = 0,
      }) : super(key: key);
    
      final List<Map<String, dynamic>> listOfMap;
      final int level;
    
      @override
      Widget build(BuildContext context) {
        return ListView.builder(
          itemCount: listOfMap.length,
          itemBuilder: (context, index) {
            final map = listOfMap[index];
    
            List<Map<String, dynamic>>? innerList =
                map["list"] as List<Map<String, dynamic>>?;
    
            return ExpansionTile(
              trailing:
                  (innerList?.isEmpty ?? true) ? const SizedBox.shrink() : null,
              title: ListTile(
                title: CheckBoxTitle(
                  title: map["title"] as String,
                  level: level,
                ),
              ),
              children: [
                if (innerList?.isNotEmpty ?? false)
                  RecursiveListView(
                    listOfMap: innerList!,
                    level: level + 1,
                  ),
              ],
            );
          },
          physics: level == 0 ? null : const NeverScrollableScrollPhysics(),
          shrinkWrap:
              level != 0, // to make the list fill the entire screen at level 0
        );
      }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search