- Install
- Set up an editor
- Test drive
- Write your first Flutter app, part 1
- Learn more
- Flutter for Android developers
- Flutter for iOS developers
- Flutter for React Native developers
- Flutter for web developers
- Flutter for Xamarin.Forms developers
- Introduction to declarative UI
- Cookbook
- Codelabs
- Tutorials
- User interface
- Introduction to widgets
- Layouts in Flutter
- Layout tutorial
- Dealing with box constraints
- Adding interactivity to your Flutter app
- Adding assets and images
- Navigation & routing
- Introduction to animations
- Animations overview
- Animations tutorial
- Hero Animations
- Staggered Animations
- Advanced UI
- Slivers
- Taps, drags, and other gestures
- Widget catalog
- Data & backend
- State management
- State management
- Start thinking declaratively
- Differentiate between ephemeral state and app state
- Simple app state management
- List of state management approaches
- JSON and serialization
- Firebase
- Accessibility & internationalization
- Accessibility
- Internationalizing Flutter apps
- Platform integration
- Writing custom platform-specific code
- Packages & plugins
- Using packages
- Developing packages & plugins
- Background processes
- Tools & techniques
- Android Studio / IntelliJ
- Visual Studio Code
- Upgrading Flutter
- Hot reload
- Code formatting
- Debugging Flutter apps
- Using OEM debuggers
- Flutter's build modes
- Testing Flutter apps
- Performance best practices
- Flutter performance profiling
- Creating flavors for Flutter
- Preparing an Android App for Release
- Preparing an iOS App for Release
- Continuous Delivery using fastlane with Flutter
- Bootstrap into Dart
- Inside Flutter
- Platform specific behaviors and adaptations
- Technical Overview
- Technical videos
- FAQ
- Flutter widget index
- Install
- Windows install
- MacOS install
- Linux install
- Set up an editor
- Write your first Flutter app, part 1
- Learn more
- Cupertino (iOS-style) widgets
- Layout widgets
- Animation and motion widgets
- Retrieve the value of a text field
- Basic widgets
- Material Components widgets
- Animate the properties of a Container
- Fade a Widget in and out
- Add a Drawer to a screen
- Displaying SnackBars
- Exporting fonts from a package
- Updating the UI based on orientation
- Using Themes to share colors and font styles
- Using custom fonts
- Working with Tabs
- Building a form with validation
- Create and style a text field
- Focus on a Text Field
- Handling changes to a text field
- Retrieve the value of a text field
- Adding Material Touch Ripples
- Handling Taps
- Implement Swipe to Dismiss
- Display images from the internet
- Fade in images with a placeholder
- Working with cached images
- Basic List
- Create a horizontal list
- Creating a Grid List
- Creating lists with different types of items
- Place a floating app bar above a list
- Working with long lists
- Report errors to a service
- Animating a Widget across screens
- Navigate to a new screen and back
- Navigate with named routes
- Pass arguments to a named route
- Return data from a screen
- Send data to a new screen
- Fetch data from the internet
- Making authenticated requests
- Parsing JSON in the background
- Working with WebSockets
- Persist data with SQLite
- Reading and Writing Files
- Storing key-value data on disk
- Play and pause a video
- Take a picture using the Camera
- An introduction to integration testing
- Performance profiling
- Scrolling
- An introduction to unit testing
- Mock dependencies using Mockito
- An introduction to widget testing
- Finding widgets
- Tapping, dragging and entering text
- Development
- Introduction to widgets
- Layout tutorial
- Dealing with box constraints
- Adding interactivity to your Flutter app
- Adding assets and images
- Navigation & routing
- Navigate to a new screen and back
- Send data to a new screen
- Return data from a screen
- Navigate with named routes
- Animating a Widget across screens
- AnimatedList
- Sample App Catalog
- Animations overview
- Animations tutorial
- Staggered Animations
- Slivers
- Taps, drags, and other gestures
- Accessibility widgets
- Assets, images, and icon widgets
- Async widgets
- Input widgets
- Interaction model widgets
- Painting and effect widgets
- Scrolling widgets
- Styling widgets
- Text widgets
- State management
- Start thinking declaratively
- Differentiate between ephemeral state and app state
- Simple app state management
- List of state management approaches
- JSON and serialization
- Accessibility
- Internationalizing Flutter apps
- Writing custom platform-specific code
- Using packages
- Fetch data from the internet
- Developing packages & plugins
- Background processes
- Android Studio / IntelliJ
- Set up an editor
- Flutter inspector
- Creating Useful Bug Reports
- Visual Studio Code
- Set up an editor
- Upgrading Flutter
- Hot reload
- Code formatting
Adding interactivity to your Flutter app
How do you modify your app to make it react to user input? In this tutorial, you’ll add interactivity to an app that contains only non-interactive widgets. Specifically, you’ll modify an icon to make it tappable by creating a custom stateful widget that manages two stateless widgets.
Layout tutorial showed you how to create the layout for the following screenshot.
When the app first launches, the star is solid red, indicating that this lake has previously been favorited. The number next to the star indicates that 41 people have favorited this lake. After completing this tutorial, tapping the star removes its favorited status, replacing the solid star with an outline and decreasing the count. Tapping again favorites the lake, drawing a solid star and increasing the count.
To accomplish this, you’ll create a single custom widget that includes both the star and the count, which are themselves widgets. Tapping the star changes state for both widgets, so the same widget should manage both.
You can get right to touching the code in Step 2: Subclass StatefulWidget. If you want to try different ways of managing state, skip to Managing state.
Stateful and stateless widgets
A widget is either stateful or stateless. If a widget can change—when a user interacts with it, for example—it’s stateful.
A stateless widget never changes. Icon, IconButton, and Text are examples of stateless widgets. Stateless widgets subclass StatelessWidget.
A stateful widget is dynamic: for example, it can change its appearance in response to events triggered by user interactions or when it receives data. Checkbox, Radio, Slider, InkWell, Form, and TextField are examples of stateful widgets. Stateful widgets subclass StatefulWidget.
A widget’s state is stored in a State object, separating the widget’s state from its appearance. The state consists of values that can change, like a slider’s current value or whether a checkbox is checked. When the widget’s state changes, the state object calls setState()
, telling the framework to redraw the widget.
Creating a stateful widget
In this section, you’ll create a custom stateful widget. You’ll replace two stateless widgets—the solid red star and the numeric count next to it—with a single custom stateful widget that manages a row with two children widgets: an IconButton
and Text
.
Implementing a custom stateful widget requires creating two classes:
- A subclass of
StatefulWidget
that defines the widget. - A subclass of
State
that contains the state for that widget and defines the widget’sbuild()
method.
This section shows you how to build a stateful widget, called FavoriteWidget
, for the lakes app. After setting up, your first step is choosing how state is managed for FavoriteWidget
.
Step 0: Get ready
If you’ve already built the app in Layout tutorial (step 6), skip to the next section.
- Make sure you’ve set up your environment.
- Create a basic “Hello World” Flutter app.
- Replace the
lib/main.dart
file with main.dart. - Replace the
pubspec.yaml
file with pubspec.yaml. - Create an
images
directory in your project, and add lake.jpg.
Once you have a connected and enabled device, or you’ve launched the iOS simulator (part of the Flutter install), you are good to go!
Step 1: Decide which object manages the widget’s state
A widget’s state can be managed in several ways, but in our example the widget itself, FavoriteWidget
, will manage its own state. In this example, toggling the star is an isolated action that doesn’t affect the parent widget or the rest of the UI, so the widget can handle its state internally.
Learn more about the separation of widget and state, and how state might be managed, in Managing state.
Step 2: Subclass StatefulWidget
The FavoriteWidget
class manages its own state, so it overrides createState()
to create a State
object. The framework calls createState()
when it wants to build the widget. In this example, createState()
returns an instance of _FavoriteWidgetState
, which you’ll implement in the next step.
class FavoriteWidget extends StatefulWidget { @override _FavoriteWidgetState createState() => _FavoriteWidgetState(); }
Step 3: Subclass State
The _FavoriteWidgetState
class stores the mutable data that can change over the lifetime of the widget. When the app first launches, the UI displays a solid red star, indicating that the lake has “favorite” status, along with 41 likes. These values are stored in the _isFavorited
and _favoriteCount
fields:
class _FavoriteWidgetState extends State<FavoriteWidget> { bool _isFavorited = true; int _favoriteCount = 41; // ··· }
The class also defines a build()
method, which creates a row containing a red IconButton
, and Text
. You use IconButton (instead of Icon
) because it has an onPressed
property that defines the callback function (_toggleFavorite
) for handling a tap. You’ll define the callback function next.
class _FavoriteWidgetState extends State<FavoriteWidget> { // ··· @override Widget build(BuildContext context) { return Row( mainAxisSize: MainAxisSize.min, children: [ Container( padding: EdgeInsets.all(0), child: IconButton( icon: (_isFavorited ? Icon(Icons.star) : Icon(Icons.star_border)), color: Colors.red[500], onPressed: _toggleFavorite, ), ), SizedBox( width: 18, child: Container( child: Text('$_favoriteCount'), ), ), ], ); } }
The _toggleFavorite()
method, which is called when the IconButton
is pressed, calls setState()
. Calling setState()
is critical, because this tells the framework that the widget’s state has changed and that the widget should be redraw. The function argument to setState()
toggles the UI between these two states:
- A
star
icon and the number 41 - A
star_border
icon and the number 40
void _toggleFavorite() { setState(() { if (_isFavorited) { _favoriteCount -= 1; _isFavorited = false; } else { _favoriteCount += 1; _isFavorited = true; } }); }
Step 4: Plug the stateful widget into the widget tree
Add your custom stateful widget to the widget tree in the app’s build()
method. First, locate the code that creates the Icon
and Text
, and delete it. In the same location, create the stateful widget:
@@ -10,2 +5,2 @@ | |
10 5 | class MyApp extends StatelessWidget { |
11 6 | @override |
@@ -38,11 +33,7 @@ | |
38 33 | ], |
39 34 | ), |
40 35 | ), |
41 | - |
36 | + FavoriteWidget(), |
42 | - Icons.star, |
43 | - color: Colors.red[500], |
44 | - ), |
45 | - Text('41'), |
46 37 | ], |
47 38 | ), |
48 39 | ); |
@@ -117,3 +108,3 @@ | |
117 108 | ); |
118 109 | } |
119 110 | } |
That’s it! When you hot reload the app, the star icon should now respond to taps.
Problems?
If you can’t get your code to run, look in your IDE for possible errors. Debugging Flutter Apps might help. If you still can’t find the problem, check your code against the interactive lakes example on GitHub.
If you still have questions, refer to any one of the developer community channels.
The rest of this page covers several ways a widget’s state can be managed, and lists other available interactive widgets.
Managing state
Who manages the stateful widget’s state? The widget itself? The parent widget? Both? Another object? The answer is… it depends. There are several valid ways to make your widget interactive. You, as the widget designer, make the decision based on how you expect your widget to be used. Here are the most common ways to manage state:
How do you decide which approach to use? The following principles should help you decide:
If the state in question is user data, for example the checked or unchecked mode of a checkbox, or the position of a slider, then the state is best managed by the parent widget.
If the state in question is aesthetic, for example an animation, then the state is best managed by the widget itself.
If in doubt, start by managing state in the parent widget.
We’ll give examples of the different ways of managing state by creating three simple examples: TapboxA, TapboxB, and TapboxC. The examples all work similarly—each creates a container that, when tapped, toggles between a green or grey box. The _active
boolean determines the color: green for active or grey for inactive.
These examples use GestureDetector to capture activity on the Container.
The widget manages its own state
Sometimes it makes the most sense for the widget to manage its state internally. For example, ListView automatically scrolls when its content exceeds the render box. Most developers using ListView don’t want to manage ListView’s scrolling behavior, so ListView itself manages its scroll offset.
The _TapboxAState
class:
- Manages state for
TapboxA
. - Defines the
_active
boolean which determines the box’s current color. - Defines the
_handleTap()
function, which updates_active
when the box is tapped and calls thesetState()
function to update the UI. - Implements all interactive behavior for the widget.
// TapboxA manages its own state. //------------------------- TapboxA ---------------------------------- class TapboxA extends StatefulWidget { TapboxA({Key key}) : super(key: key); @override _TapboxAState createState() => _TapboxAState(); } class _TapboxAState extends State<TapboxA> { bool _active = false; void _handleTap() { setState(() { _active = !_active; }); } Widget build(BuildContext context) { return GestureDetector( onTap: _handleTap, child: Container( child: Center( child: Text( _active ? 'Active' : 'Inactive', style: TextStyle(fontSize: 32.0, color: Colors.white), ), ), width: 200.0, height: 200.0, decoration: BoxDecoration( color: _active ? Colors.lightGreen[700] : Colors.grey[600], ), ), ); } } //------------------------- MyApp ---------------------------------- class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', home: Scaffold( appBar: AppBar( title: Text('Flutter Demo'), ), body: Center( child: TapboxA(), ), ), ); } }
The parent widget manages the widget’s state
Often it makes the most sense for the parent widget to manage the state and tell its child widget when to update. For example, IconButton allows you to treat an icon as a tappable button. IconButton is a stateless widget because we decided that the parent widget needs to know whether the button has been tapped, so it can take appropriate action.
In the following example, TapboxB exports its state to its parent through a callback. Because TapboxB doesn’t manage any state, it subclasses StatelessWidget.
The ParentWidgetState class:
- Manages the
_active
state for TapboxB. - Implements
_handleTapboxChanged()
, the method called when the box is tapped. - When the state changes, calls
setState()
to update the UI.
The TapboxB class:
- Extends StatelessWidget because all state is handled by its parent.
- When a tap is detected, it notifies the parent.
// ParentWidget manages the state for TapboxB. //------------------------ ParentWidget -------------------------------- class ParentWidget extends StatefulWidget { @override _ParentWidgetState createState() => _ParentWidgetState(); } class _ParentWidgetState extends State<ParentWidget> { bool _active = false; void _handleTapboxChanged(bool newValue) { setState(() { _active = newValue; }); } @override Widget build(BuildContext context) { return Container( child: TapboxB( active: _active, onChanged: _handleTapboxChanged, ), ); } } //------------------------- TapboxB ---------------------------------- class TapboxB extends StatelessWidget { TapboxB({Key key, this.active: false, @required this.onChanged}) : super(key: key); final bool active; final ValueChanged<bool> onChanged; void _handleTap() { onChanged(!active); } Widget build(BuildContext context) { return GestureDetector( onTap: _handleTap, child: Container( child: Center( child: Text( active ? 'Active' : 'Inactive', style: TextStyle(fontSize: 32.0, color: Colors.white), ), ), width: 200.0, height: 200.0, decoration: BoxDecoration( color: active ? Colors.lightGreen[700] : Colors.grey[600], ), ), ); } }
A mix-and-match approach
For some widgets, a mix-and-match approach makes the most sense. In this scenario, the stateful widget manages some of the state, and the parent widget manages other aspects of the state.
In the TapboxC
example, on tap down, a dark green border appears around the box. On tap up, the border disappears and the box’s color changes. TapboxC
exports its _active
state to its parent but manages its _highlight
state internally. This example has two State objects, _ParentWidgetState
and _TapboxCState
.
The _ParentWidgetState
object:
- Manages the
_active
state. - Implements
_handleTapboxChanged()
, the method called when the box is tapped. - Calls
setState()
to update the UI when a tap occurs and the_active
state changes.
The _TapboxCState
object:
- Manages the
_highlight
state. - The
GestureDetector
listens to all tap events. As the user taps down, it adds the highlight (implemented as a dark green border). As the user releases the tap, it removes the highlight. - Calls
setState()
to update the UI on tap down, tap up, or tap cancel, and the_highlight
state changes. - On a tap event, passes that state change to the parent widget to take appropriate action using the widget property.
//---------------------------- ParentWidget ---------------------------- class ParentWidget extends StatefulWidget { @override _ParentWidgetState createState() => _ParentWidgetState(); } class _ParentWidgetState extends State<ParentWidget> { bool _active = false; void _handleTapboxChanged(bool newValue) { setState(() { _active = newValue; }); } @override Widget build(BuildContext context) { return Container( child: TapboxC( active: _active, onChanged: _handleTapboxChanged, ), ); } } //----------------------------- TapboxC ------------------------------ class TapboxC extends StatefulWidget { TapboxC({Key key, this.active: false, @required this.onChanged}) : super(key: key); final bool active; final ValueChanged<bool> onChanged; _TapboxCState createState() => _TapboxCState(); } class _TapboxCState extends State<TapboxC> { bool _highlight = false; void _handleTapDown(TapDownDetails details) { setState(() { _highlight = true; }); } void _handleTapUp(TapUpDetails details) { setState(() { _highlight = false; }); } void _handleTapCancel() { setState(() { _highlight = false; }); } void _handleTap() { widget.onChanged(!widget.active); } Widget build(BuildContext context) { // This example adds a green border on tap down. // On tap up, the square changes to the opposite state. return GestureDetector( onTapDown: _handleTapDown, // Handle the tap events in the order that onTapUp: _handleTapUp, // they occur: down, up, tap, cancel onTap: _handleTap, onTapCancel: _handleTapCancel, child: Container( child: Center( child: Text(widget.active ? 'Active' : 'Inactive', style: TextStyle(fontSize: 32.0, color: Colors.white)), ), width: 200.0, height: 200.0, decoration: BoxDecoration( color: widget.active ? Colors.lightGreen[700] : Colors.grey[600], border: _highlight ? Border.all( color: Colors.teal[700], width: 10.0, ) : null, ), ), ); } }
An alternate implementation might have exported the highlight state to the parent while keeping the active state internal, but if you asked someone to use that tap box, they’d probably complain that it doesn’t make much sense. The developer cares whether the box is active. The developer probably doesn’t care how the highlighting is managed, and prefers that the tap box handles those details.
Other interactive widgets
Flutter offers a variety of buttons and similar interactive widgets. Most of these widgets implement the Material Design guidelines, which define a set of components with an opinionated UI.
If you prefer, you can use GestureDetector to build interactivity into any custom widget. You can find examples of GestureDetector in Managing state, and in the Flutter Gallery.
When you need interactivity, it’s easiest to use one of the prefabricated widgets. Here’s a partial list:
Standard widgets
Material Components
- Checkbox
- DropdownButton
- FlatButton
- FloatingActionButton
- IconButton
- Radio
- RaisedButton
- Slider
- Switch
- TextField
Resources
The following resources may help when adding interactivity to your app.
- Gestures, a section in Introduction to widgets
How to create a button and make it respond to input. - Gestures in Flutter
A description of Flutter’s gesture mechanism. - Flutter API documentation
Reference documentation for all of the Flutter libraries. - Flutter Gallery
Demo app showcasing many Material Components and other Flutter features. - Flutter’s Layered Design (video)
This video includes information about state and stateless widgets. Presented by Google engineer, Ian Hickson.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论