TranslateProject/translated/tech/20201103 Create a list in a Flutter mobile app.md
2021-05-26 00:48:16 +08:00

15 KiB
Raw Blame History

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

了解如何创建Flutter应用的界面以及如何在它们之间进行数据传递。 移动设备以及相互协作导致人们盯着手机看

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

测试Flutter应用

(Vitaly Kuprenko, CC BY-SA 4.0)

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

查看Flutter应用的主要部分

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

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

应用启动时,main()会被调用,然后执行MyApp()MyApp是一个无状态的Widget(StatelessWidget),它包含了MaterialApp()widgetMaterialApp中包含了所有必要的应用设置(应用的主题、要打开的初始页面等):

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()是应用的初始页面是一个有状态的widget 它包含了一个传给其构造函数的参数(从上面的代码看,我们传了一个title变量给初始页面的构造函数):

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

  final [String][8] title;

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

有状态的widget表示这个widget可以拥有自己的状态: _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++;
   });
 }
 ...
}

不管是有状态的,还是无状态的微件(widget),他们都有一个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][9](Icons.add),
   ),
 );
}

修改你的应用

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

创建一个新的Dart文件

(Vitaly Kuprenko, CC BY-SA 4.0)

将新建的文件命名为items_list_page

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

导入Flutter包

(Vitaly Kuprenko, CC BY-SA 4.0)

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

然后, 在类名MyHomePage右击,**MyHomePage class > Refactor > Rename…**将其重命名为ItemsListPage:

重命名StatefulWidget类

(Vitaly Kuprenko, CC BY-SA 4.0)

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

State类被自动重命名

(Vitaly Kuprenko, CC BY-SA 4.0)

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

IDE建议自动完成的代码

(Vitaly Kuprenko, CC BY-SA 4.0)

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

添加缺失的导入语句

(Vitaly Kuprenko, CC BY-SA 4.0)

到此,你已经完成了初始设置。现在你需要在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][8] title;
 final [String][8] 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][17].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][7] 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][9](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][7] 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][9](
             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…"

查看微件选项

(Vitaly Kuprenko, CC BY-SA 4.0)

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

查找Hero微件

(Vitaly Kuprenko, CC BY-SA 4.0)

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

在Hero微件上添加tag属性为model.id

(Vitaly Kuprenko, CC BY-SA 4.0)

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

修改item_details_page.dart文件

(Vitaly Kuprenko, CC BY-SA 4.0)

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

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

测试Flutter应用

(Vitaly Kuprenko, CC BY-SA 4.0)

收尾

这篇教程,让你学到了:

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

如果你想了解更多, 查看Flutter的文档 (一些视频和样例项目的链接, 还有一些创建Flutter应用的“秘方”)与源码, 源码的开源协议是BSD 3。


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

作者:Vitaly Kuprenko 选题:lujun9972 译者:ywxgod 校对:校对者ID

本文由 LCTT 原创编译,Linux中国 荣誉推出