我的第一个 Flutter APP 应用

10 篇文章 0 订阅
1 篇文章 0 订阅

本教程 follow Flutter official website ,进行了排坑与更详细的步骤介绍与讲解,希望在此取得比官网更高的学习效率。

我们将实现:取名字的APP。

主要功能:

  • 选择(收藏)、取消。一次生成十个名称,滚动生成新一批名称。
  • 点击导航栏右边的icon,进入到仅列出收藏名称的新页面

将学到:

  • Flutter应用程序的基本结构.
  • 查找和使用packages来扩展功能.
  • 使用热重载加快开发周期.
  • 如何实现有状态的widget.
  • 如何创建一个无限的、懒加载的列表.
  • 如何创建并导航到第二个页面.
  • 如何使用主题更改应用程序的外观.

您需要准备的:

1 创建 Flutter app


 知识库:

  • Flutter 提供了一套丰富的 material widgets,所以本示例使用 Material(视觉设计语言)来构建。
  • 在 Flutter 中,大多数东西都是 widget,包括对齐(alignment)、填充(padding)和布局(layout)。
  • widget的主要工作是提供一个 build() 方法来描述如何根据其他较低级别的 widget 来显示自己。
  • 本示例中 Center widget 可以将其子 widget 树对其到屏幕中心。
  • 格式化:右键选择 Format Document(快捷键:shift + alt + F)。

       逗号结尾使构建方法的自动格式化更好。(可尝试一下删去逗号之后格式化的效果)

1.1 替换 lib/main.dart 中所有代码,启动后如下图:

import 'package:flutter/material.dart';

void main() => runApp(new MyApp()); // main函数使用了(=>)符号, 这是Dart中单行函数或方法的简写

class MyApp extends StatelessWidget {
  // 该应用程序继承了 StatelessWidget,这将会使应用本身也成为一个widget。
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Welcome to Flutter',
      home: new Scaffold( 
      // Scaffold是Material library中提供的一个widget, 它提供了默认的导航栏、标题和包含主屏幕widget树的body属性。
        appBar: new AppBar(
          title: new Text('Welcome to Flutter'),
          centerTitle: true, // 标题居中
        ),
        body: new Center(
          child: new Text('Hello World'),
        ),
      ), // 逗号结尾使构建方法的自动格式化更好。
    );
  }
}

2 使用外部包(package)


知识库:

  • 我们将引入 english_words 的开源软件包,它会帮助我们生成所需要的英文单词。
  • 可在 pub.dartlang.org 上搜索各种软件包,他们都是开源的。
  • pubspec 管理静态资源(图片、软件包等),所以我们稍后会在 pubspec.yaml 中添加依赖的软件包。

2.1 在 pubspec.yaml 中添加 english_words 到依赖项列表

dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^0.1.0
  english_words: ^3.1.0

在控制台输入 flutter packages get 安装依赖项

(即使安装了依赖项,后期还可能报错 Target of URI doesn't exist 'package:flutter/material.dart',再次执行此命令即可)

2.2 在 lib/main.dart 中, 引入 english_words

import 'package:english_words/english_words.dart';

引入后会提示 未使用,建议移除 (如下)Don't worry, let's use it.

2.3 使用 English words 包随机生成英文单词替换“Hello World”. 

// 实例化随机生成英文单词的对象
final wordPair = new WordPair.random(); 

// 在body中使用
body: new Center(
  child: new Text(wordPair.asPascalCase),
),

附第2步完整 lib/main.dart 代码和实现效果

import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart'; // 引入包

void main() => runApp(new MyApp()); // main函数使用了(=>)符号, 这是Dart中单行函数或方法的简写

class MyApp extends StatelessWidget {
  // 该应用程序继承了 StatelessWidget,这将会使应用本身也成为一个widget。
  @override
  Widget build(BuildContext context) {
    final wordPair = new WordPair.random(); // 实例化wordPair
    return new MaterialApp(
      title: 'Welcome to Flutter',
      home: new Scaffold( 
      // Scaffold是Material library中提供的一个widget, 它提供了默认的导航栏、标题和包含主屏幕widget树的body属性。
        appBar: new AppBar(
          title: new Text('Welcome to Flutter'),
          centerTitle: true, // 标题居中
        ),
        body: new Center(
          child: new Text(wordPair.asPascalCase),
        ),
      ), // 逗号结尾使构建方法的自动格式化更好。
    );
  }
}

 

第3步: 添加一个 有状态的部件(Stateful widget) 


知识库:

  • Stateless widgets 是不可变的,Stateful widgets 是可变.
  • 实现一个 stateful widget 至少需要两个类:
  1. StatefulWidget 类。
  2. State 类。

3.1 在 main.dart 中添加一个有状态的 widget → RandomWords,

RandomWords 创建其 State 类 → RandomWordsState,作用是为 widget 维护数据。

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

3.2 添加 RandomWordsState 类.

该应用程序的大部分代码都在该类中,该类持有 RandomWords 的状态,保存数据实现业务逻辑

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

通过下面高亮显示的代码,将生成单词对代的码从MyApp移动到RandomWordsState中

body: new Center(
  child: new RandomWords(),
),

3.3 热重载查看效果

// 可能会出现此警告,这可能是误报,重新启动即可
Reloading...
Not all changed program elements ran during view reassembly; consider restarting.

附第3步完整 lib/main.dart 代码和实现效果

import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart'; // 引入包

void main() => runApp(new MyApp()); // main函数使用了(=>)符号, 这是Dart中单行函数或方法的简写

class MyApp extends StatelessWidget {
  // 该应用程序继承了 StatelessWidget,这将会使应用本身也成为一个widget。
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Welcome to Flutter',
      home: new Scaffold( 
      // Scaffold是Material library中提供的一个widget, 它提供了默认的导航栏、标题和包含主屏幕widget树的body属性。
        appBar: new AppBar(
          title: new Text('Welcome to Flutter'),
          centerTitle: true, // 标题居中
        ),
        body: new Center(    
          child: new RandomWords(),
        ),
      ),
    );
  }
}
class RandomWordsState extends State<RandomWords> {
  @override
  Widget build(BuildContext context) {
    final wordPair = new WordPair.random();
    return new Text(wordPair.asPascalCase);
  }
}
class RandomWords extends StatefulWidget {
  @override
  createState() => new RandomWordsState();
}

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


知识库:

以注释的形式嵌在代码中,更易理解!

4.1 向 RandomWordsState 类中添加业务逻辑

class RandomWordsState extends State<RandomWords> {
  final _suggestions = <WordPair>[]; // 保存数据(Dart语言中使用下划线前缀标识符,会强制将其变成私有的)
  final _biggerFont = const TextStyle(fontSize: 18.0); // 改变字体大小

  Widget _buildSuggestions() {
    return new ListView.builder( //  ListView 的 builder 工厂构造函数支持按需建立一个懒加载的列表视图。
      padding: const EdgeInsets.all(16.0),
      // 对于每个建议的单词对都会调用一次itemBuilder,然后将单词对添加到ListTile行中
      // 在偶数行,该函数会为单词对添加一个ListTile row.
      // 在奇数行,该函数会添加一个分割线widget,来分隔相邻的词对。
      // 注意,在小屏幕上,分割线看起来可能比较吃力。
      itemBuilder: (context, i) {
        // 在每一列之前,添加一个1像素高的分隔线widget
        if (i.isOdd) return new Divider();

        // 语法 "i ~/ 2" 表示i除以2,但返回值是整形(向下取整),比如i为: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]);
      }
    );
  }

  // 对于每一个单词对,_buildSuggestions函数都会调用一次_buildRow
  Widget _buildRow(WordPair pair) {
    return new ListTile(
      title: new Text(
        pair.asPascalCase,
        style: _biggerFont,
      ),
    );
  }
}

 

4.2 更新 RandomWordsState 的 build 方法以使用 _buildSuggestions()

@override
Widget build(BuildContext context) {
  return new Scaffold (
    appBar: new AppBar(
      title: new Text('Startup Name Generator'),
    ),
    body: _buildSuggestions(),
  );
}

4.3 更新MyApp的build方法

从 MyApp 中删除 Scaffold 和 AppBar 实例,换成用 RandomWordsState 管理。

这使得用户在下一步中从一个屏幕导航到另一个屏幕时, 可以更轻松地更改导航栏中的的路由名称。

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

 附第4步完整 lib/main.dart 代码和实现效果

import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart'; // 引入包

void main() => runApp(new MyApp()); // main函数使用了(=>)符号, 这是Dart中单行函数或方法的简写

class MyApp extends StatelessWidget {
  // 该应用程序继承了 StatelessWidget,这将会使应用本身也成为一个widget。
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Startup Name Generator',
      home: new RandomWords(),
    );
  }
}
class RandomWordsState extends State<RandomWords> {
  final _suggestions = <WordPair>[];
  final _biggerFont = const TextStyle(fontSize: 18.0);
  Widget _buildSuggestions() {
    return new ListView.builder(
      padding: const EdgeInsets.all(16.0),
      // 对于每个建议的单词对都会调用一次itemBuilder,然后将单词对添加到ListTile行中
      // 在偶数行,该函数会为单词对添加一个ListTile row.
      // 在奇数行,该函数会添加一个分割线widget,来分隔相邻的词对。
      // 注意,在小屏幕上,分割线看起来可能比较吃力。
      itemBuilder: (context, i) {
        // 在每一列之前,添加一个1像素高的分隔线widget
        if (i.isOdd) return new Divider();

        // 语法 "i ~/ 2" 表示i除以2,但返回值是整形(向下取整),比如i为: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]);
      }
    );
  }
  Widget _buildRow(WordPair pair) {
    return new ListTile(
      title: new Text(
        pair.asPascalCase,
        style: _biggerFont,
      ),
    );
  }
  @override
  Widget build(BuildContext context) {
    return new Scaffold (
      appBar: new AppBar(
        title: new Text('Startup Name Generator'),
      ),
      body: _buildSuggestions(),
    );
  }
}
class RandomWords extends StatefulWidget {
  @override
  createState() => new RandomWordsState();
}

 

第5步: 添加选择❤️取消交互


 知识库:

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

5.1 添加存储用户收藏单词的 Set

添加一个 _saved Set(集合) 到 RandomWordsState。

在这里,Set 比 List 更合适,因为 Set 中不允许重复的值。

final _saved = new Set<WordPair>();

5.2 检查是否收藏过

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

final alreadySaved = _saved.contains(pair);

5.3 添加心形 ❤️ icon

在 _buildRow()中, 添加一个❤️到 ListTiles

trailing: new Icon(
  alreadySaved ? Icons.favorite : Icons.favorite_border,
  color: alreadySaved ? Colors.red : null,
),

5.4 添加❤️的交互能力

在 _buildRow中让列表变得可以点击,❤️可变色。

当被点击时,函数调用 setState()通知框架状态已经改变。

如果单词条目已经添加到收藏夹中, 再次点击它将其从收藏夹中删除。

onTap: () {
  setState(() {
    if (alreadySaved) {
      _saved.remove(pair);
    } else {
      _saved.add(pair);
    }
  },
}

附第5步完整 lib/main.dart 代码和实现效果 

import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart'; // 引入包

void main() => runApp(new MyApp()); // main函数使用了(=>)符号, 这是Dart中单行函数或方法的简写

class MyApp extends StatelessWidget {
  // 该应用程序继承了 StatelessWidget,这将会使应用本身也成为一个widget。 在Flutter中,大多数东西都是widget,包括对齐(alignment)、填充(padding)和布局(layout)
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Startup Name Generator',
      home: new RandomWords(),
    );
  }
}
class RandomWordsState extends State<RandomWords> {
  final _suggestions = <WordPair>[];
  final _saved = new Set<WordPair>();
  final _biggerFont = const TextStyle(fontSize: 18.0);
  
  Widget _buildSuggestions() {
    return new ListView.builder(
      padding: const EdgeInsets.all(16.0),
      // 对于每个建议的单词对都会调用一次itemBuilder,然后将单词对添加到ListTile行中
      // 在偶数行,该函数会为单词对添加一个ListTile row.
      // 在奇数行,该函数会添加一个分割线widget,来分隔相邻的词对。
      // 注意,在小屏幕上,分割线看起来可能比较吃力。
      itemBuilder: (context, i) {
        // 在每一列之前,添加一个1像素高的分隔线widget
        if (i.isOdd) return new Divider();

        // 语法 "i ~/ 2" 表示i除以2,但返回值是整形(向下取整),比如i为: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]);
      }
    );
  }
  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);
        }
      });
    },
    );
  }
  @override
  Widget build(BuildContext context) {
    return new Scaffold (
      appBar: new AppBar(
        title: new Text('Startup Name Generator'),
        centerTitle: true,// 标题居中
      ),
      body: _buildSuggestions(),
    );
  }
}
class RandomWords extends StatefulWidget {
  @override
  createState() => new RandomWordsState();
}


 第6步: 导航到新页面

知识库:

  • 我们将学习如何在主路由和新路由之间切换。
  • 在Flutter中,导航器管理应用程序的路由栈。
  • 将路由推入(push)到导航器的栈中,将会显示更新为该路由页面。
  • 从导航器的栈中弹出(pop)路由,将显示返回到前一个路由。
  • 导航器会主动在应用栏中添加一个“返回”按钮。我们不必显式实现Navigator.pop。

6.1 在 RandomWordsState 的 build 方法中为 AppBar 添加一个列表icon。点击icon,切换路由,进入新页面。

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

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

6.2 向 RandomWordsState 类添加一个_pushSaved() 方法.

void _pushSaved() {
  Navigator.of(context).push( // Navigator.push 会使路由入栈(导航管理器的栈)
    new MaterialPageRoute( // 新页面的内容在 MaterialPageRoute 的 builder属性中构建,
      builder: (context) { // builder是一个匿名函数。
        final tiles = _saved.map(
          (pair) {
            return new ListTile(
              title: new Text(
                pair.asPascalCase,
                style: _biggerFont,
              ),
            );
          },
        );
        // 在每个ListTile之间添加1像素的分割线。 
        final divided = ListTile
          .divideTiles(
            context: context,
            tiles: tiles,
          )
          .toList();
        // builder返回一个Scaffold,其中包含名为“Saved Suggestions”的新路由的应用栏。
        // 新路由的body由包含ListTiles行的ListView组成; 每行之间通过一个分隔线分隔。
        return new Scaffold(
          appBar: new AppBar(
            title: new Text('Saved Suggestions'),
          ),
          body: new ListView(children: divided),
        );
      },
    ),
  );
}

附第6步完整 lib/main.dart 代码和实现效果 

import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart'; // 引入包

void main() => runApp(new MyApp()); // main函数使用了(=>)符号, 这是Dart中单行函数或方法的简写

class MyApp extends StatelessWidget {
  // 该应用程序继承了 StatelessWidget,这将会使应用本身也成为一个widget。 在Flutter中,大多数东西都是widget,包括对齐(alignment)、填充(padding)和布局(layout)
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Startup Name Generator',
      home: new RandomWords(),
    );
  }
}
class RandomWordsState extends State<RandomWords> {
  final _suggestions = <WordPair>[];
  final _saved = new Set<WordPair>();
  final _biggerFont = const TextStyle(fontSize: 18.0);
  
  Widget _buildSuggestions() {
    return new ListView.builder(
      padding: const EdgeInsets.all(16.0),
      // 对于每个建议的单词对都会调用一次itemBuilder,然后将单词对添加到ListTile行中
      // 在偶数行,该函数会为单词对添加一个ListTile row.
      // 在奇数行,该函数会添加一个分割线widget,来分隔相邻的词对。
      // 注意,在小屏幕上,分割线看起来可能比较吃力。
      itemBuilder: (context, i) {
        // 在每一列之前,添加一个1像素高的分隔线widget
        if (i.isOdd) return new Divider();

        // 语法 "i ~/ 2" 表示i除以2,但返回值是整形(向下取整),比如i为: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]);
      }
    );
  }
  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);
        }
      });
    },
    );
  }
  @override
  Widget build(BuildContext context) {
    return new Scaffold (
      appBar: new AppBar(
        title: new Text('Startup Name Generator'),
        centerTitle: true,// 标题居中
        actions: <Widget>[
          new IconButton(icon: new Icon(Icons.list), onPressed: _pushSaved),
        ],
      ),
      body: _buildSuggestions(),
    );
  }

  void _pushSaved() {
    Navigator.of(context).push( // Navigator.push 会使路由入栈(导航管理器的栈)
      new MaterialPageRoute( // 新页面的内容在 MaterialPageRoute 的 builder属性中构建,
        builder: (context) { // builder是一个匿名函数。
          final tiles = _saved.map(
            (pair) {
              return new ListTile(
                title: new Text(
                  pair.asPascalCase,
                  style: _biggerFont,
                ),
              );
            },
          );
          // 在每个ListTile之间添加1像素的分割线。 
          final divided = ListTile
            .divideTiles(
              context: context,
              tiles: tiles,
            )
            .toList();
          // builder返回一个Scaffold,其中包含名为“Saved Suggestions”的新路由的应用栏。
          // 新路由的body由包含ListTiles行的ListView组成; 每行之间通过一个分隔线分隔。
          return new Scaffold(
            appBar: new AppBar(
              title: new Text('Saved Suggestions'),
            ),
            body: new ListView(children: divided),
          );
        },
      ),
    );
  }
}
class RandomWords extends StatefulWidget {
  @override
  createState() => new RandomWordsState();
}

 

第7步:使用主题更改UI


更改主题为红色

主题颜色常量查询 Colors

可使用 ThemeData 来更改其他 UI 颜色

附第7步 MyApp 类的完整代码和实现效果

class MyApp extends StatelessWidget {
  // 该应用程序继承了 StatelessWidget,这将会使应用本身也成为一个widget。 在Flutter中,大多数东西都是widget,包括对齐(alignment)、填充(padding)和布局(layout)
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Startup Name Generator',
      theme: new ThemeData(
        primaryColor: Colors.red,
      ),
      home: new RandomWords(),
    );
  }
}

 

 

希望在这里有所收获 ~

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值