Flutter 学习笔记2 - 首个应用

创建一个最简单的 App

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

Flutter 提供了一套丰富的 Material widgets。

有个非常重要的概念 Widget,几乎所有东西都是一个 widget。MyApp 继承 StatelessWidget 就是让自己变成一个 widget。

widget 最主要的工作就是提供 build() 方法来描述如何组织显示内部低层的 widget。

Scaffold 是 Material library 中提供的一个widget,它提供了默认的导航栏、标题和包含主屏幕 widget 树的 body 属性。

引入第三方包

打开 pubspec.yaml,引入 english_words 这个包。

dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^0.1.2
  english_words: ^3.1.0

点击上方的 “Packages get” 以加载引入的包,或者执行命令 flutter packages get 也是可以的。

《Flutter 学习笔记2 - 首个应用》 屏幕快照 2018-04-06 下午1.05.00.png

然后在 main.dart 中引入这个包

import 'package:english_words/english_words.dart';

修改代码使用这个包

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // 创建 wordPair
    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),
        ),
      ),
    );
  }
}

wordPair 是随机的,每次 Reload 都能看到不一样的文字。

添加一个 Stateful widget

Stateless widgets 是不可变的,所有属性都是 final 的。MyApp 本身就是一个 Stateless widget,实现了 build 方法。

Stateful widgets 维持着整个生命周期中可变的状态。实现一个 stateful widget 需要至少两个类:State 类和一个创建 State 实例的 StatefulWidget。StatefulWidget 本身不可变,但是 State 类在 widget 生命周期中一直存留。

  1. 添加 RandomWordsState。大部分代码会在这个类里,它是一个 State,它为 RandomWords widget 保存维持状态。这个例子里它的作用是:

    • 保存生成的单词对,随着用户滑动而无限增长
    • 用户通过点击列表的心形图标来添加或移除喜欢的单词对
    class RandomWordsState extends State<RandomWords> {
      // TODO Add build() method
    }
    
  2. 添加 stateful 类型的 RandomWords widget,在 main.dart 中,MyApp 类外的任何地方定义都可以。主要作用是创建 State。

    class RandomWords extends StatefulWidget {
      @override
      RandomWordsState createState() => RandomWordsState();
    }
    
  3. 实现 build 方法

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

    build 方法返回一个 Widget,而 Text 本身是 StatelessWidget 的子类。

  4. 使用 RandomWords 这个 Widget

    将 MyApp 中 body 里的

    child: Text(wordPair.asPascalCase),
    

    修改成

    child: RandomWords(),
    

    RandomWords 是一个 stateful widget,它通过 createState 创建了一个 RandomWordsState,这个 State 来为这个 Widget 保存状态,State 本身通过 build 返回了一个 Text。

创建无限滚动的列表

  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 类提供了一个 builder 属性——itemBuilder,它是一个工厂构建者,且通过匿名函数提供回调功能,这个匿名函数有两个参数,BuildContext 和行迭代器,迭代器从 0 开始且每次调用方法时递增。

    class RandomWordsState extends State<RandomWords> {
      // ...
      Widget _buildSuggestions() {
        return ListView.builder(
          padding: const EdgeInsets.all(16.0),
          itemBuilder: (context, i) {
            // 奇数行,返回 1 像素的分割线,相比于 Android,这里分割线本身也是一个 Item,也占了一个 position
            if (i.isOdd) return Divider();
    
            // 整除 2,由于奇数行是分割线,所以能执行到这里时,i 的值为 0, 2, 4, ...
            final index = i ~/ 2;
            // _suggestions 里的单词都被用过了
            if (index >= _suggestions.length) {
              // 再添加 10 条进去
              _suggestions.addAll(generateWordPairs().take(10));
            }
            // 使用单词对创建一个 ListTile
            return _buildRow(_suggestions[index]);
          });
      }
    }
    
  3. 在 RandomWordsState 中添加 _buildRow 函数

    Widget _buildRow(WordPair pair) {
      return ListTile(
        title: Text(
          pair.asPascalCase,
          style: _biggerFont, // 使用定义的文字样式控制大小
        ),
      );
    }
    
  4. 修改 RandomWordsState 的 build 方法,使用 _buildSuggestions

    class RandomWordsState extends State<RandomWords> {
      // ...
      @override
      Widget build(BuildContext context) {
        return Scaffold (
          appBar: AppBar(
            title: Text('Startup Name Generator'),
          ),
          body: _buildSuggestions(),
        );
      }
    }
    
  5. 修改 MyApp 的 build 方法

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

    这样整个页面的状态都转移到了 RandomWords 这个 Stateful widget 里,它最后通过 State 的 build 方法来获取显示的 Widget,而这个 Widget 的 body 调用 _buildSuggestions 方法返回 ListView。

添加交互

添加心形图标,相当于收藏功能。

  1. 在 RandomWordsState 添加私有属性 _saved 存储用户点击收藏后的单词对

    final _saved = Set<WordPair>();
    
  2. _buildRow 函数中,添加 alreadySaved 属性,是个 bool 类型,用于判断这个单词对是否已经在之前定义的 _saved 集合中

    final alreadySaved = _saved.contains(pair);
    
  3. _buildRow 里,添加心形图标,图标是在列表项里的,所以在创建 ListTile 里面添加 Icon

    Widget _buildRow(WordPair pair) {
      final alreadySaved = _saved.contains(pair);
      return ListTile(
        title: Text(
          pair.asPascalCase,
          style: _biggerFont,
        ),
        trailing: Icon(
          // 判断是否在收藏,显示不同的图标和颜色
          alreadySaved ? Icons.favorite : Icons.favorite_border,
          color: alreadySaved ? Colors.red : null,
        ),
      );
    }
    
  4. 增加交互,当点击心形图标时,调用 setState() 去通知框架状态 State 被改变了,再次点击后,从收藏中移除

    Widget _buildRow(WordPair pair) {
      ...
      return ListTile(
        ...
        // 一整行的点击事件
        onTap: () {
          setState(() {
            if (alreadySaved) {
              _saved.remove(pair);
            } else {
              _saved.add(pair);
            }
          });
        },
      );
    }
    

    和 Android 不同,在 Flutter 的响应式风格框架中,调用 setState() 会再次触发 State 的 build(),然后页面就被修改了。不是主动去修改界面,而是修改数据,让界面自动更新

切换新页面

Flutter 中页面叫 route,Navigator 维护着一个存放所有 route 的栈,进栈显示,消失就从栈移除,和 Android 差不多。

  1. 在 RandomWordsState 中的 build 方法里,在 AppBar 添加一个列表图标,点击时就去一个新的 route 页面,包含所有的收藏单词对。

    class RandomWordsState extends State<RandomWords> {
      ...
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('Startup Name Generator'),
            // 是一个数组,现在有一个 IconButton,点击时就去调用 _pushSaved 方法
            actions: <Widget>[
              IconButton(icon: Icon(Icons.list), onPressed: _pushSaved),
            ],
          ),
          body: _buildSuggestions(),
        );
      }
      
      void _pushSaved() {
      }
      ...
    }
    
  2. 用户点击 AppBar 上的图标时,创建一个 route 并通过 Navigator 的 push 方法添加到栈顶

    void _pushSaved() {
      Navigator.of(context).push(
        MaterialPageRoute<void>(
          builder: (BuildContext context) {
            final Iterable<ListTile> tiles = _saved.map(
                  (WordPair pair) {
                return ListTile(
                  title: Text(
                    pair.asPascalCase,
                    style: _biggerFont,
                  ),
                );
              },
            );
            final List<Widget> divided = ListTile
                .divideTiles( // 添加水平分隔线
                  context: context,
                  tiles: tiles, // divided 持有上面的列表项
                )
                .toList(); // 转化成列表
    
            return Scaffold( // 返回一个页面
              appBar: AppBar(
                title: const Text('Saved Suggestions'),
              ),
              body: ListView(children: divided), // 通过上面的 divided 构建 ListView
            );
          },
        ),
      );
    }
    

    添加 MaterialPageRoute,在它的 build 方法中创建列表项 ListTile,即 divided,然后新页面用它构建 ListView。

    新页面导航栏上有个返回按钮,点击就可以返回。

修改主题

通过 ThemeData 类修改主题为白色

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Startup Name Generator',
      theme: ThemeData(    
        primaryColor: Colors.white,
      ),
      home: RandomWords(),
    );
  }
}
    原文作者:七适散人
    原文地址: https://www.jianshu.com/p/6c9273575ee6
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞