在 Flutter 移动应用中创建列表

学习如何创建 Flutter 应用屏幕并在它们之间传递数据。
59 位读者喜欢这个。
Mobile devices and collaboration leads to staring at our phones

图片由 Mapbox Uncharted ERG 提供,CC-BY 3.0 US

Flutter 是一个流行的开源工具包,用于构建跨平台应用。在“使用 Flutter 创建移动应用”中,我演示了如何在 Linux 上安装 Flutter 并创建你的第一个应用。在本文中,我将向你展示如何在你的应用中添加一个项目列表,每个项目都打开一个新屏幕。这是一种常见的移动应用设计方法,所以你可能之前见过它,但这里有一个截图来帮助你可视化它

Flutter 使用 Dart 语言。 在下面的一些代码片段中,你将看到以斜杠开头的语句。两个斜杠 (/ /) 用于代码注释,用于解释某些代码片段。三个斜杠 (/ / /) 表示 Dart 的文档注释,用于解释 Dart 类及其属性和其他有用信息。

检查 Flutter 应用的主要部分

Flutter 应用程序的典型入口点是一个 main() 函数,通常在名为 lib/main.dart 的文件中找到

void main() {
 runApp(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。它允许你在那里调用 setState() 方法来重建页面的用户界面 (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++; 
   });
 }
 ...
}

有状态和无状态小部件中的 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 文件夹,然后选择 新建 > Dart 文件来创建一个新的 .dart 文件

将文件命名为 items_list_page

切换回你的 main.dart 文件,剪切 MyHomePage_MyHomePageState 代码,并将它粘贴到你的新文件中。接下来,将你的光标放在 StatefulWidget 上(在下面用红色下划线标注),按 Alt+Enter,然后选择 package:flutter/material.dart

这会将 flutter/material.dart 添加到你的文件中,以便你可以使用 Flutter 提供的默认材料小部件。

然后,右键单击 MyHomePage class > Refactor > Rename… 并将这个类重命名为 ItemsListPage

Flutter 识别到你重命名了 StatefulWidget 类,并自动重命名它的 State 类

返回到 main.dart 文件并将名称 MyHomePage 更改为 ItemsListPage。一旦你开始输入,你的 Flutter 集成开发环境(可能是 IntelliJ IDEA Community Edition、Android Studio 和 VS Code 或 VSCodium)会建议如何自动完成你的代码

按 Enter 完成你的输入。它会自动将缺少的导入添加到文件的顶部

你已经完成了你的初始设置。现在你需要在 lib 文件夹中创建一个新的 .dart 文件,并将它命名为 item_model。(请注意,类具有 UpperCamelCase 名称,但文件具有 snake_case 名称。)将此代码粘贴到新文件中

/// 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 代码。它可以在其 State 类中引用 StatefulWidget 字段。

添加一些动画

现在,是时候添加一些基本动画了

  1. 转到 ItemWidget 代码。
  2. 将光标放在 build() 方法中的 Icon() 小部件上。
  3. 按 Alt+Enter 并选择“Wrap with widget…”

开始键入“Hero”并选择 Hero((Key key, @required this, tag, this.create)) 的建议

接下来,将 tag 属性 tag: model.id 添加到 Hero 小部件

最后一步是在 item_details_page.dart 文件中进行相同的更改

前面的步骤用 Hero() 小部件包装了 Icon() 小部件。你还记得在 ItemModel 中你添加了 id field 但没有在任何地方使用它吗? Hero 小部件为子小部件采用唯一的标签。如果 Hero 检测到不同的应用程序屏幕 (MaterialPageRoute) 具有带有相同标签的 Hero 小部件,它会自动在这些页面之间进行动画过渡。

通过在 Android 模拟器或物理设备上运行该应用程序来测试它。当你打开和关闭项目详细信息页面时,你将看到一个漂亮的图标动画

总结

在本教程中,你学习了

  • 标准、自动创建的应用程序的组件
  • 如何添加在彼此之间传递数据的多个页面
  • 如何为这些页面添加一个简单的动画

如果你想了解更多信息,请查看 Flutter 的 文档(其中包含指向示例项目、视频和用于创建 Flutter 应用程序的“配方”的链接)和 源代码,这些代码是在 BSD 3-Clause License 下开源的。

接下来阅读什么
标签
User profile image.
Vitaly Kuprenko 是 Cleveroad 的一名作家。 它是一家总部位于乌克兰的 flutter 应用程序开发公司。 他喜欢撰写关于技术和数字营销的文章。

评论已关闭。

Creative Commons License这项工作是根据 Creative Commons Attribution-Share Alike 4.0 International License 许可的。
© . All rights reserved.