【Flutter】编写第一个 Flutter 应用

第1步:创建初始Flutter应用

创建一个简单的 Flutter 应用。主要编辑 Dart 代码所在的 lib / main.dart

  1. 替换 lib / main.dart 。
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'),
        ),
      ),
    );
  }
}
  1. 运行应用程序。现在应该可以看到下面的页面。


    18288956071032318.jpg

第2步:使用外部 package

在这一步,将使用名为 english_words 的开源软件包 ,其中包含数千个最常用的英文单词以及一些实用功能。
可以在 pub.dartlang.org 上找到 english_words 软件包以及其他许多开源软件包。

  1. pubspec 文件管理着 Flutter 应用程序的静态资源文件(assets)。 在 pubspec.yaml 文件中, 将 english_words(3.1.0或更高版本)添加到依赖列表。新的一行高亮如下:
dependencies:
  flutter:
    sdk: flutter
 
  cupertino_icons: ^0.1.0
  english_words: ^3.1.0 // 新增的
  1. 在 Android Studio 的 editor 视图中查看 pubspec 时, 点击右上角的 Packages get ,将把 package 拉取到项目中。在控制台中看到以下内容:
flutter packages get
Running "flutter packages get" in startup_namer...
Process finished with exit code 0
  1. lib/main.dart 中,为 english_words 添加导入:
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';// 新增的
  1. 改用英文单词的 package 来生成文本,而不是字符串 “Hello World” 。
    对代码进行以下更改,如所示:
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.
        ),
      ),
    );
  }
}
  1. 运行:


    248652834188319698.jpg

第3步:添加有状态的widget

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

Statefulwidget 在其生命周期保持的状态可能会变化,实现一个有状态的 widget 至少需要两个类:StatefulWidgets类和State类,其中StatefulWidgets类创建了一个State类的实例。StatefulWidget类本身是不可变的,但State类可存在于Widget的整个生命周期中。

在这一步,将添加一个有状态的 RandomWords widget ,它可以创建其 State 类 RandomWordsState 。 State 类会为 widget 保存被推荐和被收藏的词组。

  1. 将有状态的 RandomWords widget 添加到 main.dart 。它可以在 MyApp 类之外的任何位置使用,但当前将把它放在文件底部。 RandomWords widget 除了创建 State 类之外几乎没有任何其他代码:
class RandomWords extends StatefulWidget {
  @override
  createState() => new RandomWordsState();
}
  1. 添加 RandomWordsState 类。这个类保存了 RandomWords widget 的状态,该应用程序的大部分代码都放在该类中。这个类将保存随着用户的滑动操作而生成的无限增长的词组,以及保存用户收藏的词组,用户通过触发心形图标来添加或删除收藏的词组列表。

添加 state 类之后,必须有 build 方法,并将生成单词的代码行从 MyApp 类移动到 RandomWordsState 类的 build 方法中,生成词组。

class RandomWordsState extends State<RandomWords> {
  @override
  Widget build(BuildContext context) {
    final wordPair = new WordPair.random();
    return new Text(wordPair.asPascalCase);
  }
}
  1. 从 MyApp 中删除生成单词的代码:
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // final wordPair = new WordPair.random();  // Delete this line

    return new MaterialApp(
      title: 'Welcome to Flutter',
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text('Welcome to Flutter'),
       ),
        body: new Center(
          // [图片上传中...(369675901483756576.jpg-18adf7-1531203134196-0)]
child: new Text(wordPair.asPascalCase), // Change the highlighted text to...
          child: new RandomWords(), // ... this highlighted text
        ),
      ),
    );
  }
}
  1. 重启应用:


    369675901483756576.jpg

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

在这一步,可以扩展 RandomWordsState 类,生成并展示词组列表。当用户滑动列表,ListView widget 中显示的列表将无限增长。

  1. _suggestions 变量向 RandomWordsState 类中添加一个数组列表,用来保存推荐词组。 该变量以下划线(_)开头,在 Dart 语言中使用下划线前缀表示强制私有。

    此外,添加一个 biggerFont 变量来增大字体。

    class RandomWordsState extends State<RandomWords> {
      final _suggestions = <WordPair>[];
    
      final _biggerFont = const TextStyle(fontSize: 18.0);
      ...
    }
    
  2. 向 RandomWordsState 类添加一个 _buildSuggestions() 函数,用于构建一个显示词组的 ListView 。

    ListView 类提供了一个 itemBuilder 属性,这是一个工厂 builder 并作为匿名函数进行回调。它有两个传入参数— BuildContext 上下文和行迭代器 i 。对于每个推荐词组都会执行一次函数调用,迭代器从 0 开始,每调用一次函数就累加 1 。这个模块允许推荐列表在用户滑动时无限增长。

    添加如下高亮代码行:

    class RandomWordsState extends State<RandomWords> {
      ...
      Widget _buildSuggestions() {
        return new ListView.builder(
          padding: const EdgeInsets.all(16.0),
          // The itemBuilder callback is called once per suggested word pairing,
          // and places each suggestion into a ListTile row.
          // For even rows, the function adds a ListTile row for the word pairing.
          // For odd rows, the function adds a Divider widget to visually
          // separate the entries. Note that the divider may be difficult
          // to see on smaller devices.
          itemBuilder: (context, i) {
            // Add a one-pixel-high divider widget before each row in theListView.
            if (i.isOdd) return new Divider();
    
            // The syntax "i ~/ 2" divides i by 2 and returns an integer result.
            // For example: 1, 2, 3, 4, 5 becomes 0, 1, 1, 2, 2.
            // This calculates the actual number of word pairings in the ListView,
            // minus the divider widgets.
            final index = i ~/ 2;
            // If you've reached the end of the available word pairings...
            if (index >= _suggestions.length) {
              // ...then generate 10 more and add them to the suggestions list.
              _suggestions.addAll(generateWordPairs().take(10));
            }
            return _buildRow(_suggestions[index]);
          }
        );
      }
    }
    
  3. 对于每个词组,_buildSuggestions 函数都调用一次 _buildRow 函数。这个函数每次会在一个 ListTile widget 中展示一条新词组,这将在下一步操作中,使一行数据更有表现力。

    添加 _buildRow 函数到 RandomWordsState 类中:

    class RandomWordsState extends State<RandomWords> {
      ...
    
      Widget _buildRow(WordPair pair) {
        return new ListTile(
          title: new Text(
            pair.asPascalCase,
            style: _biggerFont,
          ),
        );
      }
    }
    
  4. 更新 RandomWordsState 类的 build 方法来使用 _buildSuggestions() 函数,而不是直接调用单词生成库。对高亮部分进行修改:

    class RandomWordsState extends State<RandomWords> {
      ...
      @override
      Widget build(BuildContext context) {
        final wordPair = new WordPair.random(); // Delete these two lines.
        Return new Text(wordPair.asPascalCase);
        return new Scaffold (
          appBar: new AppBar(
            title: new Text('Startup Name Generator'),
          ),
        body: _buildSuggestions(),
        );
      }
      ...
    }
    
  5. 更新 MyApp 类的 build 方法。从 MyApp 中删除 Scaffold 和 AppBar 实例。这些将由 RandomWordsState 类进行统一管理,这样在下一步操作中,可以使用户从一个页面导航到另一页面时,更方便的更改应用栏中的页面名称。

    用下面高亮的 build 方法替换原始代码:

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

重启应用程序,将可以看到一个词组清单。尽量向下滑动,将继续看到新的词组。


74782939917607052.jpg

第5步:添加可交互性

在这一步,将为每一行添加可点击的心形图标。当用户点击列表中的条目,切换其“收藏”状态,词组就会添加到收藏栏,或从已保存词组的收藏栏中删除。

  1. 添加一个 Set 集合 _saved 到 RandomWordsState 类。保存用户收藏的词组。Set 集合比 List 更适用于此,因为它不允许重复元素。

    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() 的 ListTiles widget 中,添加一个心形图标来使用收藏功能,随后将添加与心形图标进行交互的功能。

    添加以下高亮代码行:

    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. 重启应用。现在应该可以在每一行看到心形图标,但还没有交互功能。

  5. _buildRow 函数中使心形可点击。如果词条已经被加入收藏,再次点击它将从收藏中删除。当心形图标被点击,函数将调用 setState() 通知应用框架state已经改变。

    添加高亮代码行:

    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的更新。

热重载应用。可以点击任意一行来收藏或取消收藏条目。 请注意,点击一行可以产生从心形图标展开的泼墨动画效果。

733844901585464410.jpg

第6步:导航到新页面

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

在 Flutter 中, Navigator 管理着包含了应用程序所有路由的一个堆栈。将一个路由push到 Navigator 的堆栈,将显示更新为新页面路由。将一个路由 pull 出 Navigator 的堆栈,显示将返回到前一个页面路由。

  1. 在 RandomWordsState 类的 build 方法中,向 AppBar 添加一个列表图标。当用户点击列表图标时,包含了已收藏条目的新路由将被 push 到 Navigator 堆栈并显示新页面。

    小贴士: 某些 widget 属性使用独立 widget(child) 和其他属性例如 action 组成一个子 widget 数组(children),用方括号([])表示。

    将该图标及其相应的 action 操作添加到 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() {
      }
    }
    

    重新加载应用程序。列表图标将出现在应用栏中。点击它不会有任何响应,因为 _pushSaved 这个函数还未实现功能。

  3. 当用户点击应用栏中的列表图标时,将建立一个新路由并 push 到 Navigator 的路由堆栈中,这个操作将改变界面显示,展示新的路由页面。

    新页面的内容使用匿名函数在 MaterialPageRoute widget的builder属性中创建。

    将函数调用添加到 Navigator.push 中作为参数,如高亮代码所示,将路由 push 到 Navigator 的堆栈中。

    void _pushSaved() {
      Navigator.of(context).push(
      );
    }
    
  4. 添加 MaterialPageRoute widget 及其 builder 属性。先添加生成 ListTile widget 的代码。其中 ListTile 的 divideTiles() 方法为每个 ListTile widget 之间添加水平间距。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 widget ,其中包含了应用栏标题名为 “Saved Suggestions” 的新路由页面。新页面的body属性由包含多个 ListTile widget 的 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. 热重载应用程序。对一些条目点击收藏,然后点击应用栏右侧的列表图标。显示出包含收藏夹列表的新页面。注意,Navigator 会在应用栏左侧添加一个“返回”按钮。不必再显式实现 Navigator.pop 。点击返回按钮会返回到主页面。

698831790289287084.jpg
499341336619050828.jpg

第7步:使用主题更改UI

在最后一步中,将使用该应用的主题。 theme 控制的是应用程序的观感。可以使用默认主题,该主题取决于使用的模拟器或真机,也可以自定义主题以反映你的品牌。

  1. 可以通过配置 ThemeData 类轻松更改应用程序的主题。应用程序目前使用默认主题,现在将更改主要颜色为白色。

    将高亮代码添加到 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(),
        );
      }
    }
    
  2. 热重载应用程序。请注意,整个背景都是白色的,甚至包括应用栏。

  3. 作为读者的练习,可使用 ThemeData 来改变用户界面的其他方面。 Material 库中的 Colors 类提供了多种可以使用的颜色常量,而热重载使用户界面的修改变得简单快捷。

390394729575969454.jpg
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容