flutter基础知识

# flutter基础知识

[TOC]

# 一、开发环境

# 1.1 开发工具

  • Android Studio
    • https://develop.android.com/studio/intro
  • 安装Flutter和Dart插件
    • Android Studio -> Files > Settings > Plugins -> Browse repositories -> Flutter plugin
  • 获取Flutter镜像
    • Flutter官网 -> Using Flutter in China
  • 获取Flutter SDK

# 二、入门基础知识

# 2.1 创建并运行项目

$ flutter create <projectname>
$ flutter run -d 'iPhone X'
1
2

# 2.2 Widget

import 'package:flutter/XXX.dart';
1
  • 写一个Hello world
import 'package:flutter/material.dart';

void main() {
    runApp(
    	Center(
        	child: Text(
            	'Hello, world!',
                textDirection: TextDirection.ltr,
            ),
        ),
    );
}
1
2
3
4
5
6
7
8
9
10
11
12

# 三、项目结构、资源、依赖和本地化

# 3.1 项目文件结构

- projectname
	-
	- android			      - Android部分的工程文件
	- build		      		- 项目的构建输出目录
	- ios	        			- iOS部分的工程文件
	- lib		        		- 项目中的Dart源文件
		- assets	        			  - 存放静态资源
    - models	        			  - 存放所有数据类
    - navigator	        			- 存放导航相关
    - pages	        			    - 存放所有的页面
    - provider	        			- 存放所有的 Provider(状态管理)
    - requests	        			  - 存放请求相关
    - routes	        			    - 存放路由相关
    - utils	        			    - 存放所有的工具类
    - widgets	        		   	- 存放所有封装好的组件
		- src	        		      	- 包含其他源文件
		- main.dart	        			- 项目入口文件
	- test				      - 测试相关文件
	- pubspec.yaml		  - 项目依赖配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 3.2 图片资源

mipmap-mdpi、hdpi、xhdpi、xxhdpi、xxxhdpi (opens new window)

  • assets -存放任意类型文件,需在pubspec.yaml中声明。

    • assets可以被放置到任何属性文件夹--Flutter并没有预先定义的文件结构。所以需要在pubspec.yaml文件中声明assets的位置,然后Flutter会把它们识别出来。
  assets:
  assets/filename (此行的assets即为文件夹的名字)
1
2
  • 参考教程:https://flutterchina.club/assets-and-images/

# 3.2 strings资源

# 四、动画Animation

# 4.1 logo放大动画

# 4.1.1 基础版

import 'package:flutter/material.dart';

class ScaleAnimationRoute extends StatefulWidget {
    
    _ScaleAnimationRouteState createState() => new _ScaleAnimationRouteState();
}

// 需要继承TickerProvider,如果有多个AnimationController,则应该使用TickerProviderStateMixin。
class _ScaleAnimationRouteState extends State<ScaleAnimationRoute>
    with SingleTickerProviderStateMixin {
    Animation<double> animation;
    // AnimationController是一个特殊的Animation对象,在屏幕刷新的每一帧,就会生成一个新的值
  	// 默认情况下,AnimationController在给定的时间段内会线性的生成从0.0到1.0的数字
  	// 用来控制动画的开始与结束以及设置动画的监听
    AnimationController controller;

    initState() {
        // 执行的是父级的initState()
        super.initState();  
        // vsync参数,存在vsync时会防止屏幕外动画(动画的UI不在当前屏幕时)消耗不必要的资源
    	// duration 动画的时长,设置的 seconds: 2 为2秒,当然也可以设置毫秒 milliseconds:2000.
        controller = new AnimationController(
            duration: const Duration(seconds: 1), vsync: this);
        // 使用弹性曲线
        animation=CurvedAnimation(parent: controller, curve: Curves.bounceIn);
        // 图片宽高从0变到300
        animation = new Tween(begin: 0.0, end: 300.0).animate(controller)
            ..addListener(() {  // 用来更新UI
				// 要改变 widget 的宽高,那么就需要 setState(...) 来让 widget 重绘
                setState(() => {});
            });
        // 启动动画(正向执行)
        controller.forward();
    }

    
    Widget build(BuildContext context) {
        return Container(
            color: Colors.blue[50],
            child: Center(
                child: Image.asset("images/logo.png",
                                   width: animation.value, height: animation.value),
            ));
    }

    dispose() {
        //路由销毁时需要释放动画资源,防止内存泄漏
        controller.dispose();
        super.dispose();
    }
}
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

# 4.1.2 升级版

import 'package:flutter/material.dart';

class AnimatedImage extends AnimatedWidget {
  AnimatedImage({Key key, Animation<double> animation})
      : super(key: key, listenable: animation);

  Widget build(BuildContext context) {
    final Animation<double> animation = listenable;
    return new Center(
      child: Image.asset("images/logo.png",
          width: animation.value,
          height: animation.value
      ),
    );
  }
}


class ScaleAnimationRoute extends StatefulWidget {
  
  _ScaleAnimationRouteState createState() => new _ScaleAnimationRouteState();
}

class _ScaleAnimationRouteState extends State<ScaleAnimationRoute>
    with SingleTickerProviderStateMixin {

  Animation<double> animation;
  AnimationController controller;

  initState() {
    super.initState();
    controller = new AnimationController(
        duration: const Duration(seconds: 3), vsync: this);
    //图片宽高从0变到300
    animation = new Tween(begin: 0.0, end: 300.0).animate(controller);
    //启动动画
    controller.forward();
  }

  
  Widget build(BuildContext context) {
    return AnimatedImage(animation: animation,);
  }

  dispose() {
    //路由销毁时需要释放动画资源
    controller.dispose();
    super.dispose();
  }
}
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

# 4.1.3 高级版

import 'package:flutter/material.dart';

class ScaleAnimationRoute extends StatefulWidget {
  
  _ScaleAnimationRouteState createState() => new _ScaleAnimationRouteState();
}

class GrowTransition extends StatelessWidget {
  GrowTransition({this.child, this.animation});

  final Widget child;
  final Animation<double> animation;

  Widget build(BuildContext context) {
    return Container(
        color: Colors.white,
        child: new Center(
          child: new AnimatedBuilder(
              animation: animation,
              builder: (BuildContext context, Widget child) {
                return new Container(
                    height: animation.value,
                    width: animation.value,
                    child: child);
              },
              child: child),
        ));
  }
}

class _ScaleAnimationRouteState extends State<ScaleAnimationRoute>
    with SingleTickerProviderStateMixin {
  Animation<double> animation;
  AnimationController controller;

  initState() {
    super.initState();
    controller = new AnimationController(
        duration: const Duration(seconds: 3), vsync: this);
    //图片宽高从0变到300
    animation = new Tween(begin: 0.0, end: 300.0).animate(controller);
    //启动动画
    controller.forward();
  }

  
  Widget build(BuildContext context) {
    return GrowTransition(
      child: Image.asset("images/logo.png"),
      animation: animation,
    );
  }

  dispose() {
    //路由销毁时需要释放动画资源
    controller.dispose();
    super.dispose();
  }
}
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
  • 动画放大再缩小再放大...(循环动画)
initState() {
    super.initState();
    controller = new AnimationController(
        duration: const Duration(seconds: 3), vsync: this);
    //图片宽高从0变到300
    animation = new Tween(begin: 0.0, end: 300.0).animate(controller);
    animation.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        //动画执行结束时反向执行动画
        controller.reverse();
      } else if (status == AnimationStatus.dismissed) {
        //动画恢复到初始状态时执行动画(正向)
        controller.forward();
      }
    });
    //启动动画
    controller.forward();
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 4.2 Hero动画

# 五、Provider状态管理

# 尽量在 Model 中使用私有变量“_”。

# 5.1 在子页面中获取状态

# 5.1.1 Provider.of(context)

当状态改变时,会刷新整个页面,不能获取最佳性能。

import 'package:flutter/material.dart';

// ChangeNotifier 用来自动管理所有听众
class CounterModel with ChangeNotifier {
  int _count = 0;
  int get value => _count;

  void increment() {
    _count++;
    notifyListeners();  // 通知所有听众进行刷新
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
Widget build(BuildContext context) {
    final _counter = Provider.of<CounterModel>(context);
    final textSize = Provider.of<int>(context).toDouble();

    return Scaffold(
        body: Center(
            child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                    Text(
                        'value: ${_counter.value}',
                        style: TextStyle(fontSize: textSize),
                    ),
                    RaisedButton(
                        child: Text("animate"),
                        onPressed: counter.increment
                    ),
                ]),
        ));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 5.1.2 Consumer

  • 极大缩小控件刷新范围,只会刷新consumer部分。
  • 作者只提供到了consume6。
class SecondPage extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Consumer2<CounterModel,int>(
        builder: (context, CounterModel counter, int textSize, _) => Center(
              child: Text(
                'Value: ${counter.value}',
                style: TextStyle(
                  fontSize: textSize.toDouble(),
                ),
              ),
            ),
      ),
      floatingActionButton: Consumer<CounterModel>(
        builder: (context, CounterModel counter, child) => FloatingActionButton(
              onPressed: counter.increment,
              child: child,
            ),
        child: Icon(Icons.add),
      ),
    );
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 5.1.3 Selector

  • 避免一个 Provider 可能会为不同业务的多个控件提供不同的数据。
// A: 从顶层获取的 Provider 的类型
// S:获取到的 Provider 中真正对我们有用的类型,需要在 selector 中返回该类型,使整个刷新范围从Provider变成了S
Selector<A, S>(
    // 储存 selector 过滤后的值,也就是 selector 返回的 S 并拿收到通知之后新的 S 与 cache 的 S 比较,来判断这个 Selector 是否需要刷新,默认 preview != next 就刷新。
    // 这里写false则不刷新
    shouldRebuild: (pre, next) => false,
    // 入参会将获取的顶层 provider 传入,再返回具体关注的那部分 S。
    selector: (context, provider) => provider,
    // 第一个参数是具体返回Widget的地方,第二个参数是S,第三个参数用于优化一些不用刷新的部分。
    builder: (context, provider, child) {
        return null;
    },
)
1
2
3
4
5
6
7
8
9
10
11
12
13