Flutter混编:在Android原生中混编Flutter

目前《闲鱼》客户端已经在商品详情页使用纯Flutter编写了,单页面纯Flutter写是没有问题的,在这里顺便提一下怎么简单辨认一个页面是Flutter还是原生(不严谨,在你知道它是有Flutter的情况下,因为RN也会这样的):

  1. 打开手机的“开发者模式”
  2. 打开“显示布局边界”
  3. 切回APP

以《闲鱼》商品列表页和商品详情页为例:

商品列表页和商品详情页

商品列表页和商品详情页

接下来我们打开布局边界显示,可以看到闲鱼的商品列表页(左边)是原生写的,所以可以看到布局层,颜色越深表示布局层数越多,而商品详情页(右边)是Flutter写的,只有一层布局:

布局边界

布局边界

好,题外话扯完,下面开始在Android中来混编Flutter,就使用之前的《日报》的例子。

计划是这样的:

  • 将外层UI框架,包括toolbar,转移到原生中来处理
  • 将ListView,日报数据请求逻辑等由Flutter来处理

这基本可以是一个次简单的混编例子。

最简单的。。。还要数Flutter官方的那个例子(生肉警告):https://github.com/flutter/flutter/wiki/Add-Flutter-to-existing-apps

新建Flutter Module

想在Native代码中混编Flutter,需要在Flutter中建立一个Module,官方通过命令行创建,但Android Studio的Flutter插件同样提供了这个功能,而且更加直观,所以这里以Android Studio为例:

新建Flutter Module

新建Flutter Module

接下来项目路径,名称等:

Flutter Module路径配置

Flutter Module路径配置

之后设置包名,再点击finish就好了。

新建Android工程

在Flutter Module同一父目录下新建Android工程,如果已有的工程也可以,同样道理。

新建Android工程

新建Android工程

之后一路next下去。

Android工程中引入Flutter

在建立的Android工程的settings.gradle中加入以下代码:

1
2
3
4
5
setBinding(new Binding([gradle: this]))                                 
evaluate(new File(                                                      
  settingsDir.parentFile,                                               
  'flutter_daily_module/.android/include_flutter.groovy'                          
))

其中settingsDir.parentFile表示当前目录的父级目录,flutter_daily_module是前面所建立的Flutter Module目录。

Sync一下就可以看到多了一个Flutter的library module

Flutter Module

Flutter Module

之后在Application Module的Build.gradle中依赖刚刚引入的library:

1
implementation project(':flutter')

Android原生代码调用Flutter

先来一个简单的Demo。

我们把Flutter中的代码改成如下:

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

void main() => runApp(_widgetForRoute(window.defaultRouteName));

Widget _widgetForRoute(String route) {
  switch (route) {
    case 'route1':
      return Center(
        child: Text('route: $route', textDirection: TextDirection.ltr),
      );
    case 'route2':
      return Center(
        child: Text('route: $route', textDirection: TextDirection.ltr),
      );
    default:
      return Center(
        child: Text('Unknown route: $route', textDirection: TextDirection.ltr),
      );
  }
}

上面代码中会返回一个Widget,如果传入参数为route1就会在widget居中位置显示route: router1

在Android项目默认生成的MainActivity中,我们来展示一个route1。

1
2
3
4
5
6
7
8
9
View flutterView = Flutter.createView(
        MainActivity.this,
        getLifecycle(),
        "route1"
);
FrameLayout.LayoutParams layout = new FrameLayout.LayoutParams(600, 800);
layout.leftMargin = 100;
layout.topMargin = 400;
addContentView(flutterView, layout);

运行原生Android工程,可以看到如下效果:

FlutterView运行效果

FlutterView运行效果

FlutterView不是唯一的使用方式,还有一种通过FlutterFragment来调用Flutter的代码方式,如下代码所示:

1
2
3
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.fl_flutter_view, Flutter.createFragment("route1"));
fragmentTransaction.commit();

其中fl_flutter_view对应Android中在xml中定义的一个FrameLayout:

1
2
3
4
<FrameLayout
    android:id="@+id/fl_flutter_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

运行效果图如下所示:

FlutterFragment运行效果

FlutterFragment运行效果

Android原生外层框架中嵌入Flutter日报列表

完成上面步骤以后,我们已经了解在Android中是怎么调用Flutter的,下面把实战系列文章的《日报》列表页嵌入这个“外框”中。

《日报》纯Flutter项目源码:
https://github.com/KevinWu1993/DailyFlutter

我们先把Flutter中列表页的主要代码同步到flutter_daily_module中。

原本《日报》的main.dart代码如下:

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

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '日报',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: DailyPage(title: '日报'),
    );
  }
}

Flutter Module中main.dart我们改成如下:

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
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter_daily_module/daily/daily_page.dart';

void main() => runApp(_widgetForRoute(window.defaultRouteName));

Widget _widgetForRoute(String route) {
  switch (route) {
    case 'dailyInNative':
      return MaterialApp(
        title: '日报',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: DailyPage(title: '日报', isShowToolbar: false,),
      );
    default:
      return MaterialApp(
        title: '日报',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: DailyPage(title: '日报'),
      );
  }
}

要注意的是default选项,因为Flutter Module也是可以独立运行的,所以留着这个case。

同样的在DailyPage这一个Widget的构造方法里面,增加一个可选“是否显示Toolbar”的参数,目的是在独立运行flutter项目的时候显示状态栏,而在作为library module混编进Android的时候隐藏Toolbar,使用Android原生的Toolbar。

1
DailyPage({Key key, this.title, this.isShowToolbar = true}) : super(key: key);

对于Flutter Toolbar,在DailyPage中构建的方法如下:

1
2
3
4
5
6
7
8
Widget _buildAppBar(BuildContext buildContext) {
  if (widget.isShowToolbar)
    return new AppBar(
      title: Text(widget.title),
    );
  else
    return null;
}

完成上述步骤后,在Android原生代码中调用如下:

1
2
3
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.fl_flutter_view, Flutter.createFragment("dailyInNative"));
fragmentTransaction.commit();

到此就完成了日报列表的嵌入,那么我们来运行一下:

原生运行效果

原生运行效果

而当使用Flutter独立模式运行的时候,它是这样的:

Flutter独立运行效果

Flutter独立运行效果

无图无真相,以一个布局边界对比图来结束这篇文章吧,注意看Toolbar,一个是原生的,一个是Flutter的:

布局边界对比

布局边界对比

文章作者: Kevin Wu

文章链接: https://kevinwu.cn/p/964c6c3/

猜你喜欢

转载自blog.csdn.net/MYBOYER/article/details/90737342