Tuesday, August 23, 2022
HomeWeb DevelopmentExpansionPanel in Flutter: A information with examples

ExpansionPanel in Flutter: A information with examples


Each cellular working system affords an inbuilt UI toolkit that comprises numerous widgets, that are normally not very customizable. Flutter comes with a versatile widget system that implements the Materials Design specification and motivates cellular app builders to create futuristic, minimal UIs.

Not like platform-specific UI widgets, Flutter affords many customizable widget decisions for every generic requirement, so constructing your Flutter app in response to your distinctive design sketch is straightforward. One in all these is the ExpansionPanel widget, which helps us to create expandable/collapsible lists.

We are able to add a number of ExpansionPanel widgets inside one ExpansionPanelList widget to create expandable/collapsible lists in our Flutter apps. These widgets have an increase/collapse icon-button for customers to indicate/cover further content material. Flutter builders usually use a separate particulars display screen to show giant content material segments for explicit listing objects (i.e., displaying product particulars).

The ExpansionPanel widget helps builders present small- and medium-sized content material segments for every listing merchandise with out a want for display screen navigation. In UI/UX specs, this UI factor will be known as Accordion, Expandable, or Collapsible.

On this tutorial, I’ll clarify tips on how to use and customise the ExpansionPanel widget with sensible examples. Additionally, we’ll examine it with the ExpansionTile widget that gives comparable options.

Soar forward:

Flutter ExpansionPanel tutorial

Let’s create a brand new Flutter undertaking to work with the ExpansionPanel widget. You may as well use this instance code in your present Flutter tasks.

If you’re new to Flutter, set up Flutter improvement instruments in response to the official Flutter set up information. You possibly can run the upcoming examples on Google Chrome, a bodily cellular machine, or an emulator/simulator. On this tutorial, I’ll use Chrome to preview the instance app.

First, create a brand new Flutter app with the next command:

flutter create expansionpanel_example
cd expansionpanel_example

Enter the flutter run command to ensure that all the pieces works high quality.

Utilizing ExpansionPanel with ExpansionPanelList

Let’s create a easy information web page for making a Flutter app with a number of ExpansionPanel widgets and a ExpansionPanelList widget. The person can faucet on a specific step to increase it and see extra particulars.

In most situations, we usually load information to the app frontend by way of a backend net service with an asynchronous operate, however for our tutorial, we’ll render hardcoded information from a synchronous operate to get began with ExpansionPanel shortly.

Add the next code to your primary.dart file:

import 'bundle:flutter/materials.dart';

void primary() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : tremendous(key: key);
  static const String _title="Flutter Tutorial";
  @override
  Widget construct(BuildContext context) {
    return MaterialApp(
      title: _title,
      residence: Scaffold(
        appBar: AppBar(title: const Textual content(_title)),
        physique: const Steps(),
      ),
    );
  }
}

class Step {
  Step(
    this.title,
    this.physique,
    [this.isExpanded = false]
  );
  String title;
  String physique;
  bool isExpanded;
}

Listing<Step> getSteps() {
  return [
    Step('Step 0: Install Flutter', 'Install Flutter development tools according to the official documentation.'),
    Step('Step 1: Create a project', 'Open your terminal, run `flutter create <project_name>` to create a new project.'),
    Step('Step 2: Run the app', 'Change your terminal directory to the project directory, enter `flutter run`.'),
  ];
}

class Steps extends StatefulWidget {
  const Steps({Key? key}) : tremendous(key: key);
  @override
  State<Steps> createState() => _StepsState();
}

class _StepsState extends State<Steps> {
  ultimate Listing<Step> _steps = getSteps();
  @override
  Widget construct(BuildContext context) {
    return SingleChildScrollView(
      baby: Container(
        baby: _renderSteps(),
      ),
    );
  }
  Widget _renderSteps() {
    return ExpansionPanelList(
      expansionCallback: (int index, bool isExpanded) {
        setState(() {
          _steps[index].isExpanded = !isExpanded;
        });
      },
      kids: _steps.map<ExpansionPanel>((Step step) {
        return ExpansionPanel(
          headerBuilder: (BuildContext context, bool isExpanded) {
            return ListTile(
              title: Textual content(step.title),
            );
          },
          physique: ListTile(
            title: Textual content(step.physique),
          ),
          isExpanded: step.isExpanded,
        );
      }).toList(),
    );
  }
}

Discover the next details in regards to the above instance code:

  • The Steps widget is accountable for rendering the whole expandable listing on the display screen
  • The getSteps synchronous operate returns all hardcoded steps as cases of the Merchandise class, and the _steps widget state variable holds all objects as a Dart Listing
  • We use two parameters from the ExpansionPanelList class:
    • kids to set all ExpansionPanel cases by changing the _steps listing
    • expansionCallback to replace the _steps listing primarily based on the current person interplay with the increase/collapse button
  • We used the ListTile class fairly than merely utilizing Textual content to show a well-styled Materials listing

Run the above code. You will notice steps to create a Flutter undertaking, as proven within the following preview:

The example of our Flutter ExpansionPanel widget

Check the app by including extra steps, or attempt to generate some dynamic information with the Listing.generate manufacturing unit constructor.


Extra nice articles from LogRocket:


If it’s essential to load information out of your net backend, you possibly can wrap the ExpansionPanelList widget with FutureBuilder as common:

import 'bundle:flutter/materials.dart';

void primary() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : tremendous(key: key);
  static const String _title="Flutter Tutorial";
  @override
  Widget construct(BuildContext context) {
    return MaterialApp(
      title: _title,
      residence: Scaffold(
        appBar: AppBar(title: const Textual content(_title)),
        physique: const Steps(),
      ),
    );
  }
}

class Step {
  Step(
    this.title,
    this.physique,
    [this.isExpanded = false]
  );
  String title;
  String physique;
  bool isExpanded;
}

Future<Listing<Step>> getSteps() async {
  var _items = [
    Step('Step 0: Install Flutter', 'Install Flutter development tools according to the official documentation.'),
    Step('Step 1: Create a project', 'Open your terminal, run `flutter create <project_name>` to create a new project.'),
    Step('Step 2: Run the app', 'Change your terminal directory to the project directory, enter `flutter run`.'),
  ];
  return Future<Listing<Step>>.delayed(const Length(seconds: 2), () => _items);
}

class Steps extends StatelessWidget {
  const Steps({Key? key}) : tremendous(key: key);
  @override
  Widget construct(BuildContext context) {
    return SingleChildScrollView(
      baby: Container(
        baby: FutureBuilder<Listing<Step>>(
          future: getSteps(),
          builder: (BuildContext context, AsyncSnapshot<Listing<Step>> snapshot) {
            if(snapshot.hasData) {
              return StepList(steps: snapshot.information ?? []);
            }
            else {
              return Middle(
                baby: Padding(
                  padding: EdgeInsets.all(16),
                  baby: CircularProgressIndicator(),
                ),
              );
            }
          }
        ),
      ),
    );
  }
}

class StepList extends StatefulWidget {
  ultimate Listing<Step> steps;
  const StepList({Key? key, required this.steps}) : tremendous(key: key);
  @override
  State<StepList> createState() => _StepListState(steps: steps);
}

class _StepListState extends State<StepList> {
  ultimate Listing<Step> _steps;
  _StepListState({required Listing<Step> steps}) : _steps = steps;
  @override
  Widget construct(BuildContext context) {
    return ExpansionPanelList(
      expansionCallback: (int index, bool isExpanded) {
        setState(() {
          _steps[index].isExpanded = !isExpanded;
        });
      },
      kids: _steps.map<ExpansionPanel>((Step step) {
        return ExpansionPanel(
          headerBuilder: (BuildContext context, bool isExpanded) {
            return ListTile(
              title: Textual content(step.title),
            );
          },
          physique: ListTile(
            title: Textual content(step.physique),
          ),
          isExpanded: step.isExpanded,
        );
      }).toList(),
    );
  }
}

We made three updates to the earlier supply code, as defined beneath:

  1. Made the getSteps operate asynchronous with a synthetic delay, so now you possibly can even fetch information for the expandable listing from an online service by way of your favourite networking consumer library (i.e., Dio)
  2. Wrapped the expandable listing with a FutureBuilder by making a second widget known as StepList, which makes use of conditional rendering to show a round loading animation throughout the synthetic community latency
  3. Made the Steps widget stateless since we don’t maintain any information within the state there

Run the above code — you will notice the expandable listing after a two-second delay:

Loading expandable list data asynchronously with FutureBuilder

Utilizing both of those two approaches, you possibly can supply options for any scenario the place it’s essential to use the ExpansionPanel widget.

Now, let’s examine the options that the ExpansionPanel affords! Within the upcoming examples, we’ll replace the synchronous model as a result of its implementation is minimal in comparison with the asynchronous one. Copy the primary instance supply code to your primary.dart file once more and prepare to proceed with the tutorial.

Learn how to customise the ExpansionPanel widget’s UI

If you use ExpansionPanel together with ListTile, you’re going to get a user-friendly expandable listing as we noticed in earlier examples. You possibly can customise it in response to your private choice or app theme. For instance, you alter components’ background colour, as follows:

return ExpansionPanel(
  //.....
  //...
  backgroundColor: const Coloration(0xffeeeeff),
);

You possibly can change the expandable listing’s divider colour, as proven within the following code snippet:

return ExpansionPanelList(
  dividerColor: Colours.teal,
  //....
  //...

It’s additionally doable to set a customized padding for the header. Take a look at the next instance:

return ExpansionPanelList(
  expandedHeaderPadding: EdgeInsets.all(6),
  //....
  //...

The next _renderSteps methodology implementation makes use of the above parameters to use a number of UI customizations.

Widget _renderSteps() {
    return ExpansionPanelList(
      dividerColor: Colours.teal,
      expandedHeaderPadding: EdgeInsets.all(0),
      expansionCallback: (int index, bool isExpanded) {
        setState(() {
          _steps[index].isExpanded = !isExpanded;
        });
      },
      kids: _steps.map<ExpansionPanel>((Step step) {
        return ExpansionPanel(
          backgroundColor: const Coloration(0xffeeeeff),
          headerBuilder: (BuildContext context, bool isExpanded) {
            return ListTile(
              title: Textual content(step.title),
            );
          },
          physique: ListTile(
            title: Textual content(step.physique),
          ),
          isExpanded: step.isExpanded,
        );
      }).toList(),
    );
  }

Now, you will notice a custom-made expandable listing UI, as proven within the following preview:

A customized version of our ExpansionPanel UI

Adjusting ExpansionPanel’s animation and contact suggestions

The Flutter widget system helps you to change the velocity of ExpansionPanel’s animation. For instance, you possibly can decelerate its animation by extending the animation period as follows:

return ExpansionPanelList(
  animationDuration: const Length(milliseconds: 1500),
  //....
  //...

The ExpansionPanel widget opens/closes the content material part provided that customers faucet on the proper aspect icon button, however customers can do the identical motion by tapping on the whole header part for those who use the next setup:

return ExpansionPanel(
  canTapOnHeader: true,
  //...
  //..

This configuration is an effective person expertise enchancment in case your app customers usually use small-screen gadgets — they don’t have to faucet proper on the small increase/collapse icon button to activate the increase/collapse motion.

Auto-expanding ExpansionPanel primarily based on widget state

In earlier examples, we used the isExpanded class variable within the Step class, however we didn’t explicitly set a worth for it from the getSteps operate. All we obtained was growth panels collapsed, initially.

We are able to set an preliminary worth for the ExpansionPanel class’s isExpanded parameter to set an auto-expanded merchandise. Use the next synchronous getSteps operate implementation:

Listing<Step> getSteps() {
  return [
    Step('Step 0: Install Flutter',
        'Install Flutter development tools according to the official documentation.',
        true),
    Step('Step 1: Create a project', 'Open your terminal, run `flutter create <project_name>` to create a new project.'),
    Step('Step 2: Run the app', 'Change your terminal directory to the project directory, enter `flutter run`.'),
  ];
}

Right here, we set true for isExpanded within the first listing factor. Discover the next code line within the _renderSteps methodology:

isExpanded: step.isExpanded,

The above line passes the isExpanded from the Step occasion to the ExpansionPanel, so now we will see the primary panel will get auto-expanded initially:

Open a specific panel automatically in ExpansionPanelList

Equally, you possibly can even management initially-opened panels out of your net backends!

Increasing and collapsing all objects without delay

Have you ever ever seen, that in some apps, we will increase/collapse all expandable segments without delay, with one button? This characteristic is useful if customers have to learn all hidden content material with out tapping on every growth panel. Use the next construct methodology implementation for _StepsState:

@override
  Widget construct(BuildContext context) {
    return SingleChildScrollView(
      baby: Column(
        kids: <Widget>[
          Padding(
            padding: EdgeInsets.all(12),
            child: ElevatedButton(
              child: const Text('Expand all'),
              onPressed: () {
                setState(() {
                  for(int i = 0; i < _steps.length; i++) {
                    _steps[i].isExpanded = true;
                  }
                });
              },
            ),
          ),
          _renderSteps()
        ],
      ),
    );
  }

Right here, we created a button to increase all panels without delay. The setState methodology name units isExpanded as true for all listing merchandise cases, so when you faucet on the button, all steps get expanded as follows:

Implementing an expand all button for ExpansionPanelList

Equally, you possibly can implement a button to break down all panels by setting the isExpanded parameter to false:

_steps[i].isExpanded = false;

Implementing a collapse all button for ExpansionPanelList

Creating radio growth panels with ExpansionPanelRadio

The default ExpansionPanelList widget behaves like a gaggle of checkboxes, so once we faucet on one panel, that specific panel will get expanded and we’ve got to click on it once more to break down it.

However what if we have to construct an expandable listing that behaves like a gaggle of radio buttons? We are able to solely hold one panel expanded, like with the checkboxes group.

As an answer, you would possibly take into consideration writing some customized logic to replace the _steps listing the way in which we applied increase/collapse all options, however the Flutter widget system truly affords inbuilt ExpansionPanelRadio for this requirement.

Use the next code for the _renderSteps operate implementation:

Widget _renderSteps() {
    return ExpansionPanelList.radio(
      kids: _steps.map<ExpansionPanelRadio>((Step step) {
        return ExpansionPanelRadio(
          headerBuilder: (BuildContext context, bool isExpanded) {
            return ListTile(
              title: Textual content(step.title),
            );
          },
          physique: ListTile(
            title: Textual content(step.physique),
          ),
          worth: step.title
        );
      }).toList(),
    );
  }

Right here, we used the ExpansionPanelRadio widget with the ExpansionPanelList.radio constructor. The ExpansionPanelRadio widget doesn’t settle for the isExpanded parameter as ExpansionPanel does; as a substitute, it accepts a singular worth with the worth parameter. Additionally, we don’t have to name setState from expansionCallback, because the Flutter framework affords an inbuilt implementation to routinely collapse open panels as soon as the person opens one other.

As soon as you utilize the above code snippet, you will notice the next outcome:

ExpansionPanelRadio usage example

If it’s essential to open a specific panel initially, you are able to do it with the distinctive identifier you added with the worth parameter, as proven beneath:

return ExpansionPanelList.radio(
  initialOpenPanelValue: 'Step 0: Set up Flutter',
  //....
  //...

Notice that right here we used the merchandise title string because the distinctive worth for demonstration functions. For manufacturing apps, make sure that to make use of a greater distinctive worth like a product identifier.

Constructing nested growth panels

In most purposes, utilizing one degree for growth panels is sufficient, resembling in our earlier examples. However whenever you develop complicated apps (i.e., desktop apps) with Flutter, typically it’s essential to add nested growth panels.

The Flutter widget system is so versatile that it helps you to create nested growth panels. However, how can we outline a mannequin to carry information for one growth panel?

We are able to certainly use a recursive definition for the Step class as follows:

class Step {
  Step(
    this.title,
    this.physique,
    [this.subSteps = const <Step>[]]
  );
  String title;
  String physique;
  Listing<Step> subSteps;
}

Now, we will render a nested growth panel set by utilizing the subSteps listing. The next instance code provides one other step to our Flutter tutorial app with two sub-steps:

import 'bundle:flutter/materials.dart';

void primary() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : tremendous(key: key);
  static const String _title="Flutter Tutorial";
  @override
  Widget construct(BuildContext context) {
    return MaterialApp(
      title: _title,
      residence: Scaffold(
        appBar: AppBar(title: const Textual content(_title)),
        physique: const Steps(),
      ),
    );
  }
}

class Step {
  Step(
    this.title,
    this.physique,
    [this.subSteps = const <Step>[]]
  );
  String title;
  String physique;
  Listing<Step> subSteps;
}

Listing<Step> getSteps() {
  return [
    Step('Step 0: Install Flutter', 'Install Flutter development tools according to the official documentation.'),
    Step('Step 1: Create a project', 'Open your terminal, run `flutter create <project_name>` to create a new project.'),
    Step('Step 2: Run the app', 'Change your terminal directory to the project directory, enter `flutter run`.'),
    Step('Step 3: Build your app', 'Select a tutorial:', [
      Step('Developing a to-do app', 'Add a link to the tutorial video'),
      Step('Developing a 2-D game', 'Add a link to the tutorial video'),
    ]),
  ];
}

class Steps extends StatefulWidget {
  const Steps({Key? key}) : tremendous(key: key);
  @override
  State<Steps> createState() => _StepsState();
}

class _StepsState extends State<Steps> {
  ultimate Listing<Step> _steps = getSteps();
  @override
  Widget construct(BuildContext context) {
    return SingleChildScrollView(
      baby: Container(
          baby: _renderSteps(_steps)
      ),
    );
  }

  Widget _renderSteps(Listing<Step> steps) {
    return ExpansionPanelList.radio(
      kids: steps.map<ExpansionPanelRadio>((Step step) {
        return ExpansionPanelRadio(
          headerBuilder: (BuildContext context, bool isExpanded) {
            return ListTile(
              title: Textual content(step.title),
            );
          },
          physique: ListTile(
            title: Textual content(step.physique),
            subtitle: _renderSteps(step.subSteps)
          ),
          worth: step.title
        );
      }).toList(),
    );
  }
}

Right here, we name the _renderSteps methodology recursively with step.subSteps for rendering nested growth panels. When you run the above code, you will notice sub-steps for the final step, as proven beneath:

Implementing nested expansion panels with a recursive method

The above instance renders solely two ranges of nested growth panels, however the recursive methodology helps extra, so how will you modify the getSteps methodology supply for displaying three growth ranges? You possibly can simply add one other growth degree by passing your sub-steps for the Growing a to-do app step.

ExpansionPanel vs. ExpansionTile

We’ve examined all the options that ExpansionPanel affords. Subsequent, let’s examine it with an identical widget and focus on when it’s essential to use every. Take a look at the next desk that compares ExpansionPanel with ExpansionTile:

Comparability issue ExpansionPanel ExpansionTile
Really useful mum or dad widget ExpansionPanelList solely ListView, Column, Drawer, or any container-type widget that may maintain single or a number of widgets
The supported means of including content material/physique Accepts a single widget (usually a ListTile) by way of the physique parameter Accepts a number of widgets (usually ListTiles) by way of the kids parameter
Pre-defined styling Doesn’t supply pre-defined styling for the header and content material  —  the developer has to make use of a ListTile widget for implementing an growth listing in response to the Materials specification. It additionally renders an arrow icon that isn’t customizable. Presents pre-defined styling for the header by letting builders set a title and subtitle, since this widget works as an extension of ListTile
Supported UI customizations Presents the header builder operate for dynamic rendering primarily based on the growth state. Unable to customise the arrow icon, however the default icon (ExpandIcon) adheres to the Materials specification. Capable of set customized growth icons, change the icon place, and add main/trailing widgets
Rendering with asynchronous information sources Doable with a FutureBuilder occasion, as common Doable with a FutureBuilder occasion, as common

In accordance with the above comparability, we will perceive that the ExpansionPanel is extra like a content material widget that customers can increase/collapse, so we will use it, for instance, to show extra particulars a few explicit product with out having to navigate to a second display screen. Additionally, you could simplify a fancy app display screen by grouping widgets with the ExpansionPanelRadio widget and displaying a single group of widgets at a time.

However, ExpansionTile is a widget appropriate for creating sublists as a result of you possibly can immediately use a number of ListTiles within the kids parameter. For instance, you could implement a settings panel or submenu with the ExpansionTile widget. See the flutter_settings_screens implementation to be taught extra about implementing settings panels with ExpansionTile.

Conclusion

On this tutorial, we discovered tips on how to use the ExpansionPanel widget in Flutter by modifying a sensible instance primarily based on numerous necessities. You should utilize this widget to create expandable particulars sections in response to the Materials Design specification.

ExpansionPanel fulfills the generic UI necessities of including an expandable listing, however as we seen within the comparability part, it has some limitations in comparison with the ExpansionTile. Nonetheless, it adheres to the Materials specification, so usually we don’t want superior customizations for ExpansionPanel because it affords an amazing, developer-friendly expandable listing design together with ListTile.

Should you face limitations with ExpansionPanel or ExpansionTile, you possibly can examine the flutter-expandable neighborhood bundle. It affords the mixed options of each ExpansionPanel and ExpansionTile in a extra versatile means.

Flutter affords a whole bunch of inbuilt widgets, and the Flutter improvement staff strives to enhance present widgets in response to developer suggestions, to allow them to introduce new options or enhancements that will end in deprecating various, community-based widgets. Subsequently, utilizing native widgets like ExpansionPanel and ExpansionTile makes your app steady and compliant with the Materials Design specification.

Strive implementing your subsequent app prototype’s expandable listing with ExpansionPanel!

: Full visibility into your net and cellular apps

LogRocket is a frontend utility monitoring resolution that permits you to replay issues as in the event that they occurred in your individual browser. As an alternative of guessing why errors occur, or asking customers for screenshots and log dumps, LogRocket helps you to replay the session to shortly perceive what went improper. It really works completely with any app, no matter framework, and has plugins to log further context from Redux, Vuex, and @ngrx/retailer.

Along with logging Redux actions and state, LogRocket information console logs, JavaScript errors, stacktraces, community requests/responses with headers + our bodies, browser metadata, and customized logs. It additionally devices the DOM to file the HTML and CSS on the web page, recreating pixel-perfect movies of even essentially the most complicated single-page net and cellular apps.

.

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

- Advertisment -
Google search engine

Most Popular

Recent Comments