在Android项目中接入Flutter,在Flutter使用安卓布局

开头

在flutter开发中,始终会有下面两个无法避免的问题:

  • 原生项目往flutter迁移,就需要在原生项目中接入flutter
  • flutter项目中要使用到一些比较成熟的应用,就无法避免去用到原生的各种成熟库,比如音视频之类的

这篇文章,将会对上面两种情况,分别进行介绍

在Android中接入flutter界面

在android项目中需要将flutter以module的形式接入

创建flutter module

进入当前android项目,在根目录运行如下命令:

flutter create -t module my_flutter

上面表示创建一个名为 my_flutter 的flutter module

之后运行

cd my_flutter
cd .android/
./gradlew flutter:assembleDebug

同时,确保你的在你的android项目目录下 app/build.gradle ,有添加如下代码:

android {
 compileSdkVersion 28
 defaultConfig {
 ...
 }
 buildTypes {
 ...
 }
 //flutter相关声明
 compileOptions {
 sourceCompatibility 1.8
 targetCompatibility 1.8
 }
}

接着,在android项目 根目录下的 settings.gradle 中添加如下代码

include ':app'
setBinding(new Binding([gradle: this]))
evaluate(new File(
 rootDir.path + '/my_flutter/.android/include_flutter.groovy'
))

最后,需要在android项目下的 app/build.gradle 中引入 my_flutter

dependencies {
 ...
 //导入flutter
 implementation project(':flutter')
}

到这里,基本上就可以开始接入flutter的内容了

不过这时候还有一个问题需要注意,如果你的android项目已经迁移到了androidx,可能你会遇到下面的这种问题

手把手教你在Android项目中接入Flutter,在Flutter使用安卓布局

这种问题明显是因为flutter创建moudle时,并未做到androidx的转换,因为创建moudle的命令还不支持androidx

下面就开始解决这个问题

解决androidx带来的问题

首先,如果你原先的android项目已经迁移到了androidx,那么在根目录下的 grale.properties 一定有如下内容

# 表示使用 androidx
android.useAndroidX=true
# 表示将第三方库迁移到 androidx
android.enableJetifier=true

下面进入到 my_flutter 目录下,在 你的android项目/my_flutter/.android/Flutter/build.gradle 中对库的依赖部分进行修改

如果默认的内容如下:

dependencies {
 testImplementation 'junit:junit:4.12'
 implementation 'com.android.support:support-v13:27.1.1'
 implementation 'com.android.support:support-annotations:27.1.1'
}

将所有依赖修改为androidx的版本:

dependencies {
 testImplementation 'junit:junit:4.12'
 implementation 'androidx.legacy:legacy-support-v13:1.0.0'
 implementation 'androidx.annotation:annotation:1.0.0'
}

在android studio上点击完 Sync Now 同步之后

再进入下面的目录 你的android项目/my_flutter/.android/Flutter/src/main/java/io/flutter/facade/ 目录下,对 Flutter.java和 FlutterFragment.java 分别进行修改

修改FlutterFragment.java

原本的依赖如下

将报错部分替换为androidx的版本

import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;

修改Flutter.java

原本的依赖如下

手把手教你在Android项目中接入Flutter,在Flutter使用安卓布局

将报错部分替换为androidx的版本

import androidx.annotation.NonNull;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.OnLifecycleEvent;

那么现在,androidx带来的问题就解决了,下面就开始准备正式接入Flutter

在flutter中编辑入口

进入 my_flutter 目录中的lib目录,可以看到会有系统自带的 main.dart 文件,这是一个默认的计数器页面,我们修改一部分:

void main() => runApp(getRouter(window.defaultRouteName));
Widget getRouter(String name) {
 switch (name) {
 case 'route1':
 return MyApp();
 default:
 return Center(
 child: Text('Unknown route: $name', textDirection: TextDirection.ltr),
 );
 }
}

将入口更换为通过“route1" 命名进入进入

接下来就是在android中进行操作了

在android中接入flutter

进入到android项目,在MainActivity中,我们做如下操作:

 bt_flutter.setOnClickListener {
 val flutterView = Flutter.createView(
 this@MainActivity,
 lifecycle,
 "route1"
 )
 val layout = ConstraintLayout.LayoutParams(
 ViewGroup.LayoutParams.MATCH_PARENT,
 ViewGroup.LayoutParams.MATCH_PARENT
 )
 layout.leftMargin = 0
 layout.bottomMargin = 26
 addContentView(flutterView, layout)
 }

从上面的代码可以看到,我们通过一个按钮的点击事件去展示了flutter的计数器页面。实际效果如下:

image.png

那么android接入flutter就结束了,下面是在flutter中接入android

在Flutter中接入android界面

我们可以新建一个flutter项目,用于测试这个例子

因为用到了kotin,所以使用以下命令

flutter create -a kotlin counter_native

项目创建好之后,就可以开始了,在开始之前,我们首先可以了解以下如何在flutter中拿到android中的数据

获取android数据

关于如何去获取数据,主要还是使用 MethodChannel

看一下android中MainActivity的代码

class MainActivity: FlutterActivity() {
 private val channelName = "samples.flutter.io/counter_native";
 override fun onCreate(savedInstanceState: Bundle?) {
 super.onCreate(savedInstanceState)
 GeneratedPluginRegistrant.registerWith(this)
 MethodChannel(flutterView, channelNameTwo).setMethodCallHandler { methodCall, result ->
 when(methodCall.method){
 "getCounterData" -> {
 result.success(getCounterData())
 }
 else -> {
 result.notImplemented();
 }
 }
 }
 }
 private fun getCounterData():Int{
 return 100;
 }
}

MethodChannel的结果回调中,我们进行了筛选,如果方法名是 getCounterData就直接返回100

接下来在flutter中编写下面的代码:

static const platform =
 const MethodChannel('samples.flutter.io/counter_native');
void getCounterData() async {
 int data;
 try {
 final int result = await platform.invokeMethod('getCounterData');
 data = result;
 } on PlatformException catch (e) {
 data = -999;
 }
 setState(() {
 counterData = data;
 });
 }

效果如下:

image.png

获取android的数据就说到这里,下面就是去获取android的页面了

获取android的布局

相较于数据而言,拿到android的布局就要复杂的多

创建android视图

在android项目里面,创建一个想要展示在flutter中的布局,这里,我们结合xml文件来创建布局,不过使用xml的方式,会出现R文件找不到的情况,这时候编译器会报错,暂时不用去管:

class CounterView(context: Context, messenger: BinaryMessenger, id: Int)
 : PlatformView, MethodChannel.MethodCallHandler {
 private var methodChannel: MethodChannel =
 MethodChannel(messenger, "samples.flutter.io/counter_view_$id")
 private var counterData: CounterData = CounterData()
 private var view: View = LayoutInflater.from(context).inflate(R.layout.test_layout, null);
 private var myText: TextView
 init {
 methodChannel.setMethodCallHandler(this)
 myText = view.findViewById(R.id.tv_counter)
 }
 override fun getView(): View {
 return view
 }
 override fun dispose() {
 }
 override fun onMethodCall(methodCall: MethodCall, result: MethodChannel.Result) {
 when (methodCall.method) {
 "increaseNumber" -> {
 counterData.counterData++
 myText.text = "当前Android的Text数值是:${counterData.counterData}"
 result.success(counterData.counterData)
 }
 "decreaseNumber" -> {
 counterData.counterData--
 myText.text = "当前Android的Text数值是:${counterData.counterData}"
 result.success(counterData.counterData)
 }
 "decreaseSize" -> {
 if(myText.textSize > 0){
 val size = myText.textSize
 myText.setTextSize(TypedValue.COMPLEX_UNIT_PX,size-1)
 result.success(myText.textSize)
 } else{
 result.error("出错", "size无法再小了!", null)
 }
 }
 "increaseSize" -> {
 if(myText.textSize < 100){
 val size = myText.textSize
 myText.setTextSize(TypedValue.COMPLEX_UNIT_PX,size+1)
 result.success(myText.textSize)
 } else{
 result.error("出错", "size无法再大了!", null)
 }
 }
 "setText" -> {
 myText.text = (methodCall.arguments as String)
 result.success(myText.text)
 }
 else -> {
 result.notImplemented();
 }
 }
 }
}

上面的 CounterData 类是用于存储数据创建的一个类:

class CounterData(var counterData: Int = 0) {
}

接下来,我们创建一个 CounterViewFactory 类用于获取到布局:

class CounterViewFactory(private val messenger: BinaryMessenger)
 : PlatformViewFactory(StandardMessageCodec.INSTANCE) {
 override fun create(context: Context, id: Int, o: Any?): PlatformView {
 return CounterView(context, messenger, id)
 }
}

最后创建一个 CounterViewPlugin.kt 文件,它用于注册视图,相当于初始化入口

class CounterViewPlugin{
 fun registerWith(registrar: Registrar) {
 registrar.platformViewRegistry().registerViewFactory("samples.flutter.io/counter_view", CounterViewFactory(registrar.messenger()))
 }
}

创建完成后,在MainActivity中进行视图注册:

override fun onCreate(savedInstanceState: Bundle?) {
 super.onCreate(savedInstanceState)
 CounterViewPlugin().registerWith(flutterView.pluginRegistry.registrarFor("CounterViewPlugin"))
 ...
 }

接下来,就是在flutter中需要做的一些事情了

在flutter中获取android视图

在flutter里面,想要拿到android的视图,需要通过 AndroidView 去获取

Widget build(BuildContext context) {
 if (Platform.isAndroid) {
 return AndroidView(
 viewType: 'samples.flutter.io/counter_view',
 onPlatformViewCreated: _onPlatformViewCreated,
 );
 }
 return Text(
 '$defaultTargetPlatform 还不支持这个布局');
 }

在 onPlatformViewCreated 方法中,我们需要创建 MethodChannel,用于调用android中编写的方法,我们可以封装一个Controller去处理这些逻辑:

 final CounterController counterController;
 void _onPlatformViewCreated(int id) {
 if (widget.counterController == null) {
 return;
 }
 widget.counterController.setId(id);
 }

下面是 CounterController

typedef void CounterViewCreatedCallBack(CounterController controller);
class CounterController {
 MethodChannel _channel;
 void setId(int id){
 _channel = new MethodChannel('samples.flutter.io/counter_view_$id');
 print("id");
 }
 Future increaseNumber() async {
 final int result = await _channel.invokeMethod(
 'increaseNumber',
 );
 print("result:${result}");
 }
 Future decreaseNumber() async {
 final int result = await _channel.invokeMethod(
 'decreaseNumber',
 );
 }
 Future increaseSize() async {
 final result = await _channel.invokeMethod(
 'increaseSize',
 );
 }
 Future decreaseSize() async {
 final result = await _channel.invokeMethod(
 'decreaseSize',
 );
 }
 Future setText(String text) async {
 final result = await _channel.invokeMethod(
 'setText',text,
 );
 }
}

效果如下:

image.png

最后

给大家分享一份移动架构大纲,包含了移动架构师需要掌握的所有的技术体系,大家可以对比一下自己不足或者欠缺的地方有方向的去学习提升;

需要高清架构图以及图中视频资料和文章项目源码的可以加入我的技术交流群:825106898私聊群主小姐姐免费获取

image
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容