Flutter 学习记2 - 首个应用

创建一个最简单的 App

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

void main() 是入口方法,=> 用于单行方法,就是函数签名和函数体间的连接符号,感觉作用和 Kotlin 的单行函数体用 = 类似。既然这样,把代码修改成

void main() {
  runApp(new MyApp());
}

应该也可以了,实际运行确实可以。

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

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

引入第三方包

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

dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^0.1.0
  english_words: ^3.1.0

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

屏幕快照 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) {
    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'),
          child: new 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. 添加 stateful 类型的 RandomWords widget,在 main.dart 中,MyApp 类外的任何地方定义都可以。主要作用是创建 State。 class RandomWords extends StatefulWidget { @override createState() => new RandomWordsState(); }
  2. 添加 RandomWordsState。大部分代码会在这个类里,它是一个 State,它为 RandomWords widget 保存维持状态。这个例子里它的作用是:
    • 保存生成的单词对,随着用户滑动而无限增长
    • 用户通过点击列表的心形图标来添加或移除喜欢的单词对

    class RandomWordsState extends State<RandomWords> { }

  3. 实现 build 方法 class RandomWordsState extends State<RandomWords> { @override Widget build(BuildContext context) { final wordPair = new WordPair.random(); return new Text(wordPair.asPascalCase); } } build 方法返回一个 Widget,而 Text 本身是 StatelessWidget 的子类。
  4. 使用 RandomWords 这个 Widget 将 MyApp 中 body 里的 child: new Text(wordPair.asPascalCase), 修改成 child: new RandomWords(), RandomWords 是一个 stateful widget,它通过 createState 创建了一个 RandomWordsState,这个 State 来为这个 Widget 保存状态,State 本身通过 build 返回了一个 Text。

由于加了新的 Widget 和 State,Reload 不管用了,需要重新运行。

创建无限滚动的列表

  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 开始且每次调用方法时递增。 The itemBuilder callback is called once per suggested word pairing 是说每添加一个单词对就会调用这个回调方法一次吗?由于可以懒加载,是先记录下先不执行等要显示时才显示? 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, itemBuilder: (context, i) { // 奇数行,返回 1 像素的分割线,相比于 Android,这里分割线本身也是一个 Item,也占了一个 position if (i.isOdd) return new Divider(); // "i ~/ 2" 就是整除把 // For example: 1, 2, 3, 4, 5 becomes 0, 1, 1, 2, 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. 添加 _buildRow 函数 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 方法 class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: 'Startup Name Generator', home: new RandomWords(), ); } } 这样整个页面的状态都转移到了 RandomWords 这个 Stateful widget 里,它最后通过 State 的 build 方法来获取显示的 Widget,而这个 Widget 的 body 调用 _buildSuggestions 方法返回 ListView。

添加交互

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

  1. 在 RandomWordsState 添加属性 _saved,这是一个 Set,用于存储用户点击收藏后的单词对,应该和 Java 的 Set 类似,也不许有重复元素 final _saved = new 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 new ListTile( title: new Text( pair.asPascalCase, style: _biggerFont, ), trailing: new Icon( // 判断是否在收藏,显示不同的图标和颜色 alreadySaved ? Icons.favorite : Icons.favorite_border, color: alreadySaved ? Colors.red : null, ), ); }
  4. 增加交互,当点击心形图标时,调用 setState() 去通知框架状态 State 被改变了,再次点击后,从收藏中移除 Widget _buildRow(WordPair pair) { ... return new ListTile( ... // 一整行的点击事件 onTap: () { setState(() { if (alreadySaved) { _saved.remove(pair); } else { _saved.add(pair); } }); }, ); } 和 Android 不同,在 Flutter 的 react style 框架中,调用 setState() 会再次触发 State 的 build(),然后页面就被修改了。不是主动去修改界面,而是修改数据,让界面自动更新

切换新页面

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

  1. 在 RandomWordsState 中的 build 方法里,在 AppBar 添加一个列表图标,点击时就去一个新的 route 页面,包含所有的收藏单词对。 class RandomWordsState extends State<RandomWords> { ... @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text('Startup Name Generator'), // 是一个数组,现在有一个 IconButton,点击时就去调用 _pushSaved actions: <Widget>[ new IconButton(icon: new Icon(Icons.list), onPressed: _pushSaved), ], ), body: _buildSuggestions(), ); } void _pushSaved() { } ... }
  2. 用户点击 AppBar 上的图标时,创建一个 route 并通过 Navigator 的 push 方法添加到栈顶 void _pushSaved() { Navigator.of(context).push( new MaterialPageRoute( builder: (context) { // 对 _saved 里的每一个单词对创建一个 ListTile final tiles = _saved.map( (pair) { return new ListTile( title: new Text( pair.asPascalCase, style: _biggerFont, ), ); }, ); final divided = ListTile .divideTiles( // 用于在列表项间添加水平边距,相当于水平分割线吧 context: context, tiles: tiles, // 这就是上面创建的列表项,所以 divided 持有最后的列表 ) .toList(); // 转化为列表 return new Scaffold( // 返回一个页面 appBar: new AppBar( title: new Text('Saved Suggestions'), ), body: new ListView(children: divided), // 通过上面的 divided 构建 ); }, ), ); } 添加 MaterialPageRoute,在它的 build 方法中创建列表项 ListTile,即 divided,然后新页面用它构建 ListView。 新页面导航栏上有个返回按钮,点击就可以返回。

修改主题

  1. 通过 ThemeData 类修改主题为白色 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(), ); } }

参考:Write your first app

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Ryan Miao

Spring resource bundle多语言,单引号format异常

Spring resource bundle多语言,单引号format异常 source code 前言 十一假期被通知出现大bug,然后发现是多语言翻译问题。...

40180
来自专栏一枝花算不算浪漫

[Java拾遗一] XML的书写规范与解析.

512200
来自专栏MasiMaro 的技术博文

定时器的实现

IO定时器每隔1s就会触发一次,从而进入到定时器例程中,如果某个操作是每n秒执行一次(n为正整数)可以考虑在定时器例程中记录一个计数器大小就为n,每次进入定时器...

23140
来自专栏面朝大海春暖花开

springMVC返回modelmap跟new hashMap的区别

平时写接口,newHashMap,@ResponseBody 返回json对象,没什么问题

17620
来自专栏Java Web

初学Java Web(6)——JSP学习总结

为什么要学习 JSP Servlet 的短板: Servlet 的出现,是为了解决动态输出网页的问题。 虽然这样做目的能达到,但是存在一些缺陷: 在 Servl...

49470
来自专栏跟着阿笨一起玩NET

运行时自定义PropertyGrid显示属性项目

在PropertyGrid所显示的属性内容包括属性分类(Category)及组件属性,

21920
来自专栏iOSer成长记录

OpenGL ES(二) 三角形

15730
来自专栏抠抠空间

SQLALchemy的其他常用操作

15250
来自专栏ccylovehs

原生js格式化json工具

54510
来自专栏逸鹏说道

C# 温故而知新:Stream篇(五)下

对于重写的方法这里不再重复说明,大家可以参考我写的第一篇 以下是memoryStream独有的方法 virtual byte[] GetBuffer() 这个方...

371100

扫码关注云+社区

领取腾讯云代金券