Flutter学习(一)——Widget学习

官方文档以“一切皆为组件”作为它的口号,比如一个布局元素、一个动画亦或是装饰效果等。所以要想学好 Flutter,组件是最好的起点。

首先,我们先不必纠结这个概念,只需要将 widget 想象为一个可视化组件或与应用可视化方面交互的组件,跟 iOS 中的 view 可视化控件不同的是,widget 不是一个控件,而是对控件的描述,随着学习的深入,再来深入了解。

两种 Widget

  • StatelessWidget
    StatelessWidget 是无状态组件,它只是用来展示信息,在你构建初始化后不再进行改变;

  • StatefulWidget
    StatefulWidget,是有状态组件,拥有一个 state 对象来存储它的状态数据,并在 widget 树重建时携带着它,因此状态不会丢失。一个 StatefulWidget 类对应一个 state 类,state 是与对应 StatefulWidget 维护的状态,这种 Widget 可以通过改变状态使得 UI 发生变化。

组件介绍

本来想写一些常用组件的基本使用,但是觉得这些东西随处可得,我也就偷个懒吧。

这里推荐一个很赞的项目 FlutterUnit,它罗列了非常多的组件,还可以直观的看到效果和代码。

自己开发时,想知道某个 Widget 怎么使用的话,就去Widget使用速查文档查一下吧。

基础组件,是搭建项目的基石,也是我们用的最多的组件。这里罗列几个最常用的。

Widget 的自定义

系统提供的组件肯定是不能满足开发时各种奇奇怪怪的需求的,所以,我们还需要知道如何去自定义 Widget 来弥补系统组件的不足。在我熟悉的 iOS 开发中,自定义视图无非就是三中方式:

  1. 组合形成新的样式
  2. 自定义绘制
  3. 集成系统视图,在里面进行魔改

在 Flutter 中,其实也是一样的道理,这里就介绍一下前两种方法,第三种的使用场景还是比较少的。

组合现有 Widget

现有 Widget 组合即是利用基础 Widget 根据需求来组合成一个通用的 Widget,这样在使用过程中避免设置过多的属性,且增强其复用性。 比如,在实际开发中,我们经常需要为信息流列表定义一些显示的 item,来显示信息。这些 item 就是我们组合 Widget 形成的一个新的 Widget。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class DispalyItem extends StatelessWidget {
const DispalyItem({ Key? key }) : super(key: key);

@override
Widget build(BuildContext context) {

var box = Container(
color: Colors.red,
height:160,
width: 100,
);

var title = const Text("I'm title");

return Container(
width: 100,
height: 160,
color: Colors.grey,
child: Stack(
alignment: Alignment.bottomCenter,
children: [box,
Positioned(
child: title,
bottom: 10,
)
]
)
);
}
}

上面使用了 Stack 布局组件来使多个子组件堆叠展示。Stack 布局组件在屏幕上的效果是后面的子组件会覆盖在前面的子组件之上。

当然,我们也可以将展示图片和标题,作为参数让外部使用者传入进来。只需要定义对应的属性,然后在初始化方法加入对应的初始化参数,最后 build 的时候,使用属性值来显示就可以了。

自定义绘制

首先,需要先认识一下 CustomPainter,这是一个抽象类,需要自定义绘制的话,需要继承它并重写它的 paint 方法,在重写的 paint 方法中来绘制自己需要的图形。使用时,创建系统提供的 CustomPaint 组件,在其属性 painter 中提供我们自定义类的对象。

  1. 创建自定义 CustomPainter 子类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    class PieChartPainter extends CustomPainter{

    Paint getColoredPaint(Color color){
    Paint paint=Paint();
    paint.color=color;
    return paint;
    }

    @override
    void paint(Canvas canvas, Size size) {
    double wheelSize=min(size.width, size.height)/2;
    double nbElem=8;
    double radius=(2*pi)/nbElem;
    Rect boundingRect=Rect.fromCircle(center: Offset(wheelSize,wheelSize), radius: wheelSize);
    canvas.drawArc(boundingRect, 0, radius, true, getColoredPaint(Colors.orange));
    canvas.drawArc(boundingRect, radius, radius, true, getColoredPaint(Colors.black));
    canvas.drawArc(boundingRect, radius*2, radius, true, getColoredPaint(Colors.green));
    canvas.drawArc(boundingRect, radius*3, radius, true, getColoredPaint(Colors.red));
    canvas.drawArc(boundingRect, radius*4, radius, true, getColoredPaint(Colors.blue));
    canvas.drawArc(boundingRect, radius*5, radius, true, getColoredPaint(Colors.yellow));
    canvas.drawArc(boundingRect, radius*6, radius, true, getColoredPaint(Colors.purple));
    canvas.drawArc(boundingRect, radius*7, radius, true, getColoredPaint(Colors.white));
    }

    @override
    bool shouldRepaint(covariant CustomPainter oldDelegate)=>oldDelegate!=this;
    }
  2. 展示

    1
    2
    3
    4
    5
    6
    Container(
    child: CustomPaint(
    size: const Size(300, 300),
    painter: PieChartPainter(),
    ),
    )

生命周期两三事

要写出一个合理优秀的 Widget,了解其属性及绘制方式是基础,理解生命周期,让我们知道在什么时候应该做什么事,对我们构建项目是极为重要的。

App 生命周期

Flutter 封装了一个枚举 AppLifecycleState 来描述这个生命周期,其中罗列了 App 生命周期的几个状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
enum AppLifecycleState {
/// 应用程序是可见的,并且响应用户输入
resumed,

/// 应用程序处于非活动状态,未接收用户输入。
///
/// 处于此状态的应用程序应假定它们可能随时被 [paused]
inactive,

/// 应用程序当前对用户不可见,不响应用户输入,后台运行。
paused,

/// 应用程序仍驻留在 flutter 引擎上,但已从任何主体视图分离。
detached,
}

使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
void main() {
runApp(const MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}

class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);

final String title;

@override
State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with WidgetsBindingObserver {

@override
void initState() {
super.initState();

WidgetsBinding.instance?.addObserver(this);
}

@override
void didChangeAppLifecycleState(AppLifecycleState state) {
print('$state');
}

@override
void dispose(){
WidgetsBinding.instance?.removeObserver(this);
super.dispose();
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Container(
child: Text("Lifecycle")
)
),
);
}
}

Widget 生命周期

这个生命周期也仅仅是针对有状态组件,对于无状态组件生命周期只有 build 这个过程,也只会渲染一次。

整个过程分为四个阶段:

  1. 初始化阶段

    createState 和 initState

  2. 组件创建阶段

    didChangeDependencies 和 build;

  3. 组件刷新阶段

    在组件运行过程中会多次被触发,这也是优化过程中需要着重需要注意的点。

    didChangeDependencies、build、didUpdateWidget

    addPostFrameCallback(只调用一次)

  4. 组件销毁阶段

    deactivate 和 dispose。

详细说明

  • createState
    StatefulWidget 创建 State 的方法,只调用一次。

  • initState
    StatefulWidget 创建后调用的第一个方法,该 widiget 插入到 widget 树结构时被调用,只调用一次,因此,该函数一般用来初始化操作,状态初始化、订阅子树事件通知。

  • didChangeDependencies
    当调用 initState 后会立即调用这个方法,这个方法是在 state 对象被创建好了但没有准备好构建 build 的时候调用的。之后只有当 StatefulWidget 依赖的 InheritedWidget 发生变化之后,didChangeDependencies 才会调用,所以 didChangeDependencies 可以调用多次。

  • build
    在 didChangeDeoendencies 之后立即调用,来构建 widget 子树。触发的时机较多:initState()、didChangeDependencies()、setState()、didUpdateWidget()等方法后重新 build。一般不在 build 中调用除创建 Widget 的方法,否则会影响渲染效率。

  • addPostFrameCallback
    StatefulWidget 渲染结束之后的回调,只会调用一次,一般是在 initState 里添加回调。通过 addPostFrameCallback 可以做一些安全的操作,在有些时候是很有用的,它会在当前 Frame 绘制完后进行回调,并只会回调一次,如果要再次监听需要再设置。

    1
    2
    3
    4
    5
    6
    void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_){
    print("Frame has been rendered");
    });
    }
  • didUpdateWidget
    在 widget 重新构建时,由 flutter framework 判断检测 widget 树中同一位置新旧节点,决定是否更新,调用该方法。

  • deactivate
    当 widget 对象从树中被移除时,会调用此方法。

  • dispose
    当 widget 对象从树中被永久移除时调用 ,可以在此方法中释放资源。

  • reassemble
    专门为了开发调试而提供的,在热重载时会被调用。此回调在Release模式下永远不会被调用。