写给React Native程序员的Flutter入门指南

Android社区 收藏文章

本文面向希望基于现有的 React Native 的知识结构使用 Flutter 开发移动端应用的开发者。如果你已经对 RN 的框架有所了解,那么你可以通过这个文档入门 Flutter 开发。

本文可以当做查询手册使用,里面涉及到的问题基本上可以满足需求。

本文结构如下:

1.针对 JavaScript 开发者的 Dart 介绍(上)

2.基本知识(上)

3.项目结构和资源(上)

4.Flutter widgets(上)

5.视图(上)

6.布局(上)

7.风格化(上)

8.状态管理(下)

9.道具(下)

10.本地存储(下)

11.路径(下)

12.手势检测和触摸事件处理(下)

13.发起 HTTP 网络请求(下)

14.输入表单(下)

15.平台相关代码(下)

16.调试(下)

17.动画(下)

18.React Native 和 Flutter 小部件对等的组件(下)

------一、针对 JavaScript 开发者的 Dart 介绍

和 React Native 一样,Flutter 使用 reactive 风格的视图。然而,RN 需要被转译为本地对应的 widget,而 Flutter 是直接编译成本地原生代码。Flutter 可以控制屏幕上的每一个像素,如此可以避免由于使用 JavaScript Bridge 导致的性能问题。

Dart 学习起来非常简单而且有如下特性:

  • 它针对 web 服务和移动应用开发提供了一种开源的,可扩展的编程语言。

  • 它提供了一种面向对象的单继承语言,使用 C 语言风格的语法并且可通过 AOT 编译为本地代码。

  • 可转译为 JavaScript 代码。

  • 支持接口和抽象类。

下面的几个例子解释了 JavaScript 和 Dart 的区别。

1.1 入口函数

JavaScript 并没有预定义的入口函数。

// JavaScript
function startHere() {
  // Can be used as entry point
  //这里可以当做入口函数
}

在 Dart 里,每个应用程序必须有一个最顶级的 main() 函数,该函数作为应用程序的入口函数。

// Dart
main() {
}

可以在 DartPad 查看效果。

页面链接:https://dartpad.cn/0df636e00f348bdec2bc1c8ebc7daeb1

1.2 在控制台打印输出

在 Dart 中如果需要在控制台进行输出,调用 print()。

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

1.3 变量

Dart 是类型安全的,它结合静态类型检查和运行时检查来保证变量的值总是和变量的静态类型相匹配。虽然类型是语法要求,有些类型标注也并不是必须要填的,因为 Dart 使用类型推断。

1.3.1 创建变量并赋值

在 JavaScript 中,变量是无法指定类型的。

在 Dart 中,变量要么被显式定义类型,要么系统会自动判断变量的类型。

// JavaScript
var name = 'JavaScript';
// Dart
String name = 'dart'; // Explicitly typed as a string.

String name = 'dart'; // 显式声明为字符串。

var otherName = 'Dart'; // Inferred string.

var otherName = 'Dart'; // 推断为字符串。

// Both are acceptable in Dart.

// 两种定义方式在 Dart 中都可以。

如果想了解更多相关信息,请转向该页面 Dart’s Type System。

页面链接:https://dart.dev/guides/language/sound-dart

1.3.2 默认值

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

在 Dart 中,未初始化的变量会有一个初始值 null。因为数字在 Dart 是对象,甚至未初始化的数字类型的变量也会是 null。

// JavaScript
var name; // == undefined
// Dart
var name; // == null
int x; // == null

如果想了解更多详细内容,请查看这个文档 variables。

文档链接:https://dart.dev/guides/language/language-tour#variables

1.4 检查 null 或者零值

在 JavaScript 中,1 或者任何非空对象都相当于 true。

// 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。

// 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');
}

1.5 函数

Dart 和 JavaScript 中的函数很相似。最大的区别是声明格式。

// JavaScript
function fn() {
  return true;
}
// Dart
fn() {
  return true;
}
// can also be written as
bool fn() {
  return true;
}

如果想了解更多相关信息,请转向该页面 functions。

页面链接:https://dart.dev/guides/language/language-tour#functions

1.6 异步编程

1.6.1 Futures

和 JavaScript 类似,Dart 支持单线程。在 JavaScript 中, Promise 对象代表异步操作的完成或者失败。

Dart 使用 Future 对象来实现该机制。

// JavaScript
class Example {
  _getIPAddress() {
    const url = 'https://httpbin.org/ip';
    return fetch(url)
      .then(response => response.json())
      .then(responseJson => {
        const ip = responseJson.origin;
        return ip;
      });
  }
}

function main() {
  const example = new Example();
  example
    ._getIPAddress()
    .then(ip => console.log(ip))
    .catch(error => console.error(error));
}

main();
// Dart
import 'dart:convert';
import 'package:http/http.dart' as http;

class Example {
  Future<String> _getIPAddress() {
    final url = 'https://httpbin.org/ip';
    return http.get(url).then((response) {
      String ip = jsonDecode(response.body)['origin'];
      return ip;
    });
  }
}

main() {
  final example = new Example();
  example
      ._getIPAddress()
      .then((ip) => print(ip))
      .catchError((error) => print(error));
}

如果想了解更多相关信息,请参考 Futures 的相关文档。

 Futures 文档链接:https://dart.dev/tutorials/language/futures

1.6.2 async 和 await

async 函数声明定义了一个异步函数。

在 JavaScript 中, async 函数返回一个 Promise。await 操作符用于等待 Promise。

// JavaScript
class Example {
  async _getIPAddress() {
    const url = 'https://httpbin.org/ip';
    const response = await fetch(url);
    const responseJson = await response.json();
    const ip = responseJson.origin;
    return ip;
  }
}

async function main() {
  const example = new Example();
  try {
    const ip = await example._getIPAddress();
    console.log(ip);
  } catch (error) {
    console.error(error);
  }
}

main();

在 Dart 中,async 函数返回一个 Future,而函数体会在未来执行。await 操作符用于等待 Future。

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

class Example {
  Future<String> _getIPAddress() async {
    final url = 'https://httpbin.org/ip';
    final response = await http.get(url);
    String ip = jsonDecode(response.body)['origin'];
    return ip;
  }
}

main() async {
  final example = new Example();
  try {
    final ip = await example._getIPAddress();
    print(ip);
  } catch (error) {
    print(error);
  }
}

如果想了解更多相关信息,请参考 async and await 的相关文档。

文档链接:https://dart.dev/guides/language/language-tour#asynchrony-support

二、基本知识

2.1 如何创建一个 Flutter 应用?

如果要使用 React Native 创建应用,你需要在命令行里运行 create-react-native-app。

$ create-react-native-app <projectname>

要在 Flutter 中创建应用,完成下面其中一项即可:

  • 在命令行中运行命令 flutter create。不过要提前确认 Flutter SDK 已经在系统环境变量 PATH 中定义。

  • 使用带有 Flutter 和 Dart 插件的 IDE。
$ flutter create <projectname>

如果想要了解更多内容,详见 Getting Started,在该页面会手把手教你创建一个点击按钮进行计数的应用。创建一个 Flutter 项目就可以构建 Android 和 iOS 设备上运行应用所需的所有文件。

2.2 我如何运行应用呢?

在 React Native, 你可以在项目文件夹中运行 npm run 或者 yarn run。

你可以通过如下几个途径运行 Flutter 应用程序:

  • 在项目根目录运行 flutter run。

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

你的应用程序会在已连接的设备、iOS 模拟器或者 Android 模拟器上运行。

如果想了解更多相关信息,可以参考 Flutter 的 Getting Started 相关文档。

2.3 如何导入 widget

在 React Native 中,你需要导入每一个所需的组件。

//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 库并导入。

import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter/my_widgets.dart';

无论你导入哪个库,Dart 仅仅引用你应用中用到的 widget。

如果想了解更多相关信息,可以参考 Flutter Widgets Catalog。

页面链接:https://flutter-io.cn/docs/development/ui/widgets

2.4 在 Flutter 里有没有类似 React Native 中 “Hello world!” 应用程序?

在 React Native,HelloWorldApp 继承自 React.Component 并且通过返回 view 对象实现了 render 方法。

// 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 中,你可以使用核心 widget 库中的 Center 和 Text widget 创建对应的 “Hello world!” 应用程序。Center widget 是 widget 树中的根,而且只有 Text 一个子 widget。

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

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

下面的图片展示了 Android 和 iOS 中的基本 Flutter “Hello world!” 应用程序的界面。

    Android  

 iOS

现在大家已经明白了最基本的 Flutter 应用,接下来会告诉大家如何利用 Flutter 丰富的 widget 库来创建主流的华丽的应用程序。

2.5 我如何使用 widget 并且把它们封装起来组成一个 widget 树?

在 Flutter 中,几乎任何元素都是 widget。

widget 是构建应用软件用户界面的基本元素。你可以将 widget 按照一定的层次组合,成为 widget 树。每个 widget 内嵌在父 widget 中,并且继承了父 widget 的属性。甚至应用程序本身就是一个 widget。并没有一个独立的应用程序对象。反而 root widget 充当了这个角色。

一个 widget 可以定义:

  • 一个结构化的元素 - 类似按钮或者菜单

  • 一个风格化的元素 - 类似字体或者颜色方案

  • 布局元素 - 类似填充区或者对齐元素

下面的示例展示了使用 Material 库里 widget 实现的 “Hello world!” 应用程序。在这个示例中,该 widget 树是包含在 MaterialApproot widget 里的。

// 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'),
        ),
      ),
    );
  }
}

下面的图片为大家展示了通过 Material Design widget 所实现的 “Hello world!” 应用。你可以免费获得比 “Hello world!” 应用更多的功能。

 Android  

 iOS

当编写应用代码的时候,你将用到下述两种 widget :

无状态 widget (StatelessWidget) 就像它的名字一样,是一个没有状态的 widget。无状态 widget 一旦创建,就不会改变。而有状态 widget (StatefulWidget) 会基于接收到的数据或者用户输入的数据动态改变状态。

无状态 widget 和有状态 widget 之间的主要区别是有状态 widget 包含一个 State 对象,会缓存状态数据,并且 widget 树的重构也会携带该数据。因此状态不会丢失。

在简单的或者基本的应用程序中,封装 widget 非常简单,但是随着代码量的增加并且应用程序的功能变得更加复杂,你应该将层级复杂的 widget 封装到函数中或者稍小一些的类。创建独立的函数和 widget 可以让你更好地复用应用中组件。

2.6 如何创建可复用的组件?

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

// 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 函数所示的那样。

// 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 的曲括号 { } 来表示可选参数 optional parameters。

如果将这些参数设定为必填参数,要么从构造函数中删掉曲括号,或者在构造函数中加上 @required。

下面的截图展示了可复用的 CustomCard 类的示例。

 Android

iOS

三、项目结构和资源

3.1 该从哪开始写代码呢?

从 main.dart 文件开始。当你创建 Flutter 应用的时候会自动生成这个文件。

// Dart

void main(){

 print('Hello, this is the main function.');

}

在 Flutter 中,入口文件是 ’projectname’/lib/main.dart 而程序执行是从 main 函数开始的。

3.2 Flutter 应用程序中的文件是如何组织的?

当你创建一个新的 Flutter 工程的时候,它会创建如下所示的文件夹结构。你可以自定义这个结构,不过这是整个开发的起点。

└ projectname

  ┬

  ├ android      - 包含 Android 相关文件。

  ├ build        - 存储 iOS 和 Android 构建文件。

  ├ ios          - 包含 iOS 相关文件。

  ├ lib          - 包含外部可访问 Dart 源文件。

    ┬

    └ src        - 包含附加源文件。

    └ main.dart  - Flutter 程序入口和新应用程序的起点。

                   当你创建 Flutter 工程的时候会自动生成这些文件。

                   你从这里开始写 Dart 代码

  ├ test         - 包含自动测试文件。

  └ pubspec.yaml - 包含 Flutter 应用程序的元数据。

                   这个文件相当于 React Native 里的 package.json 文件。

3.3 我该把资源文件放到哪并且如何调用呢?

一个 Flutter 资源就是打包到你应用程序里的一个文件并且在程序运行的时候可以访问。Flutter 应用程序可以包含下述几种资源类型:

  • 静态数据 比如 JSON 文件

  • 配置文件

  • 图标和图片 (JPEG, PNG, GIF, Animated GIF, WebP, Animated WebP, BMP, and WBMP)

Flutter 使用 pubspec.yaml 文件来确定应用程序中的资源。该文件在工程的根目录。

flutter:
  assets:
    - assets/my_icon.png
    - assets/background.png

assets 确定了需要包含在应用程序中的文件。每个资源都会在 pubspec.yaml 中定义所存储的相对路径。资源定义的顺序没有特殊要求。实际的文件夹(在这里指 assets )也没影响。但是, 由于资源可以放置于程序的任何目录,所以放在 assets 文件夹是比较好的。

在构建期间,Flutter 会将资源放到一个称为 asset bundle 的归档文件中,应用程序可以在运行时访问该文件。当一个资源在 pubspec.yaml 中被声明时,构建进程会查询和这个文件相关的子文件夹路径。这些文件也会被包含在 asset bundle 中。当你为应用程序选择和屏幕显示分辨率相关的图片时,Flutter 会使用 asset variants。

在 React Native,你可以在源码文件夹中通过添加文件来增加一个静态图片并且在代码中引用它。

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

在 Flutter 中,如果要增加静态图片的话就在 widget 的 build 方法中使用 AssetImage 类。

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

3.4 如何在网络中加载图片?

在 React Native,你可以在 Image 的 source 属性中设置 uri 和所需的尺寸。

在 Flutter 中,使用 Image.network 构造函数来实现通过地址加载图片的操作。

// Flutter

body: Image.network(

          'https://flutter.io/images/owl.jpg',

3.5 我如何安装依赖包和包插件?

Flutter 支持使用开发者向 Flutter 和 Dart 生态系统贡献的代码包。这样可以使大量开发者快速构建应用程序而无需重复造车轮。而平台相关的代码包就被称为包插件。

在 React Native 中,你可以在命令行中运行 yarn add {package-name} 或者 npm install --save {package-name} 来安装代码包。

在 Flutter 中,安装代码包需要按照如下的步骤:

  1. 在 pubspec.yaml 的 dependencies 区域添加包名和版本。下面的例子向大家展示了如何将 google_sign_in 的 Dart package 添加到 pubspec.yaml 中。一定要检查一下 YAML 文件中的空格。因为 空格很重要!
dependencies:

  flutter:

    sdk: flutter

  google_sign_in: ^3.0.3
  1. 在命令行中输入 flutter packages get 来安装代码包。如果使用 IDE,它自己会运行 flutter packages get, 或者它会提示你是不是要运行该命令。

  2. 向下面代码一样在程序中引用代码包:
import 'package:flutter/cupertino.dart';

四、Flutter widgets

在 Flutter 中,你可以基于 widget 打造你自己的 UI,通过 widget 当前的设置和状态会呈现相应的页面效果。

widget 常常通过很多小的,单一功能的 widget 组成,这样的封装往往能实现很棒的效果。比如, Container widget 包含多种 widget,分别负责布局、绘图、位置变化和尺寸变化。Container widget 包括 LimitedBox, ConstrainedBox, Align, Padding, DecoratedBox, 和 Transform widget。与其继承 Container 来实现自定义效果, 不如直接修改这些 widget 来实现效果。

Center widget 是另一个用于控制布局的示例。如果要居中一个 widget, 就把它封装到 Center widget 中,然后使用布局 widget 来进行对齐行、列和网格。这些布局 widget 并不可见。而他们的作用就是控制其它 widget 的布局。如果想搞清楚为什么一个 widget 会有这样的效果,有效的方法是研究它临近的 widget。

五、视图

5.1 与 View 等价容器的是什么?

在 React Native 中, View 是支持 Flexbox 布局、风格化、触摸事件处理和访问性控制的容器。

在 Flutter 中,你可以使用 Widgets 库中的核心布局 widget,比如 Container, Column, Row, 和 Center。

5.2 和 FlatList 或者 SectionList 相对应的是什么?

List 是一个可以滚动的纵向排列的组件列表。

在 React Native 中,FlatList 或 SectionList 用于渲染简单的或者分组的列表。

// React Native

<FlatList

  data={[ ... ]}

  renderItem={({ item }) => <Text>{item.key}</Text>}

/>

ListView 是 Flutter 最常用的滑动 widget。默认构造函数需要一个数据列表的参数。ListView 非常适合用于少量子 widget 的列表。如果列表的元素比较多,可以使用 ListView.builder,它会按需构建子项并且只创建可见的子项。

// Flutter

var data = [ ... ];

ListView.builder(

  itemCount: data.length,

  itemBuilder: (context, int index) {

    return Text(

      data[index],

    );

  },

)

<figure class="Editable-styled" data-block="true" data-editor="b4g4h" data-offset-key="75v8o-0-0" style="margin-top: 1.4em;margin-bottom: 1.4em;color: rgb(26, 26, 26);font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Microsoft YaHei", "Source Han Sans SC", "Noto Sans CJK SC", "WenQuanYi Micro Hei", sans-serif;font-size: 16px;text-align: start;white-space: pre-wrap;background-color: rgb(255, 255, 255);">

Android

iOS

5.3 如何使用 Canvas 绘图?

在 React Native 中,canvas 组件是不可见的,所以需要使用类似 react-native-canvas 这样的组件。

// 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 进行绘图。

下面的示例代码展示了如何使用 CustomPaint 进行绘图。它实现了抽象类 CustomPainter,然后将它赋值给 CustomPainter 的 painter 属性。CustomPainter 子类必须实现 paint 和 shouldRepaint 方法。

// 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(),

      ),

    );

  }

}

<figure class="Editable-styled" data-block="true" data-editor="b4g4h" data-offset-key="6ha6d-0-0" style="margin-top: 1.4em;margin-bottom: 1.4em;color: rgb(26, 26, 26);font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Microsoft YaHei", "Source Han Sans SC", "Noto Sans CJK SC", "WenQuanYi Micro Hei", sans-serif;font-size: 16px;text-align: start;white-space: pre-wrap;background-color: rgb(255, 255, 255);">

Android 

iOS

六、布局

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

在 React Native 中,大多数布局需要通过向指定的组件传递属性参数进行设置。比如,你可以使用 View 的 style 来设置 flexbox 属性。如果要整理一列的组件,你可以使用如下的属性设置:flexDirection: “column”。

// React Native

<View

  style={{

    flex: 1,

    flexDirection: 'column',

    justifyContent: 'space-between',

    alignItems: 'center'

  }}

>

在 Flutter 中,布局主要是由专门的 widget 定义的,它们同控制类 widget 和样式属性一起发挥功能。

比如,Column 和 Row widget 接受一个数组的子元素并且分别按照纵向和横向进行排列。

// 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 在核心 widget 库中提供多种不同的布局 widget。比如Padding, Align, 和 Stack。

Android

iOS

6.2 如何为 widget 分层?

在 React Native 中,组件可以通过 absolute 划分层次。

在 Flutter 中使用 Stack widget 将子 widget 进行分层。该 widget 可以将整体或者部分的子 widget 进行分层。

Stack widget 将子 widget 根据容器的边界进行布局。如果你仅仅想把子 widget 重叠摆放的话,这个 widget 非常合适。

// 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'),

    ),

  ],

)

上面的示例代码使用 Stack 将一个 Container (将 Text 显示在一个半透明的黑色背景上) 覆盖在一个 CircleAvatar 上。Stack 使用对齐属性和 Alignment 坐标微调文本。

Android 

 iOS

如果想了解更多相关信息,请参考 Stack 类文档。

文档链接:https://api.flutter.dev/flutter/widgets/Stack-class.html

七、风格化

7.1 如何设置组件的风格?

在 React Native 中,内联风格化和 stylesheets.create 可以用于设置组件的风格。

// 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 中, Text widget 可以接受 TextStyle 作为它的风格化属性。如果你想在不同的场合使用相同的文本风格,你可以创建一个 TextStyle 类,并且在多个 Text widget 中使用它。

// 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)

      ),

    ],

  ),

)

Android

iOS

<figure class="Editable-styled" data-block="true" data-editor="b4g4h" data-offset-key="lml4-0-0" style="margin-top: 1.4em;margin-bottom: 1.4em;color: rgb(26, 26, 26);font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Microsoft YaHei", "Source Han Sans SC", "Noto Sans CJK SC", "WenQuanYi Micro Hei", sans-serif;font-size: 16px;text-align: start;white-space: pre-wrap;background-color: rgb(255, 255, 255);">

7.2 我如何使用 Icons 和 Colors 呢?

React Native 并不包含默认图标,所以需要使用第三方库。

在 Flutter 中,引用 Material 库的时候就同时引入了 Material icons 和 colors。

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

当使用 Icons 类时,确保在项目的 pubspec.yaml 文件中设置 uses-material-design: true。这样保证 MaterialIcons 相关字体被包含在你的应用中。

name: my_awesome_application

flutter: uses-material-design: true

Flutter 的 Cupertino (iOS-style) 包为 iOS 设计语言提供高分辨率的 widget。要使用 CupertinoIcons 字体,在项目的 pubspec.yaml 文件中添加 cupertino_icons 的依赖即可。

name: my_awesome_application

dependencies:

  cupertino_icons: ^0.1.0

要在全局范围内自定义组件的颜色和风格,使用 ThemeData 为不同的主题指定默认颜色。在 MaterialApp 的主题属性中设置 ThemeData 对象。Colors 类提供 Material Design color palette 中所提供的颜色配置。

下面的示例代码将主色调设置为 blue 然后文本颜色设置为 red。

class SampleApp extends StatelessWidget {

  @override

 widget build(BuildContext context) {

    return MaterialApp(

      title: 'Sample App',

      theme: ThemeData(

        primarySwatch: Colors.blue,

        textSelectionColor: Colors.red

      ),

      home: SampleAppPage(),

    );

  }

}

7.3 如何增加风格化主题?

在 React Native,常用主题都定义在 stylesheets 中。

在 Flutter 中,为所有组件创建统一风格可以在 ThemeData 类中���义,并将它赋值给 MaterialApp 的主题属性。

  @override

 widget build(BuildContext context) {

    return MaterialApp(

      theme: ThemeData(

        primaryColor: Colors.cyan,

        brightness: Brightness.dark,

      ),

      home: StylingPage(),

    );

  }

Theme 可以在不使用 MaterialApp widget 的情况下使用。Theme 接受一个 ThemeData 参数,并且将 ThemeData 应用于它的全部子 widget。

 @override

 widget build(BuildContext context) {

    return Theme(

      data: ThemeData(

        primaryColor: Colors.cyan,

        brightness: brightness,

      ),

      child: Scaffold(

         backgroundColor: Theme.of(context).primaryColor,

              ...

              ...

      ),

    );

  }

(未完待续)

相关标签

扫一扫

在手机上阅读