Flutter基础-(2)基本概念及首个应用

上一篇文章 中,我们讲解了 Flutter 开发环境搭建 , 以及运行了官方demo简单体验了下 Flutter app .

此篇我们将开始对一些 Flutter app 中的一些概念做一些讲解 , 一些基本的操作做一些示例 , 主要是参考官网的教程 Write Your First Flutter App

若你对面向对象编程熟悉,以及对基本编程概念如变量、循环、条件了解 , 则适合阅读本篇教程 . 不必需要拥有 Dart 或移动编程经验.

为了更好的阅读体验 , 请点击 阅读原文 :)

  • 步骤 1 : 创建及启动 Flutter app
  • 步骤 2 : 使用一个外部的程序包
  • 步骤 3 : 增加一个 Stateful Widget
  • 步骤 4 : 创建一个无限滚动的 ListView
  • 步骤 5 : 增加交互
  • 步骤 6 : 跳转到新页面
  • 步骤 7 : 通过主题改变UI
  • 完成!

我们将创建什么

我们将实现一个简单的移动应用 , 它会生成创业公司的名称 . 用户可以选择和反选名称 , 保存最好的那些 . 代码一次生成 10 个名称 . 当用户滑动时 , 新一批的名称就会生成 . 用户可以点击导航栏右上的按钮进入一个只展示喜好的名称的列表新页面.

image

我们将学到:

  • Flutter app 的基本结构
  • 使用额外的包去拓展功能
  • 使用热部署来快速开发
  • 如何去实现一个stateful 小部件
  • 如何创建一个无线滑动,懒加载的列表
  • 如何跳转去下一个界面
  • 如果通过主题去修改app外观

步骤 1 : 创建及启动 Flutter app

这里创建一个简单的 flutter app

flutter create flutter_first_app
cd flutter_first_app
flutter run

如有疑问 , 可参考 前一篇文章 指引

简单地 , 我们先将 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'),
        ),
      ),
    );
  }
}

重新运行得到结果

image

发现

  • 这个例子创建了一个 Material Design 风格的app . Material 是一种在移动端及web上标准的视觉设计语言 . Flutter 提供了丰富的 Material 风格小部件
  • main 方法使用了一个大箭头=>写法 , 它是一行代码功能或方法的缩写
  • app 继承 StatefulWidget 使得其自身也是个widget . 在 Flutter 里 , 大多数元素都是 widget , 包括对齐方式(alignment)、 内边距(padding)、布局(layout) .
  • Material 库 中的脚手架小部件 (Scaffold widget) , 提供了一个默认的导航栏、 标题、 内容属性在屏幕中维持了部件树🌲.部件子树可以是很复杂的.
  • 一个小部件的主要工作就是提供 build()方法 , 它是用来表明如何展示其他低层级的widget.
  • 这个示例的部件树由 包含一个 Text 子部件 的Center Widget 组成 . 这个 Center Widget 将其子部件树排列在屏幕中间 .

步骤 2 : 使用一个外部的程序包

在这个步骤里 , 我们将开始使用一个开源程序包 english_words , 它包含了较多的常用的英文单词还有一些工具方法 .

我们可以在 pub.dartlang.org 找到 english_words 及 其他开源程序包

1. pubspec 文件负责管理 Flutter 应用的资源. 在 pubspec.yaml 文件中,添加 english_words(3.1.0或更高版本)到依赖里.

dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^0.1.0
  english_words: ^3.1.0

**2. **当我们在IDEA 视图中 , 修改yaml文件后 , 可点击右上方的 Packages get 使之生效.它会拉取我们才添加的依赖包, 控制台中打印

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

**3. ** 在 lib/main.dart文件中,添加 import 语句 , 导入依赖相关类

import 'package:english_words/english_words.dart';

**4. ** 用开源库生成文本代替原来的 '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.
        ),
      ),
    );
  }
}

**5. **若app正在运行, 可以通过点击⚡️按钮进行热部署. 每次点击或者保存时 , 会生成新的一个随机单词. 这是因为单词是在 build(...) 方法中生成, 它会在每次 MaterialApp 需要渲染或触发平台检视时执行.

步骤 3 : 增加一个 Stateful Widget

Stateless widgets 是不可变的 , 意味着其属性是不可改变的 - 所有值均为final .

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

在这个步骤里 , 我们将添加一个 stateful widget - RandomWords , 它创建自己的 State 类 - RandomWordsState . state 类将为widget最终维持建议的和喜好的单词.

**1. ** 添加 stateful RandomWords 到 main.dart

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

**2. ** 添加 RandomWordsState . 大部分app的代码会在这个类中 , 将维持着 RandomWords 部件的状态 . 这个类将会保存生成的词对 , 它们随着用户滑动页面无线增加 . 然后喜好的词对 , 用户通过点击列表的心形按钮进行添加或移除 .

我们一步一步来创建这个类

class RandomWordsState extends State<RandomWords> {
}

**3. ** 在添加 state 类后 , IDE会提示错误, 需要我们取实现未实现的方法 .

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

**4. ** 移除单词生成代码 ,

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

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

这个步骤里, 我们将扩充 RandomWordsState 类 来生成和展示单词对的列表. 当用户滑动页面, ListView widget 展示的列表将会无限增加. ListView 的 builder 工厂构造器允许我们视需懒加载创建列表视图

**1. **在 RandomWordsState 类中添加成员变量 _suggestions 列表用来保存推荐的单词对.在 Dart 语言中 , 以 _下划线开头的变量/方法为私有访问权限.

同样的 , 添加 biggerFont 变量用来使字体大小更大

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

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

**2 ~ 3. **添加 _buildSuggestions()方法到 RandomWordsState 类中. 此方法负责构建 ListView 和展示建议的单词对.

ListView 类提供了一个 builder 属性 , itemBuilder 一个工厂构建者和指定匿名函数的回调功能.两个参数被传递给函数- BuildContext 还有行迭代器 i . 迭代器从 0 开始且每次方法调用递增.

添加 _buildRow 方法

Widget _buildSuggestions() {
    return new ListView.builder(
        // padding 16
        padding: const EdgeInsets.all(16.0),
        // 每一对单词对调用一次itemBuilder 回调 ,然后放置一个推荐的单词对在行内
        // 偶数行 , 函数增加个内容行显示单词对,
        // 奇数行 , 函数添加一条分割线小部件 (Divider Widget)去显示分割条目

        itemBuilder: (context, i) {
          if (i.isOdd) return new Divider();

          // index 为 i/2 的余整数
          final index = i ~/ 2;
          if (index >= _suggestions.length) {
            // ...生成10个词对,添加到list
            _suggestions.addAll(generateWordPairs().take(10));
          }
          return _buildRow(_suggestions[index]);
        }
    );
  }

**4. **更新 RandomWordsState 的 build 方法 , 使用 _buildSuggestions ,不再直接使用调用单词生产库.

class RandomWordsState extends State<RandomWords> {
  ...
  @override
  Widget build(BuildContext context) {
    return new Scaffold (
      appBar: new AppBar(
        title: new Text('Startup Name Generator'),
      ),
    body: _buildSuggestions(),
    );
  }
  ...
}

**5. **更新 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(),
    );
  }
}
image

步骤 5 : 增加交互

这个步骤中 , 我们将增加可点击的心形图标到每一行 . 当用户点击列表条目时 , 触发其 "favorite" 状态 ,状态改变会将对应单词对添加到保存的集合或从中移除

**1. ** 添加 _saved 集合到 RandomWordsState 里 . 集合存储用户喜好的单词对 , 更倾向于用 Set 是因为 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 ~ 4. **还是在_buildRow 里 , 添加心形图标 . 重启应用 , 我们可以看到心形已被添加 , 只是暂时没有交互事件

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

**5. **在_buildRow 中设置心形可点击. 如果一个单词条目被添加到喜欢的集合时, 再次单击它就能从喜欢的集合中移除 . 当心形被点击 , 函数会调用 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 react式的框架中 , 调用 setState() 会为 State 对象触发 build() 方法 , 最后更新到UI上.

image

步骤 6 : 跳转到新页面

在这个步骤里 , 我们将添加一个页面 (在Flutter里叫 route ) 展示喜好的推荐词对 . 我们将学到如何从主页面导航到新页面 .

在 Flutter 中 , Navigator 管理着一个包含app页面的栈 . 推送一个页面进入 Navigator 的栈中, 则会更新显示这个页面 . 从 Navigator栈中推出一个页面 , 则会显示上一个页面 .

**1 ~ 3. ** 在 RandomWordsState 的 build 方法中给 AppBar 添加一个列表图标 . 当用户点击图标 , 一个包含喜好列表的页面会被推送呈现 .

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

然后在 RandomWordsState 中添加 _pushSaved方法

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

**4 ~ 6. ** 添加 MaterialPageRoute 及它的 builder . 添加代码生成 ListTile 行 . ListTile 的divideTiles() 方法在每一条条目间增加水平距离 . divided 变量保存着最终的行 , 通过函数 toList() 转换为列表

builder 属性返回一个 Scaffold , 包含了新页面的导航栏 ,名为 "Saved Suggestion" .新页面的内容部分由 ListView包含 ListTiles 行组成 , 每一行由一个 divider 分隔 .

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

步骤 7 : 通过主题改变UI

在这个最终步骤中, 我们将改变app的主题 .

**1. **我们可以简单地通过配置 ThemeData 类 改变app的主题 . 当前app是默认主题, 我们将改变主色为紫色

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

完成!

至此 , 我们第一个 app 已经完成 . GitHub 地址

功能相对来说较简单 , 但是大体上让我们对开发 Flutter app 有了一定了解. 之后我们将延续阅读官网的教程 , 开始较全面地了解构建UI相关的部分.

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

推荐阅读更多精彩内容