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 theMerchandise
class, and the_steps
widget state variable holds all objects as a DartListing
- We use two parameters from the
ExpansionPanelList
class:kids
to set allExpansionPanel
cases by changing the_steps
listingexpansionCallback
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 utilizingTextual 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:
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:
- 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) - Wrapped the expandable listing with a
FutureBuilder
by making a second widget known asStepList
, which makes use of conditional rendering to show a round loading animation throughout the synthetic community latency - 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:
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:
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:
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:
Equally, you possibly can implement a button to break down all panels by setting the isExpanded
parameter to false
:
_steps[i].isExpanded = false;
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:
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:
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 ListTile s) 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 ListTile
s 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
!
LogRocket: 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.