四.创建你的第一个Flutter APP

创建你的第一个Flutter APP

本文指引你创建你的第一个Flutter APP。如果你熟悉面向对象编程和基本的编程的概念,
如变量,循环,条件等,那你就能完全理解本文。你不需要关于 Dart 和手机编程方面的经验。

  • Step 1: 创建并启动Flutter APP
  • Step 2: 使用扩展包
  • Step 3: 添加一个带状态的部件
  • Step 4: 创建一个无限滚动的ListView
  • Step 5: 加入交互元素
  • Step 6: 导航到新窗口
  • Step 7: 使用全新的主题
  • 圆满结束

APP功能

您将实现一个简单的移动应用程序,为初创公司推荐名称,用户可以选择和取消选择名称,然后保存最好的名称,代码一次生成10个名称,
当用户滚动界面时,会生成一组新的名称。用户可以点击顶栏右上角的列表图标,会转换到只显示红心名称的界面。

下面的最终效果图显示了APP的最终功能

《四.创建你的第一个Flutter APP》

将要学习的内容:

– Flutter APP的基础结构

– 查找并使用包来扩展功能

– 使用热加载来缩小开发周期

– 如何实现一个带有状态的组件

– 如何创建一个无限的、延迟加载的列表

– 如何创建并导航到另一个界面

– 如何使用 Themes 改变应用程序的外观

将要使用的东西:

– Flutter SDK: 包含Flutter的引擎,框架,组件,工具,和 Dart SDK。它的版本需要 v0.1.4 或更高

– Android Studio IDE: 使用它来写代码,但是也可以使用其他IDE,或者直接使用命令行工具

– IDE 插件: Flutter和Dart插件必须安装到IDE中,除了Android Studio之外,这两个插件也可以在VS Code和IntelliJ IDEs中

关于如何搭建环境,请参阅Flutter Installation and Setup

Step 1: 创建并启动Flutter APP

通过参阅Getting Started with your first Flutter app来创建一个简单的,模板化的APP,
项目命名为 startup_namer (代替 myapp)。通过修改这个简单的APP来完善你的APP。

在开发APP时,你主要修改的是 lib/main.dart 中的代码,这个文件中是 Dart 语言的代码。

注:当写APP的代码时,缩进会变得扭曲,你可以使用 Flutter 工具来自动修复这个问题:

– 在 Android Studio / IntelliJ IDEA 中可以在 Dart 文件上点击右键,然后选择
Reformat Code with dartfmt

– 在 VS Code 中,点击右键并选择
Format Document

– 在终端中,运行 flutter format 命令

1, 替换 lib/main.dart 中的代码:
删除 lib/main.dart 中的所有代码,替换成以下代码,新代码会在界面中间显示 “Hello World”

import 'package:flutter/material.dart';

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

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

2, 运行这个APP,你会看到以下界面:

《四.创建你的第一个Flutter APP》

观察

  • 这个示例创建一个 Material (质感) APP,Material是一种在Web和手机上,标准的视觉设计语言,
    Flutter 提供了丰富的 Material 组件。
  • 主方法使用了胖箭头符号(=>), 它是只有一行代码的函数和方法的简写形式。
  • MyApp 继承了 StatelessWidget ,使得它自己成为了一个小组件,在Flutter中, 几乎所有东西都是一个组件,包括对其,填充和布局。
  • Material 库中的 Scaffold 组件,提供了一个默认的 bar (顶栏),title,和 body 属性,Scaffold 为主屏幕保存了一组组件树。组件树可能会非常复杂。
  • 组件的主要共组是提供一个 build() 方法,这个方法描述了如何显示其他小组件
  • 这个例子中的组件树包含 Center 组件,Center组件包含一个 Text 子组件, Center 组件将其子组件居中对齐。

Step 2: 使用扩展包

在这一步中,将要使用一个扩展包,名为 english_words , 它包含了几千个最常用的英语单词和一些实用功能。

可以在 pub.dartlang.org中查找 english_words 包,当然也可以在这里查找其他包。

1, pubspec 文件管理 Flutter App的资源,在 pubspec.yaml 文件中添加 english_words(3.1.0或更高的版本) 到依赖列表中,新的局部代码如下所示:

dependencies:  flutter:  sdk: flutter   cupertino_icons: ^0.1.0  english_words: ^3.1.0

2, 在Android Studio的编辑视图中打开 pubspec 文件,然后点击右上角的 Packages get, 这个操作会把包加入你的项目中,你应该从控制台中看到以下内容:

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

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

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

当你键入代码时,Android Studio会给你一些导入库的建议,键入完成后,编辑器会呈现灰色的字符串来提示你导入的库未使用(到目前为止)。

4, 使用 english_words 包生成的字符串来代替”Hello World”

注:“Pascal case” (又名“upper camel case”,大驼峰命名法), 意为每个单词的首字母都大写,包括第一个单词。因此,“uppercamelcase” 将变为 “UpperCamelCase”。

对主文件做出以下更改:

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

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

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

如果你的APP正在运行,使用热加载按钮来更新运行中的APP, 每次你点击了热加载按钮,或者保存了文件,你都会看到在运行的应用程序中随机选择的一个单词对。
这是因为单词对时在 build() 方法中随机生成的。

《四.创建你的第一个Flutter APP》

报错?

如果APP没有正确运行,认真寻找错误。如若必要,参照下列链接中的代码:
pubspec.yaml (pubspec.yaml 文件不会再改变了)
lib/main.dart

Step 3: 添加一个带状态的部件

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

Stateful 组件可以保存状态,它在其生命周期内可以改变状态,实现了 stateful 的组件至少需要两个类:StatefulWidget 与 State 。
StatefulWidget 创建一个 State 的实例,StatefulWidget 本身是不可变的,但是 State 是可变的,并且存在生命周期。

在这一步中,你将创建一个有状态的组件: RandomWords , 然后,它创建了他的状态类: RandomWordsState , 状态类最终保存了喜欢的单词对。

1, 在 main.dart 中添加一个状态组件 RandomWords , 它可以写在MyApp类外的任何地方,但是推荐把它写在文件底部,RandomWords 除了创建它自己的状态部件外,没有其他多余的功能:

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

2, 创建 RandomWordsState 类,App中的大部分代码都在这个类中,它保存了 RandomWords 类的状态,它会保存生成的单词对,
当用户滚动屏幕时,它保存的单词对会无限增加,同时在用户添加和删除单词对时,它也会切换心型图标的颜色。

你将一步一步地构建这个类,下面,首先创建一个最基本的类:

class RandomWordsState extends State<RandomWords> {}

3, 创建完成之后,IDE会提示这个类没有 build() 方法,下一步,将要写一个基本的 build() 方法,将之前 MyApp 中随机生成单词对的代码移动到这个方法中:

class RandomWordsState extends State<RandomWords> {
  @override
  Widget build(BuildContext context) {
    final wordPair = new WordPair.random();
    return new Text(wordPair.asPascalCase);
  }
}

4, 删除 MyApp 中的单词对生成代码

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final wordPair = new WordPair.random();  // 删除这一行 
    return new MaterialApp(
      title: 'Welcome to Flutter',
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text('Welcome to Flutter'),
        ),
        body: new Center(
          //child: new Text(wordPair.asPascalCase), // 删除这一行           child: new RandomWords(), // 添加这一行         ),
      ),
    );
  }
}

重启APP,这时若使用热加载,会看到以下警告:

Reloading...
Not all changed program elements ran during view reassembly; consider
restarting.

这可能是误报,但是它会自动重启来确保你的改变会映射到UI中。

APP应该像之前一样,在每次热加载和保存项目之后显示一个随机的单词对。

《四.创建你的第一个Flutter APP》

报错?

如果APP没有正确运行,认真寻找错误。如若必要,参照下列链接中的代码:
lib/main.dart

Step 4: 创建一个无限滚动的ListView

在这一步中,将要扩展 RandomWordsState ,让它显示单词对的列表,当用户滚动时,显示在 ListView 组件中的列表会无限增长。
ListView组件的 builder 工厂构造器允许你根据需要,创建一个惰性加载的列表。

1, 在 RandomWordsState 中添加一个 _suggestions 列表,这个列表保存了建议的单词对。这个变量以一个下划线开头,在 Dart 中,以下划线开头的变量是私有的。

另外,添加一个 _biggerFont 变量保存字体大小:

class RandomWordsState extends State<RandomWords> {
  final _suggestions = <WordPair>[];

  final _biggerFont = const TextStyle(fontSize: 18.0);
  //... }

2, 在 RandomWordsState 中添加一个 _buildSuggestions() 方法,这个方法构造了显示单词对列表的 ListView 。

ListView 提供了一个构造属性 itemBuilder , 这个属性需要一个匿名函数作为其回调。这个匿名函数有两个参数— BuildContext 和迭代行数 i ,迭代从 0 开始,
每次对一个单词对执行回调后都会加一。这种模式允许单词对列表随着用户滚动屏幕而增长。

_buildSuggestions()的代码如下所示:

class RandomWordsState extends State<RandomWords> {
  ...
  Widget _buildSuggestions() {
    return new ListView.builder(
      padding: const EdgeInsets.all(16.0),

      // itemBuilder 在每次生成一个单词对时被回调
      // 每一行都是用 ListTile 代表
      // 对于偶数行,这个回调函数都添加一个 ListTile 组件来保存单词对
      // 对于奇数行,这个回调函数会添加一个 Divider 组件来在视觉上分割单词对。
      // 注意,在小设备上看到分割物可能会非常困难
      itemBuilder: (context, i) {

        //  在 ListView 组件的每行之前,先添加一个像素高度的分割。
        if (i.isOdd) return new Divider();

        // 这个"i ~/ 2"的表达式将i 除以 2,然后会返回一个整数结果。
        // 例: 1, 2, 3, 4, 5 会变成 0, 1, 1, 2, 2。
        // 这个表达式会计算 ListView 中单词对的真实数量
        final index = i ~/ 2;

        // 如果到达了单词对列表的结尾处...
        if (index >= _suggestions.length) {

          // ...然后生成10个单词对到建议的名称列表中。
          _suggestions.addAll(generateWordPairs().take(10));
        }
        return _buildRow(_suggestions[index]);
      }
    );
  }
}

3, 在每次生成单词对后,_buildSuggestions 会调用 _buildRow。这个函数使用 ListTile 组件显示每一个新的单词对,这使得每一行更加好看。

在 RandomWordsState 中添加 _buildRow:

class RandomWordsState extends State<RandomWords> {
  ...
  Widget _buildRow(WordPair pair) {
    return new ListTile(
      title: new Text(
        pair.asPascalCase,
        style: _biggerFont,
      ),
    );
  }
}

4, 使用 _buildSuggestions() 修改 RandomWordsState 的build() 方法, 不再使用直接生成单词的方式,如下所示:

class RandomWordsState extends State<RandomWords> {
  ...
  @override
  Widget build(BuildContext context) {
    final wordPair = new WordPair.random(); // 删除
    return new Text(wordPair.asPascalCase); // 删除
    return new Scaffold (
      appBar: new AppBar(
        title: new Text('Startup Name Generator'),
      ),
      body: _buildSuggestions(),
    );
  }
  ...
}

修改 MyApp 的 build() 方法,移除 Scaffold 和 AppBar 实例,
MyApp 将由 RandomWordsState 来管理,这使得用户可以更容易地在APP顶栏中更换界面,因为用户可以在接下来的步骤中从一个屏幕导航到另一个屏幕。

用下列的build() 方法替换调原来的方法:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Startup Name Generator',
      home: new RandomWords(),
    );
  }
}

重启APP,你会看到单词对列表,向下滚动,你会继续看到新的单词配对

《四.创建你的第一个Flutter APP》

报错?

如果APP没有正确运行,认真寻找错误。如若必要,参照下列链接中的代码:
lib/main.dart

Step 5: 加入交互元素

在这一步中,会为每一行加入可点击的心形图标,当用户点击列表中的一个条目时,会切换它的“收藏”状态,这个单词对会被添加或从收藏夹中删除。

1, 在 RandomWordsState 中添加名为 _save 的 Set 集合,这个集合保存了用户喜欢的单词对,这里,Set 要优于 List,因为Set不允许出现重复元素。

class RandomWordsState extends State<RandomWords> {
  final _suggestions = <WordPair>[];

  final _saved = new Set<WordPair>();

  final _biggerFont = const TextStyle(fontSize: 18.0);
  ...
}

2, 在 _buildRow 方法中添加 alreadySaved 字段用来判断单词对是否已经加入了收藏夹中。

Widget _buildRow(WordPair pair) {
  final alreadySaved = _saved.contains(pair);
  ...
}

3, 在 _buildRow 方法中,往 ListTile 中添加心形图标,来标记收藏,然后,再添加与心形图标的交互元素。代码如下所示:

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,
    ),
  );
}

4, 重启APP, 你应该能看到每一行都会有一个心形图标了,但是现在还没有办法与其交互。

5, 让心形图标可以变得点击。如果一个单词对已经保存在收藏夹中了,再一次点击它,会把单词对从收藏夹中删除,
当图标被点击后,会调用 setState() 回调函数来通知框架状态已经改变了。

代码如下所示:

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: () {
      setState(() {
        if (alreadySaved) {
          _saved.remove(pair);
        } else {
          _saved.add(pair);
        }
      });
    },
  );
}

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

热加载APP后,你能够点击任意一行喜欢或不喜欢的条目,注意,点击一行会产生一段渲染的动画。

《四.创建你的第一个Flutter APP》

报错?

如果APP没有正确运行,认真寻找错误。如若必要,参照下列链接中的代码:
lib/main.dart

Step 6: 导航到新窗口

在这一步中,将要添加一个新界面(在Flutter中,叫 route ),这个新界面显示最爱的单词对,你将学会如何在home界面和新界面之间来回导航。

在Flutter中,导航器管理包含 route 的堆栈。添加一个 route 在导航栈上,新界面将通过该 route 进入。从导航栈中弹出一条 route,将回到原来的界面。

1, 在 RandomWordsState 类中 build() 方法的 AppBar 上添加一个列表图标,当用户单击列表图标时,一个包含收藏项的新 route 被推送到导航器中。

注: 有些组件的子组件是单个组件 (child)。另一些组件,像 action 的子组件是一个组件数组 (children),数组用一对方括号表示([])。

将图标及其对应的动作添加到 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: new Icon(Icons.list), onPressed: _pushSaved),
        ],
      ),
      body: _buildSuggestions(),
    );
  }
  ...
}

2, 在 RandomWordsState 中添加 _pushSaved() 方法:

class RandomWordsState extends State<RandomWords> {
  ...
  void _pushSaved() {
  }
}

热加载APP之后,会看到列表图标出现在顶栏中,现在点击它不会发生任何事情,因为 _pushSaved() 方法是空的。

3, 当用户点击APP顶栏上的列表图标时,创建一个 route ,并将它添加到导航栈中,这个动作会使APP显示一个新界面。

新界面的内容由 MaterialPageRoute 的 builder 属性构建,这个属性应该是一个匿名函数。

添加一条新路径的代码:

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

4, 添加 MaterialPageRoute 和它的 builder 属性,然后添加生成ListTile行的代码。
ListTile 的 divideTiles() 方法在每行之间添加了一个水平的间距。divided 变量保存了最后一行,最后由 toList() 函数转换为列表:

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

5, builder 返回一个 Scaffold 组件,为包含一个新界面的顶栏,名为 “Saved Suggestions.”。新界面的 body 属性是包含若干 ListTiles 的 ListView ,每一行使用分割线分割。

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

        return new Scaffold(
          appBar: new AppBar(
            title: new Text('Saved Suggestions'),
          ),
          body: new ListView(children: divided),
        );
      },
    ),
  );
}

6, APP热加载之后,选择一些最喜欢的,然后点击顶栏中的列表图标,新界面是一个收藏夹,注意,导航加入了一个”Back”的按钮在顶栏中,这里,不需要显式地调用 Navigator.pop,点击后退按钮会返回到主界面。

《四.创建你的第一个Flutter APP》

报错?

如果APP没有正确运行,认真寻找错误。如若必要,参照下列链接中的代码:
lib/main.dart

Step 7: 使用全新的主题

这一步,将更换APP的主题,主题控制着APP的外观,你可以使用默认的主题,默认主题由你的物理设备或模拟器决定,你可以使用自定义的主题来改变APP的外观。

1, 通过配置 ThemeData 类,可以很容易的更改APP的主题,下面,将主题颜色改为白色:

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(),
    );
  }
}

2, 热重载应用。注意到整个背景是白色的,甚至包括导航栏。

3, 留给读者一个练习:ThemeData 类可以改变UI的多个方面,
Material 库中的 Colors 类提供了很多可以使用的颜色常量,热重载可以让颜色测试变得更快。

《四.创建你的第一个Flutter APP》

报错?

如果APP没有正确运行,认真寻找错误。如若必要,参照下列链接中的代码:
lib/main.dart

圆满结束

你已经写完了能够运行在IOS和安卓两个平台上的交互式 Flutter APP, 在这篇文章中,你学会了:

  • 从头创建一个 Flutter APP
  • 编写 Dart 代码
  • 利用第三方库
  • 使用热加载来提高开发效率
  • 创建带状态的组件,为APP增加了交互性
  • 创建了一个延迟加载的、无限滚动的列表,并显示了一个列表视图和列表框
  • 创建了一条 route ,并添加了在主route和新route之间移动的逻辑
  • 了解了如何使用主题来改变应用程序UI的外观
    原文作者:且随疾风
    原文地址: https://zhuanlan.zhihu.com/p/34617369
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞