flutter笔记4:使用material原生控件开发一个APP

接着上一篇,我们做一个这样的APP:


flutter官方案例

开始之前,我发现了一个好玩的东西,每次我们在终端中输入命令:

flutter run

终端里会有这个东西:


APP调试器

按照上图所示,我们的进入这个网页看看是个啥:


调试器主页

好高大上的感觉,具体是干嘛的,我也不知道,有兴趣的同学可以点进去把玩把玩,以后搞明白了再更吧。

由于Flutter官方的更新,使用外部包需要到文件管理的配置文件中添加依赖的项目,所以需要到项目根目录下的pubspec.yaml文件中添加如下代码:

dependencies:
  flutter:
  sdk: flutter

  cupertino_icons: ^0.1.0
  english_words: ^3.1.0    //将本行代码添加到示例中的位置

第一步

先创建一个列表。

回到main.dart中,把原来的代码全部清空,复制以下代码:

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) {
    return new MaterialApp(
      title: 'Startup Name Generator',
      home: new RandomWords(),//定义子组件为有状态控件RandomWords类的实例
    );
  }
}

//定义有状态控件RandomWords类
class RandomWords extends StatefulWidget {
  @override
  createState() => new RandomWordsState();//创建有状态控件RandomWords的状态类实例:RandomWordsState
}

//定义状态类RandomWordsState
class RandomWordsState extends State<RandomWords> {
  @override
  final _suggestions = <WordPair>[];  //用于保存随机字符串词组,注意这是一个数组变量
  final _biggerFont = const TextStyle(fontSize: 18.0);   //用于标识字符串的样式

  //构建一个脚手架,里面塞入前面定义好的_buildSuggestions类
  Widget build(BuildContext context) {
    return new Scaffold (
      appBar: new AppBar(
        title: new Text('Startup Name Generator'),
      ),
      body: _buildSuggestions(), 
    );
  }

  //定义一个子控件,这个控件就是放置随机字符串词组的列表
  Widget _buildSuggestions() {
    return new ListView.builder(  //ListView(列表视图)是material.dart中的基础控件
      padding: const EdgeInsets.all(16.0),  //padding(内边距)是ListView的属性,配置其属性值
      //通过ListView自带的函数itemBuilder,向ListView中塞入行,变量 i 是从0开始计数的行号
      //此函数会自动循环并计数,咋结束的我也不知道,走着瞧咯
      itemBuilder: (context, i) {
        if (i.isOdd) return new Divider();//奇数行塞入分割线对象
        final index = i ~/ 2;  //当前行号除以2取整,得到的值就是_suggestions数组项索引号
        // 如果计算得到的数组项索引号超出了_suggestions数组的长度,那_suggestions就再生10个随机组合的字符串词组
        if (index >= _suggestions.length) {
          _suggestions.addAll(generateWordPairs().take(10));
        }
        return _buildRow(_suggestions[index]);//把这个数据项塞入ListView中
      }
    );
  }

  //定义的_suggestions数组项属性
  Widget _buildRow(WordPair pair) {
    //ListTile和Text都是material.dart中的基础控件
    return new ListTile(
      title: new Text(
        pair.asPascalCase,  //使用驼峰样式
        style: _biggerFont,
      ),
    );
  }
    
}

看到这里,是不是有点晕,各种声明、各种引用,还有回调,把上面的代码,用下面的图解析下结构,看看到底怎么个情况:


代码图解

可以发现在StatelessWidget和State类中都有一个Widget类型的函数build(),感觉有点像类的初始化方法construct(),而RandomWords对象为什么只使用了createState()却没有build(),我也不知道,走着瞧吧。当对象实例化的时候,首先执行Widget build(BuildContext context){}函数,函数中BuildContext类型的参数context,到目前为止还不知道干嘛用的,暂且忽略其意义吧。

material类型的子控件都通过回调函数的方式创建,我读起来有些不习惯,但通过回调,免去了先声明再使用的麻烦,并且可以直接对对象的属性进行配置,通过build()一层层回调,代码简洁不少,而代码中使用到的material内置控件,我就不一一介绍了,有兴趣的同学请参考material官方API,注意material控件索引在页面右边的列表,别找到左边去了。

注意,遇到这种声明类属性的格式:_[变量名]。按官方的意思是,如果变量名的前缀有_下划线,表示强制转换为私有变量,相当于声明变量为private,但使用这个变量的时候,还是要将下划线进行完整的书写。

保存代码后运行一下,可以看到APP变成了这个样子:


无限滚动的列表

向下滚动试试,发现可以一直滚下去~

第二步

向列表里加个收藏标签按钮,使每行可以标记收藏或取消收藏。这个收藏标签就是状态,既然要修改状态,肯定要到state中进行啦。

到对象RandomWordsState中定义一个对象,用于存储标记。为什么要单独存储标记呢?因为这样就不需要往行对象(ListTile)中添加标记,降低了对象的复杂度。如下:

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

  final _saved = new Set<WordPair>();  //新加这一句

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

为什么存储标记的是对象而不是一个数组呢?大概是想顺便教我们使用一下Set对象吧,据说Set对象不允许有重复的项目,方便后面模拟堆栈的效果,非常适合这种场景。然后我们到每个行添加一个标记收藏的控件:

Widget _buildRow(WordPair pair) {
    //定义一个布尔变量,用于判断行控件ListTile是否被标记为收藏
    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);
          }
        });
    },
    );
  }

注意,在onTap事件中,使用到了setState()方法(用过vue或react的玩家是不是很熟悉呀),在这个方法里修改变量值,即可触发state对象执行build()方法重绘对象。这里每次变更对象_saved后,都会重绘ListTile对象,而静态变量alreadySaved也被重新定义,因此不用担心alreadySaved值不被更新的问题,如果去除final关键字,Dart语法会报错,还请路过大神点拨一下原因。

保存代码后,可以看到APP刷新了,每一行都添加了一个心型图标,反复戳这个行,还自动配有动画效果:


实现标记按钮

第三步

加入一个导航栏样式的堆栈。

先往主页面控件(AppBar)中添加一个可以进入收藏列表的入口:

class RandomWordsState extends State<RandomWords> {
  ...
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('Startup Name Generator'),
        //为AppBar对象的actions属性添加一个IconButton对象,actions属性值可以是Widget类型的数组
        actions: <Widget>[
          new IconButton(icon: new Icon(Icons.list), onPressed: _pushSaved), 
          //可以试试添加这两行后APP上有什么效果
          new IconButton(icon: new Icon(Icons.add), onPressed: _pushSaved), 
          new IconButton(icon: new Icon(Icons.create), onPressed: _pushSaved), 
        ],
      ),
      body: _buildSuggestions(),
    );
  }
  ...
}

由于没有定义_pushSaved函数,直接复制上面注释说明的代码会报错,因此,定义一个void类型的函数_pushSaved:

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

这时候可以看到右上角多了3个图标,如图:

创建收藏夹入口图标

然后我们往_pushSaved()函数中添加代码,让收藏夹玩起来:

void _pushSaved() {
    //创建导航栏控件Navigator,然后往里面塞入MaterialPageRoute控件
    Navigator.of(context).push(
      new MaterialPageRoute(
        builder: (context) {
          //通过遍历_saved对象,获取已收藏的行对象
          final tiles = _saved.map(
                (pair) {
              return new ListTile(
                title: new Text(
                  pair.asPascalCase,
                  style: _biggerFont,
                ),
              );
            },
          );
          //函数的的链式调用,获取到添加好分割线的ListTile控件
          final divided = ListTile
              .divideTiles(            //divideTiles()函数,向每个tile间隔插入一个1像素宽的边框
                context: context, 
                tiles: tiles,
              )
              .toList();  //不要漏掉这个函数,否则进入收藏夹直接崩溃
          return new Scaffold(
            appBar: new AppBar(
              title: new Text('收藏的列表项目'),
            ),
            body: new ListView(children: divided),  //直接将准备好的ListTile塞入其中,完成内容填充
          );
        },
      ),
    );
  }

保存代码后,刷新APP,如图:


两个页面相互跳转

如上图所示,红色的箭头表示点击按钮后页面的切换,绿色箭头表示收藏夹的值的对照,有没有发现我们并没有写返回主页按钮的代码,这个返回按钮从哪来的呢?是由Navigator对象自动生成的,并且自动指向到主页面的路由,不过遗憾的是,没有加入返回手势的支持,如有需要,还得自己写,具体怎么写,跟着我的笔记走吧,我现在也不知道。

大家注意看主页列表和收藏夹列表的区别,两者都是列表,都是使用的ListView和ListTile对象,但实现的方式完全不同,插入行对象和分割线的差异很有意思,有兴趣的同学可以自行修改下代码,看看能不能将两种列表的构建方法对调一下,参考官方资料ListViewListTile

第四步

变更UI主题风格。

这一步超级简单,往MaterialApp对象里添加theme属性值即可:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Startup Name Generator',
      //添加theme属性值,塞入ThemeData对象
      theme: new ThemeData(
        primaryColor: Colors.white,
      ),
      home: new RandomWords(),
    );
  }
}

再保存,刷新APP试试,主题变成了白色风格:

变更风格

ThemeData()方法本身提供了很多配色方案,玩家可以参考ThemeData官方说明,掌握其强大功能。没想到变更主题如此轻松,在当前APP界越来越追求视觉体验升级的趋势下,使用flutter开发APP的玩家应该欣慰不少吧~

好勒,这次官方萌新课程的搬运就到这里,我是真的超喜欢这个萌新课程,连课程总结都帮我写好了:

  • 从头开始创建一个Flutter应用程序。
  • 书写了Dart语言的代码。
  • 学会了调用外部的第三方库。
  • 体验热更新带来的开发周期加速。
  • 学会使用有状态控件,增强了应用的交互。
  • 使用ListView和ListTiles创建了一个支持懒加载的无限滚动列表。
  • 创建了一组路由并实现了主路由和新路由之间的跳转逻辑。
  • 了解如何使用主题更改应用UI的外观。

自我总结一下,flutter是一个控件高度集成化的开发平台,控件的完整度极高,控件之间的交互实现也倾向于傻瓜化,比如自动生成返回按钮、主题风格全局可控。只要把握好状态值和控件之间的嵌套关系,开发者几乎不需要单独敲代码实现跳转逻辑,代码简直不要太简洁,不知不觉间就写好了一个APP。而VScode中的Dart Code插件也实在太好用,代码提示、函数用法和参数都有详尽的说明,看得出谷歌拿出了十足的诚意要在跨平台开发上面大搞特搞一番。

Dart Code插件提示

当然了,满屏幕的回调函数让我这种编程思维还停留在C语言时代的菜鸟来说,扶墙~ 头有点晕,还需要点时间慢慢适应一下下,也由于我没有那么深厚的技术功底,对这个教程的理解还比较有限,可能有写的不对或不好的地方,也欢迎大家指正,尤其我花了一天一夜写了这篇稿也是蛮不容易了,有路过的高手不说两句也是哪啥了是吧。当然,我有空的时候抓紧读一读Dart 语法基础官方原版,有了新的发现也会写稿分享出来。

好啦就写到这里,广告时间,对flutter感兴趣的小伙伴可以关注我,欢迎大家到Flutter圈子中投稿,也可以联系管理员加入我们的flutter微信群嗨聊,谢谢捧场~!
flutter 中文社区(官方QQ群:338252156)

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

推荐阅读更多精彩内容