# 前言

为更好的学习Flutter，特地将Flutter的学习从零整理及总结成本书，方便大家的快速学习！

其他权威网站：[Flutter中文网](https://flutterchina.club/)、[Flutter实战书籍](https://book.flutterchina.club)

## 主流跨平台语言对比

主流跨平台语言(Flutter和RN、Weex)之间的对比：

![主流跨平台语言对比](https://3320264277-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LVSU1YOqp0suas4rOgR%2F-LVmK6J0OWPnjWfRJhIf%2F-LVmK80dIOI3ZTrgHTb_%2F%E4%B8%BB%E6%B5%81%E8%B7%A8%E5%B9%B3%E5%8F%B0%E8%AF%AD%E8%A8%80%E5%AF%B9%E6%AF%94.jpg?generation=1547032635475282\&alt=media)

* 渲染性能

和RN和Weex将javascript转化为原生控件渲染不同，Flutter完全挣脱了原生控件的“束缚”，如下图所示，Flutter使用了分层架构，分为Framework和Engine两个部分，其中Framework层提供各种基础组件库，包括各种Widget，动画等，Engine层则完全由C和C++实现，使用Skia进行渲染（对！就是chrome用的那个图形渲染框架），官方宣称可以达到原生app的渲染性能。

![Flutter结构](https://3320264277-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LVSU1YOqp0suas4rOgR%2F-LVmK6J0OWPnjWfRJhIf%2F-LVmK80fSYlDAsqF2OEx%2FFlutter%E7%BB%93%E6%9E%84.jpg?generation=1547032634695252\&alt=media)

* Google新操作系统Fuchsia(被认为是Android的继任者)也使用Flutter作为其UI框架，今后的发展不可限量。


# 第一章：入门

本章介绍Flutter入门必须掌握的知识点。

[点击此处，进入本章第1节的正式学习 >](/1-ru-men/flutter-kai-fa-huan-jing-da-jian)


# 第1节：Flutter开发环境搭建

本节学习内容：Flutter的环境搭建及创建运行您的第一个Flutter项目。

本节学习用时：**30分钟**

本节学习方式：动手实践

本节目录：

一、检测您是否具备了Flutter的开发环境

二、创建并运行第一个Flutter项目

附

## 一、检测您是否具备了Flutter的开发环境

因为您可能之前已经有配置过Flutter的开发环境了，所以为了避免重复安装，我们通过在终端运行`flutter doctor`命令，来进行检测，查看是否缺少。

### 1、环境已完善时

如果您的环境之前已经配置过且已完全配置好的话，您的命令执行结果可能会如图所示：

> ![flutter doctor 检测出无问题](https://3320264277-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LVSU1YOqp0suas4rOgR%2F-LVmK6J0OWPnjWfRJhIf%2F-LVmK7x6AMMVMhxj-baY%2Fflutter%20doctor%20%E6%A3%80%E6%B5%8B%E5%87%BA%E6%97%A0%E9%97%AE%E9%A2%98.png?generation=1547032637145912\&alt=media)

### 2、环境未完善时

* 如果有提示错误的(**如果你是第一次，那肯定有很多问题**)，请参照本节最后的`【附1】、flutter doctor问题解决`中的内容进行解决。

## 二、创建并运行第一个Flutter项目

### 1、最简单的创建Flutter项目的方法

在终端执行`flutter create originflutterproject`即可在指定目录建立Flutter项目。

> ![flutter create originflutterproject](https://3320264277-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LVSU1YOqp0suas4rOgR%2F-LVmK6J0OWPnjWfRJhIf%2F-LVmK7x8CfXEcmqGodFg%2Fflutter%20create%20originflutterproject.png?generation=1547032639368745\&alt=media)

### 2、运行Flutter项目

在指定设备上运行Flutter项目

> ![flutter run -d deviceid](https://3320264277-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LVSU1YOqp0suas4rOgR%2F-LVmK6J0OWPnjWfRJhIf%2F-LVmK7xA1z_vTNMQE9Uj%2Fflutter%20run%20-d%20deviceid.png?generation=1547032639371510\&alt=media)

如要了解更多请查看：[官网Flutter起步: 体验](https://flutterchina.club/get-started/test-drive/#terminal)

## 附

### 【附1】、flutter doctor问题解决

flutter doctor该命令的作用：查看是否需要安装其它依赖项来完成安装。

下面涵盖的错误介绍及解决有：

* 1.1、*-bash: flutter: command not found*
* 1.2、缺少依赖项
* 其他问题，请查看`【附2】、flutter 安装过程中的其他问题解决`

#### 1.1、`-bash: flutter: command not found`

原因：Flutter相关命令无效(此时`flutter -h`肯定也是一样的问题)

附：完好的flutter -h结果应为如图所示：

> ![ flutter -h 检测flutter](https://3320264277-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LVSU1YOqp0suas4rOgR%2F-LVmK6J0OWPnjWfRJhIf%2F-LVmK7xCOK-Ao5KLddt5%2F%20flutter%20-h%20%E6%A3%80%E6%B5%8Bflutter.png?generation=1547032640282983\&alt=media)

解决：下载Flutter SDK，并配置Flutter环境变量到PATH中即可。

步骤：

* ①下载Flutter SDK。

  [Flutter SDK官网地址](https://flutter.io/docs/get-started/install/macos)。（如果您觉得速度慢，这里提供一个已经下载好的地址：[Flutter SDK 1.0.0 网盘下载地址](https://pan.baidu.com/s/1yB5WRTvIRA2eISpUHtGP3w) 密码:nm09）
* ②配置Flutter环境变量方法：即&#x5728;*\~/.bash\_profile*文件里添加需要配置的内容即可。

  下面是我的\~/.bash\_profile文件内容：

> ![我的\~/.bash\_profile](https://3320264277-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LVSU1YOqp0suas4rOgR%2F-LVmK6J0OWPnjWfRJhIf%2F-LVmK7xE6Ogu_4Ay5OUl%2F%E6%88%91%E7%9A%84~%3A.bash_profile.png?generation=1547032638097245\&alt=media)

但在这里目前我们只需要上图中我圈出来的那行`export PATH=/Applications/flutter/bin:$PATH #配置Flutter环境变量`即可。这里我们先满足目前认知Flutter所需的即可，没必要把如下其他行是暂时不需要的所有的都加上去，当然你加上去可以以后更省事。

```bash
# Flutter
export PATH=/Applications/flutter/bin:$PATH  #配置Flutter环境变量

# 设置pub源，才能让你在国内愉快的使用别人写的库
export PUB_HOSTED_URL=https://pub.flutter-io.cn
export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn

# ANDROID
export ANDROID_HOME=/Users/lichaoqian/Library/Android/sdk #android sdk目录，替换为你自己的即可
export PATH=${PATH}:${ANDROID_HOME}/tools
export PATH=${PATH}:${ANDROID_HOME}/platform-tools
```

#### 1.2、缺少依赖项

如果您不缺依赖项，那您命令的执行结果会为

如果flutter doctor 检测出问题，此时的命令执行结果一般如下：

> ![flutter doctor 检测出问题](https://3320264277-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LVSU1YOqp0suas4rOgR%2F-LVmK6J0OWPnjWfRJhIf%2F-LVmK7xG3X0vZQw30C2X%2Fflutter%20doctor%20%E6%A3%80%E6%B5%8B%E5%87%BA%E9%97%AE%E9%A2%98.png?generation=1547032638457744\&alt=media)

原因：缺少依赖项

解决：遇到什么错误，根据提示操作即可。

①、根据提示终端执行命令，解决`iOS toolchain - develop for iOS devices （Xcode 10.1）`后

> ![iOS toolchain问题解决后](https://3320264277-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LVSU1YOqp0suas4rOgR%2F-LVmK6J0OWPnjWfRJhIf%2F-LVmK7xIrtslVba6HpHt%2FiOS%20toolchain%E9%97%AE%E9%A2%98%E8%A7%A3%E5%86%B3%E5%90%8E.png?generation=1547032640160309\&alt=media)

②、根据提示[下载Android SDK](http://www.androiddevtools.cn/)，并放置ANDROD\_HOME指定的目录下后，

> ![Android SDK安装后](https://3320264277-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LVSU1YOqp0suas4rOgR%2F-LVmK6J0OWPnjWfRJhIf%2F-LVmK7xKLsiwX-AmvHHG%2FAndroid%20SDK%E5%AE%89%E8%A3%85%E5%90%8E.png?generation=1547032639498415\&alt=media)

③、为Android Studio安装Flutter Plugins后

> ![Android Studio安装Flutter Plugins后](https://3320264277-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LVSU1YOqp0suas4rOgR%2F-LVmK6J0OWPnjWfRJhIf%2F-LVmK7xMscepa2z52ggY%2FAndroid%20Studio%E5%AE%89%E8%A3%85Flutter%20Plugins%E5%90%8E.png?generation=1547032638776240\&alt=media)

安装过程中，如遇Plugins搜索不到时候，如

> ![Android Plugins搜索不到的示例截图](https://3320264277-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LVSU1YOqp0suas4rOgR%2F-LVmK6J0OWPnjWfRJhIf%2F-LVmK7xOgZ4k7Rx0DgyU%2FAndroid%20Plugins%E6%90%9C%E7%B4%A2%E4%B8%8D%E5%88%B0%E7%9A%84%E7%A4%BA%E4%BE%8B%E6%88%AA%E5%9B%BE.png?generation=1547032640981274\&alt=media)

请确保如下图中的路径`Perferences -> Appearance & Behavior -> System Settings -> Updates -> Use secure connection`中的勾是取消的，如果不是请取消掉，并重启Android Studio后，重新搜索即可。

> ![Android Plugins搜索不到的处理1](https://3320264277-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LVSU1YOqp0suas4rOgR%2F-LVmK6J0OWPnjWfRJhIf%2F-LVmK7xQGHEQCtZiVj-_%2FAndroid%20Plugins%E6%90%9C%E7%B4%A2%E4%B8%8D%E5%88%B0%E7%9A%84%E5%A4%84%E7%90%861.png?generation=1547032641026729\&alt=media)

或者可以直接从`http://plugins.jetbrains.com/`上下载插件后，通过选择插件安装方式为Install plugin from disk来进行安装，如下图：

> ![插件安装方式选择为Install plugin from disk](https://3320264277-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LVSU1YOqp0suas4rOgR%2F-LVmK6J0OWPnjWfRJhIf%2F-LVmK7xSJe3o0va68_BT%2F%E6%8F%92%E4%BB%B6%E5%AE%89%E8%A3%85%E6%96%B9%E5%BC%8F%E9%80%89%E6%8B%A9%E4%B8%BAInstall%20plugin%20from%20disk.png?generation=1547032640289865\&alt=media)

附：这是[已经下载好后存在网盘上的Flutter插件](https://pan.baidu.com/s/1yyUxP2lRb7hamJN-MHA1zw)

④为Intelij IDEA安装Flutter Plugins后的结果，即为不缺依赖的情况了。

其他资料：官网中的[入门: 在macOS上搭建Flutter开发环境](https://flutterchina.club/setup-macos/#使用镜像)

如果还没解决安装问题，请查看`【附2】、flutter 安装过程中的其他问题解决`

### 【附2】、flutter 安装过程中的其他问题解决

![flutter doctor其他问题1](https://3320264277-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LVSU1YOqp0suas4rOgR%2F-LVmK6J0OWPnjWfRJhIf%2F-LVmK7xUTlhBWoD7evp2%2Fflutter%20doctor%E5%85%B6%E4%BB%96%E9%97%AE%E9%A2%981.png?generation=1547032637306168\&alt=media)

原因：您的Flutter SDK 要求你Android Studio中的Flutter plugin版本至少到16.0.0。但你当前的Android Studio版本却只允许你最高只能装到Flutter plugin version 12.1。

解决：升级你的Android Studio后，重新安装其Flutter plugin。


# 第2节：Flutter的Hello World

本节学习内容：分别通过多种方法实现Hello world! ，让您对Flutter有一个初步的认识！

本节学习用时：**3分钟**

本节学习方式：**推荐直接看懂代码即可**

本节目录：

一、本节内容介绍

二、本节代码解释

## 一、本节内容介绍

### 1、本节代码

本节所有代码文件为：`main.dart`。

> 您可自己通过`flutter create helloworldproject`创建helloworldproject项目后，将其lib文件夹下的代码文件替换为如下即可。

#### 1.1、其中完整的`main.dart`代码如下：

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

// 方法①
// >>>>>>>>>>>>> start >>>>>>>>>>>>>
// void main() => runApp(const Center(
//     child: Text('1.Hello, world! in Main', textDirection: TextDirection.ltr)));
// <<<<<<<<<<<<<  end  <<<<<<<<<<<<<


// 方法②
// >>>>>>>>>>>>> 方法② >>>>>>>>>>>>>
// void main() => runApp(MyApp());

// class MyApp extends StatelessWidget {
//   @override
//   Widget build(BuildContext context) {
//     return const Center(
//         child: Text('2.Hello, world! in MyApp return!', textDirection: TextDirection.ltr));
//   }
// }
// <<<<<<<<<<<<< 方法② <<<<<<<<<<<<<


// 方法③
// >>>>>>>>>>>>> 方法③ >>>>>>>>>>>>>
// void main() => runApp(MyApp());

// class MyApp extends StatelessWidget {
//    helloWorldPage() {
//      return Center(
//        child: Text('3.Hello world! in helloWorldPage',
//            textDirection: TextDirection.ltr),
//      );
//    }

//    @override
//    Widget build(BuildContext context) {
//      return helloWorldPage();
//  }
// }
// <<<<<<<<<<<<< 方法③ <<<<<<<<<<<<<


// 方法④
// >>>>>>>>>>>>> 方法④ >>>>>>>>>>>>>
void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return HelloWorldPage();
  }
}

class HelloWorldPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text('4.Hello world! in HelloWorldPage',
          textDirection: TextDirection.ltr),
    );
  }
}
// <<<<<<<<<<<<< 方法④ <<<<<<<<<<<<<
```

### 2、代码说明

略


# 第3节：Flutter的最基础知识

本节学习内容：通过最基础的Flutter控件等实现一个app ，让您对Flutter构建的app有一个初步的认识！

本节学习用时：**30分钟**

本节学习方式：**推荐直接看懂代码即可**

本节目录：

一、本节内容介绍

二、本节代码解释

## 一、本节内容介绍

本节通过实现以下界面/功能效果，为您介绍一些最基础的Flutter知识点。

### 1、主要介绍的知识点有：

* MaterialApp的应用
* Scaffold的应用
* 文本的应用：Text
* 按钮的应用：FloatingActionButton
* 页面的跳转

### 2、要实现的效果如下图所示：

> ![materialappproject效果图](https://3320264277-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LVSU1YOqp0suas4rOgR%2F-LVmK6J0OWPnjWfRJhIf%2F-LVmK9HePnclvsLrLsE6%2Fmaterialappproject.png?generation=1547032640959086\&alt=media)

### 3、实现本节效果的代码

本节所有代码文件为：`main.dart`、`NewRoute.dart`。

> 您可自己通过`flutter create materialappproject`创建materialappproject项目后，将其lib文件夹下的代码文件替换为如下即可。

#### 3.1、其中完整的`main.dart`代码如下：

```dart
import 'package:flutter/material.dart';
import 'NewRoute.dart';

// 写法①
// >>>>>>>>>>>>> runApp写法① >>>>>>>>>>>>>
// void main() {
//   runApp(new MaterialApp(
//     title: '1.MaterialApp in main',
//     home: new HelloWorldPage(),
//   ));
// }
// <<<<<<<<<<<<< runApp写法① <<<<<<<<<<<<<


// 写法②
// >>>>>>>>>>>>> runApp写法② >>>>>>>>>>>>>
void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '2.MaterialApp in MyApp',

      // 页面跳转的方式②：注册路由表
      // 详情查看[官网路由管理](https://book.flutterchina.club/chapter2/flutter_router.html)
      // 缺点：路由传递的参数无法动态修改(如果路由有参数)
      routes: {
        "new_page": (context) => NewRoute(),
      },

      home: HelloWorldPage(),
    );
  }
}
// <<<<<<<<<<<<< runApp写法② <<<<<<<<<<<<<


class HelloWorldPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    //Scaffold是Material中主要的布局组件.
    return new Scaffold(
        appBar: new AppBar(
          leading: new IconButton(
            icon: new Icon(Icons.menu),
            tooltip: 'Navigation menu',
            onPressed: null,
          ),
          title: new Text('MaterialApp Example title'),
          actions: <Widget>[
            new IconButton(
              icon: new Icon(Icons.search),
              tooltip: 'Search',
              onPressed: null,
            ),
          ],
        ),
        //body占屏幕的大部分
        body: new Center(
          child: new Text('Hello, world2!'),
        ),
        floatingActionButton: new FloatingActionButton(
          tooltip: 'Add', // used by assistive technologies
          child: new Icon(Icons.add),
          // 1、无方法
          // 写法①
          // onPressed: null,
          // 写法②
          // onPressed: () {}
          // 写法③
          // onPressed: () => {}

          // 2、有方法，但该方法无参数
          // 写法①
          // onPressed: printLog,
          // 写法②
          // onPressed: () {
          //   printLog();
          // },

          // 3、有方法，且该方法有参数
          // onPressed:() {
          //   printText("Hello world");
          // }

          // 4.页面跳转方式
          // 写法①
          // onPressed: () {
          //   goNextPage(context);
          // },
          // 写法②
          // onPressed: () {
          //   Navigator.pushNamed(context, "new_page");
          // },
          // 写法③
          onPressed: () {
            Navigator.push(context, new MaterialPageRoute(builder: (context) {
              return new NewRoute();
            }));
          },
        )
    );
  }
}

void printLog() {
  print("This is printLog Method");
}

void printText($text) {
  print("The text is " + $text);
}

void goNextPage(context) {
  // 导航到新路由
  Navigator.push(context, new MaterialPageRoute(builder: (context) {
    return new NewRoute();
  }));
}
```

#### 3.2、其中完整的`NewRoute.dart`代码如下：

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

class NewRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("New route"),
      ),
      body: Center(
        child: Text("This is new route"),
      ),
    );
  }
}
```

## 二、本节代码解释

### 1、MaterialApp的使用方式介绍

```dart
// 写法①
import 'package:flutter/material.dart';

void main() {
  runApp(new MaterialApp(
    title: '1.MaterialApp in main',
    home: new HelloWorldPage(),
  ));
}


// ------------------- 我是分割线，下面介绍的是另一种写法 ------------------- //
// 写法②
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '2.MaterialApp in MyApp',
      home: HelloWorldPage(),
    );
  }
}
```

### 2、Scaffold的使用方式介绍

以创建一个新页面`NewRoute.dart`为例，该文件代码如下：

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

class NewRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("New route"),
      ),
      body: Center(
        child: Text("This is new route"),
      ),
    );
  }
}
```

### 3、文本Text以及按钮Button的使用介绍

```dart
class HelloWorldPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    //Scaffold是Material中主要的布局组件.
    return new Scaffold(
      appBar: new AppBar(
        leading: new IconButton(
          icon: new Icon(Icons.menu),
          tooltip: 'Navigation menu',
          onPressed: null,
        ),
        title: new Text('MaterialApp Example title'),
        actions: <Widget>[
          new IconButton(
            icon: new Icon(Icons.search),
            tooltip: 'Search',
            onPressed: null,
          ),
        ],
      ),
      //body占屏幕的大部分
      body: new Center(
        child: new Text('Hello, world2!'),
      ),
      floatingActionButton: new FloatingActionButton(
        tooltip: 'Add', // used by assistive technologies
        child: new Icon(Icons.add),
        onPressed: null,
      ),
    );
  }
}
```

## 三、按钮Button

### 1、按钮形式

```dart
    floatingActionButton: new FloatingActionButton(
          tooltip: 'Add', // used by assistive technologies
          child: new Icon(Icons.add),
          // 点击进行页面跳转
          onPressed:() {
            goNextPage(context);
          }
          ),
    );
```

### 2、按钮的事件写法

#### 2.1、无方法

```dart
// 写法①
onPressed: null,
// 写法②
onPressed: () {}
// 写法③
onPressed: () => {}
```

#### 2.2、有方法，但该方法无参数

设方法为：

```dart
void printLog() {
  print("This is printLog Method");
}
```

则按钮点击事件为：

```dart
// 写法①
onPressed: printLog,

// 写法②
onPressed: () {
    printLog();
},
```

#### 2.3、有方法，且该方法有参数

设方法为：

```dart
void printText($text) {
  print("The text is " + $text);
}
```

则按钮点击事件为：

```dart
onPressed:() {
    printText("Hello world");
}
```

## 四、新页面的创建

以创建一个新页面`NewRoute.dart`为例，该文件代码如下：

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

class NewRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("New route"),
      ),
      body: Center(
        child: Text("This is new route"),
      ),
    );
  }
}
```

## 五、页面的跳转 & 路由的管理

### 5.1 直接跳转

以跳转到上述建立的`NewRoute.dart`为例，则跳转方法：

方式①：

```dart
import 'NewRoute.dart';

// 按钮处的写法
onPressed: () {
    Navigator.push(context, new MaterialPageRoute(builder: (context) {
        return new NewRoute();
    }));
},
```

方式②：

```dart
import 'NewRoute.dart';

// 定义导航到新路由的方法
void goNextPage(context) {
  Navigator.push(context, new MaterialPageRoute(builder: (context) {
    return new NewRoute();
  }));
}

// 则按钮处的写法，自然如下：
onPressed:() {
    goNextPage(context);
}
```

### 5.2 使用路由管理

```dart
import 'NewRoute.dart';

// 在MaterialApp中注册路由表
routes: {
    "new_page": (context) => NewRoute(),
},

// 按钮位置写法
onPressed: () {
    Navigator.pushNamed(context, "new_page");
},
```


# 第二章：原生项目集成Flutter

本章介绍在已有的原生项目中集成Flutter，以使得您的原生项目中的部分界面/功能可以通过Flutter来实现。

本章目前主要介绍在iOS项目中集成Flutter、在Android项目中集成Flutter两个部分。

其他参考资料：

* [flutter自家的混编wiki:Add Flutter to existing apps](https://github.com/flutter/flutter/wiki/Add-Flutter-to-existing-apps)

[点击此处，进入本章第1节的正式学习 >](/2-ji-cheng/ios-xiang-mu-ji-cheng-flutter)


# 第1节：iOS项目集成Flutter

本节学习内容：在已有的iOS项目中集成Flutter，以使得您的iOS项目中的部分界面/功能可以通过Flutter来实现。

本节学习用时：**30分钟**

本节学习方式：动手实践

本节目录：

一、集成方式介绍

二、集成方法一：手动配置iOS与Flutter的混编环境

三、集成方法二：Pod集成Flutter方式

四、集成后调用Flutter界面的代码编写

### 一、集成方式介绍

本节将介绍两种在已有的iOS项目中集成Flutter的方法，分别为①手动配置iOS与Flutter的混编环境、②Pod集成Flutter方式。

下面表格是两种集成Flutter的方法比较：

|                      | 手动集成Flutter         | Pod集成Flutter                                  |
| -------------------- | ------------------- | --------------------------------------------- |
| 集成速度                 | 需要自己建立文件、进行相应配置，并替换 | 执行Pod及Run Script即可                            |
| Flutter项目更新时         | 每次都需要替换             | 不用替换                                          |
| Flutter项目与iOS同时路径变更时 | 不影响iOS工程            | <p>Flutter工程需要重新编译执行；<br>然后iOS工程需再重新执行Pod</p> |

### 二、集成方法一：手动配置iOS与Flutter的混编环境

#### 1.1 集成后的最后项目结构如图：

> ![myiosproject project structure](https://3320264277-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LVSU1YOqp0suas4rOgR%2F-LVmK6J0OWPnjWfRJhIf%2F-LVmK98jUxoUpPS9Hj2M%2Fmyiosproject%20project%20structure.png?generation=1547032644160943\&alt=media)

#### 1.2 集成后的最后文件结构如图：

> ![myiosproject file structure](https://3320264277-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LVSU1YOqp0suas4rOgR%2F-LVmK6J0OWPnjWfRJhIf%2F-LVmK98lQ15N827TYDPd%2Fmyiosproject%20file%20structure.png?generation=1547032643664725\&alt=media)

#### 1.3 详细的集成实现步骤如下：

* ①、通过终端命令在指定目录(这里我们选择iOS项目的同级目录)创建myfluttermodule。`flutter create -t module myfluttermodule`
* ②、Xcode：创建iOS项目，设为myiosproject；
* ③、Xcode：创建配置衔接文件
  * 在②中建立的iOS项目中新建`Config`目录，用于存放/管理接下来Xcode工程需要创建的的配置衔接文件；
  * 选择创建`Configuration Settings File`文件，分别创建名为 `Flutter.xcconfig`、`Debug.xcconfig`、`Release.xcconfig`的三个配置文件；

    > ![New Configuration Settings File](https://3320264277-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LVSU1YOqp0suas4rOgR%2F-LVmK6J0OWPnjWfRJhIf%2F-LVmK98nihawMDA1mCjL%2FNew%20Configuration%20Settings%20File.png?generation=1547032643288932\&alt=media)
  * 完善`Flutter.xcconfig`、`Debug.xcconfig`、`Release.xcconfig`三个配置文件的内容为分别如下：

    > Flutter.xcconfig 配置文件内容如下：
    >
    > ```
    > //Flutter.xconfig,用于指向外目录flutter module的`Generated.xcconfig`文件路径引用文件
    >
    > //这里填写前面建立的 myfluttermodule 的Generated.xcconfig的路径
    > #include "../../myfluttermodule/.ios/Flutter/Generated.xcconfig"
    > ENABLE_BITCODE=NO
    > ```
    >
    > Debug.xcconfig 配置文件内容如下：
    >
    > ```
    > //Debug.xconfig，Xcode的环境配置文件
    >
    > #include "Flutter.xcconfig"
    > //#include "../Pods/Target Support Files/******.debug.xcconfig"//pod路径
    > #include "../Pods/Target Support Files/Pods-myiosproject/Pods-myiosproject.debug.xcconfig"//pod路径
    > FLUTTER_BUILD_MODE=debug
    > ```
    >
    > Release.xcconfig 配置文件内容如下：
    >
    > ```
    > //Release.xconfig，Xcode的环境配置文件
    >
    > #include "Flutter.xcconfig"
    > //#include "../Pods/Target Support Files/******.release.xcconfig"//pod路径
    > #include "../Pods/Target Support Files/Pods-myiosproject/Pods-myiosproject.release.xcconfig"//pod路径
    > FLUTTER_BUILD_MODE=release
    > ```
  * 修改Xcode Project的环境配置选择

    > ![myiosproject Project Info Configurations](https://3320264277-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LVSU1YOqp0suas4rOgR%2F-LVmK6J0OWPnjWfRJhIf%2F-LVmK98pk4R9I0YCrn7K%2Fmyiosproject%20Project%20Info%20Configurations.png?generation=1547032643212420\&alt=media)
* ④、Xcode -> Target -> Build Phases -> New Run Script Phase，添加要执行的脚本内容引入`xcode-backend.sh`，此步很重要。

  ```
  "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" build
  "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" embed
  ```

  > ![myiosproject New Script xcode-backend.sh](https://3320264277-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LVSU1YOqp0suas4rOgR%2F-LVmK6J0OWPnjWfRJhIf%2F-LVmK98ru43_BJEJt1Wz%2Fmyiosproject%20New%20Script%20xcode-backend.sh.png?generation=1547032642078761\&alt=media)
* ⑤、添加/替换flutter编译产物，**如果之前已添加，则这里每次有有新的时候，注意要替换掉，否则界面还是旧的。**

  > 即复制 myfluttermodule/.ios/Flutter下的`App.framework`、`engine`、`flutter_assets`进行替换。
  >
  > 然后Xcode重新编译即可。
  >
  > 注意：`flutter_assets` 并不能使用`Create groups` 的方式添加，只能使用`Creat folder references` 的方式添加进Xcode项目内，否则跳转flutter会页面渲染失败（页面空白）。
* ⑥、至此，恭喜你，到这里您可向往常一样直接执行已集成Flutter的Xcode项目了。

### 三、集成方法二：Pod集成Flutter方式

#### 2.1 最后的项目结构如图：

> ![myiosproject2 project structure](https://3320264277-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LVSU1YOqp0suas4rOgR%2F-LVmK6J0OWPnjWfRJhIf%2F-LVmK98tLOoqd1HdcM9w%2Fmyiosproject2%20project%20structure.png?generation=1547032642851264\&alt=media)

#### 2.2 实现步骤如下：

* ①、通过终端命令在指定目录(这里我们选择iOS项目的同级目录)创建myfluttermodule。`flutter create -t module myfluttermodule`。这里因为我们已经在1中实现，所以不再重复创建。
* ②、Xcode：创建myiosproject2；添加如Podfile，及在Podfile中如结构图中所示，补充

  ```
      flutter_application_path = './../myfluttermodule/'
      eval(File.read(File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')), binding)
  ```

  后，执行pod install

  > ![myiosproject2 pod install](https://3320264277-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LVSU1YOqp0suas4rOgR%2F-LVmK6J0OWPnjWfRJhIf%2F-LVmK98vufh52A7AfoC3%2Fmyiosproject2%20pod%20install.png?generation=1547032641749913\&alt=media)
* ③、Xcode -> Target -> Build Phases -> New Run Script Phase，添加要执行的脚本内容引入`xcode-backend.sh`，此步很重要。同1中一样的做法。
* ④、至此，混编环境配置成功！

### 四、集成后调用Flutter界面的代码编写

```
#import "AppDelegate.h"
#import <Flutter/Flutter.h>

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
//    ViewController *viewController = [[ViewController alloc] init];
//    UIViewController *rootViewController = [[UINavigationController alloc] initWithRootViewController:viewController];
    UIViewController *rootViewController = [[FlutterViewController alloc] initWithProject:nil nibName:nil bundle:nil];
    self.window.rootViewController = rootViewController;
    [self.window makeKeyAndVisible];

    return YES;
}
```

而后，正常执行Xcode项目即可。

## 三、更多混编

更多混编知识，请查看[官网：使用平台通道编写平台特定的代码](https://flutterchina.club/platform-channels/)

> ![platform channel](https://3320264277-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LVSU1YOqp0suas4rOgR%2F-LVmK6J0OWPnjWfRJhIf%2F-LVmK98xJ68VljDY4Hmy%2Fplatform%20channel.png?generation=1547032641865740\&alt=media)

## 附：


# 第2节：Android项目集成Flutter

本节学习内容：在已有的Android项目中集成Flutter，以使得您的Android项目中的部分界面/功能可以通过Flutter来实现。

本节学习用时：**3分钟**

本节学习方式：动手实践

本节目录：

一、集成方式介绍

二、集成方法介绍

三、集成后调用Flutter界面的代码编写

## 一、集成方式介绍

本节将介绍两种在已有的Android项目中集成Flutter的方法。

待补充。。。。。。。


# 第三章：原生项目与Flutter交互

本章介绍

* ①通过Flutter的平台通道在Flutter工程中建立提供给原生项目的方法及接收从原生项目返回的值。
* ②原生iOS项目在交互时的代码编写
* ③原生Android项目在交互时的代码编写

[点击此处，进入本章第1节的正式学习 >](/3-jiao-hu/yong-yu-ce-shi-jiao-hu-de-flutter-xiang-mu)


# 第1节：用于测试交互的Flutter项目

本节学习内容：通过Flutter的平台通道在Flutter工程中建立提供给原生项目的方法及接收从原生项目返回的值。

本节学习用时：**30分钟**

本节学习方式：动手实践

本节目录：

一、本节内容介绍

二、本节代码解释

## 一、本节内容介绍

### 1、主要介绍的知识点有：

* 在iOS界面中点击一个按钮跳转到Flutter页面，该Flutter页面有需要传参Main页面

### 2、要实现的效果如下图所示：

> ![OC跳到Flutter有需要传参的Main页面](https://3320264277-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LVSU1YOqp0suas4rOgR%2F-LVmK6J0OWPnjWfRJhIf%2F-LVmK8KW0H6yf_t22Vt3%2FOC%E8%B7%B3%E5%88%B0Flutter%E6%9C%89%E9%9C%80%E8%A6%81%E4%BC%A0%E5%8F%82%E7%9A%84Main%E9%A1%B5%E9%9D%A2.png?generation=1547032636330053\&alt=media)

### 3、实现本节效果的代码

本节所有代码文件为：`main.dart`、`StatelessMainPage.dart`、`StatefulMainPage.dart`、`NewRouteNoneParam.dart`、`NewRouteWithParam`。

> 您可自己通过`flutter create materialappproject`创建materialappproject项目后，将其lib文件夹下的代码文件替换为如下即可。

#### 3.1、其中完整的`main.dart`代码如下：

```dart
import 'package:flutter/material.dart';
import 'StatelessMainPage.dart';
import 'StatefulMainPage.dart';
import 'NewRouteNoneParam.dart';
import 'NewRouteWithParam.dart';



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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Platform Channel Demo',
      home: StatelessMainPage(), //不需传参数的Flutter页面
      //home: StatefulMainPage(), //需传参数的Flutter页面
      routes: {
        'StatelessMainPage':(context) => StatelessMainPage(),
        'StatefulMainPage':(context) => StatefulMainPage(),
        'NewRouteNoneParam':(context) => NewRouteNoneParam(),
        'NewRouteWithParam':(context) => NewRouteWithParam()
      }
    );
  }
}
```

#### 3.2、其中完整的`StatelessMainPage.dart`代码如下：

```dart
import 'package:flutter/material.dart';
import 'StatefulMainPage.dart';
import 'NewRouteNoneParam.dart';
import 'NewRouteWithParam.dart';

import 'package:flutter/services.dart';
import 'dart:async';

// 已写在main.dart中
//void main() => runApp(MyApp());
//
//class MyApp extends StatelessWidget {
//  @override
//  Widget build(BuildContext context) {
//    return MaterialApp(
//      title: 'Platform Channel Demo',
//      home: StatelessMainPage(),
//      routes: {
//        'newRoute':(context) => NewRoute(),
//        'newRouteWithParam':(context) => NewRouteWithParam(),
//        'StatefulMainPage':(context) => StatefulMainPage()
//      }
//    );
//  }
//}

class StatelessMainPage extends StatelessWidget {
  // 创建一个给native的channel (类似iOS的通知）
  static const methodChannel = const MethodChannel('com.dvlproad.ciyouzen/platform_channel');

  // 给客户端发送一些东东, 这里先不用去拿回什么
  Future<Null> _showToast() async {
    try {
      await methodChannel.invokeMethod('showToast','这是我在Flutter中写的文字');
    } on PlatformException {

    }
  }

  Future<Null> _goBack() async {
    try {
      await methodChannel.invokeMethod('goBack');
    } on PlatformException {

    }
  }

  Future<Null> _goiosPage() async {
    try {
      Map<String, dynamic> map = { "content": "flutter传给iOS的内容","data":[1,2,3,4,5]};
      await methodChannel.invokeMethod('goiosPage', map);
    } on PlatformException {

    }
  }

  @override
  Widget build(BuildContext context) {
    //Scaffold是Material中主要的布局组件.
    return new Scaffold(
        appBar: new AppBar(
          leading: new IconButton(
            icon: new Icon(Icons.menu),
            tooltip: 'Navigation menu',
            onPressed: null,
          ),
          title: new Text('platform channel Example title'),
          actions: <Widget>[
//            new IconButton(
//              icon: new Icon(Icons.search),
//              tooltip: 'Search',
//              //onPressed: null,
//              onPressed: () {
//                goStatefulMainPage(context);
//              },
//            ),
            new FlatButton(
                child: Image.asset('lib/Resources/nav_right_gray_normal.png'),
                onPressed: (){
                  goStatefulMainPage(context);
                }),
          ],
        ),
        //body占屏幕的大部分
        body: new Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              new Text('Hello, platform channel!'),
              new RaisedButton(
                  child: Text('showToast'),
                  onPressed: (){
                    _showToast();
                  }
              ),
              new RaisedButton(
                  child: Text('goBack'),
                  onPressed: (){
                    _goBack();
                  }
              ),
              new RaisedButton(
                  child: Text('goiosPage'),
                  onPressed: (){
                    _goiosPage();
                  }
              ),

            ],
          ),
        ),
        floatingActionButton: new FloatingActionButton(
          tooltip: 'Add', // used by assistive technologies
          child: new Icon(Icons.add),
          onPressed: () {
             goNextPageNoneParam(context);
          },
        )
    );
  }
}

void goNextPageNoneParam(context) {
  Navigator.push(context, new MaterialPageRoute(builder: (context) {
    return new NewRouteNoneParam();
  }));
}


void goNextPageWithParams(context) {
  Navigator.push(context, new MaterialPageRoute(builder: (context) {
    return new NewRouteWithParam();
  }));
}

void goStatefulMainPage(context) {
  Navigator.push(context, new MaterialPageRoute(builder: (context) {
    return new StatefulMainPage();
  }));
}
```

#### 3.3、其中完整的`StatefulMainPage.dart`代码如下：

```dart
import 'package:flutter/material.dart';
import 'NewRouteNoneParam.dart';

import 'package:flutter/services.dart';
import 'dart:async';


// 已写在main.dart中
//void main() => runApp(MyApp());
//
//class MyApp extends StatelessWidget {
//  @override
//  Widget build(BuildContext context) {
//    return MaterialApp(
//      title: 'Platform Channel Demo',
//
//      home: StatefulMainPage(),
//    );
//  }
//}

class StatefulMainPage extends StatefulWidget {

  @override
  State<StatefulWidget> createState() {
    // TODO: implement createState
    return StatefulMainPageState();
  }
}

class StatefulMainPageState extends State<StatefulMainPage> {
  // 创建一个给native的channel (类似iOS的通知）
  static const methodChannel = const MethodChannel(
      'com.dvlproad.ciyouzen/platform_channel');

  // 给客户端发送一些东东, 这里先不用去拿回什么
  Future<Null> _showToast() async {
    try {
      await methodChannel.invokeMethod('showToast','这是我在Flutter中写的文字');
    } on PlatformException {

    }
  }

  Future<Null> _goBack() async {
    try {
      await methodChannel.invokeMethod('goBack');
    } on PlatformException {

    }
  }

  Future<Null> _goiosPage() async {
    try {
      Map<String, dynamic> map = { "content": "flutter传给iOS的内容","data":[1,2,3,4,5]};
      await methodChannel.invokeMethod('goiosPage', map);
    } on PlatformException {

    }
  }

  String imageName = "lib/Resources/message_arrow.png";
  Future<Null> _changeLeftBarButtonAction() async {
    try {
      Map<String, dynamic> map = { "content": "flutter传给iOS的内容","data":[1,2,3,4,5]};
      final Map dict = await methodChannel.invokeMethod('changeLeftBarButtonAction', map);

      setState(() {
        imageName = dict["imageName"];
        print(imageName);
      });
    } on PlatformException {

    }
  }

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    _changeLeftBarButtonAction();
  }

  @override
  Widget build(BuildContext context) {
    //Scaffold是Material中主要的布局组件.
    return new Scaffold(
        appBar: new AppBar(
          leading: new FlatButton(
//              child: Image.asset('lib/Resources/nav_back_white_normal.png'),
              child: Image.asset(imageName),
              onPressed: (){
                  _goBack();
              }),

//          new IconButton(
//            icon: new Icon(Icons.menu),
////          icon: Image.asset('lib/Resources/message_arrow.png'),
//            tooltip: 'Navigation menu',
//            onPressed: null,
//          ),
          title: new Text('StatefulMainPage'),
          actions: <Widget>[
//            new IconButton(
//              icon: new Icon(Icons.search),
//              tooltip: 'Search',
//              onPressed: null,
//            ),
            new FlatButton(
                child: Image.asset('lib/Resources/nav_right_gray_normal.png'),
                onPressed: null
            ),
          ],
        ),
        //body占屏幕的大部分
        body: new Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Image.asset('lib/Resources/message_arrow.png'),
              new Text('Hello, StatefulMainPage!'),
              new RaisedButton(
                  child: Text('showToast'),
                  onPressed: (){
                    _showToast();
                  }
              ),
              new RaisedButton(
                  child: Text('goBack'),
                  onPressed: (){
                    _goBack();
                  }
              ),
              new RaisedButton(
                  child: Text('goiosPage'),
                  onPressed: (){
                    _goiosPage();
                  }
              ),

            ],
          ),
        ),
        floatingActionButton: new FloatingActionButton(
          tooltip: 'Add', // used by assistive technologies
          child: new Icon(Icons.add),
          onPressed: () {
            goNextPageNoneParam(context);
          },
        )
    );
  }
}

void goNextPageNoneParam(context) {
  Navigator.push(context, new MaterialPageRoute(builder: (context) {
    return new NewRouteNoneParam();
  }));
}
```

#### 3.4、其中完整的`NewRouteNoneParam.dart`代码如下：

```dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'dart:async';

class NewRouteNoneParam extends StatelessWidget {
  static const methodChannel = const MethodChannel('com.dvlproad.ciyouzen/platform_channel');
  Future<Null> _goBack() async {
    try {
      await methodChannel.invokeMethod('goBack');
    } on PlatformException {

    }
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("New route none Param"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text("This is new route"),
            RaisedButton(
              child: Text('goback'),
              onPressed: _goBack
            )
          ],
        )

      ),
    );
  }
}
```

#### 3.5、其中完整的`NewRouteWithParam.dart`代码如下：

```dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'dart:async';

class NewRouteWithParam extends StatelessWidget {
  static const methodChannel = const MethodChannel('com.dvlproad.ciyouzen/platform_channel');
  Future<Null> _goBack() async {
    try {
      await methodChannel.invokeMethod('goBack');
    } on PlatformException {

    }
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("New route with params"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text("This is new route with params"),
            RaisedButton(
              child: Text('goback'),
              onPressed: _goBack
            )
          ],
        )

      ),
    );
  }
}
```


# 第2节：交互时iOS端的代码编写

本节学习内容：原生iOS项目在交互时的代码编写。

本节学习用时：**30分钟**

本节学习方式：动手实践

本节目录：

一、本节内容介绍

二、本节代码解释

## 一、本节内容介绍

略！

## 二、本节代码解释

### 1、本节代码

```
- (void)showMainFlutterViewControllerWithoutParam {
    self.navigationController.navigationBarHidden = YES;

    FlutterViewController *flutterViewController = [[FlutterViewController alloc] initWithProject:nil nibName:nil bundle:nil];

    NSString *channelName = @"com.dvlproad.ciyouzen/platform_channel";// 要与.dart中一致
    FlutterMethodChannel *messageChannel = [FlutterMethodChannel methodChannelWithName:channelName binaryMessenger:flutterViewController];

    [messageChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult  _Nonnull result) {
        // call.method获取flutter给回到的方法名,要匹配到channelName对应的多个 发送方法名,一般需要判断区分
        // call.arguments获取到flutter给到的参数,(比如跳转到另一个页面所需要参数)
        // result是给flutter的回调,只能回调一次
        NSString *message = [NSString stringWithFormat:@"flutter回调:\n nmethod = %@\n arguments = %@", call.method, call.arguments];
        NSLog(@"%@", message);

        if ([call.method isEqualToString:@"showToast"]) {
            [CJToast shortShowMessage:message];
            return;

        } else if ([call.method isEqualToString:@"goBack"]) {
            [self.navigationController popViewControllerAnimated:YES];
            return;

        } else if ([call.method isEqualToString:@"goiosPage"]) {
            OCCallFlutterViewController *viewController = [[OCCallFlutterViewController alloc] init];
            viewController.parames = call.arguments;
            [self.navigationController pushViewController:viewController animated:YES];
            return;

        } else if ([call.method isEqualToString:@"changeLeftBarButtonAction"]) {
            NSDictionary *params = @{@"imageName":@"lib/Resources/message_arrow.png"};
            result(params);
            return;
        }

        result(FlutterMethodNotImplemented);
    }];

    [self.navigationController pushViewController:flutterViewController animated:YES];
}
```


# 第3节：交互时Android端的代码编写


# 第四章：Flutter登录页实战

本章介绍

* ①通过Flutter构建适用于多平台的登录页，并在Flutter工程中建立提供给原生项目的方法及接收从原生项目返回的值。
* ②原生iOS项目使用Flutter实现的登录页时候的交互代码编写
* ③原生Android使用Flutter实现的登录页时候的交互代码编写

[点击此处，进入本章第1节的正式学习 >](/4-deng-lu-ye-shi-zhan/deng-lu-ye-zai-flutter-gong-cheng-zhong-de-dai-ma)


# 第1节：登录页在Flutter工程中的代码


# 第2节：登录页在iOS工程中的代码


# 第3节：登录页在Android工程中的代码


