在 Flutter 移动应用程序中创建一个列表
Flutter

> 
> 了解如何创建 Flutter 应用的界面以及如何在它们之间进行数据传递。
> 
> 
> 


![](/data/attachment/album/202105/31/201442luk1u6vqz3h3k8jn.jpg)


Flutter 是一个流行的开源工具包，它可用于构建跨平台的应用。在文章《[用 Flutter 创建移动应用](/article-12693-1.html)》中，我已经向大家展示了如何在 Linux 中安装 [Flutter](https://flutter.dev/) 并创建你的第一个应用。而这篇文章，我将向你展示如何在你的应用中添加一个列表，点击每一个列表项可以打开一个新的界面。这是移动应用的一种常见设计方法，你可能以前见过的，下面有一个截图，能帮助你对它有一个更直观的了解：


![测试 Flutter 应用](/data/attachment/album/202105/31/201524m9453rl9mtmdzx99.gif "Testing the Flutter app")


Flutter 使用 [Dart](https://dart.dev/) 语言。在下面的一些代码片段中，你会看到以斜杠开头的语句。两个斜杠（`//`）是指代码注释，用于解释某些代码片段。三个斜杠（`///`）则表示的是 Dart 的文档注释，用于解释 Dart 类和类的属性，以及其他的一些有用的信息。


### 查看Flutter应用的主要部分


Flutter 应用的典型入口点是 `main()` 函数，我们通常可以在文件 `lib/main.dart` 中找到它:



```
void main() {
 runApp(MyApp());
}

```

应用启动时，`main()` 会被调用，然后执行 `MyApp()`。 `MyApp` 是一个无状态微件（`StatelessWidget`），它包含了`MaterialApp()` 微件中所有必要的应用设置（应用的主题、要打开的初始页面等）：



```
class MyApp extends StatelessWidget {
 @override
 Widget build(BuildContext context) {
   return MaterialApp(
     title: 'Flutter Demo',
     theme: ThemeData(
       primarySwatch: Colors.blue,
       visualDensity: VisualDensity.adaptivePlatformDensity,
     ),
     home: MyHomePage(title: 'Flutter Demo Home Page'),
   );
 }
}

```

生成的 `MyHomePage()` 是应用的初始页面，是一个有状态的微件，它包含包含可以传递给微件构造函数参数的变量（从上面的代码看，我们传了一个 `title` 变量给初始页面的构造函数）：



```
class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

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

```

有状态微件（`StatefulWidget`）表示这个微件可以拥有自己的状态：`_MyHomePageState`。调用 `_MyHomePageState` 中的 `setState()` 方法，可以重新构建用户界面：



```
class _MyHomePageState extends State<MyHomePage> {
 int _counter = 0; // Number of taps on + button.

 void _incrementCounter() { // Increase number of taps and update UI by calling setState().
   setState(() {
     _counter++;
   });
 }
 ...
}

```

不管是有状态的，还是无状态的微件，它们都有一个 `build()` 方法，该方法负责微件的 UI 外观。



```
@override
Widget build(BuildContext context) {
 return Scaffold( // Page widget.
   appBar: AppBar( // Page app bar with title and back button if user can return to previous screen.
     title: Text(widget.title), // Text to display page title.
   ),
   body: Center( // Widget to center child widget.
     child: Column( // Display children widgets in column.
       mainAxisAlignment: MainAxisAlignment.center,
       children: <Widget>[
         Text( // Static text.
           'You have pushed the button this many times:',
         ),
         Text( // Text with our taps number.
           '$_counter', // $ sign allows us to use variables inside a string.
           style: Theme.of(context).textTheme.headline4,// Style of the text, “Theme.of(context)” takes our context and allows us to access our global app theme.
         ),
       ],
     ),
   ),
        // Floating action button to increment _counter number.
   floatingActionButton: FloatingActionButton(
     onPressed: _incrementCounter,
     tooltip: 'Increment',
     child: Icon(Icons.add),
   ),
 );
}

```

### 修改你的应用


一个好的做法是，把 `main()` 方法和其他页面的代码分开放到不同的文件中。要想将它们分开，你需要右击 `lib` 目录，然后选择 “New > Dart File” 来创建一个 .dart 文件：


![创建一个新的 Dart 文件](/data/attachment/album/202105/31/201524vap110sxfx80p21z.png "Create a new Dart file")


将新建的文件命名为 `items_list_page`。


切换回到 `main.dart` 文件，将 `MyHomePage` 和 `_MyHomePageState` 中的代码，剪切并粘贴到我们新建的文件。然后将光标放到 `StatefulWidget` 上（下面红色的下划线处）, 按 `Alt+Enter` 后出现下拉列表，然后选择 `package:flutter/material.dart`：


![导入 Flutter 包](/data/attachment/album/202105/31/201524zlsefqh50efa8g5a.png "Importing Flutter package")


经过上面的操作我们将 `flutter/material.dart` 包添加到了 `main.dart` 文件中，这样我们就可以使用 Flutter 提供的默认的 material 主题微件。


然后, 在类名 `MyHomePage` 右击，“Refactor > Rename...”将其重命名为 `ItemsListPage`：


![重命名 StatefulWidget 类](/data/attachment/album/202105/31/201525tzuhx5xtxk4tp99s.png "Renaming StatefulWidget class")


Flutter 识别到你重命名了 `StatefulWidget` 类，它会自动将它的 `State` 类也跟着重命名:


![State 类被自动重命名](/data/attachment/album/202105/31/201525d6w2q2kiiijzni0w.png "State class renamed automatically")


回到 `main.dart` 文件，将文件名 `MyHomePage` 改为 `ItemsListPage`。 一旦你开始输入, 你的 Flutter 集成开发环境（可能是 IntelliJ IDEA 社区版、Android Studio 和 VS Code 或 [VSCodium](https://opensource.com/article/20/6/open-source-alternatives-vs-code)），会给出自动代码补完的建议。


![IDE 建议自动补完的代码](/data/attachment/album/202105/31/201525xbzpb6bwwf595clw.png "IDE suggests autocompleting code")


按回车键即可完成输入，缺失的导入语句会被自动添加到文件的顶部。


![添加缺失的导入语句](/data/attachment/album/202105/31/201526erzso7rg6g0sdsqk.png "Adding missing import ")


到此，你已经完成了初始设置。现在你需要在 `lib` 目录创建一个新的 .dart 文件，命名为 `item_model`。（注意，类命是大写驼峰命名，一般的文件名是下划线分割的命名。）然后粘贴下面的代码到新的文件中：



```
/// Class that stores list item info:
/// [id] - unique identifier, number.
/// [icon] - icon to display in UI.
/// [title] - text title of the item.
/// [description] - text description of the item.
class ItemModel {
 // class constructor
 ItemModel(this.id, this.icon, this.title, this.description);

 // class fields
 final int id;
 final IconData icon;
 final String title;
 final String description;
}

```

回到 `items_list_page.dart` 文件，将已有的 `_ItemsListPageState` 代码替换为下面的代码:



```
class _ItemsListPageState extends State<ItemsListPage> {

// Hard-coded list of [ItemModel] to be displayed on our page.
 final List<ItemModel> _items = [
   ItemModel(0, Icons.account_balance, 'Balance', 'Some info'),
   ItemModel(1, Icons.account_balance_wallet, 'Balance wallet', 'Some info'),
   ItemModel(2, Icons.alarm, 'Alarm', 'Some info'),
   ItemModel(3, Icons.my_location, 'My location', 'Some info'),
   ItemModel(4, Icons.laptop, 'Laptop', 'Some info'),
   ItemModel(5, Icons.backup, 'Backup', 'Some info'),
   ItemModel(6, Icons.settings, 'Settings', 'Some info'),
   ItemModel(7, Icons.call, 'Call', 'Some info'),
   ItemModel(8, Icons.restore, 'Restore', 'Some info'),
   ItemModel(9, Icons.camera_alt, 'Camera', 'Some info'),
 ];

 @override
 Widget build(BuildContext context) {
   return Scaffold(
       appBar: AppBar(
         title: Text(widget.title),
       ),
       body: ListView.builder( // Widget which creates [ItemWidget] in scrollable list.
         itemCount: _items.length, // Number of widget to be created.
         itemBuilder: (context, itemIndex) => // Builder function for every item with index.
             ItemWidget(_items[itemIndex], () {
           _onItemTap(context, itemIndex);
         }),
       ));
 }

 // Method which uses BuildContext to push (open) new MaterialPageRoute (representation of the screen in Flutter navigation model) with ItemDetailsPage (StateFullWidget with UI for page) in builder.
 _onItemTap(BuildContext context, int itemIndex) {
   Navigator.of(context).push(MaterialPageRoute(
       builder: (context) => ItemDetailsPage(_items[itemIndex])));
 }
}


// StatelessWidget with UI for our ItemModel-s in ListView.
class ItemWidget extends StatelessWidget {
 const ItemWidget(this.model, this.onItemTap, {Key key}) : super(key: key);

 final ItemModel model;
 final Function onItemTap;

 @override
 Widget build(BuildContext context) {
   return InkWell( // Enables taps for child and add ripple effect when child widget is long pressed.
     onTap: onItemTap,
     child: ListTile( // Useful standard widget for displaying something in ListView.
       leading: Icon(model.icon),
       title: Text(model.title),
     ),
   );
 }
}

```

为了提高代码的可读性，可以考虑将 `ItemWidget` 作为一个单独的文件放到 `lib` 目录中。


现在唯一缺少的是 `ItemDetailsPage` 类。在 `lib` 目录中我们创建一个新文件并命名为 `item_details_page`。然后将下面的代码拷贝进去：



```
import 'package:flutter/material.dart';

import 'item_model.dart';

/// Widget for displaying detailed info of [ItemModel]
class ItemDetailsPage extends StatefulWidget {
 final ItemModel model;

 const ItemDetailsPage(this.model, {Key key}) : super(key: key);

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

class _ItemDetailsPageState extends State<ItemDetailsPage> {
 @override
 Widget build(BuildContext context) {
   return Scaffold(
     appBar: AppBar(
       title: Text(widget.model.title),
     ),
     body: Center(
       child: Column(
         children: [
           const SizedBox(height: 16),
           Icon(
             widget.model.icon,
             size: 100,
           ),
           const SizedBox(height: 16),
           Text(
             'Item description: ${widget.model.description}',
             style: TextStyle(fontSize: 18),
           )
         ],
       ),
     ),
   );
 }
}

```

上面的代码几乎没什么新东西，不过要注意的是 `_ItemDetailsPageState` 里使用了 `widget.item.title` 这样的语句，它让我们可以从有状态类中引用到其对应的微件（`StatefulWidget`）。


### 添加一些动画


现在让我们来添加一些基础的动画：


1. 找到 `ItemWidget` 代码块（或者文件）
2. 将光标放到 `build()` 方法中的 `Icon()` 微件上
3. 按 `Alt+Enter`，然后选择“Wrap with widget...”


![查看微件选项](/data/attachment/album/202105/31/201526rrcimnffoz2bfz05.png "Wrap with widget option")


输入 `Hero`，然后从建议的下拉列表中选择 `Hero((Key key, @required this, tag, this.create))`：


![查找 Hero 微件](/data/attachment/album/202105/31/201526c33izlcl6gklkyet.png "Finding the Hero widget")


下一步, 给 Hero 微件添加 `tag` 属性 `tag: model.id`：


![在 Hero 微件上添加 tag 属性为 model.id](/data/attachment/album/202105/31/201527u3ifl1y4uxu1sevv.png "Adding the tag property model.id to the Hero widget")


最后我们在 `item_details_page.dart` 文件中做相同的修改：


![修改item_details_page.dart文件](/data/attachment/album/202105/31/201527t90kkp90mm6op9wx.png "Changing item_details_page.dart file")


前面的步骤，其实我们是用 `Hero()` 微件对 `Icon()` 微件进行了封装。还记得吗？前面我们定义 `ItemModel` 类时，定义了一个 `id field`，但没有在任何地方使用到。因为 Hero 微件会为其每个子微件添加一个唯一的标签。当 Hero 检测到不同页面（`MaterialPageRoute`）中存在相同标签的 Hero 时，它会自动在这些不同的页面中应用过渡动画。


可以在安卓模拟器或物理设备上运行我们的应用来测试这个动画。当你打开或者关闭列表项的详情页时，你会看到一个漂亮的图标动画：


![测试 Flutter 应用](/data/attachment/album/202105/31/201524m9453rl9mtmdzx99.gif "Testing the Flutter app")


### 收尾


这篇教程，让你学到了:


* 一些符合标准的，且能用于自动创建应用的组件。
* 如何添加多个页面以及在页面间传递数据。
* 如何给多个页面添加简单的动画。


如果你想了解更多，查看 Flutter 的 [文档](https://flutter.dev/docs)（有一些视频和样例项目的链接，还有一些创建 Flutter 应用的“秘方”）与 [源码](https://github.com/flutter)，源码的开源许可证是 BSD 3。




---


via: <https://opensource.com/article/20/11/flutter-lists-mobile-app>


作者：[Vitaly Kuprenko](https://opensource.com/users/kooper) 选题：[lujun9972](https://github.com/lujun9972) 译者：[ywxgod](https://github.com/ywxgod) 校对：[wxy](https://github.com/wxy)


本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


OK
Flutter is a popular open source toolkit for building cross-platform apps. In "[Create a mobile app with Flutter](https://opensource.com/article/20/9/mobile-app-flutter)," I demonstrated how to install [Flutter](https://flutter.dev/) on Linux and create your first app. In this article, I'll show you how to add a list of items in your app, with each item opening a new screen. This is a common design method for mobile apps, so you've probably seen it before, but here's a screenshot to help you visualize it:

![Testing the Flutter app Testing the Flutter app](https://opensource.com/sites/default/files/uploads/flutter_test.gif)

(Vitaly Kuprenko, CC BY-SA 4.0)


Flutter uses the [Dart](https://dart.dev/) language. In some of the code snippets below, you'll see statements beginning with slashes. Two slashes (`/ /`

) is for code comments, which explain certain pieces of code. Three slashes (`/ / /`

) denotes Dart's documentation comments, which explain Dart classes and their properties and other useful information.

## Examine a Flutter app's main parts

A typical entry point for a Flutter application is a `main()`

function, usually found in a file called `lib/main.dart`

:

```
void main() {
runApp(MyApp());
}
```


This method is called when the app is launched. It runs `MyApp()`

, a StatelessWidget containing all necessary app settings in the `MaterialApp()`

widget (app theme, initial page to open, and so on):

```
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
```


The initial page generated is called `MyHomePage()`

. It's a stateful widget that contains variables that can be passed to a widget constructor parameter (take a look at the code above, where you pass the variable `title`

to the page constructor):

```
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
```


StatefulWidget means that this page has its own state: `_MyHomePageState`

. It lets you call the `setState()`

method there to rebuild the page's user interface (UI):

```
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0; // Number of taps on + button.
void _incrementCounter() { // Increase number of taps and update UI by calling setState().
setState(() {
_counter++;
});
}
...
}
```


A `build()`

function in stateful and stateless widgets is responsible for UI appearance:

```
@override
Widget build(BuildContext context) {
return Scaffold( // Page widget.
appBar: AppBar( // Page app bar with title and back button if user can return to previous screen.
title: Text(widget.title), // Text to display page title.
),
body: Center( // Widget to center child widget.
child: Column( // Display children widgets in column.
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text( // Static text.
'You have pushed the button this many times:',
),
Text( // Text with our taps number.
'$_counter', // $ sign allows us to use variables inside a string.
style: Theme.of(context).textTheme.headline4,// Style of the text, “Theme.of(context)” takes our context and allows us to access our global app theme.
),
],
),
),
// Floating action button to increment _counter number.
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
```


## Modify your app

It's good practice to separate the `main()`

method and other pages' code into different files. To do so, you need to create a new .dart file by right-clicking on the **lib** folder then selecting **New > Dart File**:

![Create a new Dart file Create a new Dart file](https://opensource.com/sites/default/files/uploads/flutter_new-dart-file_0.png)

(Vitaly Kuprenko, CC BY-SA 4.0)

Name the file `items_list_page`

.

Switch back to your `main.dart`

file, cut the `MyHomePage`

and `_MyHomePageState`

code, and paste it into your new file. Next, set your cursor on `StatefulWidget`

(underlined below in red), press Alt+Enter, and select `package:flutter/material.dart`

:

![Importing Flutter package Importing Flutter package](https://opensource.com/sites/default/files/uploads/flutter_import-package.png)

(Vitaly Kuprenko, CC BY-SA 4.0)

This adds `flutter/material.dart`

to your file so that you can use the default material widgets Flutter provides.

Then, right-click on **MyHomePage class > Refactor > Rename…** and rename this class to `ItemsListPage`

:

![Renaming a class Renaming StatefulWidget class](https://opensource.com/sites/default/files/uploads/flutter_rename-class.png)

(Vitaly Kuprenko, CC BY-SA 4.0)

Flutter recognizes that you renamed the StatefulWidget class and automatically renames its State class:

![State class renamed automatically State class renamed automatically](https://opensource.com/sites/default/files/uploads/flutter_stateclassrenamed.png)

(Vitaly Kuprenko, CC BY-SA 4.0)

Return to the `main.dart`

file and change the name `MyHomePage`

to `ItemsListPage`

. Once you start typing, your Flutter integrated development environment (probably IntelliJ IDEA Community Edition, Android Studio, and VS Code or [VSCodium](https://opensource.com/article/20/6/open-source-alternatives-vs-code)) suggests how to autocomplete your code:

![IDE suggests autocompleting code IDE suggests autocompleting code](https://opensource.com/sites/default/files/uploads/flutter_autocomplete.png)

(Vitaly Kuprenko, CC BY-SA 4.0)

Press Enter to complete your input. It will add the missing import to the top of the file automatically:

![Importing Adding missing import](https://opensource.com/sites/default/files/uploads/flutter_import-input.png)

(Vitaly Kuprenko, CC BY-SA 4.0)

You've completed your initial setup. Now you need to create a new .dart file in the **lib** folder and name it `item_model`

. (Note that classes have UpperCamelCase names, but files have snake_case names.) Paste this code into the new file:

```
/// Class that stores list item info:
/// [id] - unique identifier, number.
/// [icon] - icon to display in UI.
/// [title] - text title of the item.
/// [description] - text description of the item.
class ItemModel {
// class constructor
ItemModel(this.id, this.icon, this.title, this.description);
// class fields
final int id;
final IconData icon;
final String title;
final String description;
}
```


Return to `items_list_page.dart`

, and replace the existing `_ItemsListPageState`

code with:

```
class _ItemsListPageState extends State<ItemsListPage> {
// Hard-coded list of [ItemModel] to be displayed on our page.
final List<ItemModel> _items = [
ItemModel(0, Icons.account_balance, 'Balance', 'Some info'),
ItemModel(1, Icons.account_balance_wallet, 'Balance wallet', 'Some info'),
ItemModel(2, Icons.alarm, 'Alarm', 'Some info'),
ItemModel(3, Icons.my_location, 'My location', 'Some info'),
ItemModel(4, Icons.laptop, 'Laptop', 'Some info'),
ItemModel(5, Icons.backup, 'Backup', 'Some info'),
ItemModel(6, Icons.settings, 'Settings', 'Some info'),
ItemModel(7, Icons.call, 'Call', 'Some info'),
ItemModel(8, Icons.restore, 'Restore', 'Some info'),
ItemModel(9, Icons.camera_alt, 'Camera', 'Some info'),
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: ListView.builder( // Widget which creates [ItemWidget] in scrollable list.
itemCount: _items.length, // Number of widget to be created.
itemBuilder: (context, itemIndex) => // Builder function for every item with index.
ItemWidget(_items[itemIndex], () {
_onItemTap(context, itemIndex);
}),
));
}
// Method which uses BuildContext to push (open) new MaterialPageRoute (representation of the screen in Flutter navigation model) with ItemDetailsPage (StateFullWidget with UI for page) in builder.
_onItemTap(BuildContext context, int itemIndex) {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => ItemDetailsPage(_items[itemIndex])));
}
}
// StatelessWidget with UI for our ItemModel-s in ListView.
class ItemWidget extends StatelessWidget {
const ItemWidget(this.model, this.onItemTap, {Key key}) : super(key: key);
final ItemModel model;
final Function onItemTap;
@override
Widget build(BuildContext context) {
return InkWell( // Enables taps for child and add ripple effect when child widget is long pressed.
onTap: onItemTap,
child: ListTile( // Useful standard widget for displaying something in ListView.
leading: Icon(model.icon),
title: Text(model.title),
),
);
}
}
```


Consider moving `ItemWidget`

to a separate file in the **lib** folder to improve the readability of your code.

The only thing missing is the `ItemDetailsPage`

class. Create a new file in the **lib** folder and name it `item_details_page`

. Then copy and paste this code there:

```
import 'package:flutter/material.dart';
import 'item_model.dart';
/// Widget for displaying detailed info of [ItemModel]
class ItemDetailsPage extends StatefulWidget {
final ItemModel model;
const ItemDetailsPage(this.model, {Key key}) : super(key: key);
@override
_ItemDetailsPageState createState() => _ItemDetailsPageState();
}
class _ItemDetailsPageState extends State<ItemDetailsPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.model.title),
),
body: Center(
child: Column(
children: [
const SizedBox(height: 16),
Icon(
widget.model.icon,
size: 100,
),
const SizedBox(height: 16),
Text(
'Item description: ${widget.model.description}',
style: TextStyle(fontSize: 18),
)
],
),
),
);
}
}
```


Almost nothing new here. Notice that `_ItemDetailsPageState`

is using the `widget.item.title`

code. It enables referring to the `StatefulWidget`

fields in its `State`

class.

## Add some animation

Now, it's time to add some basic animation:

- Go to
`ItemWidget`

code. - Put the cursor on the
`Icon()`

widget in the`build()`

method. - Press Alt+Enter and select "Wrap with widget…"

![Wrap with widget option Wrap with widget option](https://opensource.com/sites/default/files/uploads/flutter_wrapwithwidget.png)

(Vitaly Kuprenko, CC BY-SA 4.0)

Start typing "Hero" and select the suggestion for `Hero((Key key, @required this, tag, this.create))`

:

![Finding the Hero widget Finding the Hero widget](https://opensource.com/sites/default/files/uploads/flutter_hero.png)

(Vitaly Kuprenko, CC BY-SA 4.0)

Next, add the tag property `tag: model.id`

to the Hero widget:

![Adding the tag property model.id to the Hero widget Adding the tag property model.id to the Hero widget](https://opensource.com/sites/default/files/uploads/flutter_hero-tag.png)

(Vitaly Kuprenko, CC BY-SA 4.0)

And the final step is to make the same change in the `item_details_page.dart`

file:

![Changing item_details_page.dart file Changing item_details_page.dart file](https://opensource.com/sites/default/files/uploads/flutter_details-tag.png)

(Vitaly Kuprenko, CC BY-SA 4.0)

The previous steps wrapped the `Icon()`

widget with the `Hero()`

widget. Do you remember in `ItemModel`

you added the `id field`

but didn't use it anywhere? The Hero widget takes a unique tag for the child widget. If Hero detects that different app screens (MaterialPageRoute) have a Hero widget with the same tag, it'll automatically animate the transition between these pages.

Test it out by running the app on an Android emulator or physical device. When you open and close the item details page, you'll see a nice animation of the icon:

![Testing the Flutter app Testing the Flutter app](https://opensource.com/sites/default/files/uploads/flutter_test.gif)

(Vitaly Kuprenko, CC BY-SA 4.0)

## Wrapping up

In this tutorial, you learned:

- The components of a standard, automatically created app
- How to add several pages that pass data among each other
- How to add a simple animation for those pages

If you want to learn more, check out Flutter's [docs](https://flutter.dev/docs) (with links to sample projects, videos, and "recipes" for creating Flutter apps) and the [source code](https://github.com/flutter), which is open source under a BSD 3-Clause License.

## Comments are closed.