0

I've been trying to design the layout of my ExpansionTile just like the design below but I couldn't figure out how to change the layout. any suggestion on how to change the border radius, change the background color and also make a gap between each other?.

I tried adding boxDecoration in each container but the style only apply to outside but not on each expansionTile.

import 'package:flutter/material.dart';

class MyReoderWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ReorderItems(topTen: ['j']);
  }
}
class DataHolder {
  List<String> parentKeys;

  Map<String, List<String>> childMap;

  DataHolder._privateConstructor();

  static final DataHolder _dataHolder = DataHolder._privateConstructor();

  static DataHolder get instance => _dataHolder;

  factory DataHolder.initialize({@required parentKeys}) {
    _dataHolder.parentKeys = parentKeys;
    _dataHolder.childMap = {};
    for (String key in parentKeys) {
      _dataHolder.childMap.putIfAbsent(

    }
    return _dataHolder;
  }
}

class ReorderItems extends StatefulWidget {
  final List<String> topTen;
  ReorderItems({this.topTen});
  @override
  _ReorderItemsState createState() => _ReorderItemsState();
}

class _ReorderItemsState extends State<ReorderItems> {
  @override
  void initState() {
    super.initState();
    // initialize the children for the Expansion tile
    // This initialization can be replaced with any logic like network fetch or something else.
    DataHolder.initialize(parentKeys: widget.topTen);
  }

  @override
  Widget build(BuildContext context) {
    return PrimaryScrollController(
      key: ValueKey(widget.topTen.toString()),
      controller: ScrollController(),
      child: Container(
        decoration: BoxDecoration(),
        child: ReorderableListView(
          onReorder: onReorder,
          children: getListItem(),
        ),
      ),
    );
  }

  List<ExpansionTile> getListItem() => DataHolder.instance.parentKeys
      .asMap()
      .map((index, item) => MapEntry(index, buildTenableListTile(item, index)))
      .values
      .toList();

  ExpansionTile buildTenableListTile(String mapKey, int index) => ExpansionTile(
        key: ValueKey(mapKey),
        title: Text(mapKey),
        leading: Icon(Icons.list),
        children: [
          Container(
            decoration: BoxDecoration(
              borderRadius: BorderRadius.all(Radius.circular(20))
            ),
            key: ValueKey('$mapKey$index'),
            height: 200,
            child: Container(
              padding: EdgeInsets.only(left: 30.0),
              child: ReorderList(
                parentMapKey: mapKey,
              ),
            ),
          ),
        ],
      );

  void onReorder(int oldIndex, int newIndex) {
    if (newIndex > oldIndex) {
      newIndex -= 1;
    }
    setState(() {
      String game = widget.topTen[oldIndex];
      DataHolder.instance.parentKeys.removeAt(oldIndex);
      DataHolder.instance.parentKeys.insert(newIndex, game);
    });
  }
}

class ReorderList extends StatefulWidget {
  final String parentMapKey;
  ReorderList({this.parentMapKey});
  @override
  _ReorderListState createState() => _ReorderListState();
}

class _ReorderListState extends State<ReorderList> {
  @override
  Widget build(BuildContext context) {
    return PrimaryScrollController(
      controller: ScrollController(),
      child: ReorderableListView(
        // scrollController: ScrollController(),
        onReorder: onReorder,
        children: DataHolder.instance.childMap[widget.parentMapKey]
            .map(
              (String child) => Container(
                child: ListTile(
                  key: ValueKey(child),
                  leading: Icon(Icons.list),
                  title: Text(child),
                ),
              ),
            )
            .toList(),
      ),
    );
  }

  void onReorder(int oldIndex, int newIndex) {
    if (newIndex > oldIndex) {
      newIndex -= 1;
    }
    List<String> children = DataHolder.instance.childMap[widget.parentMapKey];
    String game = children[oldIndex];
    children.removeAt(oldIndex);
    children.insert(newIndex, game);
    DataHolder.instance.childMap[widget.parentMapKey] = children;
    // Need to set state to rebuild the children.
    setState(() {});
  }
}

1 Answers1

1

You can do it using custom expandable container.

Demo

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Calendar',
      theme: ThemeData(
        primarySwatch: Colors.grey,
      ),
      debugShowCheckedModeBanner: false,
      home: Material(
        child: MyReoderWidget(),
      ),
    );
  }
}

class CustomModel {
  String title;
  bool isExpanded;
  List<String> subItems;

  CustomModel({this.title, this.subItems, this.isExpanded = false});
}

class MyReoderWidget extends StatefulWidget {
  @override
  _MyReoderWidgetState createState() => _MyReoderWidgetState();
}

class _MyReoderWidgetState extends State<MyReoderWidget> {
  List<CustomModel> listItems;

  @override
  void initState() {
    super.initState();
    listItems = List<CustomModel>();
    listItems.add(CustomModel(
        title: "App Name 1", subItems: ["Card Name 1", "Card Name 2"]));
    listItems.add(CustomModel(
        title: "App Name 2", subItems: ["Card Name 3", "Card Name 4"]));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.black,
      body: ListView(
          children: listItems
              .map((model) => new Padding(
                    padding: EdgeInsets.only(
                      bottom: 10,
                    ),
                    child: ExpandableCardContainer(
                      isExpanded: model.isExpanded,
                      collapsedChild: createHeaderCard(model),
                      expandedChild: Column(
                        children: <Widget>[
                          Padding(
                            padding: EdgeInsets.only(
                              bottom: 10,
                            ),
                            child: createHeaderCard(model),
                          )
                        ]..addAll(model.subItems
                            .map((e) => createChildCard(e))
                            .toList()),
                      ),
                    ),
                  ))
              .toList()),
    );
  }

  Widget createHeaderCard(CustomModel model) {
    return Container(
      child: Row(
        children: <Widget>[
          Icon(
            Icons.more_vert,
            color: Colors.white,
          ),
          Expanded(
            child: Text(
              model.title,
              style: TextStyle(color: Colors.white),
            ),
          ),
          GestureDetector(
            onTap: () {
              setState(() {
                model.isExpanded = !model.isExpanded;
              });
            },
            child: Icon(
              model.isExpanded
                  ? Icons.keyboard_arrow_up
                  : Icons.keyboard_arrow_down,
              color: Colors.white,
            ),
          )
        ],
      ),
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(10),
        color: Color(0xFF132435),
      ),
      height: 50,
    );
  }

  Widget createChildCard(String subItems) {
    return Container(
      margin: EdgeInsets.only(left: 30, bottom: 10),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Icon(
            Icons.more_vert,
            color: Colors.white,
          ),
          Expanded(
            child: Text(
              subItems,
              style: TextStyle(color: Colors.white),
            ),
          ),
        ],
      ),
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(10),
        color: Color(0xFF132435),
      ),
      height: 50,
    );
  }
}

class ExpandableCardContainer extends StatefulWidget {
  final bool isExpanded;
  final Widget collapsedChild;
  final Widget expandedChild;

  const ExpandableCardContainer(
      {Key key, this.isExpanded, this.collapsedChild, this.expandedChild})
      : super(key: key);

  @override
  _ExpandableCardContainerState createState() =>
      _ExpandableCardContainerState();
}

class _ExpandableCardContainerState extends State<ExpandableCardContainer> {
  @override
  Widget build(BuildContext context) {
    return new AnimatedContainer(
      duration: new Duration(milliseconds: 200),
      curve: Curves.easeInOut,
      child: widget.isExpanded ? widget.expandedChild : widget.collapsedChild,
    );
  }
}

Sanjay Sharma
  • 3,687
  • 2
  • 22
  • 38
  • Hi thank you so much for the answer and it look exactly like the design but in the original code, the items parent and child where reorderable. please can you help me fix that. thank you –  May 01 '20 at 22:01
  • If you find it helpful you can mark and upvote it :) For reorderable thing, I would suggest you use state management libraries such as BLOC or provider and rebuild it based on the index as a key. You can check [this](https://stackoverflow.com/a/56726755/4788260) – Sanjay Sharma May 01 '20 at 22:07
  • Ok I'll do that and I'm new to Flutter so are you saying that I can achieve like the design above by continue working on your code and following the example link you provided?. I'm stuck with this for a few day so I'll really appreciated if you can guided me. thank you –  May 01 '20 at 22:14
  • Here use GestureDetector is not very friendly for user. If use InkWell add click event for total Row will be better. – Chuanhang.gu May 02 '20 at 02:08
  • @aasenomad You can use the code which I shared and achieve a draggable item feature by using the example which I shared. – Sanjay Sharma May 02 '20 at 07:12
  • @SanjaySharma hi so sorry to bother you again, is there any way you can teach me steps by step or help me again?. I'm kinda stuck and don't know the next step to achieve it. thanks –  May 04 '20 at 21:09