Flutter入门总结

1. 前言

原文发布在语雀:

Flutter入门总结 · 语雀www.yuque.com《Flutter入门总结》

本文主要涉及内容如下:

文章写得比较仓促,欢迎指正。

示例代码在这里

2. 环境搭建

  1. 安装flutter sdk
  2. 配置 iOS Android 开发环境
  3. 配置编辑器(VSCode、 Android Studio)
  4. 启动

3. Widget

Everything’s a Widget

3.1 组件介绍

3.1.1 Text

创建带格式的文本widget。
可控制字体、字号、粗细、对齐、颜色、溢出隐藏等属性。

Text(
  'Hello Flutter! Hello Flutter! Hello Flutter!',
  style: TextStyle(
    fontSize: 30,
    fontWeight: FontWeight.bold,
  ),
  maxLines: 1,
  overflow: TextOverflow.ellipsis,
)

《Flutter入门总结》
《Flutter入门总结》

3.1.2 Image

可显示asset图片、网络图片、本地图片、内存中的图片。

  1. new Image.asset, for obtaining an image from an AssetBundle using a key.
  2. new Image.network, for obtaining an image from a URL.
  3. new Image.file, for obtaining an image from a File.
  4. new Image.memory, for obtaining an image from a Uint8List.
Image.network('https://i.loli.net/2019/03/07/5c80d019a6663.jpg', fit: BoxFit.fitWidth)

《Flutter入门总结》
《Flutter入门总结》

3.1.3 Container

一个拥有更多UI装饰能力的widget, 类似div view。
支持 padding margin 背景图片 border 3d变换等。通常作为其他widget的容器。

Container(
  margin: EdgeInsets.all(10.0),
  color: Colors.blue,
  width: 100.0,
  height: 100.0,
);

《Flutter入门总结》
《Flutter入门总结》

3.1.4 Row、Column

其设计应当是借鉴前端flex布局,继承Flex widget。属性及能力与前端flex基本一致。

Row(
  mainAxisAlignment: MainAxisAlignment.spaceBetween,
  children: <Widget>[
    Container(
      width: 80,
      height: 100,
      color: Colors.blueGrey,
      child: Center(
        child: Text('block 1'),
      ),
    ),
    Container(
      width: 100,
      height: 180,
      color: Colors.pink,
      child: Center(
        child: Text('block 2'),
      ),
    ),
    Container(
      width: 80,
      height: 140,
      color: Colors.teal,
      child: Center(
        child: Text('block 3'),
      ),
    ),
  ],
);

《Flutter入门总结》
《Flutter入门总结》

主要属性:

  1. mainAxisAlignmentMainAxisAlignment 主轴对齐方式:
  2. center
  3. end
  4. spaceAround
  5. spaceBetween
  6. spaceEvenly
  7. start

其中spaceAround、spaceBetween以及spaceEvenly的区别在于距离首尾widget到边界的距离分别是空白区域的1/2、0、1。

  1. crossAxisAlignmentCrossAxisAlignment 交叉轴对齐方式:
  2. baseline
  3. center
  4. start
  5. end
  6. stretch

3.1.5 Stack

允许子元素堆叠,类似前端绝对定位。
子widget分为两种:

  1. Positioned,相当于前端中设置 position: absolute, 此时可以设置 top left等属性进行定位
  2. unPositioned, 普通widget,可以被Positioned widget 遮挡
Container(
  width: double.infinity,  // 在宽度上强制撑满   height: 300,
  child: Stack(
    children: <Widget>[
      Container(
        color: Colors.blueGrey,
        width: 100,
        height: 100,
        alignment: Alignment.center,
        child: Text('block 1 unPositioned', style: TextStyle(color: Colors.white)),
      ),
      Positioned(
        top: 80,
        left: 80,
        child: Container(
          color: Colors.pink,
          width: 100,
          height: 100,
          alignment: Alignment.center,
          child: Text('block 2 positioned', style: TextStyle(color: Colors.white)),
        ),
      ),
      Positioned(
        right: 150,
        bottom: 60,
        child: Container(
          color: Colors.teal,
          width: 100,
          height: 100,
          alignment: Alignment.center,
          child: Text('block 3 positioned', style: TextStyle(color: Colors.white),),
        ),
      ),
    ],
  ),
);

《Flutter入门总结》
《Flutter入门总结》

3.2 StatelessWidget 与 StatefulWidget

StatelessWidget 无状态widget,StatefulWidget 为持有内部状态的Widget。
在Flutter中,所有widget都是不可变的(immutable),那么StatefulWidget如何持有内部状态呢。
StatefulWidget由以下两部分组成:

  1. StatefulWidget 类
  2. State类

StatefulWidget始终是不变的,但State具有自己的生命周期,可以保持、修改状态,必须使用setState()修改状态。

以下为 flutter create 生成demo中的示例:

// StatefulWidget 类 class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

// State 类 class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

实现效果为,点击按钮增加 _counter,并在页面上显示。

3.3 State生命周期

State文档

| 生命周期 | 描述 | | — | — | | initState | Called when this object is inserted into the tree
当widget插入渲染树时调用 | | build | Describes the part of the user interface represented by this widget
描述此窗口小部件表示的用户界面部分
构建widget时调用 | | didChangeDependencies | Called when a dependency of this State object changes
当State依赖的InheritedWidget改变时调用 | | didUpdateWidget | Called whenever the widget configuration changes
当组件状态发生改变时调用 | | deactivate | Called when this object is removed from the tree
从渲染树中移除时调用 | | dispose | Called when this object is removed from the tree permanently
当widget销毁时调用 |

盗闲鱼的一张图:

《Flutter入门总结》
《Flutter入门总结》

3.4 widget 间通信

Flutter与React Vue类似,都是「状态流向下,事件流向上」,即单向数据流。
先看下示例效果:

《Flutter入门总结》

三个组件,Parent为父组件,还有两个子组件Child1, Child2。

3.4.1 状态流向下传递

Child1接收Parent组件的state,并显示。

// child1.dart 
class Child1 extends StatelessWidget {
  final String child1Data;

  Child1({
    this.child1Data,
  });

  @override
  Widget build(BuildContext context) {
    return Card(
      margin: EdgeInsets.all(20),
      elevation: 4,
      child: Column(
        children: <Widget>[
          ListTile(
            title: Text('Child1', style: TextStyle(fontWeight: FontWeight.w500, fontSize: 20)),
            subtitle: Text('接收父组件data'),
          ),
          Divider(),
          Container(
            padding: EdgeInsets.all(20),
            child: Text('child1 Data 的值: $child1Data', style: TextStyle(fontSize: 18)),
          ),
        ],
      ),
    );
  }
}
// parent.dart 
class Parent extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _ParentState();
}

class _ParentState extends State<Parent> {
  String parentData;

  void _onTextFieldChanged(value) {
    _updateParentData(value);
  }

  void _updateParentData(value) {
    setState(() {
      parentData = value;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        Card(
          margin: EdgeInsets.all(10),
          elevation: 4,
          child: Column(
            children: <Widget>[
              ListTile(
                title: Text('Parent', style: TextStyle(fontWeight: FontWeight.w500, fontSize: 20)),
              ),
              Divider(),
              Container(
                padding: EdgeInsets.all(20),
                child: Text('Parent Data 的值: $parentData', style: TextStyle(fontSize: 18)),
              ),
              TextField(
                decoration: InputDecoration(fillColor: Colors.blue.shade100, filled: true, labelText: 'Parent 输入框'),
                onChanged: _onTextFieldChanged,
              ),
              Divider(),
              Child1(child1Data: parentData),
            ],
          ),
        )
      ],
    );
  }
}

在parent widget中我们主要做了两件事:

  1. 监听TextField输入框变化并将值存储在state parentData中
  2. 将parentData 传递给child1 widget

《Flutter入门总结》
《Flutter入门总结》

在child1 widget中我们接收这个参数并将其显示在Text widget中

《Flutter入门总结》
《Flutter入门总结》

3.4.2 事件流向上传递

child2 widget收集自身输入框的数据并将输入传递给parent widget并显示。

同react一样,parent widget将修改state的方法传递给child2 widget,child2调用方法时携带需要传递的数据作为参数即可实现state由下往上传递。

// parent.dart 
class Parent extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _ParentState();
}

class _ParentState extends State<Parent> {
  String parentData;

  void _onTextFieldChanged(value) {
    _updateParentData(value);
  }

  void _updateParentData(value) {
    setState(() {
      parentData = value;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        Card(
          margin: EdgeInsets.all(10),
          elevation: 4,
          child: Column(
            children: <Widget>[
              ListTile(
                title: Text('Parent', style: TextStyle(fontWeight: FontWeight.w500, fontSize: 20)),
              ),
              Divider(),
              Container(
                padding: EdgeInsets.all(20),
                child: Text('Parent Data 的值: $parentData', style: TextStyle(fontSize: 18)),
              ),
              TextField(
                decoration: InputDecoration(fillColor: Colors.blue.shade100, filled: true, labelText: 'Parent 输入框'),
                onChanged: _onTextFieldChanged,
              ),
              Divider(),
              Child1(child1Data: parentData),
              Child2(updateParentData: _updateParentData),
            ],
          ),
        )
      ],
    );
  }
}
// parent2.dart 
class Child2 extends StatefulWidget {

  final Function updateParentData;

  Child2({
    this.updateParentData,
  });

  @override
  State<StatefulWidget> createState() => _Child2State();

}

class _Child2State extends State<Child2> {
  String child2Data;

  void _onTextFieldChanged(value) {
    setState(() {
      child2Data = value;
      widget.updateParentData(value);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Card(
      margin: EdgeInsets.all(20),
      elevation: 4,
      child: Column(
        children: <Widget>[
          ListTile(
            title: Text('Child2', style: TextStyle(fontWeight: FontWeight.w500, fontSize: 20)),
            subtitle: Text('通过回调传递data到父组件'),
          ),
          Divider(),
          Container(
            padding: EdgeInsets.all(20),
            child: Text('child2 Data 的值: $child2Data', style: TextStyle(fontSize: 18)),
          ),
          Divider(),
          TextField(
            decoration: InputDecoration(fillColor: Colors.blue.shade100, filled: true, labelText: 'Child2 输入框'),
            onChanged: _onTextFieldChanged,
          ),
        ],
      ),
    );
  }
}

在parent中将修改state的方法传递给child2。

《Flutter入门总结》
《Flutter入门总结》

child2中,监听TextField变化,并调用parent传递的方法,将输入作为参数。

《Flutter入门总结》
《Flutter入门总结》

3.4.3 InheritedWidget

类似React context,允许跨级传输数据,子组件可订阅祖先节点state。

4. 路由和导航

管理多个页面时有两个核心概念和类:
Route
Navigator。 一个route是一个屏幕或页面的抽象,Navigator是管理route的Widget。Navigator可以通过route入栈和出栈来实现页面之间的跳转。

常用API:

  1. Navigator.push()
  2. Navigator.pop()

4.1 基础使用

// Navigator.push 
onPressed: (BuildContext context) {
  Navigator.push(
    context,
    MaterialPageRoute(builder: (context) => SecondScreen(param1: 'xxx')),
  );
},
// Navigator.pop 
onPressed: (BuildContext context) {
  Navigator.pop(context);
}

4.2 具名路由

在main方法中声明具名路由:

// main.dart 
void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: Home(),  // 这里声明的页面会被默认为 '/' 路径指向的页面       routes: <String, WidgetBuilder>{
        '/widgetExample': (BuildContext context) => WidgetExample(),
        '/dataTransfer': (BuildContext context) => DataTransfer(),
      },
    );
  }
}

也可以使用下面的写法:

// main.dart 
void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      initialRoute: '/',        // 指定默认路由       routes: <String, WidgetBuilder>{
        '/': (BuildContext context) => Home(),
        '/widgetExample': (BuildContext context) => WidgetExample(),
        '/dataTransfer': (BuildContext context) => DataTransfer(),
      },
    );
  }
}

然后就可以像下面这样调用:

Navigator.of(context).pushNamed('/widgetExample');

注意:
上面这种方式并不支持动态传参。

4.3 定制路由

可以定制路由过渡样式:
下面的示例实现的是页面旋转淡出的效果:

// home.dart 
_navCustomRoute(BuildContext context) {
    Navigator.push(
      context,
      PageRouteBuilder(
        // opaque: false,         transitionDuration: Duration(seconds: 1),
        pageBuilder: (context, _, __) => CustomRoute(),
        transitionsBuilder: (___, Animation<double> animation, ____, Widget child) {
          return FadeTransition(
            opacity: animation,
            child: RotationTransition(
              turns: Tween<double>(begin: 0.5, end: 1.0).animate(animation),
              child: child,
            ),
          );
        },
      ),

    );
  }

效果如图:

《Flutter入门总结》

5. 网络请求与JSON解析

5.1 网络请求

5.1.1 原生

import 'dart:io';

var httpClient = new HttpClient();

get() async {
  var httpClient = new HttpClient();
  var uri = new Uri.http(
      'example.com', '/path1/path2', {'param1': '42', 'param2': 'foo'});
  var request = await httpClient.getUrl(uri);
  var response = await request.close();
  var responseBody = await response.transform(UTF8.decoder).join();
}

5.1.2 http库

import 'package:http/http.dart' as http;

 void get() async {
  var client = http.Client();
  http.Response response = await client.get(url_2);
  _content = response.body;
}

5.1.3 dio库

dio是一个强大的Dart Http请求库,支持Restful API、FormData、拦截器、请求取消、Cookie管理、文件上传/下载、超时等…

void get() async {
  Dio dio = new Dio();
  Response response = await dio.get(url_3);
}

5.2 JSON解析

使用Dio进行网络请求设置返回格式为json时,我们会得到Map,使用自带的 convert 方法解码JSON也是得到Map,键值对的Map在使用中并不方便,需要进一步解析成Model实体。

常用解析策略有两种:

  1. 手动序列化和反序列化
  2. 通过代码生成自动序列化和反序列化

依据项目复杂度与应用场景灵活选择。

假定JSON结构如下,我们来看一下如何解析:

// lib/mock/mock.json

{
  "id": "487349",
  "name": "Hayden",
  "score": 1000
}

5.2.1 手动反序列化

手动JSON序列化是指使使用 dart:convert 中内置的 JSON 解码器。它将原始 JSON字 符串传递给JSON.decode() 方法,然后在返回的 Map 中查找所需的值。 它没有外部依赖或其它的设置,对于小项目很方便。

创建model:

// lib/model/student_model.dart 
class Student {
  String studentId;
  String studentName;
  int studentScores;

  Student({
    this.studentId,
    this.studentName,
    this.studentScores,
  });

  factory Student.fromJson(Map<String, dynamic> parsedJson) {
    return Student(
      studentId: parsedJson['id'],
      studentName: parsedJson['name'],
      studentScores: parsedJson['score'],
    );
  }

}

工厂函数fromJson主要功能是键名的映射,fromJson的参数parsedJson类型定义为Map是因为键名总是String,但键的类型并不确定。

接下来我们在页面中使用该Model解析上文的Json数据:

// lib/pages/json_decode.dart 
import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show rootBundle;
import 'dart:convert';

import '../model/student_model.dart';

class JsonDecode extends StatelessWidget {
  Future<String>  _loadJsonFile() async {
    return await rootBundle.loadString('lib/mock/mock.json');
  }

  Future loadStudent() async {
    // 加载JSON文件     String jsonString = await _loadJsonFile();
    // 解码     final jsonRes = json.decode(jsonString);
    // 解析     Student student = new Student.fromJson(jsonRes);

    print(student.studentName);
    print(student);
    print(jsonString);
    print(jsonRes);
    print(jsonRes['name']);
  }


  @override
  Widget build(BuildContext context) {
    loadStudent();

    return Scaffold(
      appBar: AppBar(
        title: Text('JSON 解析'),
      ),
      body: Text('请在控制台中查看打印'),
    );
  }
}

loadStudent方法中:
第一步,引入JSON文件,得到json字符串
第二部,使用自带convert 解码第一步得到的json字符串,生成对应Map
第三步,使用Studen Model解析Map,得到Student类的实例

控制台打印如下:

《Flutter入门总结》
《Flutter入门总结》

5.2.2 自动生成反序列化代码

在这里我们使用json_serializable实现自动反序列化
**

5.2.2.1 一、添加依赖包

在pubspec.yaml中添加我们需要的依赖包:

dependencies:  json_annotation: ^2.0.0  dev_dependencies:  build_runner: ^1.0.0  json_serializable: ^2.0.0

这里我们添加了三个依赖包:json_annotation build_runner json_serializable,保存修改后vscode会自动安装依赖包,也可以手动安装(cmd + shift + p 命令面板中选择 Flutter: Get Packages)

5.2.2.2 二、创建Studen实体类

// lib/model/studen_model_auto.dart 
class StudentAuto {
  String studentId;
  String studentName;
  int studentScores;

  StudentAuto({
    this.studentId,
    this.studentName,
    this.studentScores,
  });
}

5.2.2.3 三、关联

// lib/model/studen_model_auto.dart 
import 'package:json_annotation/json_annotation.dart';
part 'student_model_auto.g.dart';

@JsonSerializable()
class StudentAuto {
  @JsonKey(name: 'id')
  String studentId;
  @JsonKey(name: 'name')
  String studentName;
  @JsonKey(name: 'scores')
  int studentScores;

  StudentAuto({
    this.studentId,
    this.studentName,
    this.studentScores,
  });

  factory StudentAuto.fromJson(Map<String, dynamic> json) => _$StudentAutoFromJson(json);
  Map<String, dynamic> toJson() => _$StudentAutoToJson(this);
}

在@JsonKey中我们做了键名映射。
此时flutter会报错,因为我们还没有生成 student_model_auto.g.dart 文件

5.2.2.4 生成JSON解析文件

我们已经在StudentAuto实体类前加了注解 @JsonSerializable()
在项目根目录下执行 flutter packages pub run build_runner build

完成后会在 lib/model 目录自动生成 student_model_auto.g.dart 文件

来看下这个文件的内容,其实与手动解析是一样的:

// GENERATED CODE - DO NOT MODIFY BY HAND 
part of 'student_model_auto.dart';

// ************************************************************************** // JsonSerializableGenerator // ************************************************************************** 
StudentAuto _$StudentAutoFromJson(Map<String, dynamic> json) {
  return StudentAuto(
      studentId: json['id'] as String,
      studentName: json['name'] as String,
      studentScores: json['scores'] as int);
}

Map<String, dynamic> _$StudentAutoToJson(StudentAuto instance) =>
    <String, dynamic>{
      'id': instance.studentId,
      'name': instance.studentName,
      'scores': instance.studentScores
    };

5.2.2.5 使用model反序列化json

// lib/pages/json_decode.dart 
import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show rootBundle;
import 'dart:convert';

import '../model/student_model_auto.dart';

class JsonDecode extends StatelessWidget {
  Future<String>  _loadJsonFile() async {
    return await rootBundle.loadString('lib/mock/mock.json');
  }

  Future loadStudentAuto() async {
    String jsonString = await _loadJsonFile();
    final jsonRes = json.decode(jsonString);
    StudentAuto student = StudentAuto.fromJson(jsonRes);

    print(student.studentName);
    print(student);
    print(jsonString);
    print(jsonRes);
    print(jsonRes['name']);
  }


  @override
  Widget build(BuildContext context) {
    loadStudentAuto();

    return Scaffold(
      appBar: AppBar(
        title: Text('JSON 解析'),
      ),
      body: Text('请在控制台中查看打印'),
    );
  }
}

控制台打印如图:

《Flutter入门总结》
《Flutter入门总结》

6. 参考文献

  1. Flutter中文网
  2. Flutter快速上车之Widget
  3. [译]在 Flutter 中解析复杂的 JSON
    原文作者:陈十二
    原文地址: https://zhuanlan.zhihu.com/p/65590594
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞