第一个Flutter应用之Helloworld

背景

Flutter是谷歌的移动UI框架,可以快速在iOS和Android上构建高质量的原生用户界面。 Flutter可以与现有的代码一起工作。在全世界,Flutter正在被越来越多的开发者和组织使用,并且Flutter是完全免费、开源的。1.0版本于2018年12月5日(北京时间)发布。

安装环境和配置

1.获取 Flutter SDK

  1. 下载下面的安装包以获取最新版本 stable 的 Flutter SDK

    flutter_windows_v1.5.4-hotfix.2-stable.zip

    要查看其他发行通道和以往的版本,请参阅SDK 归档页面。

  2. 将压缩包解压,然后把其中的 flutter 目录整个放在你预想的 Flutter SDK 安装目录中(比如 C:\src\flutter;请勿将该目录放在一些需要额外操作权限的目录,比如 C:\Program Files\)。

  3. 找到 flutter 目录中的 `flutter_console.bat’ 文件,双击执行该批处理脚本。

好的,现在你可以在控制台当中使用 Flutter 的命令了。

2.更新 path 环境变量

如果你想要在普通的 Windows 控制台中使用 Flutter 命令,那就需要按照下面的步骤来将 Flutter 的二进制文件路径加入到 PATH环境变量。

  • 在开始菜单的搜索功能键入“env”,然后选择编辑当前用户的环境变量
  • 在 User variables 一栏中,检查是否有 Path 这个条目:
    • 如果存在,直接把 flutter\bin 目录的完整路径以 ; 作为分隔加到已有的值后面。
    • 如果不存在的话,在用户环境变量中创建一个新的 Path 变量,然后将 flutter\bin 所在的完整路径作为新变量的值。

注意,你需要关闭和重新启动已经打开的各个控制台窗口,这样下次启动控制台时才能访问到刚才修改的变量。

3.运行 flutter doctor

在将 Path 变量更新后,打开一个新的控制台窗口,然后将下面的命令输入进去执行。如果它提示有任何的平台相关依赖,那么你就需要按照指示完成这些配置:

C:\src\flutter>flutter doctorcm

上述命令会检查你的现有环境,然后把检测结果以报告形式呈现出来。仔细阅读它显示的内容,检查是否有尚未安装的软件或是有其他的步骤需要完成(通常会以粗体呈现)。

举个例子:

[-] Android toolchain - develop for Android devices
    • Android SDK at D:\Android\sdk
    ✗ Android SDK is missing command line tools; download from https://goo.gl/XxQghQ
    • Try re-installing or updating your Android SDK,
      visit https://flutter-io.cn/setup/#android-setup for detailed instructions.

对于安卓开发者来说,Android Studio 为 Flutter 提供了一个完整的集成开发环境。安装过程如下:

  1. 打开 Android Studio。
  2. 打开插件设置(macOS 系统打开 Preferences > Plugins, Windows 和 Linux 系统打开 File > Settings > Plugins)。
  3. 选择 Browse repositories,然后选择 Flutter 插件并点击 安装
  4. 当弹出安装 Dart 插件提示时,点击 Yes
  5. 当弹出重新启动提示时,点击 Restart

创建应用

  1. 打开 Android Studio,选择 新 Flutter 项目 (Start a new Flutter project).
  2. 选择 Flutter 应用程序 作为项目类型, 然后点 下一步
  3. 确认 Flutter SDK 路径 区域所示路径是正确的 SDK 路径。 如果你还没有安装 SDK,需要先进行安装,选择 Install SDK…
  4. 输入项目名称(比如 ‘myapp’), 然后点击下一步
  5. 点击 完成
  6. 待 Android Studio 安装 SDK 后,创建项目。

运行程序

  1. 找到 Android Studio 的工具条:
    Main IntelliJ toolbar
  2. 在 目标选择器, 选择一个 Android 设备来运行程序。 如果列表里没有可用设备,选择 **工具 > Android > AVD Manager 然后在这个窗口中创建一个新的虚拟机。更多详细介绍,参见管理 AVDs
  3. 点击工具栏中的 Run 图标,或者在菜单中选择 Run > Run

当应用编译完成后,就可以在设备上运行这个起步应用了。

Starter app on iOS

Starter app

尝试热重载 (hot reload)

Flutter 通过 热重载 提供快速开发周期,该功能支持应用程序在运行状态下重载代码 而无需重新启动应用程序或者丢失程序运行状态。 修改一下代码,然后告诉IDE或者命令行工具你需要热重载, 然后看一下模拟器或者设备上应用的变化

  1. 打开 lib/main.dart

    import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      // This widget is the root of your application.
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
            // This is the theme of your application.
            //
            // Try running your application with "flutter run". You'll see the
            // application has a blue toolbar. Then, without quitting the app, try
            // changing the primarySwatch below to Colors.green and then invoke
            // "hot reload" (press "r" in the console where you ran "flutter run",
            // or simply save your changes to "hot reload" in a Flutter IDE).
            // Notice that the counter didn't reset back to zero; the application
            // is not restarted.
            primarySwatch: Colors.blue,
          ),
          home: MyHomePage(title: 'Flutter Demo Home Page'),
        );
      }
    }
    
    class MyHomePage extends StatefulWidget {
      MyHomePage({Key key, this.title}) : super(key: key);
    
      // This widget is the home page of your application. It is stateful, meaning
      // that it has a State object (defined below) that contains fields that affect
      // how it looks.
    
      // This class is the configuration for the state. It holds the values (in this
      // case the title) provided by the parent (in this case the App widget) and
      // used by the build method of the State. Fields in a Widget subclass are
      // always marked "final".
    
      final String title;
    
      @override
      _MyHomePageState createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> {
      int _counter = 0;
    
      void _incrementCounter() {
        setState(() {
          // This call to setState tells the Flutter framework that something has
          // changed in this State, which causes it to rerun the build method below
          // so that the display can reflect the updated values. If we changed
          // _counter without calling setState(), then the build method would not be
          // called again, and so nothing would appear to happen.
          _counter++;
        });
      }
    
      @override
      Widget build(BuildContext context) {
        // This method is rerun every time setState is called, for instance as done
        // by the _incrementCounter method above.
        //
        // The Flutter framework has been optimized to make rerunning build methods
        // fast, so that you can just rebuild anything that needs updating rather
        // than having to individually change instances of widgets.
        return Scaffold(
          appBar: AppBar(
            // Here we take the value from the MyHomePage object that was created by
            // the App.build method, and use it to set our appbar title.
            title: Text(widget.title),
          ),
          body: Center(
            // Center is a layout widget. It takes a single child and positions it
            // in the middle of the parent.
            child: Column(
              // Column is also layout widget. It takes a list of children and
              // arranges them vertically. By default, it sizes itself to fit its
              // children horizontally, and tries to be as tall as its parent.
              //
              // Invoke "debug painting" (press "p" in the console, choose the
              // "Toggle Debug Paint" action from the Flutter Inspector in Android
              // Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
              // to see the wireframe for each widget.
              //
              // Column has various properties to control how it sizes itself and
              // how it positions its children. Here we use mainAxisAlignment to
              // center the children vertically; the main axis here is the vertical
              // axis because Columns are vertical (the cross axis would be
              // horizontal).
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Text(
                  'You have clicked the button this many times:',
                ),
                Text(
                  '$_counter',
                  style: Theme.of(context).textTheme.display1,
                ),
              ],
            ),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: _incrementCounter,
            tooltip: 'Increment',
            child: Icon(Icons.add),
          ), // This trailing comma makes auto-formatting nicer for build methods.
        );
      }
    }
    
  2. 修改字符串

    'You have pushed the button this many times'
    

    改为

    'You have clicked the button this many times'
    

3.保存修改: 选择 Save All,或者点击热重载

你会发现修改后的字符串几乎马上出现在正在运行的应用程序上。

Helloworld来了

替换 lib/main.dart
删除 lib/main.dart 中的所有代码,然后替换为下面的代码,它将在屏幕的中心显示”Hello World”。

lib/main.dart

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Welcome to Flutter',
      home: Scaffold(
        appBar: AppBar(
          title: Text('Welcome to Flutter'),
        ),
        body: Center(
          child: Text('Hello World'),
        ),
      ),
    );
  }
}

运行你的工程项目, 根据不同的操作系统,你会看到如下运行结果界面:

观察和分析

  • 本示例创建了一个具有 Material Design 风格的应用, Material 是一种移动端和网页端通用的视觉设计语言, Flutter 提供了丰富的 Material 风格的 widgets。
  • 主函数(main)使用了 (=>) 符号,这是 Dart 中单行函数或方法的简写。
  • 该应用程序继承了 StatelessWidget,这将会使应用本身也成为一个 widget。 在 Flutter 中,几乎所有都是 widget, 包括对齐 (alignment)、填充 (padding) 和布局 (layout)。
  • Scaffold 是 Material 库中提供的一个 widget, 它提供了默认的导航栏、标题和包含主屏幕 widget 树的 body 属性。 widget 树可以很复杂。
  • 一个 widget 的主要工作是提供一个 build() 方法来描述如何根据其他较低级别的 widgets 来显示自己。
  • 本示例中的 body 的 widget 树中包含了一个 Center widget, Center widget 又包含一个 Text 子 widget, Center widget 可以将其子 widget 树对齐到屏幕中心。

使用外部 package

使用一个名为 english_words 的开源软件包,其中包含数千个最常用的英文单词以及一些实用功能。

你可以在 Pub site 上找到 english_words 软件包以及其他许多开源软件包。

  1. pubspec 文件管理 Flutter 应用程序的 assets(资源,如图片、package等)。 在pubspec.yaml 中,将 english_words(3.1.0或更高版本)添加到依赖项列表, 如下面高亮显示的行:

    dependencies:
      flutter:
        sdk: flutter
    
      # The following adds the Cupertino Icons font to your application.
      # Use with the CupertinoIcons class for iOS style icons.
      cupertino_icons: ^0.1.2
      english_words: ^3.1.0
    
  2. 在Android Studio 的编辑器视图中查看 pubspec 时, 单击右上角的 Packages get,这会将依赖包安装到你的项目。 你可以在控制台中看到以下内容:

    $ flutter pub get
    Running "flutter pub get" in startup_namer...
    Process finished with exit code 0
    

    在执行 Packages get 命令的时候,同时会自动生成一个名为 pubspec.lock 的文件,这里包含了你依赖 package

    s 的名称和版本。

  3. 在 lib/main.dart 中引入,如下所示:

    lib/main.dart

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

    在你输入时,Android Studio会为你提供有关库导入的建议。 然后它将呈现灰色的导入字符串,让你知道导入的库截至目前尚未被使用。

  4. 接下来,我们使用 English words 包生成文本来替换字符串”Hello World”:

    {step1_base → step2_use_package}/lib/main.dart

    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        final wordPair = WordPair.random();
        return MaterialApp(
          title: 'Welcome to Flutter',
          home: Scaffold(
            appBar: AppBar(
              title: Text('Welcome to Flutter'),
            ),
            body: Center(
    //          child: Text('Hello World'),
                child: Text(wordPair.asPascalCase),
            ),
          ),
        );
      }
    }
    

5.如果应用程序正在运行,请使用热重载按钮 offline_bolt 更新正在运行的应用程序。每次单击热重载或保存项目时,都会在正在运行的应用程序中随机选择不同的单词对。 这是因为单词对是在 build 方法内部生成的。每次 MaterialApp 需要渲染时或者在 Flutter Inspector 中切换平台时 build 都会运行。

添加一个 Stateful widget

Stateless widgets 是不可变的,这意味着它们的属性不能改变 —— 所有的值都是 final。

Stateful widgets 持有的状态可能在 widget 生命周期中发生变化, 实现一个 stateful widget 至少需要两个类: 1)一个 StatefulWidget 类;2)一个 State 类,StatefulWidget 类本身是不变的, 但是 State 类在 widget 生命周期中始终存在。

添加一个 stateful widget(有状态的 widget)—— RandomWords, 它会创建自己的状态类 —— RandomWordsState,然后你需要将 RandomWords 内嵌到已有的无状态的 MyApp widget

  1. 创建一个最简的 state 类,这个类可以在任意地方创建而不一定非要在 MyApp 里, 我们的示例代码是放在 MyApp 类的最下面了:

    Create a minimal state class. Add the following to the bottom of main.dart:

    lib/main.dart (RandomWordsState)

    class RandomWordsState extends State<RandomWords> {
      // TODO Add build() method
    }
    

    注意一下 State<RandomWords> 的声明。这表明我们在使用专门用于 RandomWords 的 State 泛型类。 应用的大部分逻辑和状态都在这里 —— 它会维护 RandomWords widget 的状态。 这个类会保存代码生成的单词对,这个单词对列表会随着用户滑动而无限增长, 另外还会保存用户喜爱的单词对(第二部分), 也即当用户点击爱心图标的时候会从喜爱的列表中添加或者移除当前单词对。

    RandomWordsState 依赖 RandomWords,我们接下来会创建这个类。

  2. 添加有状态的 RandomWords widget 到 main.dartRandomWords widget 除了创建 State 类之外几乎没有其他任何东西:

    lib/main.dart (RandomWords)

    class RandomWords extends StatefulWidget {
      @override
      RandomWordsState createState() => RandomWordsState();
    }
    

    在添加状态类后,IDE 会提示该类缺少 build 方法。 接下来,你将添加一个基本的 build 方法, 该方法通过将生成单词对的代码从 MyApp 移动到 RandomWordsState 来生成单词对。

  3. 将 build() 方法添加到 RandomWordState 中,如下所示:

    lib/main.dart (RandomWordsState)

    class RandomWordsState extends State<RandomWords> {
      @override
      Widget build(BuildContext context) {
        final wordPair = WordPair.random();
        return Text(wordPair.asPascalCase);
      }
    }
    
  4. 如下所示,删除 MyApp 里生成文字的代码:

    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Welcome to Flutter',
          home: Scaffold(
            appBar: AppBar(
              title: Text('Welcome to Flutter'),
            ),
            body: Center(
    //            child: Text(wordPair.asPascalCase),
                  child: RandomWords(),
            ),
          ),
        );
      }
    }
    
  5. 重启应用。 应用应该像之前一样运行,每次热重载或保存应用程序时都会显示一个单词对。

创建一个小项目(生成建议的名字)

在这一步中,你将扩展(继承)RandomWordsState 类,以生成并显示单词对列表。 当用户滚动时,ListView 中显示的列表将无限增长。 ListView 的 builder 工厂构造函数允许你按需建立一个懒加载的列表视图。

  1. 向 RandomWordsState 类中添加一个 _suggestions 列表以保存建议的单词对, 同时,添加一个 _biggerFont 变量来增大字体大小。

    lib/main.dart

    class RandomWordsState extends State<RandomWords> {
      final _suggestions = <WordPair>[];
      final _biggerFont = const TextStyle(fontSize: 18.0);
      // ···
    }
    

    备忘

    在 Dart 语言中使用下划线前缀标识符,会强制其变成私有

    接下来,我们将向 RandomWordsState 类添加一个 _buildSuggestions() 方法, 此方法构建显示建议单词对的 ListView

    ListView 类提供了一个名为 itemBuilder 的 builder 属性,这是一个工厂匿名回调函数, 接受两个参数 BuildContext和行迭代器 i。迭代器从 0 开始,每调用一次该函数 i 就会自增, 每次建议的单词对都会让其递增两次,一次是 ListTile,另一次是 Divider。 它用于创建一个在用户滚动时候无限增长的列表。

  2. 向 RandomWordsState 类添加 _buildSuggestions() 方法,内容如下:

    lib/main.dart (_buildSuggestions)

    Widget _buildSuggestions() {
      return ListView.builder(
          padding: const EdgeInsets.all(16.0),
          itemBuilder: /*1*/ (context, i) {
            if (i.isOdd) return Divider(); /*2*/
    
            final index = i ~/ 2; /*3*/
            if (index >= _suggestions.length) {
              _suggestions.addAll(generateWordPairs().take(10)); /*4*/
            }
            return _buildRow(_suggestions[index]);
          });
    }
    
    1. 对于每个建议的单词对都会调用一次 itemBuilder,然后将单词对添加到 ListTile 行中。 在偶数行,该函数会为单词对添加一个 ListTile row, 在奇数行,该函数会添加一个分割线的 widget,来分隔相邻的词对。 注意,在小屏幕上,分割线看起来可能比较吃力。
    2. 在每一列之前,添加一个1像素高的分隔线 widget。
    3. 语法 “i ~/ 2” 表示i除以2,但返回值是整形(向下取整), 比如 i 为:1, 2, 3, 4, 5 时,结果为 0, 1, 1, 2, 2, 这个可以计算出 ListView 中减去分隔线后的实际单词对数量。
    4. 如果是建议列表中最后一个单词对,接着再生成10个单词对,然后添加到建议列表。

    对于每一个单词对,_buildSuggestions() 都会调用一次 _buildRow()。 这个函数在 ListTile 中显示每个新词对, 这使你在下一步中可以生成更漂亮的显示行,详见本 codelab 的第二部分。

  3. 在 RandomWordsState 中添加 _buildRow() 函数 :

    lib/main.dart (_buildRow)

    Widget _buildRow(WordPair pair) {
      return ListTile(
        title: Text(
          pair.asPascalCase,
          style: _biggerFont,
        ),
      );
    }
    
  4. 更新 RandomWordsState 的 build 方法以使用 _buildSuggestions(), 而不是直接调用单词生成库,代码更改后如下: (使用 Scaffold 类实现基础的 Material Design 布局)

    lib/main.dart (build)

    @override
    Widget build(BuildContext context) {
      return Scaffold(
        appBar: AppBar(
          title: Text('Startup Name Generator'),
        ),
        body: _buildSuggestions(),
      );
    }
    
  5. 更新 MyApp 的 build() 方法,修改 title 的值来改变标题, 修改 home 的值为 RandomWords widget。

    {step3_stateful_widget → step4_infinite_list}/lib/main.dart

    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Startup Name Generator',
          home:  RandomWords(),
          ),
        );
      }
    }
    
  6. 重新启动你的项目工程应用,你应该看到一个单词对列表。 尽可能地向下滚动,你将继续看到新的单词对。

  7. 向列表里添加图标

在这部分,我们将为每一行添加一个心形的(收藏)图标,下一步你将能够为这个图标加入点击收藏的功能。

添加一个 _saved Set(集合)到 RandomWordsState,这个集合存储用户喜欢(收藏)的单词对。 在这里,Set 比 List 更合适,因为 Set 中不允许重复的值。

class RandomWordsState extends State<RandomWords> {
  final List<WordPair> _suggestions = <WordPair>[];
  final Set<WordPair> _saved = new Set<WordPair>();   // 新增本行
  final TextStyle _biggerFont = const TextStyle(fontSize: 18.0);
  ...
}

在 _buildRow 方法中添加 alreadySaved 来检查确保单词对还没有添加到收藏夹中。

Widget _buildRow(WordPair pair) {
  final bool alreadySaved = _saved.contains(pair);  // 新增本行
  ...
}

同时在 _buildRow() 中, 添加一个心形 ❤️图标到 ListTiles以启用收藏功能。接下来,你就可以给心形 ❤️图标添加交互能力了。

向列表添加图标,如下所示:

Widget _buildRow(WordPair pair) {
  final bool alreadySaved = _saved.contains(pair);
  return new ListTile(
    title: new Text(
      pair.asPascalCase,
      style: _biggerFont,
    ),
    trailing: new Icon(   // 新增代码开始 ...
      alreadySaved ? Icons.favorite : Icons.favorite_border,
      color: alreadySaved ? Colors.red : null,
    ),                    // ... 新增代码结束
  );
}

热重载应用,你现在可以在每一行看到心形 ❤️图标️,但它们还没有交互。

  1. 添加交互

在这部分,我们将为刚刚的心形 ❤️图标增加交互,当用户点击列表中的条目,切换其”收藏”状态,并将该词对添加到或移除出”收藏夹”。

为了做到这个,我们在 _buildRow 中让心形 ❤️图标变得可以点击。如果单词条目已经添加到收藏夹中, 再次点击它将其从收藏夹中删除。当心形 ❤️图标被点击时,函数调用 setState() 通知框架状态已经改变。增加 onTap 方法,如下所示:

Widget _buildRow(WordPair pair) {
  final alreadySaved = _saved.contains(pair);
  return new ListTile(
    title: new Text(
      pair.asPascalCase,
      style: _biggerFont,
    ),
    trailing: new Icon(
      alreadySaved ? Icons.favorite : Icons.favorite_border,
      color: alreadySaved ? Colors.red : null,
    ),
    onTap: () {      // 增加如下 9 行代码...
      setState(() {
        if (alreadySaved) {
          _saved.remove(pair);
        } else { 
          _saved.add(pair); 
        } 
      });
    },               // ... 一直到这里
  );
}

提示: 在 Flutter 的响应式风格的框架中,调用 setState() 会为 State 对象触发 build() 方法,从而导致对 UI 的更新

热重载应用,你就可以点击任何一行测试收藏或取消收藏功能,你的点击同时自带 Material Design 里的水波动画特效。

  1. 导航到新页面

在这一步中,您将添加一个显示收藏夹内容的新页面(在 Flutter 中称为路由[route])。您将学习如何在主路由和新路由之间导航(切换页面)。

在 Flutter 中,导航器管理应用程序的路由栈。将路由推入(push)到导航器的栈中,将会显示更新为该路由页面。 从导航器的栈中弹出(pop)路由,将显示返回到前一个路由。

接下来,我们在 RandomWordsState 的 build 方法中为 AppBar 添加一个列表图标。当用户点击列表图标时,包含收藏夹的新路由页面入栈显示。将该图标及其相应的操作添加到 build 方法中:

class RandomWordsState extends State<RandomWords> {
  ...
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('Startup Name Generator'),
        actions: <Widget>[      // 新增代码开始 ...
          new IconButton(icon: const Icon(Icons.list), onPressed: _pushSaved),
        ],                      // ... 代码新增结束
      ),
      body: _buildSuggestions(),
    );
  }
  ...
}

提示: 某些 widget 属性需要单个 widget(child),而其它一些属性,如 action,需要一组widgets(children),用方括号 [] 表示。

在 RandomWordsState 这个类里添加 _pushSaved() 方法:

class RandomWordsState extends State<RandomWords> {
  ...
  // 新增代码开始
  void _pushSaved() {
  }
  // 新增代码结束 
}

热重载应用,列表图标()将会出现在导航栏中。现在点击它不会有任何反应,因为 _pushSaved 函数还是空的。

接下来,(当用户点击导航栏中的列表图标时)我们会建立一个路由并将其推入到导航管理器栈中。此操作会切换页面以显示新路由,新页面的内容会在 MaterialPageRoute 的 builder 属性中构建,builder 是一个匿名函数。

添加 Navigator.push 调用,这会使路由入栈(以后路由入栈均指推入到导航管理器的栈)

void _pushSaved() {
  Navigator.of(context).push(
  );
}

接下来,添加 MaterialPageRoute 及其 builder。 现在,添加生成 ListTile 行的代码,ListTile 的 divideTiles() 方法在每个 ListTile 之间添加 1 像素的分割线。 该 divided 变量持有最终的列表项,并通过 toList()方法非常方便的转换成列表显示。添加如下所示的代码:

void _pushSaved() {
  Navigator.of(context).push(
    new MaterialPageRoute<void>(   // 新增如下20行代码 ...
      builder: (BuildContext context) {
        final Iterable<ListTile> tiles = _saved.map(
          (WordPair pair) {
            return new ListTile(
              title: new Text(
                pair.asPascalCase,
                style: _biggerFont,
              ),
            );
          },
        );
        final List<Widget> divided = ListTile
          .divideTiles(
            context: context,
            tiles: tiles,
          )
          .toList();
      },
    ),                           // ... 新增代码结束
  );
}

builder 返回一个 Scaffold,其中包含名为”Saved Suggestions”的新路由的应用栏。新路由的body 由包含 ListTiles 行的 ListView 组成;每行之间通过一个分隔线分隔。添加水平分隔符,如下代码所示:

void _pushSaved() {
  Navigator.of(context).push(
    new MaterialPageRoute<void>(
      builder: (BuildContext context) {
        final Iterable<ListTile> tiles = _saved.map(
          (WordPair pair) {
            return new ListTile(
              title: new Text(
                pair.asPascalCase,
                style: _biggerFont,
              ),
            );
          },
        );
        final List<Widget> divided = ListTile
          .divideTiles(
            context: context,
            tiles: tiles,
          )
              .toList();

        return new Scaffold(         // 新增 6 行代码开始 ...
          appBar: new AppBar(
            title: const Text('Saved Suggestions'),
          ),
          body: new ListView(children: divided),
        );                           // ... 新增代码段结束.
      },
    ),
  );
}

热重载应用程序,点击列表项收藏一些项,点击列表图标(),在新的 route(路由)页面中显示收藏的内容。Navigator(导航器)会在应用栏中自动添加一个”返回”按钮,无需调用Navigator.pop,点击后退按钮就会返回到主页路由.

10.使用 Themes 修改 UI

这一部分,我们将会一起修改应用的主题。Flutter 里我们使用 theme 来控制你应用的外观和风格,你可以使用默认主题,该主题取决于物理设备或模拟器,也可以自定义主题以适应您的品牌。

你可以通过配置 ThemeData 类轻松更改应用程序的主题,目前我们的应用程序使用默认主题,下面将更改 primaryColor 颜色为白色。

在 MyApp 这个类里修改颜色:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Startup Name Generator',
      theme: new ThemeData(          // 新增代码开始... 
        primaryColor: Colors.white,
      ),                             // ... 代码新增结束
      home: new RandomWords(),
    );
  }
}

热重载应用。 你会发现,整个背景将会变为白色,包括 app bar(应用栏)。

遇到的问题

会遇到提示 Waiting for another flutter command to release the startup lock…
然后就一直卡着不动了。

解决办法:

1.先关闭Android Studio,打开任务管理器,看看有没有Dart.exe运行着,有的话全部结束了,然后重启试试
2.还是不行的话就删除掉 flutter\bin\cache目录下的lockfile文件,然后再次重启Android Studio即可

flutter issue: https://github.com/flutter/flutter/issues/7768

总结

完成了一个可运行在 Android 和 iOS 系统上的、包含交互的 Flutter 应用,在这个 codelab 里,你已经做了下面的事情:

  • 写了 Dart 代码
  • 使用热重载加速了开发进程
  • 实现了一个 stateful widget,为你的应用加入了交互功能
  • 创建了一个新的页面(route),为主页和这个新页面的跳转加入了逻辑
  • 学会了如何使用 themes 修改应用的 UI

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值