Close an ExpansionTile when another ExpansionTile is tapped
Asked Answered
S

7

8

I have a list of ExpansionTile with a list of ListTile in a Drawer. What I want to achieve is, when I press an ExpansionTile, the another ExpansionTile must be collapsed. I had been stuck with this problem for two days and could not find an answer. Can anybody know how to collapse the ExpansionTile programmatically?

Note:

I don't want to mess up the animation of the widget.

Here is my code,

ListView.builder(
                itemCount: userList.length,
                shrinkWrap: true,
                itemBuilder: (BuildContext context, findex) {
                  return ExpansionTile(
                    key: Key(findex.toString()),
                    title: Text(userList[findex].parentdata[0].title,
                      style: TextStyle(fontSize: 15.0, fontWeight: FontWeight.bold,color: Colors.black),
                    ),
                    onExpansionChanged: (value) {
                    },
                    children: [
                      ListView.builder(
                        itemCount: userList[findex].document.length,
                        shrinkWrap: true,
                        itemBuilder: (BuildContext context, sindex) {
                          return ListTile(
                            title: Text(
                                userList[findex].document[sindex].title,
                              style: TextStyle(fontSize: 15.0, fontWeight: FontWeight.bold,color: Colors.black),
                            ),
                            onTap: () {
                              print(
                                  userList[findex].document[sindex].title);
                            },
                          );
                        },
                      ),
                    ],
                  );
                },
              ),
Steelyard answered 20/5, 2022 at 8:25 Comment(1)
github.com/flutter/flutter/issues/60387Jalap
G
7

Try below code

declare one int variable

int selectedTile = -1;

Your widget

ListView.builder(
  key: Key(selectedTile.toString()),
  itemCount: 5,
  itemBuilder: (context, index) {
    return ExpansionTile(
      key: Key(index.toString()),
      initiallyExpanded: index == selectedTile,
      title: Text('ExpansionTile $index'),
      subtitle: Text('Trailing expansion arrow icon'),
      children: [
        ListTile(
          title: Text('This is tile number $index'),
        ),
      ],
      onExpansionChanged: ((newState) {
        if (newState)
          setState(() {
            selectedTile = index;
          });
        else
          setState(() {
            selectedTile = -1;
          });
      }),
    );
  },
);
Gloxinia answered 20/5, 2022 at 8:57 Comment(2)
It is working. but in my case the above ExpansionTile key is not working. instead of I used like this - key: UniqueKey(),Margalo
In my case the above key is working if you used key: UniqueKey(), this also working I have checked both are worked Thanks for sharingGloxinia
N
2

Use ExpansionPanel widget.

You need to create a variable and maintain the expansion state of expansion panel index.

expansionCallback: (int index, bool isExpanded) {
        setState(() {
          // when any of expansionPanel is Tapped
          // set all expansion to false
          for(int i = 0; i<_data.length; i++){
            _data[i].isExpanded = false;
          }
          // then set the tapped index to its state
          _data[index].isExpanded = !isExpanded;
        });
      },

Here is an live demo for expansion panel

Notochord answered 20/5, 2022 at 9:11 Comment(3)
In the ExpansionPanel widget, We can only tap on the icon. If we tap on the text there will be no response.Steelyard
gist.github.com/bharathraj-e/7a71a6d73ed000b137f767760bad8e16 Set canTapOnHeader: true in ExpansionPanel. Ref: api.flutter.dev/flutter/material/ExpansionPanel/…Notochord
Is the live demo working? I tap on an item nothing happens.Martinamartindale
H
2

Simple Alternative using ExpansionTileControllers

For example, if you have two ExpansionTile widgets:

First you need to declare two properties ExpansionTileController in the state class.

And then you must assign both variables to the respective ExpansionTile widget in your build method.

  final expansionTileControllerTheme = ExpansionTileController();
  final expansionTileControllerLanguage = ExpansionTileController();
.
.
.
        // App Theme Color selector
        Padding(
          padding: const EdgeInsets.symmetric(horizontal: 12),
          child: ExpansionTile(
            controller: expansionTileControllerTheme,
            onExpansionChanged: (bool isOpen) {
              if (isOpen) expansionTileControllerLanguage.collapse();
            },
            iconColor: selectedColor,
            collapsedIconColor: selectedColor,
            title: const Row(
              children: [Icon(FontAwesomeIcons.palette), SizedBox(width: 10), Text('Theme Color')],
            ),Text('Active: ${selectedColor.colorName}')]),
            children: [
              ...colorList.asMap().entries.map(
                (entry) {
                  final int index = entry.key;
                  final Color color = entry.value;

                  return RadioListTile(
                    title: Text(color.colorName, style: TextStyle(color: color)),
                    value: index,
                    groupValue: appTheme.colorIndex,
                    onChanged: (_) => changeColorIndex(index),
                  );
                },
              )
            ],
          ),
        ),

        // Language Selector
        Padding(
          padding: const EdgeInsets.symmetric(horizontal: 12),
          child: ExpansionTile(
            controller: expansionTileControllerLanguage,
            onExpansionChanged: (bool isOpen) {
              if (isOpen) expansionTileControllerTheme.collapse();
            },
            iconColor: selectedColor,
            collapsedIconColor: selectedColor,
            title: const Row(
              children: [Icon(FontAwesomeIcons.language), SizedBox(width: 12), Text('Language')],
            ),
            children: [
              ...LanguageEntity.languageList().map(
                (LanguageEntity language) {
                  return RadioListTile(
                    title: Row(
                      children: [
                        Text(language.flagEmoji, style: TextStyle(color: selectedColor)),
                        const SizedBox(width: 8),
                        Text(language.name, style: TextStyle(color: selectedColor)),
                      ],
                    ),
                    value: language.id,
                    groupValue: 2,
                    onChanged: (_) => {},
                  );
                },
              )
            ],
          ),
        ),
.
.
.

When one ExpansionTile is open, which you can detect in the event onExpansionChanged, you simply send to collapse the other one.

Visual Result:

enter image description here

Houseboat answered 27/3 at 22:41 Comment(0)
G
1

Try this:

Create a variable: int selected = -1;

And listview:

ListView.builder(
          itemCount: 10,
          shrinkWrap: true,
          itemBuilder: (BuildContext context, findex) {
            return ExpansionTile(
              initiallyExpanded: findex == selected,
              key: Key(selected.toString()),
              title: Text(userList[findex].parentdata[0].title,
                style: TextStyle(fontSize: 15.0, fontWeight: FontWeight.bold,color: Colors.black),
              ),
              onExpansionChanged: (newState) {
                setState(() {
                  selected = findex;
                });
              },
              children: [
                ListView.builder(
                  itemCount: 10,
                  shrinkWrap: true,
                  itemBuilder: (BuildContext context, sindex) {
                    return ListTile(
                      title: Text(
                        userList[findex].document[sindex].title,
                        style: TextStyle(fontSize: 15.0, fontWeight: FontWeight.bold,color: Colors.black),
                      ),
                      onTap: () {
                        print(userList[findex].document[sindex].title);
                      },
                    );
                  },
                ),
              ],
            );
          },
        ),
Grape answered 20/5, 2022 at 8:50 Comment(2)
This one is not working. The initiallyExpanded property is only called when the ExpandedTile Widget is created. So, passing a Boolean to the initiallyExpanded property on every tapping is pointless.Steelyard
this works on me, but the ExpansionTile animation not so smooth when I open the one ExpansionTile to another. Any idea for this? ThanksAnarchist
C
1
key: index == lastOne ? const Key("selected") : Key(index.toString()),
                                                initiallyExpanded: index == selectedIndex,
                                                expandedCrossAxisAlignment: CrossAxisAlignment.start,
                                                onExpansionChanged: ((newState) {
                                                  lastOne = selectedIndex;
                                                  if (newState) {
                                                    setState(() {
                                                      selectedIndex = index;
                                                    });
                                                  } else {
                                                    setState(() {
                                                      selectedIndex = -1;
                                                    });
                                                  }
                                                }),

in this way you can also have the animation as well

Cumbrance answered 12/4, 2023 at 0:42 Comment(1)
As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.Ozonolysis
S
0

Make sure ExpansionTile be in stateful widget

   ListView.builder(
                        itemCount: 5,
                          shrinkWrap: true,
                          itemBuilder: (BuildContext context, index) {
                            return CustomExpansionTile(index: index);
                          },
                        ),

// Expansion Tile Widget

       class CustomExpansionTile extends StatefulWidget {
              final int index;
            
              const CustomExpansionTile({Key? key, required this.index}) : super(key: key);
            
              @override
              State<CustomExpansionTile> createState() => _CustomExpansionTileState();
            }
            
            class _CustomExpansionTileState extends State<CustomExpansionTile> {
              int selectedIndexExpansionTile = -1;
            
              @override
              Widget build(BuildContext context) {
                return ExpansionTile(
                    initiallyExpanded: widget.index == selectedIndexExpansionTile,
                    key: Key(selectedIndexExpansionTile.toString()),
                    title: Text(
                      widget.index.toString(),
                    ),
                    onExpansionChanged: (newState) {
                 
                      if (newState) {
                        selectedIndexExpansionTile = widget.index;
                      } else {
                        selectedIndexExpansionTile = -1;
                      }
                      setState(() {});
                    },
                    children: [Text(widget.index.toString())]);
              }
            }
Stupefy answered 15/12, 2022 at 9:34 Comment(0)
C
0

Use Accordion Wizard package, if you don't need to use ListView.builder for creating expansion tiles.

Cowardice answered 21/3 at 12:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.