安装
按照官网安装指引,下载 Flutter_SDK,解压到任意目录,(安装这步 Flutter官方就没想过要优化一下,自己解压,自己配置 PATH,但是如果不会配置 PATH,还是要看下 Linux下怎么配置 PATH的)
记录下解压的目录,假设是 ~/flutter
$ sudo vim ./.zshrc
vim 中输入
export FLUTTER_HOME=~/flutter
# flutter 可执行文件地址
export PATH=$FLUTTER_HOME/bin:$PATH
# flutter pub 缓存地址
export PATH=$FLUTTER_HOME/.pub-cache/bin:$PATH
export PATH=$FLUTTER_HOME/bin/cache/dart-sdk/bin:$PATH
重启控制台或者执行 source ./.zshrc
$ flutter doctor
默认来说,页端开发 flutter 直接使用 VsCode + VsCode Flutter 插件就好
需要安卓环境的请自行安装 AS
需要 IOS环境请自行安装 XCode
初始化一个应用
按照官网的指引,初始化一个应用
初始化完应用根目录下有个文件 pubspec.yaml,标明了项目的一些基础信息,和依赖的包,作用和页端的 package.json一致
# 安装依赖和 npm install 效果一致
$ flutter pub get
组件
Flutter使用 dart 作为编码语言,它是一门强类型面向对象的编程语言,基本语言概述可以从 Dart中文网 找到,dart 发展到这个阶段,语言特性基本和 TS Kotlin很相似,不能说全部一样,但是很多都有类似的影子,比起两年前差别真的很大
设计
Flutter 设计参考了 React 的思想,组件编写和组件更新都有 React 的影子,都是由一个一个组件堆砌成一个应用,Flutter中组件称为 Widget,同样的,在 React中称为 Component
无状态组件
无状态组件类似 React 的非钩子函数组件,组件无状态,组件更新依赖外部属性
import 'package:flutter/material.dart';
class StatelessChild extends StatelessWidget {
final String text;
StatelessChild({@required this.text});
@override
Widget build(BuildContext context) {
return Text(text);
}
}
万物皆类:从最简单的文本组件开始
仔细看代码,一个简单的文本,既然用了一个 Text 组件包裹,在前端展示这种文本直接返回一个字符串就好,在 Flutter 中并不可以。
- 从代码可以看出
build方法必须返回一个Widget类型的值,上面返回的是Text,Text的继承链是Text -> StatelessWidget -> Widget,所以并没有问题。 Dart编写代码的时候,实例化类,不需要new操作符(开始我还很抗拒,后面真香系列),当然,你坚持写new也是没任何问题的。
@override
Widget build(BuildContext context) {
return new Text(text);
}
- 除了组件是一个类外,不少组件属性也是一个类,比如我需要我的字体颜色是红色,字体大小是
20,行高是1.5,字重是700,字体有下划线,就需要通过TextStyle类修改字体的样式,同时会用到Color,FontWidget``TextDecoration的类,
@override
Widget build(BuildContext context) {
return Text(text,
style: TextStyle(
color: Colors.red,
height: 1.5,
fontSize: 20,
fontWeight: FontWeight.w700,
decoration: TextDecoration.underline));
}
有状态组件
有状态组件,顾名思义就是,带有状态的组件,通过调用组件内部的方法 setState,可以让组件重新渲染,更新过程和老版 React差不多,从调用setState的组件开始,一直遍历子组件,查看需要更新的组件,需要更新的组件,会重新调用组件的 build方法,和 React 组件中的 render一致
class Parent extends StatefulWidget {
@override
State<Parent> createState() => _ParentState();
}
class _ParentState extends State<Parent> {
String text = 'init';
void changeText() {
setState(() {
text = 'text changed';
});
}
@override
Widget build(BuildContext context) {
return StatelessChild(text: text);
}
}
组件间传值
组件间传值方式,和 React 完全一致,不过,在 React中使用的是 JSX 语法编写,在 Flutter中,组件就是类,所以使用组件,就是实例化这个类,通过给类定义属性,实例化类之后,实例化的类就能拿到从父组件传过来的属性了
子组件修改
让我们修改一下上面的子组件,不仅要传一个 text 字符串过来,还需要传一个修改 text 的方法过来,然后我们子组件改成这样,onClick属性不是必传的,但是如果不传,那按钮点击没任何响应
class StatelessChild extends StatelessWidget {
final String text;
final VoidCallback onClick;
StatelessChild({@required this.text, this.onClick});
@override
Widget build(BuildContext context) {
return Column(
children: [
Text(text,
style: TextStyle(
color: Colors.red,
height: 1.5,
fontSize: 20,
fontWeight: FontWeight.w700,
decoration: TextDecoration.underline)),
IconButton(onPressed: onClick, icon: Icon(Icons.ac_unit))
],
);
}
}
父组件修改
父组件改动不多,多传一个属性,当然,不传 onClick也不会有任何报错的,因为 onClick 没有标明 @required
class Parent extends StatefulWidget {
@override
State<Parent> createState() => _ParentState();
}
class _ParentState extends State<Parent> {
String text = 'init';
void changeText() {
setState(() {
text = 'text changed';
});
}
@override
Widget build(BuildContext context) {
return StatelessChild(text: text, onClick: changeText);
}
}
示例等效 React代码
子组件
import { FC } from 'react';
interface StalessChildProps {
text: string;
onClick?: () => void;
}
const StatelessChild: FC<StalessChildProps> = ({ text, onClick }) => {
return (
<div>
<div>{text}</div>
<div onClick={onClick}>按钮</div>
</div>
);
};
export default StatelessChild;
父组件
import { Component } from 'react';
import StatelessChild from './child';
interface ParentState {
text: string;
}
interface ParentProps {}
class Parent extends Component<ParentProps, ParentState> {
constructor(props: ParentProps) {
super(props);
this.state = { text: 'init' };
}
changeText = () => {
this.setState({ text: 'text changed' });
};
render() {
return <StatelessChild text={this.state.text} onClick={this.changeText} />;
}
}
export default Parent;
布局
Flutter 里面最长见的布局,就是 Flex 布局,页端开发熟悉 Flex 布局的,可以快速上手
容器
Flutter最常见的容器是 Container,可以设置 width、height、padding、margins、borders,同时也可以修改背景,盒子的呈现方式和 HTML 的 box-sizing: border-box 一致。
除了 Container 外,还有一些别的容器,要仔细了解这些容器和它们的约束,需要认真看一下 Flutter 布局约束,很多新人会遇到各种容器溢出的问题,仔细看些这里会有不少收获
多个元素
处理多个元素在同一排或者同一列,在 HTML 中,实现方式很多,但是最简单的,可以通过设置 display: flex; 将容器设置成弹性布局,处理起来非常方便。
在 Flutter 里面,也是这样,在同一行的元素,我们使用 Row 组件,在同一列的元素,我们使用 Column组件,其表现和 display: flex; 完全一致,甚至属性都长的一样
布局拆解
// 伪代码
Container
Column
Row
Image
Expanded
Column
Text
Text
Icon
Row
Expanded
Text
Container
Expanded
Text
布局总结
属性以下几种布局不成问题
- 常规布局,
Container + Column + Row - 列表,
ListView - 层叠布局,
Stack
生命周期
Flutter生命周期和 React Class Component类型
React Class Component 生命周期
class ExampleComponent extends React.Component {
// 用于初始化 state
constructor() {}
// 用于替换 `componentWillReceiveProps` ,该函数会在初始化和 `update` 时被调用
// 因为该函数是静态函数,所以取不到 `this`
// 如果需要对比 `prevProps` 需要单独在 `state` 中维护
static getDerivedStateFromProps(nextProps, prevState) {}
// 判断是否需要更新组件,多用于组件性能优化 常用
shouldComponentUpdate(nextProps, nextState) {}
// 组件挂载后调用
// 可以在该函数中进行请求或者订阅 常用
componentDidMount() {}
// 用于获得最新的 DOM 数据
getSnapshotBeforeUpdate() {}
// 组件即将销毁
// 可以在此处移除订阅,定时器等等 常用
componentWillUnmount() {}
// 组件销毁后调用
componentDidUnMount() {}
// 组件更新后调用 常用
componentDidUpdate() {}
// 渲染组件函数
render() {}
// 以下函数不建议使用
UNSAFE_componentWillMount() {}
UNSAFE_componentWillUpdate(nextProps, nextState) {}
UNSAFE_componentWillReceiveProps(nextProps) {}
}
Flutter StatefulWidget 生命周期
class Parent extends StatefulWidget {
@override
State<Parent> createState() => _ParentState();
}
class _ParentState extends State<Parent> {
@override
void initState() {
// State 初始化,这个周期,可以获取到父组件传过来的属性
// 和 React Class Component 构造函数一致
super.initState();
}
@override
void didUpdateWidget(covariant Parent oldWidget) {
// 组件更新完成后会调用,和 React componentDidMount 一致
super.didUpdateWidget(oldWidget);
}
@override
void dispose() {
// 组件被销毁后调用,和 React componentWillUnmount 基本一致
super.dispose();
}
}
技巧
快速生成有状态组件
一个有状态组件初始化长这样,鬼才记得哦,两个类太繁琐了
class Widget extends StatefulWidget {
@override
State<Widget> createState() => _WidgetState();
}
class _WidgetState extends State<Widget> {
@override
Widget build(BuildContext context) {
// TODO: implement build
throw UnimplementedError();
}
}
在 VsCode 中可以快速将一个无状态组件切换成有状态组件
// 一个初始化的无状态组件
class Widget extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
throw UnimplementedError();
}
}
在 VsCode中生成一个无状态组件,光标选中这个类声明的这一行,会看到一个小灯泡按钮,点击这个小灯泡,就可以触发转化成有状态组件的选项,非常实用,官方生成的代码,也比自己写的 State 组件更加严谨
快速组件包裹和删除某个组件
同样也是基于 VsCode,选中某个组件,可以进行很多操作
常规组件
- 删除这个组件
- 交换父子组件(
ClipRect + Container互换位置很常见) - 用
Builder包裹组件,最常见的是StoreConnector这种类builder组件 - 用
Center Padding Container SizedBox包裹组件,修改布局 - 用
widget包裹组件,最常见的是组件外面要加一个GestureDetector
在 children 中的组件
- 支持把这个组件和兄弟组件交换位置
局限和总结
不支持生成任意子组件是 children 的组件,也即是 Row Column ListView这些组件的生成,不过现有的功能已经很给开发提效了
编码
- 文件名用蛇形,格式用
dart,例如novel_header.dart image_cover.dart - 类用大驼峰,变量和函数用小驼峰,不要蛇形和匈牙利命名法
- 用
var声明可变化的变量,用final声明不可再赋值的变量(这两个完全和JSlet和const一致) - 可以显式声明变量类型,显式申明类型的时候,不能用
var,但是可以搭配final
var now = DateTime.now();
DateTime now = DateTime.now();
now = DateTime.fromMillisecondsSinceEpoch(930989389432);
// 不可再赋值
final DateTime now = DateTime.now();
- 复杂类型请不要使用
var,必须显式声明类型,能够避免很多问题
Map<String, dynamic> map = {};
// 不建议
var map = {};
- 用
const声明常量,和不能再赋值是有差别的,通常用于绝对不会变的变量,变量也用全大写声明(不会用不要用这个)
const String PLATFORM_ANDROID = 'android';
const Map<String, String> map = {};
// 常量不能修改,编译器不会报错,但是运行会炸,并且报错还不好定位,不要做这种事情
map['a'] = '1';
- 使用
_开头的属性和函数或者类,是私有的,作用域在类里面或者文件内 - 函数的参数,包裹在
{}内的参数,是可选的,没有{}包裹,是必填参数,规则也适用于构造函数,可选参数可以设定默认值,如果是bool类型的参数,请务必设定默认值,默认值必须是常量
void func(int p1, int p2, { bool p3 = true, String p4, List<int> p5 = const [] }) {}
- 组件的参数,建议不要定义必填属性,除非目的很明确的组件
例如 Text,必填属性就一个String text,使用@requied标明可选属性里面必填属性,这两者作用一样,但是使用有差别,但是配合VsCode,会自动提示函数形参,使用起来也没多大差别
Text('123')
// VsCode 默认提示,必填参数是 text
Text(text)
StatelessChild({@required this.text, this.onClick});
StatelessChild(text: 'text 1', onClick: () {});
// VsCode 默认提示,必填参数是 text
StatelessChild(text: text);