给Android开发者Flutter上手指南

Posted bigdata.ministep.cn

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了给Android开发者Flutter上手指南相关的知识,希望对你有一定的参考价值。

[给Android开发者Flutter上手指南_慕课手记](https://www.imooc.com/article/315337)

目录

  • LinearLayout 在Flutter中等价于什么(Android)?
  • RelativeLayout 在Flutter中等价于什么(Android)?
  • 如何使用widget定义布局属性?
  • 如何分层布局?
  • 如何设置布局样式?
  • ScrollView在Flutter中等价于什么?
  • 谁是Flutter的列表组件?
  • 如何知道点击了列表中哪个item?
  • 如何动态更新ListView?

LinearLayout 在Flutter中等价于什么(Android)?

在Android中,使用LinearLayout来使你的控件呈水平或垂直排列。在Flutter中,你可以使用Row或Co​​lumn widget来实现相同的结果:

@override  
Widget build(BuildContext context)   
  return Row(  
    mainAxisAlignment: MainAxisAlignment.center,  
    children: <Widget>[  
      Text(\'Row One\'),  
      Text(\'Row Two\'),  
      Text(\'Row Three\'),  
      Text(\'Row Four\'),  
    ],  
  );  
  
@override  
Widget build(BuildContext context)   
  return Column(  
    mainAxisAlignment: MainAxisAlignment.center,  
    children: <Widget>[  
      Text(\'Column One\'),  
      Text(\'Column Two\'),  
      Text(\'Column Three\'),  
      Text(\'Column Four\'),  
    ],  
  );  
  

要了解有关构建线性布局的更多信息,可参考区贡献的媒体文章Flutter For Android Developers : How to design LinearLayout in Flutter?

RelativeLayout 在Flutter中等价于什么(Android)?

RelativeLayout用于使widget相对于彼此位置排列。在Flutter中,有几种方法可以实现相同的结果

您可以通过使用ColumnRowStack的组合来实现RelativeLayout的效果。您可以为widget构造函数指定相对于父组件的布局规则。

推荐参考在StackOverflow上的一个在Flutter中构建RelativeLayout的例子。

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

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

例如,  和  widgets 控制一个数组中的条目 并且 分别垂直和水平对齐它们。 Container widget 控制一个布局的样式和属性, 并且 Center widget 负责居中它的子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库中提供了各种布局小部件。 例如, PaddingAlign, 和 Stack

更多布局widget可参考 Layout Widgets


如何分层布局?

在Android中,我们可以使用FrameLayout布局进行分层。

Flutter 使用Stack widget 控制子widget在一层。 子widgets可以完全或者部分覆盖基础widgets。

Stack控件将其子项相对于其框的边缘定位。如果您只想重叠多个子窗口小部件,这个类很有用。

// 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 覆盖容器 (显示其“Text”在半透明的黑色背景上) 在’CircleAvatar之上.Stack偏移文本 使用alignment属性和Alignment`定位。

如何设置布局样式?

Flutter有一套独特的布局系统,PaddingCenterColumnRow、等都是widget,另外组件也通常接受用于布局样式的构造参数:比如Textwidget可以使用TextStyle属性。如果要在多个位置使用相同的文本样式, 你可以创建一个 TextStyle 类并将其应用于各个 Text widgets。

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


ScrollView在Flutter中等价于什么?

在Android中,ScrollView允许您包含一个子控件,以便在用户设备的屏幕比控件内容小的情况下,使它们可以滚动。在Flutter中,最简单的方法是使用ListView。但在Flutter中,一个ListView既是一个ScrollView,也是一个Android ListView。

在 iOS 中,你给 view 包裹上 ScrollView 来允许用户在需要时滚动你的内容。在 Flutter 中,最简单的方法是使用 ListView widget。它表现得既和 iOS 中的 ScrollView 一致,也能和 TableView 一致,因为你可以给它的 widget 做垂直排布:

@override  
Widget build(BuildContext context)   
  return ListView(  
    children: <Widget>[  
      Text(\'Row One\'),  
      Text(\'Row Two\'),  
      Text(\'Row Three\'),  
      Text(\'Row Four\'),  
    ],  
  );  
  

更多关于在 Flutter 中如何排布 widget 的文档,请参阅 layout tutorial

谁是Flutter的列表组件?

  • 在 iOS 中,通常用 UITableView 或 UICollectionView 来展示一个列表;
  • 在 Android 中,通常用 ListView 或 RecyclerView 来展示一个列表;
  • 在 RN 中,通常用 FlatList 或 SectionList 来展示一个列表;

在 Flutter 中,你可以用 ListView 来达到相似的实现:

import \'package:flutter/material.dart\';  
  
void main()   
  runApp(SampleApp());  
  
  
class SampleApp extends StatelessWidget   
  // This widget is the root of your application.  
  @override  
  Widget build(BuildContext context)   
    return MaterialApp(  
      title: \'Sample App\',  
      theme: ThemeData(  
        primarySwatch: Colors.blue,  
      ),  
      home: SampleAppPage(),  
    );  
    
  
  
class SampleAppPage extends StatefulWidget   
  SampleAppPage(Key key) : super(key: key);  
  
  @override  
  _SampleAppPageState createState() => _SampleAppPageState();  
  
  
class _SampleAppPageState extends State<SampleAppPage>   
  @override  
  Widget build(BuildContext context)   
    return Scaffold(  
      appBar: AppBar(  
        title: Text("Sample App"),  
      ),  
      body: ListView(children: _getListData()),  
    );  
    
  
  _getListData()   
    List<Widget> widgets = [];  
    for (int i = 0; i < 100; i++)   
      widgets.add(Padding(padding: EdgeInsets.all(10.0), child: Text("Row $i")));  
      
    return widgets;  
    
  

在Android ListView中,您可以创建一个适配器,然后您可以将它传递给ListView,该适配器将使用适配器返回的内容来展示每一行,从上面代码中不难看出,在Flutter中没有adapter的等价物,我们唯一要做的就是控制这个list中要展示的数据。

如何知道点击了列表中哪个item?

import \'package:flutter/material.dart\';  
  
void main()   
  runApp(SampleApp());  
  
  
class SampleApp extends StatelessWidget   
  // This widget is the root of your application.  
  @override  
  Widget build(BuildContext context)   
    return MaterialApp(  
      title: \'Sample App\',  
      theme: ThemeData(  
        primarySwatch: Colors.blue,  
      ),  
      home: SampleAppPage(),  
    );  
    
  
  
class SampleAppPage extends StatefulWidget   
  SampleAppPage(Key key) : super(key: key);  
  
  @override  
  _SampleAppPageState createState() => _SampleAppPageState();  
  
  
class _SampleAppPageState extends State<SampleAppPage>   
  @override  
  Widget build(BuildContext context)   
    return Scaffold(  
      appBar: AppBar(  
        title: Text("Sample App"),  
      ),  
      body: ListView(children: _getListData()),  
    );  
    
  
  _getListData()   
    List<Widget> widgets = [];  
    for (int i = 0; i < 100; i++)   
      widgets.add(GestureDetector(  
        child: Padding(  
          padding: EdgeInsets.all(10.0),  
          child: Text("Row $i"),  
        ),  
        onTap: ()   
          print(\'row tapped\');  
        ,  
      ));  
      
    return widgets;  
    
  

在上述代码中我们通过GestureDetector来监听item的点击事件。

如何动态更新ListView?

  • 在 Android 中,改变列表数据后通过notifyDataSetChanged来更新列表;
  • 在 iOS 中,你改变列表的数据,并通过 reloadData() 方法来通知 table 或是 collection view;

在 Flutter 中,如果你想通过 setState() 方法来更新 widget 列表,你会很快发现你的数据展示并没有变化。这是因为当 setState() 被调用时,Flutter 渲染引擎会去检查 widget 树来查看是否有什么地方被改变了。当它得到你的 ListView 时,它会使用一个==判断,并且发现两个 ListView 是相同的。没有什么东西是变了的,因此更新不是必须的。

一个更新 ListView 的简单方法是,在setState() 中创建一个新的 List,并把旧 List 的数据拷贝给新的 list。虽然这样很简单,但当数据集很大时,并不推荐这样做,来一起看个demo:

import \'package:flutter/material.dart\';  
  
void main()   
  runApp(SampleApp());  
  
  
class SampleApp extends StatelessWidget   
  // This widget is the root of your application.  
  @override  
  Widget build(BuildContext context)   
    return MaterialApp(  
      title: \'Sample App\',  
      theme: ThemeData(  
        primarySwatch: Colors.blue,  
      ),  
      home: SampleAppPage(),  
    );  
    
  
  
class SampleAppPage extends StatefulWidget   
  SampleAppPage(Key key) : super(key: key);  
  
  @override  
  _SampleAppPageState createState() => _SampleAppPageState();  
  
  
class _SampleAppPageState extends State<SampleAppPage>   
  List widgets = [];  
  
  @override  
  void initState()   
    super.initState();  
    for (int i = 0; i < 100; i++)   
      widgets.add(getRow(i));  
      
    
  
  @override  
  Widget build(BuildContext context)   
    return Scaffold(  
      appBar: AppBar(  
        title: Text("Sample App"),  
      ),  
      body: ListView(children: widgets),  
    );  
    
  
  Widget getRow(int i)   
    return GestureDetector(  
      child: Padding(  
        padding: EdgeInsets.all(10.0),  
        child: Text("Row $i"),  
      ),  
      onTap: ()   
        setState(()   
          widgets = List.from(widgets);  
          widgets.add(getRow(widgets.length + 1));  
          print(\'row $i\');  
        );  
      ,  
    );  
    
  

一个推荐的、高效的且有效的做法是,使用 ListView.Builder 来构建列表。这个方法在你想要构建动态列表,或是列表拥有大量数据时会非常好用:

import \'package:flutter/material.dart\';  
  
void main()   
  runApp(SampleApp());  
  
  
class SampleApp extends StatelessWidget   
  // This widget is the root of your application.  
  @override  
  Widget build(BuildContext context)   
    return MaterialApp(  
      title: \'Sample App\',  
      theme: ThemeData(  
        primarySwatch: Colors.blue,  
      ),  
      home: SampleAppPage(),  
    );  
    
  
  
class SampleAppPage extends StatefulWidget   
  SampleAppPage(Key key) : super(key: key);  
  
  @override  
  _SampleAppPageState createState() => _SampleAppPageState();  
  
  
class _SampleAppPageState extends State<SampleAppPage>   
  List widgets = [];  
  
  @override  
  void initState()   
    super.initState();  
    for (int i = 0; i < 100; i++)   
      widgets.add(getRow(i));  
      
    
  
  @override  
  Widget build(BuildContext context)   
    return Scaffold(  
      appBar: AppBar(  
        title: Text("Sample App"),  
      ),  
      body: ListView.builder(  
        itemCount: widgets.length,  
        itemBuilder: (BuildContext context, int position)   
          return getRow(position);  
        ,  
      ),  
    );  
    
  
  Widget getRow(int i)   
    return GestureDetector(  
      child: Padding(  
        padding: EdgeInsets.all(10.0),  
        child: Text("Row $i"),  
      ),  
      onTap: ()   
        setState(()   
          widgets.add(getRow(widgets.length + 1));  
          print(\'row $i\');  
        );  
      ,  
    );  
    
  

与创建一个 “ListView” 不同,创建一个 ListView.builder 接受两个主要参数:列表的初始长度,和一个 ItemBuilder 方法。

ItemBuilder 方法和 iOS的cellForItemAt 代理方法非常类似,它接受一个位置,并且返回在这个位置上你希望渲染的 cell。

最后,也是最重要的,注意 onTap() 函数里并没有重新创建一个 List,而是 add 了一个 widget。

参考

给 React Native 开发者的 Flutter 指南(上)

本文面向希望基于现有的 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 asyncawait

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

在 JavaScript 中, async 函数返回一个 Promiseawait 操作符用于等待 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: {
    flex1,
    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!” 应用程序的界面。

给 React Native 开发者的 Flutter 指南(上)

    Android  

给 React Native 开发者的 Flutter 指南(上)

 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!” 应用更多的功能。

给 React Native 开发者的 Flutter 指南(上)

 Android  

给 React Native 开发者的 Flutter 指南(上)

 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 类的示例。

给 React Native 开发者的 Flutter 指南(上)

 Android

给 React Native 开发者的 Flutter 指南(上)

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
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:
    sdkflutter
  google_sign_in: ^3.0.3

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

3. 向下面代码一样在程序中引用代码包:

import 'package:flutter/cupertino.dart';

四、Flutter widgets

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

widget 常常通过很多小的,单一功能的 widget 组成,这样的封装往往能实现很棒的效果。比如, Container widget 包含多种 widget,分别负责布局、绘图、位置变化和尺寸变化。Container widget 包括 LimitedBoxConstrainedBoxAlignPaddingDecoratedBox, 和 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],
    );
  },
)

给 React Native 开发者的 Flutter 指南(上)

Android

给 React Native 开发者的 Flutter 指南(上)

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(75755002 * Math.PI);
  ctx.fillRect(150100300300);
  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.0200.0), 40.0, paint);
    Paint paintRect = Paint();
    paintRect.color = Colors.lightBlue;
    Rect rect = Rect.fromPoints(Offset(150.0300.0), Offset(300.0400.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(),
      ),
    );
  }
}

给 React Native 开发者的 Flutter 指南(上)

Android 

给 React Native 开发者的 Flutter 指南(上)

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

给 React Native 开发者的 Flutter 指南(上)

Android

给 React Native 开发者的 Flutter 指南(上)

iOS

6.2 如何为 widget 分层?

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

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

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

// Flutter
Stack(
  alignment: const Alignment(0.60.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 坐标微调文本。

给 React Native 开发者的 Flutter 指南(上)

Android 

给 React Native 开发者的 Flutter 指南(上)

 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)
      ),
    ],
  ),
)

给 React Native 开发者的 Flutter 指南(上)

Android

iOS

7.2 我如何使用 IconsColors 呢?

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

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

Icon(Icons.lightbulb_outlinecolorColors.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 的依赖即可。

namemy_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(
      themeThemeData(
        primaryColorColors.cyan,
        brightnessBrightness.dark,
      ),
      homeStylingPage(),
    );
  }

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

 @override
 widget build(BuildContext context) {
    return Theme(
      dataThemeData(
        primaryColorColors.cyan,
        brightnessbrightness,
      ),
      childScaffold(
         backgroundColorTheme.of(context).primaryColor,
              ...
              ...
      ),
    );
  }


(未完待续)



以上是关于给Android开发者Flutter上手指南的主要内容,如果未能解决你的问题,请参考以下文章

Flutter -- 快速上手的Android ios开发框架

[Android Flutter] Flutter开发技术入门指南与实战解析

12-6打通Flutter与Android的任督二脉Flutter Plugin开发指南-Android端实现-2

Android Flutter全家桶学习资料(入门指南进阶实战...)

12-5打通Flutter与Android的任督二脉Flutter Plugin开发指南-Android端实现-1

给 React Native 开发者的 Flutter 指南(上)