1. 1. 针对JavaScript开发人员的Dart简介
    1. 1.1. 入口函数
    2. 1.2. 打印到控制台
    3. 1.3. 变量
      1. 1.3.1. 创建和分配变量
      2. 1.3.2. 默认值
    4. 1.4. 检查null或零
    5. 1.5. 函数
    6. 1.6. 异步编程
      1. 1.6.1. Futures
      2. 1.6.2. async 和 await
  2. 2. 基础
    1. 2.1. 如何创建Flutter应用程序?
    2. 2.2. 如何运行应用程序?
    3. 2.3. 如何导入widget?
    4. 2.4. Flutter中的"Hello world!"在React Native里怎么写?
    5. 2.5. 如何使用widget并将其嵌套成widget树?
    6. 2.6. 如何创建可重用组件?
  3. 3. 项目结构和资源目录
    1. 3.1. 我从哪里开始编写代码?
    2. 3.2. 如何在Flutter应用程序中构建文件?
    3. 3.3. 我在哪里放置我的资源和资产以及如何使用它们?
    4. 3.4. 如何通过网络加载图像?
    5. 3.5. 我如何安装软件包和软件包插件?
  4. 4. Flutter widget
  5. 5. Views
    1. 5.1. 什么相当于View容器?
    2. 5.2. 什么相当于FlatList或SectionList?
    3. 5.3. 我如何使用Canvas绘制?
  6. 6. Layouts(布局)
    1. 6.1. 如何使用widget来定义布局属性?
    2. 6.2. 如何布局widget?
  7. 7. Styling
    1. 7.1. 如何设置组件的样式?
    2. 7.2. 如何使用Icons和Colors?
    3. 7.3. 我如何添加风格主题?
  8. 8. State管理
    1. 8.1. StatelessWidget
    2. 8.2. StatefulWidget
    3. 8.3. 什么是StatefulWidget和StatelessWidget最佳实践?
      1. 8.3.1. 1.确定一个widget应该是一个StatefulWidget还是一个StatelessWidget
      2. 8.3.2. 2.确定哪个对象管理widget的状态(对于StatefulWidget)
      3. 8.3.3. 3.子类StatefulWidget和State
      4. 8.3.4. 4.将StatefulWidget添加到widget树中
  9. 9. Props
  10. 10. 本地存储
    1. 10.1. 如何在应用内持久化存储一个键值对?
  11. 11. 路由
    1. 11.1. 如何在屏幕之间使用navigate?
    2. 11.2. 如何使用选项卡导航(Tab navigation)和抽屉导航(Drawer navigation)?
      1. 11.2.1. 标签导航(Tab navigation)
      2. 11.2.2. Drawer navigation
  12. 12. 手势检测和触摸事件处理
    1. 12.1. 如何添加click或press监听器到widget?
  13. 13. 发出HTTP网络请求
    1. 13.1. 如何从API调用中获取数据?
  14. 14. 表单输入
    1. 14.1. 如何使用文本字段widget?
    2. 14.2. 如何使用表单widget?
  15. 15. 判断平台特定的代码
  16. 16. 调试
    1. 16.1. 如何访问应用内开发人员菜单?
    2. 16.2. 如何执行热重载?
    3. 16.3. 我可以用什么工具在Flutter中调试我的应用程序?
  17. 17. 动画
    1. 17.1. 如何添加简单的淡入动画?
    2. 17.2. 如何给卡片添加swipe动画?
  18. 18. React Native 和 Flutter Widget 功能相等的组件

【flutter】用于React Native开发者的flutter参考文档

本文为机翻加人工矫正,原文链接:https://flutter.io/flutter-for-react-native/

本文档适用于React Native(RN)开发人员,他们希望利用现有的RN知识,使用Flutter构建移动应用程序。如果您了解RN框架的基础知识,那么您可以使用此文档作为开始学习Flutter开发的方法。

您可以利用此文档来查找与您的需求最相关的问题。


针对JavaScript开发人员的Dart简介

像React Native一样,Flutter使用响应的view。但是,不同点是RN需要编译为native代码。而Flutter则控制屏幕上的每个像素进行渲染,从而避免因需要JavaScript桥而导致的性能问题。

Dart是一种易于学习的语言,并提供以下功能:

  • 提供用于构建Web,服务器和移动应用程序的开源,可伸缩编程语言。
  • 提供面向对象的单继承语言,该语言使用AOT编译为native的C风格语法。
  • 可选地编译为JavaScript。
  • 支持接口和抽象类。

下面介绍JavaScript和Dart之间差异的几个示例。

入口函数

JavaScript是没有入口函数的 - 可以随意定义入口。

1
2
3
4
// JavaScript
function startHere() {
// Can be used as entry point
}

在Dart中,每个应用都必须有一个最顶层的main()函数,作为应用的入口。

1
2
3
// Dart
main() {
}

DartPad中尝试一下。

打印到控制台

要在Dart中打印到控制台,请使用print

1
2
// JavaScript
console.log("Hello world!");
1
2
// Dart
print('Hello world!');

DartPad中尝试一下。

变量

Dart是类型安全的, 它使用静态类型检查和运行时检查的组合来确保变量的值始终与变量的静态类型相匹配。尽管类型是强制性的,但某些类型注释是可选的,因为Dart执行类型推断。

创建和分配变量

在JavaScript中,变量没有类型。

1
2
// JavaScript
var name = "JavaScript";

在Dart中,变量必须是明确类型的,或者类型系统必须自动推断正确的类型。

1
2
3
4
// Dart
String name = 'dart'; // Explicitly typed as a string.
var otherName = 'Dart'; // Inferred string.
// Both are acceptable in Dart.

DartPad中尝试一下。

有关更多信息,请参阅Dart的类型系统

默认值

在JavaScript中,未初始化的变量是undefined。

1
2
// JavaScript
var name; // == undefined

在Dart中,未初始化的变量的初始值为null。由于数字是Dart中的对象,因此即使数字类型的未初始化变量也具有该值null。

1
2
3
// Dart
var name; // == null
int x; // == null

DartPad中尝试一下。

有关更多信息,请参阅有关变量的文档。

检查null或零

在JavaScript中,1或任何非空对象的值都视为true。

1
2
3
4
5
6
7
8
9
// JavaScript
var myNull = null;
if (!myNull) {
console.log("null is treated as false");
}
var zero = 0;
if (!zero) {
console.log("0 is treated as false");
}

在Dart中,只有布尔值true被视为true。

1
2
3
4
5
6
7
8
9
// Dart
var myNull = null;
if (myNull == null) {
print('use "== null" to check null');
}
var zero = 0;
if (zero == 0) {
print('use "== 0" to check zero');
}

DartPad中尝试一下。

函数

Dart和JavaScript的函数大致相同。主要区别在于声明。

1
2
3
4
// JavaScript
function fn() {
return true;
}
1
2
3
4
5
6
7
8
// Dart
fn() {
return true;
}
// can also be written as
bool fn() {
return true;
}

DartPad中尝试一下。

有关更多信息,请参阅有关函数的文档。

异步编程

Futures

像JavaScript一样,Dart支持单线程执行。在JavaScript中,Promise对象表示异步操作的最终完成(或失败)及其结果值。

而Dart使用 Future 对象来处理这个问题。

1
2
3
4
5
6
7
8
9
10
11
12
// JavaScript
_getIPAddress = () => {
const url="https://httpbin.org/ip";
return fetch(url)
.then(response => response.json())
.then(responseJson => {
console.log(responseJson.origin);
})
.catch(error => {
console.error(error);
});
};
1
2
3
4
5
6
7
// Dart
_getIPAddress() {
final url = 'https://httpbin.org/ip';
HttpRequest.request(url).then((value) {
print(json.decode(value.responseText)['origin']);
}).catchError((error) => print(error));
}

DartPad中尝试一下。

有关更多信息,请参阅有关Futures的文档。

asyncawait

两者都使用async函数声明定义异步函数。

在JavaScript中,该async函数返回一个Promise对象。该await操作是用来等待Promise。

1
2
3
4
5
6
7
8
// JavaScript
async _getIPAddress() {
const url="https://httpbin.org/ip";
const response = await fetch(url);
const json = await response.json();
const data = await json.origin;
console.log(data);
}

在Dart中,一个async函数返回一个Future对象,函数的主体被安排在稍后执行。该await操作是用来等待Future.

1
2
3
4
5
6
7
// Dart
_getIPAddress() async {
final url = 'https://httpbin.org/ip';
var request = await HttpRequest.request(url);
String ip = json.decode(request.responseText)['origin'];
print(ip);
}

DartPad中尝试一下。

有关更多信息,请参阅有关async和await的文档。

基础

如何创建Flutter应用程序?

要使用React Native创建应用程序,您可以从命令行运行react-native init

1
$ react-native init <projectname>

要在Flutter中创建应用程序,请执行以下操作之一:

使用flutter create命令行中的命令。确保Flutter SDK位于PATH中。
使用安装了Flutter和Dart插件的IDE。

1
$ flutter create <projectname>

有关更多信息,请参阅”入门:概述“教程,该教程将引导您创建按钮单击计数器应用程序。创建一个Flutter项目会构建在Android和iOS设备上运行示例应用程序所需的所有文件。

如何运行应用程序?

在React Native中,您将运行react-native run-iosreact-native run-android从项目目录运行。

您可以通过几种方式运行Flutter应用程序:

  • 使用flutter run从项目的根目录。
  • 在带有Flutter和Dart插件的IDE中使用“run”选项。

需注意:在启动应用之前,请确保有一个设备是在连接的设备,iOS模拟器或Android模拟器上运行。

有关更多信息,请参阅Flutter入门文档。

如何导入widget

在React Native中,您需要导入每个必需的组件。

1
2
3
//React Native
import React from "react";
import { StyleSheet, Text, View } from "react-native";

在Flutter中,要使用Material Design库中的widget,请导入该material.dart包。要使用iOS样式widget,请导入Cupertino库。要使用更基本的widget,请导入widget库。或者,您可以编写自己的widget库并导入它。

1
2
3
4
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/widget.dart';
import 'package:flutter/my_widget.dart';

无论您导入哪个Widget软件包,Dart都只会引入应用中使用的widget。

有关更多信息,请参阅Flutter widget Catalog

Flutter中的"Hello world!"在React Native里怎么写?

在React Native中,HelloWorldAppReact.Component通过返回View组件来扩展和实现渲染方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// React Native
import React from "react";
import { StyleSheet, Text, View } from "react-native";

export default class App extends React.Component {
render() {
return (
<View style={styles.container}>
<Text>Hello world!</Text>
</View>
);
}
}

const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center"
}
});

在flutter,您可以使用创建一个相同的”Hello world!”的应用程序CenterText小工具从核心部件库。该Centerwidget成为widget树的根,并且有一个Textwidget。

1
2
3
4
5
6
7
8
9
10
11
12
13
// Flutter
import 'package:flutter/material.dart';

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

以下图片显示了基本Flutter”Hello world!”应用程序的Android和iOS UI。

现在您已经看到了最基本的Flutter应用程序,下一节将介绍如何利用Flutter丰富的widget库来创建一个现代的,优雅的应用程序。

如何使用widget并将其嵌套成widget树?

在Flutter中,几乎所有东西都是一个widget。

widget是应用程序用户界面的基本构建块。您将widget组成一个层次结构,称为widget树。每个widget嵌套在父widget中,并从其父窗口继承属性。即使应用程序对象本身也是一个widget。没有单独的“应用程序”对象。相反,根部件扮演着这个角色。

一个widget可以定义:

  • 结构元素 - 按钮或菜单
  • 风格元素 - 字体或颜色方案
  • 布局 - padding或alignment

以下示例使用Material库中的widget显示”Hello world!”应用程序。在此示例中,widget树嵌套在MaterialApp根widget中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Flutter
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Welcome to Flutter',
home: Scaffold(
appBar: AppBar(
title: Text('Welcome to Flutter'),
),
body: Center(
child: Text('Hello world'),
),
),
);
}
}

在编写应用程序时,您将使用两种类型的widget:StatelessWidgetStatefulWidget。StatelessWidget听起来就像是一个没有状态的widget。一个StatelessWidget被创建一次,永远不会改变它的外观。StatefulWidget根据收到的数据或用户输入动态改变状态。

无状态和有状态widget之间的重要区别在于,有状态widget具有一个状态对象,用于存储状态数据并跨树重建执行,因此不会丢失。

在简单或基本应用程序中,嵌套widget很容易,但随着代码库变得越来越大,应用程序变得越来越复杂,您应该将嵌套widget深入分解为返回widget或更小类的函数。创建单独的函数和widget允许您在应用程序内重用这些组件。

如何创建可重用组件?

在React Native中,您可以定义一个类来创建可重用组件,然后使用props方法来设置或返回所选元素的属性和值。在下面的例子中,CustomCard该类是定义的,然后在父类中使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// React Native
class CustomCard extends React.Component {
render() {
return (
<View>
<Text > Card {this.props.index} </Text>
<Button
title="Press"
onPress={() => this.props.onPress(this.props.index)}
/>
</View>
);
}
}

// Usage
<CustomCard onPress={this.onPress} index={item.key} />

在Flutter中,定义一个类来创建自定义widget,然后重用widget。您还可以定义和调用返回可重用widget的build函数,如以下示例中的函数中所示。

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
// Flutter
class CustomCard extends StatelessWidget {
CustomCard({@required this.index, @required this.onPress});

final index;
final Function onPress;

@override
Widget build(BuildContext context) {
return Card(
child: Column(
children: <Widget>[
Text('Card $index'),
FlatButton(
child: const Text('Press'),
onPressed: this.onPress,
),
],
)
);
}
}
...
// Usage
CustomCard(
index: index,
onPress: () {
print('Card $index');
},
)
...

在前面的例子中,CustomCard该类的构造函数使用Dart的大括号语法来表示命名的可选参数。{}

要求这些字段,请从构造函数中删除花括号,或者添加@required到构造函数中。

以下屏幕截图显示了可重用的CustomCard类的示例。

项目结构和资源目录

我从哪里开始编写代码?

main.dart文件开始。它是在您创建Flutter应用程序时自动生成的。

1
2
3
4
// Dart
void main(){
print("Hello, this is the main function.");
}

在Flutter中,入口点文件是<projectname>/lib/main.dartmain函数开始执行。

如何在Flutter应用程序中构建文件?

当您创建一个新的Flutter项目时,它会构建以下目录结构。您可以稍后自定义,但这是您开始的地方。

1
2
3
4
5
6
7
8
9
10
11
12
13

└ projectname

├ android - android源码。
├ build - 存储iOS和Android的build文件。
├ ios - iOS源码。
├ lib - 外部可访问的Dart源文件。

└ src - 额外的源文件。
└ main.dart - Flutter入口点和新应用程序的开始。
├ test - 自动化测试文件。
└ pubspec.yaml - Flutter应用程序的元数据。
这相当于React Native中的package.json文件。

我在哪里放置我的资源和资产以及如何使用它们?

Flutter资源或资产是与您的应用程序捆绑并部署的文件,可在运行时访问。扑动应用程序可以包含以下资产类型:

  • 静态数据,如JSON文件
  • 配置文件
  • 图标和图像(JPEG,PNG,GIF,动画GIF,WebP,动画WebP,BMP和WBMP)

Flutter使用pubspec.yaml位于项目根目录的文件来识别应用程序所需的资产。

1
2
3
4
flutter:
assets:
- assets/my_icon.png
- assets/background.png

该assets 节点指定应包含在应用程序中的文件。每个资产都由相对于pubspec.yaml 资产文件所在文件的显式路径标识。声明资产的顺序无关紧要。使用的实际目录(assets 在本例中)无关紧要。但是,虽然资产可以放在任何app目录中,但最好将它们放在assets目录中。

在构建期间,Flutter将资产放入称为资产包的特殊存档中,应用程序在运行时读取这些存档。在资产部分中指定资产的路径时pubspec.yaml,构建过程会查找相邻子目录中具有相同名称的任何文件。这些文件也与指定的资产一起包含在资产包中。flutter使用资产变型时,为您的应用程序选择合适分辨率的图像。

在React Native中,您可以通过将图像文件放在源代码目录中并引用它来添加静态图像。

1
<Image source={require("./my-icon.png")} />

在Flutter中,使用AssetImagewidget构建方法中的类向应用程序添加静态图像。

1
image: AssetImage('assets/background.png'),

有关更多信息,请参阅在Flutter中添加资产和图像

如何通过网络加载图像?

在React Native中,您可以在Image组件使用props属性sourceuri方式进行指定,并在Image需要时提供大小。

在Flutter中,使用Image.network构造函数来包含URL中的图像。

1
2
// Flutter
body: Image.network('https://flutter.io/images/owl.jpg'),

我如何安装软件包和软件包插件?

Flutter支持使用由其他开发者贡献给Flutter和Dart生态系统的公用package。这使您可以快速构建应用程序,而无需从头开始开发所有内容。包含平台特定代码的软件包称为package plugins。

在React Native中,您可以使用yarn add {package-name}npm install {package-name} --save从命令行安装package。

在Flutter中,使用以下说明安装包装:

1.将软件包名称和版本添加到pubspec.yaml依赖项部分。下面的例子展示了如何将·google_sign_inDart包添加到pubspec.yaml文件中。在YAML文件中工作时检查缩进,因为缩进空格很重要!

1
2
3
4
dependencies:
flutter:
sdk: flutter
google_sign_in: ^3.0.3

2.通过使用命令行来安装软件包flutter packages get。如果使用IDE,它通常会自动运行flutter packages get,或者它可能会提示您这样做。

3.将软件包导入您的应用程序代码,如下所示:

1
import 'package:flutter/cupertino.dart';

有关更多信息,请参阅使用包开发包和插件

您可以在pub.dartlang.org的Flutter Packages部分找到Flutter开发人员共享的许多packages。

Flutter widget

在Flutter中,您可以使用widget构建您的用户界面,这些widget根据当前的配置和状态描述他们的视图应该是什么样子。

widget通常由许多嵌套的小型单一用途widget组成,以产生强大的效果。例如,Containerwidget由几个widget组成,负责布局,绘制,定位和大小调整。具体地,Containerwidget包括LimitedBoxConstrainedBoxAlignPaddingDecoratedBox,和Transform widgets。Container您可以用新的和独特的方式组合这些和其他简单的widget,而不是创建自定义效果的子类。

Center部件是如何控制布局的另一个例子。要将widget居中,将其包装在Centerwidget中,然后使用布局widget进行对齐,行,列和网格。这些布局widget没有自己的可视化表示。相反,他们唯一的目的是控制另一个widget布局的某些方面。要了解widget以某种方式呈现的原因,检查相邻widget通常很有帮助。

有关更多信息,请参阅Flutter技术概述

有关Widgets包中的核心widget的更多信息,请参阅Flutter Basic WidgetsFlutter Widget 目录Flutter Widget 索引

Views

什么相当于View容器?

在React Native中,View是一个支持布局的容器Flexbox,样式,触摸处理和可访问性控件。

在Flutter中,您可以使用Widgets库中的核心布局widget,例如 ContainerColumnRowCenter

有关更多信息,请参阅Layout Widgets目录。

什么相当于FlatListSectionList

List是垂直排列的可滚动组件列表。

在React Native中,FlatListSectionList用于呈现简单或分段的列表。

1
2
3
4
5
// React Native
<FlatList
data={[ ... ]}
renderItem={({ item }) => <Text>{item.key}</Text>}
/>

ListView是Flutter最常用的滚动widget。默认的构造函数需要一个明确的子列表。ListView对于少数widget是最合适的。对于一个大的或无限的列表,使用ListView.builder,它根据需要构建它的子项,并只构建那些可见的子项。

1
2
3
4
5
6
7
8
9
10
// Flutter
var data = [ ... ];
ListView.builder(
itemCount: data.length,
itemBuilder: (context, int index) {
return Text(
data[index],
);
},
)
要了解如何实现无限滚动列表,请参阅 [编写您的第一个flutter应用程序,第1部分](https://codelabs.developers.google.com/codelabs/first-flutter-app-pt1/#0) codelab。

我如何使用Canvas绘制?

在React Native中,画布组件不存在,所以react-native-canvas使用第三方库。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// React Native
handleCanvas = canvas => {
const ctx = canvas.getContext("2d");
ctx.fillStyle = "skyblue";
ctx.beginPath();
ctx.arc(75, 75, 50, 0, 2 * Math.PI);
ctx.fillRect(150, 100, 300, 300);
ctx.stroke();
};

render() {
return (
<View>
<Canvas ref={this.handleCanvas} />
</View>
);
}

在Flutter中,您可以使用CustomPaint 和CustomPainter类绘制到画布。

以下示例显示了如何在绘制阶段使用CustomPaintwidget进行绘制。它实现了抽象类CustomPainter,并将其传递给CustomPaint的画家属性。CustomPaint子类必须实现paintshouldRepaint方法。

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
// Flutter
class MyCanvasPainter extends CustomPainter {

@override
void paint(Canvas canvas, Size size) {
Paint paint = Paint();
paint.color = Colors.amber;
canvas.drawCircle(Offset(100.0, 200.0), 40.0, paint);
Paint paintRect = Paint();
paintRect.color = Colors.lightBlue;
Rect rect = Rect.fromPoints(Offset(150.0, 300.0), Offset(300.0, 400.0));
canvas.drawRect(rect, paintRect);
}

bool shouldRepaint(MyCanvasPainter oldDelegate) => false;
bool shouldRebuildSemantics(MyCanvasPainter oldDelegate) => false;
}
class _MyCanvasState extends State<MyCanvas> {

@override
Widget build(BuildContext context) {
return Scaffold(
body: CustomPaint(
painter: MyCanvasPainter(),
),
);
}
}

Layouts(布局)

如何使用widget来定义布局属性?

在React Native中,大部分布局可以通过传递给特定组件的道具完成。例如,您可以使用View组件的props属性style来指定flexbox属性。要在列中排列组件,您需要指定一个属性,如:flexDirection:'column'

在Flutter中,布局主要由widget定义,widget专门用于提供布局,并结合控件和其样式属性。

例如,ColumnRow 将获取一组子对象,并分别垂直和水平对齐。Container 采用布局和样式属性的组合,而Center控件将其子控件居中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Flutter
Center(
child: Column(
children: <Widget>[
Container(
color: Colors.red,
width: 100.0,
height: 100.0,
),
Container(
color: Colors.blue,
width: 100.0,
height: 100.0,
),
Container(
color: Colors.green,
width: 100.0,
height: 100.0,
),
],
),
)

Flutter在其核心部件库中提供了各种布局部件。例如,PaddingAlign,和Stack

有关完整列表,请参阅Layout Widgets

如何布局widget?

在React Native中,组件可以使用absolute定位进行布局。

Flutter使用Stackwidget在图层中排列子widget。widget可以完全或部分重叠基本widget。

Stackwidget的子widget相对于它的框的边缘进行定位。如果你只是想重叠几个子部件,这个类很有用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Flutter
Stack(
alignment: const Alignment(0.6, 0.6),
children: <Widget>[
CircleAvatar(
backgroundImage: NetworkImage(
"https://avatars3.githubusercontent.com/u/14101776?v=4"),
),
Container(
decoration: BoxDecoration(
color: Colors.black45,
),
child: Text('Flutter'),
),
],
)

前面的示例用于StackContainer上覆盖ContainerText 在半透明的黑色背景上显示它)CircleAvatarStack使用对齐属性和对齐坐标偏移文本。

有关更多信息,请参阅Stack类文档。

Styling

如何设置组件的样式?

在React Native中,内联样式并Stylesheets.create用于对组件进行样式设置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// React Native
<View style={styles.container}>
<Text style={{ fontSize: 32, color: "cyan", fontWeight: "600" }}>
This is a sample text
</Text>
</View>

const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center"
}
});

在Flutter中,一个Textwidget可以TextStyle为其样式属性选择一个类。如果您想在多个位置使用相同的文本样式,则可以创建一个TextStyle类并将其用于多个Textwidget。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Flutter
var textStyle = TextStyle(fontSize: 32.0, color: Colors.cyan, fontWeight:
FontWeight.w600);
...
Center(
child: Column(
children: <Widget>[
Text(
'Sample text',
style: textStyle,
),
Padding(
padding: EdgeInsets.all(20.0),
child: Icon(Icons.lightbulb_outline,
size: 48.0, color: Colors.redAccent)
),
],
),
)

如何使用IconsColors

React Native不包含对图标的支持,因此使用第三方库。

在Flutter中,导入Material库也引入了丰富的 Material IconsColors

1
Icon(Icons.lightbulb_outline, color: Colors.redAccent)

使用Icons类时,请确保uses-material-design: true在项目pubspec.yaml文件中设置。这可确保MaterialIcons显示图标的字体包含在您的应用程序中。

1
2
name: my_awesome_application
flutter: uses-material-design: true

Flutter的Cupertino(iOS风格)包为当前的iOS设计语言提供了高保真的widgets。要使用该CupertinoIcons字体,请在项目 pubspec.yaml 文件中添加一个依赖项 cupertino_icons

1
2
3
name: my_awesome_application
dependencies:
cupertino_icons: ^0.1.0

要全局自定义组件的颜色和样式,请使用ThemeData为主题的各个方面指定默认颜色。将主题属性设置MaterialApp为该ThemeData对象。该 Colors 类从Material Design提供的主题配色。

以下示例将主题颜色设置为blue,并将文本选择设置为red。

1
2
3
4
5
6
7
8
9
10
11
12
13
class SampleApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sample App',
theme: ThemeData(
primarySwatch: Colors.blue,
textSelectionColor: Colors.red
),
home: SampleAppPage(),
);
}
}

我如何添加风格主题?

在React Native中,通用主题是为样式表中的组件定义的,然后在组件中使用。

在Flutter中,通过在ThemeData类中定义样式并将其传递给MaterialAppwidget中的theme属性,为几乎所有内容创建统一样式。

1
2
3
4
5
6
7
8
9
10
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primaryColor: Colors.cyan,
brightness: Brightness.dark,
),
home: StylingPage(),
);
}

即使不使用MaterialAppwidget,也可以用Theme。Theme在其数据参数中接受一个ThemeData,并将ThemeData应用于其所有子部件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@override
Widget build(BuildContext context) {
return Theme(
data: ThemeData(
primaryColor: Colors.cyan,
brightness: brightness,
),
child: Scaffold(
backgroundColor: Theme.of(context).primaryColor,
...
...
),
);
}

State管理

状态是可以在构建widgets时同步读取的信息,也可以是在widgets的生命周期内可能更改的信息。要在Flutter中管理应用程序状态,请使用与状态对象配对的 StatefulWidget

StatelessWidget

StatelessWidget是一个不需要状态更改的widget,它没有内部状态可管理。

如果所描述的用户界面部分不依赖于对象本身中的配置信息和widget填充的BuildContext,则无状态widget非常有用。

AboutDialogCircleAvatorText是无状态widget的示例,它们是StatelessWidget的子类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Flutter
import 'package:flutter/material.dart';

void main() => runApp(MyStatelessWidget(text: "StatelessWidget Example to show immutable data"));

class MyStatelessWidget extends StatelessWidget {
final String text;
MyStatelessWidget({Key key, this.text}) : super(key: key);

@override
Widget build(BuildContext context) {
return Center(
child: Text(
text,
textDirection: TextDirection.ltr,
),
);
}
}

在前面的例子中,您使用了MyStatelessWidget类的构造函数来传递一个finaltext。这个类扩展了StatelessWidget,它包含不可变数据。

build通常仅在三种情况下调用无状态widget的方法:

  • 当widget插入到树中时
  • 当widget的父部件更改其配置时
  • 当依赖于InheritedWidget时,更改

StatefulWidget

StatefulWidget是一个状态变化的widget。使用setState方法来管理StatefulWidget的状态变更。调用 setState 会告诉Fltter框架,某个状态发生了变化,这会导致应用程序重新运行build方法,以便应用程序能够反映变更。

state是在构建widget时可以同步读取的信息,在widget的生命周期内可能会发生更改。widget实现者有责任确保状态变化时立即通知状态。当widget可以动态更改时,使用StatefulWidget。例如,通过在表单中键入或移动滑块来更改widget的状态。或者,它可以随着时间的推移而改变-也许是一个数据提要更新了UI。

CheckboxRadioSliderInkWellFormTextField是有状态widget的示例,它们是StatefulWidget的子类。

下面的示例声明一个StatefulWidget,它需要createState()方法。此方法创建用于管理widget状态的状态对象_MyStatefulWidgetState

1
2
3
4
5
6
7
class MyStatefulWidget extends StatefulWidget {
MyStatefulWidget({Key key, this.title}) : super(key: key);
final String title;

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

下面的状态类_MyStatefulWidgetState实现了widget的build()方法。例如,当状态更改时,当用户切换按钮时,使用新的切换值调用setState。这会导致框架在UI中重新生成这个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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
bool showtext=true;
bool toggleState=true;
Timer t2;

void toggleBlinkState(){
setState((){
toggleState=!toggleState;
});
var twenty = const Duration(milliseconds: 1000);
if(toggleState==false) {
t2 = Timer.periodic(twenty, (Timer t) {
toggleShowText();
});
} else {
t2.cancel();
}
}

void toggleShowText(){
setState((){
showtext=!showtext;
});
}

@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
children: <Widget>[
(showtext
?(Text('This execution will be done before you can blink.'))
:(Container())
),
Padding(
padding: EdgeInsets.only(top: 70.0),
child: RaisedButton(
onPressed: toggleBlinkState,
child: (toggleState
?( Text('Blink'))
:(Text('Stop Blinking'))
)
)
)
],
),
),
);
}
}

什么是StatefulWidget和StatelessWidget最佳实践?

在设计widget时,需要考虑以下几点。

1.确定一个widget应该是一个StatefulWidget还是一个StatelessWidget

在Flutter中,widget是有状态的还是无状态的 - 取决于它们是否依赖于状态变化。

  • 如果widget发生更改 - 用户与其进行交互或数据源中断UI,则它是有状态的。
  • 如果一个widget是只读的或不可变的,那么它就是无状态。

2.确定哪个对象管理widget的状态(对于StatefulWidget)

在Flutter中,管理状态有三种主要方式:

  • widget管理自己的状态
  • 父widget管理widget的状态
  • 混合搭配的方法

在决定使用哪种方法时,请考虑以下原则:

  • 如果所涉及的状态是用户数据,例如复选框的选中或未选中模式,或者滑块的位置,那么状态最好由父widget管理。
  • 如果所涉及的状态是图形的,例如动画,那么widget本身就可以最好地管理状态。
  • 如果有疑问,让父widget管理子widget的状态。

3.子类StatefulWidget和State

MyStatefulWidget类管理自己的状态-它继承自StatefulWidget,它覆盖createState()方法来创建State对象,框架调用createState()来构建widget。在本例中,createState()创建了一个_MyStatefulWidgetState的实例,该实例在下一个最佳实践中实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class MyStatefulWidget extends StatefulWidget {
MyStatefulWidget({Key key, this.title}) : super(key: key);
final String title;

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

class _MyStatefulWidgetState extends State<MyStatefulWidget> {

@override
Widget build(BuildContext context) {
...
}
}

4.将StatefulWidget添加到widget树中

StatefulWidget在app的构建方法中将自定义添加到widget树。
在应用程序build方法中的widget树中添加自定义StatefulWidget

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class MyStatelessWidget extends StatelessWidget {
// This widget is the root of your application.

@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyStatefulWidget(title: 'State Change Demo'),
);
}
}

Props

在React Native中,大多数组件在使用不同的参数或属性创建时都可以自定义props。这些参数可以在子组件中使用this.props

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
// React Native
class CustomCard extends React.Component {
render() {
return (
<View>
<Text> Card {this.props.index} </Text>
<Button
title="Press"
onPress={() => this.props.onPress(this.props.index)}
/>
</View>
);
}
}
class App extends React.Component {

onPress = index => {
console.log("Card ", index);
};

render() {
return (
<View>
<FlatList
data={[ ... ]}
renderItem={({ item }) => (
<CustomCard onPress={this.onPress} index={item.key} />
)}
/>
</View>
);
}
}

在Flutter中,您可以final为参数化构造函数中的属性指定标记的局部变量或函数。

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
// Flutter
class CustomCard extends StatelessWidget {

CustomCard({@required this.index, @required this.onPress});
final index;
final Function onPress;

@override
Widget build(BuildContext context) {
return Card(
child: Column(
children: <Widget>[
Text('Card $index'),
FlatButton(
child: const Text('Press'),
onPressed: this.onPress,
),
],
));
}
}
...
//Usage
CustomCard(
index: index,
onPress: () {
print('Card $index');
},
)

本地存储

如果您不需要存储大量数据并且不需要结构,则可以使用shared_preferences它来读取和编写基本数据类型的持久键值对:布尔值,浮点数,整数,长整数和字符串。

如何在应用内持久化存储一个键值对?

在React Native中,您可以使用组件的setItemgetItem函数 AsyncStorage来存储和检索应用程序的持久性和全局性数据。

1
2
3
4
5
6
7
// React Native
await AsyncStorage.setItem( "counterkey", json.stringify(++this.state.counter));
AsyncStorage.getItem("counterkey").then(value => {
if (value != null) {
this.setState({ counter: value });
}
});

在Flutter中,使用shared_preferences插件来存储和检索应用程序持久化的全局键值数据。shared_preferences插件是iOS的NSUserDefaults和Android的SharedPreferences的封装,为简单数据提供持久存储。要使用该插件,请shared_preferencespubspec.yaml文件中添加依赖项,然后在Dart文件中导入该包。

1
2
3
4
dependencies:
flutter:
sdk: flutter
shared_preferences: ^0.3.0
1
2
// Dart
import 'package:shared_preferences/shared_preferences.dart';

要实现持久数据,请使用SharedPreferences类提供的setter方法 。setter方法可用于各种原始类型,例如setIntsetBool,和setString。要读取数据,请使用SharedPreferences类提供的相应getter方法。对于每一个setter有相应的getter方法,例如,getIntgetBool,和getString

1
2
3
4
5
6
SharedPreferences prefs = await SharedPreferences.getInstance();
_counter = prefs.getInt('counter');
prefs.setInt('counter', ++_counter);
setState(() {
_counter = _counter;
});

路由

大多数应用程序都包含几个屏幕,用于显示不同类型的信息。例如,您可能有一个产品屏幕,该屏幕显示用户可以点击产品图片以在新屏幕上获取有关该产品的更多信息的图片。

在Android中,新的屏幕是新的活动。在iOS中,新屏幕是新的ViewController。在flutter中,屏幕只是widgets!要导航到flutter中的新屏幕,请使用Navigatorwidget。

如何在屏幕之间使用navigate

在React Native中,有三个主要导航器:StackNavigator,TabNavigator和DrawerNavigator。每种都提供了配置和定义屏幕的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// React Native
const MyApp = TabNavigator(
{ Home: { screen: HomeScreen }, Notifications: { screen: tabNavScreen } },
{ tabBarOptions: { activeTintColor: "#e91e63" } }
);
const SimpleApp = StackNavigator({
Home: { screen: MyApp },
stackScreen: { screen: StackScreen }
});
export default (MyApp1 = DrawerNavigator({
Home: {
screen: SimpleApp
},
Screen2: {
screen: drawerScreen
}
}));

在Flutter中,有两个用于在屏幕之间导航的主要widget:

  • Route是应用程序屏幕或页面的抽象。
  • Navigator是一个管理路由的widget

Navigator被定义为一个widget,它管理一组具有stack规则的子widget。Navigator管理路由对象的stack,并提供管理stack的方法,如Navigator.pushNavigator.pop。路径列表可以在MaterialAppwidget中指定,或者可以在动态中构建,例如在hero动画中。以下示例指定MaterialAppwidget中的命名路由。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Flutter
class NavigationApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
...
routes: <String, WidgetBuilder>{
'/a': (BuildContext context) => usualNavscreen(),
'/b': (BuildContext context) => drawerNavscreen(),
}
...
);
}
}

要导航到命名路由,widgetof()方法Navigator用于指定BuildContext。路由的名称被传递给该pushNamed函数以导航到指定的路由。

1
Navigator.of(context).pushNamed('/a');

您还可以使用Navigatorpush方法,该方法将给定的数据添加一个RouteNavigator的历史记录中,并跳转到它。在下面的示例中,MaterialPageRoutingwidget是一种模态路由,它会对平台自适应的转换替换整个屏幕,并需要一个WidgetBuilder必需参数。

1
Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) => UsualNavscreen()));

如何使用选项卡导航(Tab navigation)和抽屉导航(Drawer navigation)?

在Material Design应用程序中,Flutter导航有两个主要选项:选项卡和抽屉。当没有足够的空间支持选项卡时,抽屉提供了一个很好的选择。

标签导航(Tab navigation)

在react-native,createBottomTabNavigatorTabNavigation用于显示选项卡和选项卡导航。

1
2
3
4
5
6
7
// React Native
import { createBottomTabNavigator } from 'react-navigation';

const MyApp = TabNavigator(
{ Home: { screen: HomeScreen }, Notifications: { screen: tabNavScreen } },
{ tabBarOptions: { activeTintColor: "#e91e63" } }
);

Flutter为抽屉和标签导航提供了几个专门的widget:

1
2
3
4
5
6
7
8
9
10
// Flutter
TabController controller=TabController(length: 2, vsync: this);

TabBar(
tabs: <Tab>[
Tab(icon: Icon(Icons.person),),
Tab(icon: Icon(Icons.email),),
],
controller: controller,
),

TabBar需要一个TabController来控制TabBarView之间的选项卡选择。TabController构造函数的 Length参数是tabs的总数。每当框架触发状态更改时,TickerProvider都需要触发通知。TickerProvidervsync。每当创建一个TabController,都将此参数传递给TabController构造函数。

TickerProvider 是一个接口,可以通过类来实现Ticker对象。当帧触发时,必须通知的任何对象都可以使用Tickers,但是它们通常是通过AnimationController间接使用的。AnimationControllers需要一个TickerProvider获得他们的Ticker, 如果你从一个状态创建一个AnimationController,那么你可以使用TickerProviderStateMixin类或者SingleTickerProviderStateMixin类来获得合适的TickerProvider

Scaffoldwidget包装了一个新的TabBarwidget,并创建了两个选项卡。TabBarViewwidget作为Scaffoldwidgetbody属性的参数传递。与TabBarwidget的选项卡对应的所有屏幕都是TabBarViewwidget和同一TabController的子窗口。

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
// Flutter
class _NavigationHomePageState extends State<NavigationHomePage> with SingleTickerProviderStateMixin {
TabController controller=TabController(length: 2, vsync: this);
@override
Widget build(BuildContext context) {
return Scaffold(
bottomNavigationBar: Material (
child: TabBar(
tabs: <Tab> [
Tab(icon: Icon(Icons.person),)
Tab(icon: Icon(Icons.email),),
],
controller: controller,
),
color: Colors.blue,
),
body: TabBarView(
children: <Widget> [
home.homeScreen(),
tabScreen.tabScreen()
],
controller: controller,
)
);
}
}

Drawer navigation

在React Native中,导入所需的react-navigation包,然后使用createDrawerNavigatorDrawerNavigation

1
2
3
4
5
6
7
8
9
// React Native
export default (MyApp1 = DrawerNavigator({
Home: {
screen: SimpleApp
},
Screen2: {
screen: drawerScreen
}
}));

在Flutter中,我们可以使用Drawerwidget与Scaffold组合来创建具有Material Design抽屉的布局。要将Drawer添加到应用中,请将其包装在一个Scaffoldwidget中。该Scaffoldwidget为遵循Material Design指南的应用程序提供了一致的可视化结构。它还支持特殊Material Design的组件,如DrawersAppBarsSnackBars

Drawerwidget是一个Material Design面板,它水平地从脚手架边缘滑入,以显示应用程序中的导航链接。可以向Drawerwidget提供ButtonTextwidget或作为子widget显示的项列表。在下面的示例中,ListTilewidget在点击时提供导航。

1
2
3
4
5
6
7
8
9
10
11
// Flutter
Drawer(
child:ListTile(
leading: Icon(Icons.change_history),
title: Text('Screen2'),
onTap: () {
Navigator.of(context).pushNamed("/b");
},
),
elevation: 20.0,
),

Scaffoldwidget还包括一个AppBarwidget,它自动显示适当的IconButton,以便在Scaffold中的Drawer可用时显示Drawer。脚手架自动处理边缘滑动手势,以显示Drawer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Flutter
@override
Widget build(BuildContext context) {
return Scaffold(
drawer: Drawer(
child: ListTile(
leading: Icon(Icons.change_history),
title: Text('Screen2'),
onTap: () {
Navigator.of(context).pushNamed("/b");
},
),
elevation: 20.0,
),
appBar: AppBar(
title: Text("Home"),
),
body: Container(),
);
}

手势检测和触摸事件处理

为了监听和响应手势,Flutter支持点击,拖动和缩放。Flutter中的手势系统有两个独立的层。第一层包括原始指针事件,它描述屏幕上指针的位置和移动(例如触摸,鼠标和手写笔移动)。第二层包括手势,其描述由一个或多个指针移动组成的语义动作。

如何添加clickpress监听器到widget

在React Native中,使用PanResponder或组件将监听添加到Touchable组件中。

1
2
3
4
5
6
7
8
9
10
11
// React Native
<TouchableOpacity
onPress={() => {
console.log("Press");
}}
onLongPress={() => {
console.log("Long Press");
}}
>
<Text>Tap or Long Press</Text>
</TouchableOpacity>

对于更复杂的手势并将多个触摸组合成单个手势,PanResponder使用。

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
// React Native
class App extends Component {

componentWillMount() {
this._panResponder = PanResponder.create({
onMoveShouldSetPanResponder: (event, gestureState) =>
!!getDirection(gestureState),
onPanResponderMove: (event, gestureState) => true,
onPanResponderRelease: (event, gestureState) => {
const drag = getDirection(gestureState);
},
onPanResponderTerminationRequest: (event, gestureState) => true
});
}

render() {
return (
<View style={styles.container} {...this._panResponder.panHandlers}>
<View style={styles.center}>
<Text>Swipe Horizontally or Vertically</Text>
</View>
</View>
);
}
}

在Flutter中,要向widget添加click(或press)监听,请使用具有onPress: field字段的按钮或可触摸widget。或者,将手势检测添加到任何widget中GestureDetector

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
// Flutter
GestureDetector(
child: Scaffold(
appBar: AppBar(
title: Text("Gestures"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Tap, Long Press, Swipe Horizontally or Vertically '),
],
)
),
),
onTap: () {
print('Tapped');
},
onLongPress: () {
print('Long Pressed');
},
onVerticalDragEnd: (DragEndDetails value) {
print('Swiped Vertically');
},
onHorizontalDragEnd: (DragEndDetails value) {
print('Swiped Horizontally');
},
);

有关更多信息,包括Flutter GestureDetector回调列表,请参阅GestureDetector类。

发出HTTP网络请求

在大多数应用程序中,从互联网上获取数据是很常见的。在Fltter中,httppackage提供了从互联网上获取数据的最简单方法。

如何从API调用中获取数据?

React Native为网络提供了Fetch API - 您可以进行获取请求,然后接收响应以获取数据。

1
2
3
4
5
6
7
8
9
10
11
// React Native
_getIPAddress = () => {
fetch("https://httpbin.org/ip")
.then(response => response.json())
.then(responseJson => {
this.setState({ _ipAddress: responseJson.origin });
})
.catch(error => {
console.error(error);
});
};

Flutter使用httppackage。要安装http,请将其添加到pubspec.yamldependencies部分。

1
2
3
4
dependencies:
flutter:
sdk: flutter
http: <latest_version>

Flutter使用dart:io核心HTTP支持客户端。要创建HTTP客户端,请导入dart:io

1
import 'dart:io';

客户端支持以下HTTP操作:GET,POST,PUT和DELETE。

1
2
3
4
5
6
7
8
9
10
11
12
// Flutter
final url = Uri.https('httpbin.org', 'ip');
final httpClient = HttpClient();
_getIPAddress() async {
var request = await httpClient.getUrl(url);
var response = await request.close();
var responseBody = await response.transform(utf8.decoder).join();
String ip = json.decode(responseBody)['origin'];
setState(() {
_ipAddress = ip;
});
}

表单输入

Text fields允许用户在应用程序中键入文本,以便用于生成表单、消息应用程序、搜索体验等。Fltter提供了两个核心文本字段widget:TextFieldTextFormField

如何使用文本字段widget?

在React Native中,要输入文本,可以使用TextInput组件显示文本输入框,然后使用回调将值存储在变量中。

1
2
3
4
5
6
// React Native
<TextInput
placeholder="Enter your Password"
onChangeText={password => this.setState({ password })}
/>
<Button title="Submit" onPress={this.validate} />

在Flutter中,使用TextEditingController该类来管理TextFieldwidget。每当文本字段被修改时,控制器都会通知其监听者。

监听器读取文本和选择属性,以了解用户在字段中键入的内容。可以通过controllerText属性访问TextField中的文本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Flutter
final TextEditingController _controller = TextEditingController();
...
TextField(
controller: _controller,
decoration: InputDecoration(
hintText: 'Type something', labelText: "Text Field "
),
),
RaisedButton(
child: Text('Submit'),
onPressed: () {
showDialog(
context: context,
child: AlertDialog(
title: Text('Alert'),
content: Text('You typed ${_controller.text}'),
),
);
},
),
)

在此示例中,当用户单击Submit按钮时,dialog将显示在TextField中输入的文本。这是使用alertDialog显示警报消息的widget实现的 ,而TextEditingControllertext属性访问了TextField输入的文本。

如何使用表单widget?

在Fltter中,使用Formwidget,其中TextFormFieldwidget和Submit按钮作为子元素传递。TextFormFieldwidget有一个名为onSavedonSaved的参数,它接受回调并在保存表单时执行。FormState对象用于保存、重置或验证每个FormField,这些FormField是此Form的后代。要获得FormState,可以使用Form.of和以表单为祖先的上下文,或者将GlobalKey传递给表单构造函数并调用GlobalKey.currentState

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
final formKey = GlobalKey<FormState>();

...

Form(
key:formKey,
child: Column(
children: <Widget>[
TextFormField(
validator: (value) => !value.contains('@') ? 'Not a valid email.' : null,
onSaved: (val) => _email = val,
decoration: const InputDecoration(
hintText: 'Enter your email',
labelText: 'Email',
),
),
RaisedButton(
onPressed: _submit,
child: Text('Login'),
),
],
),
)

以下示例显示如何Form.save()formKey(哪些是 GlobalKey)用于在提交时保存表单。

1
2
3
4
5
6
7
8
9
10
11
12
13
void _submit() {
final form = formKey.currentState;
if (form.validate()) {
form.save();
showDialog(
context: context,
child: AlertDialog(
title: Text('Alert'),
content: Text('Email: $_email, password: $_password'),
)
);
}
}

判断平台特定的代码

在构建跨平台应用程序时,您希望跨平台重用尽可能多的代码。但是,可能会出现这样的情况:根据操作系统的不同,代码是不同意义的。这需要通过声明特定平台来单独实现。

在React Native中,将使用以下实现:

1
2
3
4
5
6
7
8
// React Native
if (Platform.OS === "ios") {
return "iOS";
} else if (Platform.OS === "android") {
return "android";
} else {
return "not recognised";
}

在Flutter中,使用以下实现:

1
2
3
4
5
6
7
8
9
10
// Flutter
if (Theme.of(context).platform == TargetPlatform.iOS) {
return "iOS";
} else if (Theme.of(context).platform == TargetPlatform.android) {
return "android";
} else if (Theme.of(context).platform == TargetPlatform.fuchsia) {
return "fuchsia";
} else {
return "not recognised ";
}

调试

在运行您的应用程序之前,请使用flutter analyze。Flutter分析器(它是dartanalyzer工具的封装)检查你的代码并帮助识别可能出现的问题。如果您正在使用支持Flutter的IDE,则会自动执行此操作。

如何访问应用内开发人员菜单?

在React Native中,可以通过摇动设备来访问开发人员菜单:⌘D用于iOS模拟器,⌘M用于Android模拟器。

在Flutter中,如果您使用的是IDE,则可以使用IDE工具。如果您使用启动应用程序,flutter run也可以通过h在终端窗口中键入来访问菜单,或键入以下快捷方式:

Action 终端快捷键 debug函数和属性
Widget hierarchy of the app w debugDumpApp()
Rendering tree of the app t debugDumpRenderTree()
Layers L debugDumpLayerTree()
Accessibility S (traversal order) or U (inverse hit test order) debugDumpSemantics()
To toggle the widget inspector i WidgetsApp. showWidgetInspectorOverride
To toggle the display of construction lines p debugPaintSizeEnabled
To simulate different operating systems o defaultTargetPlatform
To display the performance overlay P WidgetsApp. showPerformanceOverlay
To save a screenshot to flutter. png s
To quit q

如何执行热重载?

Flutter的热重载功能可帮助您快速轻松地进行实践,构建用户界面,添加功能和修复错误。您不必每次重新编译应用程序,程序会自动进行热重载,以更新应用反映您的更改,并保留应用程序的当前状态。

在React Native中,快捷键是iOS模拟器的⌘R,并在Android模拟器上双击R

在Flutter中,如果您使用的是IntelliJ IDE或Android Studio,则可以选择全部保存(⌘ + s/ ctrl + s),也可以单击工具栏上的热重新加载按钮。如果使用命令行运行应用程序flutter run,请在”终端”窗口中键入r。您也可以通过R在终端窗口中输入完成重启。

我可以用什么工具在Flutter中调试我的应用程序?

当您需要调试Flutter应用程序时,可以使用多个选项和工具。

除了Flutter分析仪之外,该工具还可用于分析和调试您的Dart应用程序。如果您flutter run在终端中使用了您的应用程序 ,则可以打开打印到终端窗口的天文台URL的网页,例如: http://127.0.0.1:8100/。

除了flutter analyze之外,Dart Observatory也是一个用来分析和调试Dart应用程序的工具。如果您在终端中使用flutter run启动应用程序,则可以在输出到终端窗口的看到对应的 Observatory 的URL,例如:http://127.0.0.1:8100/

Observatory支持分析,检查堆,观察已执行的代码行,调试内存泄漏和内存碎片。欲了解更多信息,请参阅Observatory文件。当您下载并安装Dart SDK时,Observatory将免费提供。

如果您使用的是IDE,则可以使用IDE调试器调试应用程序。

如果您使用的是IntelliJ和Android Studio,则可以使用Flutter Inspector。Flutter Inspector使您更容易理解应用程序的呈现方式。它允许你:

  • 将应用程序的UI结构视为widget树
  • 在您的设备或模拟器上选择一个点,然后找到渲染这些像素的相应widget
  • 查看各个widget的属性
  • 快速识别布局问题并确定其原因

可以从”View > Tool Windows > Flutter Inspector”打开”Flutter Inspector”视图。内容仅在应用程序运行时显示。

若要检查特定widget,请在工具栏中选择”Toggle inspect mode”操作,然后在真机或模拟器上单击所需的widget。widget在应用程序的UI中被突出显示。您将在IntelliJ中的widget层次结构中看到widget,以及该widget的各个属性。

有关更多信息,请参阅 调试flutter应用程序

动画

设计良好的动画使UI感觉更直观,有助于完善的应用程序的外观和感受,并改善用户体验。Flutter的动画支持可以轻松实现简单和复杂的动画。Flutter SDK包含许多包含标准运动效果的Material Design Widget,您可以轻松地自定义这些效果来个性化应用程序。

在React Native中,Animated API用于创建动画。

在Flutter中,使用Animation类和AnimationController类。

Animation是一个抽象类,了解其当前值及其状态(completed 或者 dismissed)。AnimationController类允许您正向或反向播放动画,或者停止动画,并将动画设置为特定的值以自定义动画。

如何添加简单的淡入动画?

在下面的React Native示例中,FadeInView使用Animated API创建了一个动画组件。定义初始不透明度状态,结束状态和动画开始的持续时间。动画组件添加到Animated组件内部,透明度状态fadeAnim映射到我们要设置动画的Text组件的透明度,然后调用start()启动动画。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// React Native
class FadeInView extends React.Component {
state = {
fadeAnim: new Animated.Value(0) // Initial value for opacity: 0
};
componentDidMount() {
Animated.timing(this.state.fadeAnim, {
toValue: 1,
duration: 10000
}).start();
}
render() {
return (
<Animated.View style={{...this.props.style, opacity: this.state.fadeAnim }} >
{this.props.children}
</Animated.View>
);
}
}
...
<FadeInView>
<Text> Fading in </Text>
</FadeInView>
...

要在Flutter中创建相同的动画,请创建名为controllerAnimationController对象,并指定持续时间。默认情况下,AnimationController在给定的持续时间内线性生成范围从0.0到1.0的值。每当运行应用程序的设备准备显示新的帧时,动画控制器就会生成一个新值。通常情况下,这个速率大约是每秒60帧。

定义一个时,你必须传入一个vsync对象。存在vsync防止屏幕外动画消耗不必要的资源。您可以vsync通过添加 TickerProviderStateMixin到类定义来使用有状态的对象。一个AnimationController 需要TickerProvider,其使用配置vsync在构造参数。

定义AnimationController时,必须传入一个vsync对象。vsync的存在可以防止屏幕外动画消耗不必要的资源。通过将TickerProviderStateMixin添加到类定义中,可以将有状态对象用作vsync。一个动画控制器需要一个TickerProvider,它是使用构造函数上的vsync参数配置的。

A Tween描述了开始和结束值之间的插值或从输入范围到输出范围的映射。要通过Tween动画使用对象,请调用Tween对象的animate方法并将其传递给Animation您要修改的 对象。

Tween描述开始值和结束值之间的插值或从输入范围到输出范围的映射。若要在动画中使用Tween对象,请调用该对象的animate方法,并将要修改的Animation对象传递给该对象。

对于这个示例,使用了一个FadeTransitionwidget,并将该opacity属性映射到该animation对象。

要开始动画,请使用controller.forward()。其他操作也可以使用controller执行,例如fling()repeat()。对于这个示例,该FlutterLogowidget在FadeTransitionwidget内部使用。

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

// Flutter
import 'package:flutter/material.dart';

void main() {
runApp(Center(child: LogoFade()));
}

class LogoFade extends StatefulWidget {
_LogoFadeState createState() => _LogoFadeState();
}

class _LogoFadeState extends State<LogoFade> with TickerProviderStateMixin {
Animation animation;
AnimationController controller;

initState() {
super.initState();
controller = AnimationController(
duration: const Duration(milliseconds: 3000), vsync: this);
final CurvedAnimation curve =
CurvedAnimation(parent: controller, curve: Curves.easeIn);
animation = Tween(begin: 0.0, end: 1.0).animate(curve);
controller.forward();
}

Widget build(BuildContext context) {
return FadeTransition(
opacity: animation,
child: Container(
height: 300.0,
width: 300.0,
child: FlutterLogo(),
),
);
}

dispose() {
controller.dispose();
super.dispose();
}
}

如何给卡片添加swipe动画?

在React Native中,swipe动画使用第三方库实现,例如PanResponder

在Flutter中,要添加一个swipe动画,请使用Dismissiblewidget并嵌套子widget。

1
2
3
4
5
6
7
8
9
child: Dismissible(
key: key,
onDismissed: (DismissDirection dir) {
cards.removeLast();
},
child: Container(
...
),
),

React Native 和 Flutter Widget 功能相等的组件

React Native Component Flutter Widget
Button RaisedButton
Button FlatButton
ScrollView ListView
FlatList ListView.builder()
Image Image
Modal ModalRoute
Activity Indicator CircularProgressIndicator
Activity Indicator LinearProgressIndicator
Refresh Control RefreshIndicator
View Container
View Column
View Row
View Center
View Padding
Touchable Opacity GestureDetector
Text Input TextField
Text Text
Switch Switch
Slider Slider