Flutter 高效自学笔记(二)

3,644 阅读4分钟

Flutter 高效自学笔记(一)

这是上篇文章文末提到的「第三个教程」的快速自学笔记。

Widget 部件

读音 /'wɪdʒɪt/ 危机特,文档说借鉴了 React。我认为 Widget 相当于 React 里的组件 Component。

下文将 Widget 全部称为「部件」。

部件的特点

  1. 基于你传给它配置和 state 生成视图
  2. 当一个部件的 state 变化了,部件就会更新视图
  3. Flutter 会对比新旧视图的区别,对实际视图做最小化的变动

runApp

这是 Flutter 应用必须调用的 API,接受一个部件。个人认为这很像是 React 的 ReactDOM.render 操作。

import 'package:flutter/material.dart';

void main() {
  runApp(
    Center(
      child: Text(
        'Hello, world!',
        textDirection: TextDirection.ltr,
      ),
    ),
  );
}

以上代码的作用:

  1. Center 是个类,Center() 就是实例化出一个实例
    1. 我非常欣赏删掉 new 的做法,再也不会有人问 Center() 和 new Center() 的区别了,fuck JS。
  2. Center(...) 返回一个部件,作为根部件
  3. Center(...) 部件的子部件是 Text(...),如果只有一个子组件就用 child,如果有多个子组件就用 children
  4. Flutter 会强行让根部件占满屏幕(好评),所以最终效果就是屏幕中央有个 Hello, world! 字符串
  5. 代码中需要用 textDirection 指明文字方向,你用 MaterialApp 就可以省掉这一步,后面会讲
  6. 所有的部件类要么是 StatelessWidget 的子类,要么是 StatefulWidget 的子类
  7. 一个部件的主要功能是提供 build 方法(个人认为类似于 React 的 render),build 方法可以调用更多其他部件
  8. Flutter 会遍历这些子部件,然后计算出当前部件的结构

基础部件

常用的部件有:

  • Text:顾名思义
  • Row 和 Column:布局用的,类似于 CSS 的 flex 布局
  • Stack:布局用的,类似于 CSS 的绝对布局
  • Container:通用布局,类似于 div

MaterialApp

如果你想用一些 Material 风格的部件,只需要做这几件事就可以:

  1. 在 pubspec.yml 里添加如下代码

     flutter:
         uses-material-design: true
    
  2. runApp 的时候使用 MaterialApp,并使用 Material 部件

     runApp(MaterialApp(
         title: 'My app', // used by the OS task switcher
         home: Material(),
     ));
    
  3. import 'package:flutter/material.dart';

常用的 Material 部件

  1. Navigator:类似于 ReactRouter

  2. AppBar:顶部的 bar

     AppBar(
         leading: IconButton(
             icon: Icon(Icons.menu),
             tooltip: 'Navigation menu',
             onPressed: null,
         ),
         title: Text('Example title'),
         actions: <Widget>[
             IconButton(
             icon: Icon(Icons.search),
             tooltip: 'Search',
             onPressed: null,
             ),
         ],
     )
    
  3. Scaffold: 最常用的布局部件

     Scaffold(
       appBar: AppBar(...),
       body: Center(...),
       floatingActionButton: ...
     );
    

这些传参方式很像 Vue 的命名插槽。

用 GestureDetector 处理用户交互

这是一个自定义 button 部件:

class MyButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GestureDetector(               //  <-- 注意看这里
      onTap: () {
        print('MyButton was tapped!');
      },
      child: Container(
        height: 36.0,
        padding: ...,
        margin: ...,
        decoration: ...,
        child: Center(
          child: Text('Engage'),
        ),
      ),
    );
  }
}
  1. GestureDetector 部件并没有任何表现层,只处理用户交互。当用户点击 Container 部件时,会触发 GestureDetector 部件的 onTap 回调。
  2. GestureDetector 支持的交互相当多,如轻触、拖曳和缩放。
  3. 很多其他部件都借助 GestureDetector 来接收回调,如 IconButton, RaisedButton 和 FloatingActionButton 都接收 onPressed 回调。

改变 state

无状态部件从父级接收数据,并把数据存在自己的 final 成员变量里(类似于 JS 的 const)。

有状态的部件则知道如何创建 state 对象,并维持 state 对象。

如何使用呢?

首先声明一个 StatefulWidget

class Counter extends StatefulWidget {
  @override
  _CounterState createState() => _CounterState();
}

这个部件需要重写 createState 方法,而这个方法的返回值的类型,必须继承自 State 类。

_CounterState 是如何继承 State 的呢

class _CounterState extends State<Counter> {
  int _counter = 0;

  void _increment() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Row(
      children: <Widget>[
        RaisedButton(
          onPressed: _increment,
          child: Text('Increment'),
        ),
        Text('Count: $_counter'),
      ],
    );
  }
}
  1. 需要重写 build 方法提供视图,而且可以使用 '$_counter' 混入语法
  2. 在按钮被点击的时候调用 _increment,这个 _increment 会调用 State 提供的 setState 函数,给 setState 函数传入一个函数,这个传入的函数可以对数据进行修改。
  3. 非常像 React 的 Class 组件。区别在于
    1. Flutter 的部件和 state 是分开的类,而 React 只是把 state 用一个 hash 表示。
    2. Flutter 的 State 里面提供 build 方法,而 React 的 state 只是一个 hash
    3. Flutter 将 StatelessWidget 和 StatefulWidget 分开,能更好地组织代码

生命周期

StatefulWidget 有 createState 方法,State 有 initState 方法。

Flutter 调用 createState 方法后,得到一个 State 对象,然后调用这个 State 对象的 initState 方法。

你的自定义 State 类可以重写 initState 方法,这个方法只会调用一次。比如你可以重写 initState 来配置动画或者订阅事件。

当一个 State 对象快死的时候,Flutter 会调用 State 对象的 dispose 方法。你也可以重写 dispose 方法,比如在里面取消 timer 或者取消订阅。

keys

Flutter 默认是通过部件的 runtimeType 和出现顺序来确定一个部件的身份的,这样才能在对比新旧部件树的过程中确定哪个是哪个。如果你给部件赋予了 key,那么就不用根据「出现顺序」来唯一确定组件了,因为这种方式不太靠谱。

个人认为跟 React 的 key 有相同的作用。

key 分为兄弟间的 key 和全局 key。目前还不知道应用场景的区别。大部分时候应该只需要用兄弟间的 key 用来区分兄弟就行了。


下一篇文章打算看看 Flutter 如何发起网络请求。

未完待续……