该文已受权公众号 「码个蛋」,转载请指明出处php
Flutter 说到底只是一个 UI 框架,不少功能都须要经过原生的 Api 来实现,那么就会涉及到 Flutter 和 Native 的交互,由于本人不懂 iOS 开发,因此只能讲下 Flutter 同 Android 的交互。java
既然是互相交互,那么须要准备一个 Android 项目。接着就须要建立 flutter module,让 Android 项目依赖,建立的方法能够参考官网 Flutter Wiki,虽然是官网提供的方法,可是彻底按照这个步骤来,仍是会有坑的,这边就慢慢一步步解决坑。android
若是你用的是 Android Studio 进行开发的话,直接打开底部的 Terminal,直接建立 flutter module 依赖git
flutter create -t module flutter_native_contact
至于 module 名能够随意填写,module 建立完后结构大概是这样的github
接着切换到 module 下的 .android 文件夹,接着有坑来了,官网提供的方法是 ./gradlew flutter:assembleDebug
可能会提示命令不存在,那么直接经过 gradlew flutter:assembleDebug
来运行,等它自动跑完后,打开根目录下的 settings.gradle
文件,加入官网提供的 gradle 代码api
setBinding(new Binding([gradle: this])) // new
evaluate(new File( // new
settingsDir.parentFile, // new
'flutter_native_contact/.android/include_flutter.groovy' // new
)) // new
复制代码
你觉得这里没坑,真是图样图森破,没坑是不可能的,编译器大爷可能会给你甩这么个错误markdown
很明显能够看出是找不到咱们的文件,因此把文件名路径给补全app
evaluate(new File( // new
settingsDir.parentFile, // new
'FlutterNativeContactDemo/flutter_native_contact/.android/include_flutter.groovy' // 这里补全路径
))
复制代码
接着打开原有项目下,原有项目下,原有项目下的 app 中的 build.gradle
文件,在 android 下加上以下代码框架
compileOptions {
sourceCompatibility 1.8
targetCompatibility 1.8
}
复制代码
这个必需要加,不要问为何,我也不知道为何,最后在项目下添加 flutter module 的依赖就完成了。这个过程告诉咱们一个什么道理呢?*不要觉得官网的都对,官网讲的也不是彻底可信的,时不时给你来个坑就能卡你老半天。less
那么如何在原生界面显示 Flutter 界面呢,这个就须要经过 FlutterView 来实现了,Flutter 这个类提供了 createView
和 createFragment
两个方法,分别用于返回 FlutterView 和 FlutterFragment 实例,FlutterFragment 的实现原理也是经过 FlutterView 来实现的,能够简单看下 FlutterFragment 的源码
/** * A {@link Fragment} managing a {@link FlutterView}. * * <p><strong>Warning:</strong> This file is auto-generated by Flutter tooling. * DO NOT EDIT.</p> */
public class FlutterFragment extends Fragment {
public static final String ARG_ROUTE = "route";
private String mRoute = "/";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 获取传入的路由值,默认为 '/'
if (getArguments() != null) {
mRoute = getArguments().getString(ARG_ROUTE);
}
}
@Override
public FlutterView onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// 最后仍是挺过 createView 方法来生成页面,只不过直接放在 fragment,
// 放在 fragment 会比直接 使用 FlutterView 更方便管理,例如实现 ViewPager 等
return Flutter.createView(getActivity(), getLifecycle(), mRoute);
}
}
复制代码
在原生页面显示 Flutter 界面的第一种方式就是加载 FlutterFragment,看个比较简单的例子吧
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity">
<!-- 这个布局用于加载 fragment -->
<FrameLayout android:id="@+id/fragment_container" android:layout_width="match_parent" android:layout_height="match_parent" />
<android.support.design.widget.FloatingActionButton android:id="@+id/flutter_fragment" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="20dp" android:layout_marginBottom="50dp" android:src="@drawable/ic_add_white_36dp" app:fabSize="auto" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" />
</android.support.constraint.ConstraintLayout>
复制代码
在 Activity 能够直接经过返回 FlutterFragment 加载到 FrameLayout 便可
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
supportFragmentManager.beginTransaction()
.add(R.id.fragment_container, Flutter.createFragment("route_flutter"))
.commit()
}
}
复制代码
这样就把 Flutter 页面加载到原生界面了,会经过传递的路由值在 dart 层进行查找,因此接着就须要编写 Flutter 界面
/// runApp 内部值也能够直接传入 _buildWidgetForNativeRoute 方法
/// 这边在外层嵌套一层 MaterialApp 主要是防止一些没必要要的麻烦,
/// 例如 MediaQuery 这方面的使用等
void main() => runApp(FlutterApp());
class FlutterApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: _buildWidgetForNativeRoute(window.defaultRouteName),
debugShowCheckedModeBanner: false,
theme: ThemeData(
primaryColor: Color(0XFF008577),
accentColor: Color(0xFFD81B60),
primaryColorDark: Color(0xFF00574B),
iconTheme: IconThemeData(color: Color(0xFFD81B60)),
),
);
}
}
/// 该方法用于判断原生界面传递过来的路由值,加载不一样的页面
Widget _buildWidgetForNativeRoute(String route) {
switch (route) {
case 'route_flutter':
return GreetFlutterPage();
// 默认的路由值为 '/',因此在 default 状况也须要返回页面,不然 dart 会报错,这里默认返回空页面
default:
return Scaffold();
}
}
class GreetFlutterPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('NativeMessageContactPage'),
),
body: Center(
child: Text(
'This is a flutter fragment page',
style: TextStyle(fontSize: 20.0, color: Colors.black),
),
),
);
}
}
复制代码
运行后能够看到页面加载出来了,不过会有一段时间的空白,这个在正式打包后就不会出现,因此没必要担忧。最后的页面应该是这样的
接着看下 createView 方法,说白了,第一种方法最后仍是会经过该方式实现
@NonNull
public static FlutterView createView(@NonNull final Activity activity, @NonNull final Lifecycle lifecycle, final String initialRoute) {
// 交互前的一些初始化工做,须要完成才能够继续下一步,同时须要保证当前线程为主线程
// Looper.myLooper() == Looper.getMainLooper(),不然会甩你一脸的 IllegalStateException
FlutterMain.startInitialization(activity.getApplicationContext());
FlutterMain.ensureInitializationComplete(activity.getApplicationContext(), null);
final FlutterNativeView nativeView = new FlutterNativeView(activity);
// 将 flutter 页面绑定到相应的 activity
final FlutterView flutterView = new FlutterView(activity, null, nativeView) {
// ......
};
// 将路由值传到 flutter 层,并加载相应的页面,
if (initialRoute != null) {
flutterView.setInitialRoute(initialRoute);
}
// 绑定 lifecycle,方便生命周期管理,同 activity 绑定
// 不熟悉 LifeCycle 的同窗能够自行网上查找资料
lifecycle.addObserver(new LifecycleObserver() {
@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
public void onCreate() {
// 配置一些参数,传递到 flutter 层
final FlutterRunArguments arguments = new FlutterRunArguments();
arguments.bundlePath = FlutterMain.findAppBundlePath(activity.getApplicationContext());
arguments.entrypoint = "main";
// 最终会调用方法 nativeRunBundleAndSnapshotFromLibrary,这是一个 native 方法,进行交互
flutterView.runFromBundle(arguments);
// 进行注册
GeneratedPluginRegistrant.registerWith(flutterView.getPluginRegistry());
}
// ......
});
return flutterView;
}
复制代码
经过 createView 方法返回的 FlutterView,经过设置 Layoutparams 参数就能够添加到相应的布局上,还有一种直接经过 addContentView 方式进行加载,这里直接修改原有代码,
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// setContentView(R.layout.activity_main) 不须要这一步了
val flutterView = Flutter.createView(this@ContactActivity, lifecycle, "route_flutter")
val lp = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)
addContentView(flutterView, lp) // 直接加载到 activity 页面
}
复制代码
可是经过这样加载的话,那么整个页面都是 flutter 的页面。那么以前的效果的 FAB 则不会被加载出来了,即便没有省略 setContentView(R.layout.activity_main)
方法,这个页面的 xml 布局也会被覆盖。
那么可以在原生界面显示 flutter 页面了,如何互相交互呢,这就须要经过 PlantformChannel 来执行了,PlantformChannel 主要有三种类型,BasicMessageChannel,MethodChannel,EventChannel。经过查看源码能够发现,三个 Channel 的实现机制相似,都是经过 BinaryMessenger 进行信息交流,每一个 Channel 经过传入的 channel name 进行区分,因此在注册 Channel 的时候必需要保证 channel name 是惟一的,同时须要传入一个 BinaryMessageHandler 实例,用于传递信息的处理,当 Handler 处理完信息后,会返回一个 result,而后经过 BinaryMessenger 将 result 返回到 Flutter 层。若是须要深刻理解这边推荐一篇文章深刻理解Flutter PlatformChannel
接下来直接看例子吧,在建立 PlatformChannel 的时候须要传入一个 BinaryMessenger 实例,经过查看 FlutterView 的源码能够发现,FlutterView 就是一个 BinaryMessenger 在 Android 端的实现,因此呢,能够直接经过前面介绍的 Flutter.createView
方法获取注册 Channel 时的 BinaryMessenger 实例了,真是得来所有费工夫~由于通讯的方法可能在多个界面会使用,因此仍是封装一个通用类来处理会比较合理
BasicMessageChannel 用于传递字符串和半结构化的信息。
class FlutterPlugin(private val flutterView: FlutterView) :BasicMessageChannel.MessageHandler<Any>{
companion object {
private const val TAG = "FlutterPlugin"
@JvmStatic
fun registerPlugin(flutterView: FlutterView): FlutterPlugin {
// channel name 须要保持两侧一致
val messageChannel =
BasicMessageChannel(flutterView, Constant.MESSAGE_CHANNEL_NAME, StandardMessageCodec.INSTANCE) // MessageCodec 有多种实现方式,能够参考推荐的文章
val instance = FlutterPlugin(flutterView)
messageChannel.setMessageHandler(instance) // 注册处理的 Hnadler
return instance
}
}
override fun onMessage(`object`: Any?, reply: BasicMessageChannel.Reply<Any>?) {
// 简单的将从 Flutter 传过来的消息进行吐司,同时返回本身的交互信息
// `object` 中包含的就是 Flutter 层传递过来的信息,reply 实例用于传递信息到 Flutter 层
Toast.makeText(flutterView.context, `object`.toString(), Toast.LENGTH_LONG).show()
reply?.reply("\"Hello Flutter\"--- an message from Android")
}
}
复制代码
接着就须要有个 FlutterView 用来注册,新建一个 Activity,用于加载 Flutter 页面
class ContactActivity : AppCompatActivity() {
private lateinit var plugin: FlutterPlugin
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 传入路由值,须要在 flutter 层生成相应的界面
val flutterView = Flutter.createView(this@ContactActivity, lifecycle, "route_contact")
val lp = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)
addContentView(flutterView, lp)
plugin = FlutterPlugin.registerPlugin(flutterView)
}
override fun onDestroy() {
super.onDestroy()
}
}
复制代码
那么咱们就要在 Flutter 界面的 _buildWidgetForNativeRoute
方法加入新路由值对应的界面
Widget _buildWidgetForNativeRoute(String route) {
switch (route) {
// ...
case 'route_contact':
return FlutterContactPage();
default:
return Scaffold();
}
}
class FlutterContactPage extends StatelessWidget {
// 注册对应的 channel,要保证 channel name 和原生层是一致的
final BasicMessageChannel _messageChannel =
BasicMessageChannel(MESSAGE_CHANNEL_NAME, StandardMessageCodec());
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter Page'),
),
// 简单放一个按钮,经过 channel 传输消息过去,同时将原生层返回的消息打印出来
body: RaisedButton(
onPressed: () {
_messageChannel
.send('"Hello Native" --- an message from flutter')
.then((str) {
print('Receive message: $str');
});
},
child: Text('Send Message to Native'),
),
);
}
}
复制代码
最后的效果小伙伴能够自行执行,点击按钮后会弹出吐司,吐司内容就是 Flutter 传递的信息,同时在控制台能够看到从原生层返回的信息。
MethodChannel 用于传递方法调用(method invocation)
直接在上述例子中进行修改,例如在 Flutter 页面中实现 Activity 的 finish 方法,并传递参数到前一个界面,先作 Flutter 页面的修改,在 AppBar 上增长一个返回按钮,用于返回上层页面
class FlutterContactPage extends StatelessWidget {
// 注册对应的 channel,要保证 channel name 和原生层是一致的
final BasicMessageChannel _messageChannel =
BasicMessageChannel(MESSAGE_CHANNEL_NAME, StandardMessageCodec());
final MethodChannel _methodChannel = MethodChannel(METHOD_CHANNEL_NAME);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: InkWell(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 20.0),
child: Icon(Icons.arrow_back),
),
onTap: () {
_methodChannel
// invokeMethod 第一个值用于传递方法名,第二个值用于传递参数,
// 这边简单的传递一个字符串,固然也能够传递别的类型,map,list 等等
.invokeMethod<bool>('finishActivity', 'Finish Activity')
.then((result) { // 这边会返回一个结果值,经过判断是否成功来打印不一样的信息
print('${result ? 'has finish' : 'not finish'}');
});
},
),
title: Text('Flutter Page'),
),
body: // ...
);
}
}
复制代码
同时,咱们须要在 FlutterPlugin 这个类中,作些必要的修改,首先须要实现 MethodCallHandler
接口,该接口中须要实现 onMethodCall
方法,经过获取调用的方法名和参数值,进行相应的处理
class FlutterPlugin(private val flutterView: FlutterView) :
MethodChannel.MethodCallHandler, BasicMessageChannel.MessageHandler<Any> {
companion object {
private const val TAG = "FlutterPlugin"
@JvmStatic
fun registerPlugin(flutterView: FlutterView): FlutterPlugin {
val instance = FlutterPlugin(flutterView)
val methodChannel = MethodChannel(flutterView, Constant.METHOD_CHANNEL_NAME)
// ...
messageChannel.setMessageHandler(instance)
return instance
}
}
// ....
// call 中携带了 Flutter 层传递过来的方法名和参数信息
// 能够分别经过 call.method 和 call.arguments 来获取
override fun onMethodCall(call: MethodCall?, result: MethodChannel.Result?) {
when (call?.method) {
"finishActivity" -> {
val activity = flutterView.context as Activity
val info = call.arguments.toString()
val intent = Intent().apply {
putExtra("info", info)
}
activity.setResult(Activity.RESULT_OK, intent)
activity.finish()
// 成功时候经过 result.success 返回值,
// 若是发生异常,经过 result.error 返回异常信息
// Flutter 经过 invokeMethod().then() 来处理正常结束的逻辑
// 经过 catchError 来处理发生异常的逻辑
result?.success(true)
}
// 若是未找到对应的方法名,则经过 result.notImplemented 来返回异常
else -> result?.notImplemented()
}
}
复制代码
最终的效果,当点击返回按钮的时候,会将 Flutter 层经过 invokeMethod 传递的 arguments 属性吐司出来,同时,控制台会打印出 "has finish" 的信息
EventChannel 用于数据流(event streams)的通讯
EventChannel 的实现方式也相似,EventChannel 能够持续返回多个信息到 Flutter 层,在 Flutter 层的表现就是一个 stream,原生层经过 sink 不断的添加数据,Flutter 层接收到数据的变化就会做出新相应的处理。在 Android 端实现状态的监听能够经过广播来实现。直接看例子,仍是修改上述代码
class FlutterPlugin(private val flutterView: FlutterView) :
MethodChannel.MethodCallHandler, EventChannel.StreamHandler, BasicMessageChannel.MessageHandler<Any> {
private var mStateChangeReceiver: BroadcastReceiver? = null
companion object {
private const val TAG = "FlutterPlugin"
const val STATE_CHANGE_ACTION = "com.demo.plugins.action.StateChangeAction"
const val STATE_VALUE = "com.demo.plugins.value.StateValue"
@JvmStatic
fun registerPlugin(flutterView: FlutterView): FlutterPlugin {
// ...
val streamChannel = EventChannel(flutterView, Constant.STREAM_CHANNEL_NAME)
val instance = FlutterPlugin(flutterView)
methodChannel.setMethodCallHandler(instance)
streamChannel.setStreamHandler(instance)
messageChannel.setMessageHandler(instance)
return instance
}
}
// 实现 StreamHandler 须要重写 onListen 和 onCancel 方法
// onListen 不会每次数据改变就会调用,只在 Flutter 层,eventChannel 订阅广播
// 的时候调用,当取消订阅的时候则会调用 onCancel,
// 因此当开始订阅数据的时候,注册接收数据变化的关闭,
// 在取消订阅的时候,将注册的广播注销,防止内存泄漏
override fun onListen(argument: Any?, sink: EventChannel.EventSink?) {
mStateChangeReceiver = createEventListener(sink)
flutterView.context.registerReceiver(mStateChangeReceiver, IntentFilter(STATE_CHANGE_ACTION))
}
override fun onCancel(argument: Any?) {
unregisterListener()
}
// 在 activity 被销毁的时候,FlutterView 不必定会调用销毁生命周期,或者会延时调用
// 这就须要手动去注销一开始注册的广播了
fun unregisterListener() {
if (mStateChangeReceiver != null) {
flutterView.context.unregisterReceiver(mStateChangeReceiver)
mStateChangeReceiver = null
}
}
private fun createEventListener(sink: EventChannel.EventSink?):
BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (TextUtils.equals(intent?.action, STATE_CHANGE_ACTION)) {
// 这边广播只作简单的接收一个整数,而后经过 sink 传递到 Flutter 层
// 固然,sink 还有 error 方法,用于传递发生的错误信息,
// 以及 endOfStream 方法,用于结束接收
// 在 Flutter 层分别有 onData 对应 success 方法,onError 对应 error 方法
// onDone 对应 endOfStream 方法,根据不一样的回调处理不一样的逻辑
sink?.success(intent?.getIntExtra(STATE_VALUE, -1))
}
}
}
}
复制代码
在 Flutter 层,经过对 stream 的监听,对返回的数据进行处理,为了体现出变化,这边修改为 SatefulWidget 来存储状态
class FlutterContactPage extends StatefulWidget {
@override
_FlutterContactPageState createState() => _FlutterContactPageState();
}
class _FlutterContactPageState extends State<FlutterContactPage> {
final MethodChannel _methodChannel = MethodChannel(METHOD_CHANNEL_NAME);
final EventChannel _eventChannel = EventChannel(STREAM_CHANNEL_NAME);
final BasicMessageChannel _messageChannel =
BasicMessageChannel(MESSAGE_CHANNEL_NAME, StandardMessageCodec());
StreamSubscription _subscription;
var _receiverMessage = 'Start receive state'; // 初始的状态值
@override
void initState() {
super.initState();
// 当页面生成的时候就开始监听数据的变化
_subscription = _eventChannel.receiveBroadcastStream().listen((data) {
setState(() {
_receiverMessage = 'receive state value: $data'; // 数据变化了,则修改数据
});
}, onError: (e) {
_receiverMessage = 'process error: $e'; // 发生错误则显示错误信息
}, onDone: () {
_receiverMessage = 'receive data done'; // 发送完毕则直接显示完毕
}, cancelOnError: true);
}
@override
void dispose() {
super.dispose();
_subscription.cancel(); // 当页面销毁的时候须要将订阅取消,防止内存泄漏
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: InkWell(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 20.0),
child: Icon(Icons.arrow_back),
),
onTap: () {
// MethodChannel demo
_methodChannel
.invokeMethod<bool>('finishActivity', _receiverMessage)
.then((result) {
print('${result ? 'has finish' : 'not finish'}');
}).catchError((e) {
print('error happend: $e');
});
},
),
title: Text('Flutter Page'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
// EventChannel demo,页面直接显示信息的变化
child: Text(
_receiverMessage,
style: TextStyle(fontSize: 20.0, color: Colors.black),
),
),
// BasicMessageChannel demo
RaisedButton(
onPressed: () {
_messageChannel
.send('"Hello Native" --- an message from flutter')
.then((str) {
print('Receive message: $str');
});
},
child: Text('Send Message to Native'),
),
],
),
),
);
}
}
复制代码
同时,须要在 Activity 层调用一个定时任务不断的发送广播
class ContactActivity : AppCompatActivity() {
private var timer: Timer? = null
private var task: TimerTask? = null
private lateinit var random: Random
private lateinit var plugin: FlutterPlugin
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
random = Random() // 生成随机整数
val flutterView = Flutter.createView(this@ContactActivity, lifecycle, "route_contact")
val lp = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)
addContentView(flutterView, lp)
plugin = FlutterPlugin.registerPlugin(flutterView)
timer = Timer() // 定时器
task = timerTask { // 定时任务
sendBroadcast(Intent(FlutterPlugin.STATE_CHANGE_ACTION).apply {
putExtra(FlutterPlugin.STATE_VALUE, random.nextInt(1000))
})
}
timer?.schedule(task, 3000, 2000) // 延时 3s 开启定时器,并 2s 发送一次广播
}
override fun onDestroy() {
super.onDestroy()
// 页面销毁的时候须要将定时器,定时任务销毁
// 同时注销 Plugin 中注册的广播,防止内存泄漏
timer?.cancel()
timer = null
task?.cancel()
task = null
plugin.unregisterListener()
}
}
复制代码
最后的实现效果大概是这样的
Flutter 同 Android 端的交互到这讲的差很少了,和 iOS 的交互其实也相似,只不过在 Android 端经过 FlutterNativeView 来做为 Binarymessenger 的实现,在 iOS 端经过 FlutterBinaryMessenger 协议实现,原理是一致的。至于 Flutter 插件,其实现也是经过以上三种交互方式来实现的,可能咱们目前经过 FlutterView 来做为 BinaryMessenger 实例,插件会经过 PluginRegistry.Registrar 实例的 messenger() 方法来获取 BinaryMessenger 实例。
须要了解插件的写法也能够直接查看官方提供的检测电量插件:Flutter Battery Plugin
在平常开发过程当中,可能会遇到这么一种状况,Flutter 中没有控件,可是在原生有,好比地图控件,那么就须要在 Flutter 显示原生的控件了,那么就须要用到 AndroidView
和 UiKitView
来加载原生的控件,这边以 GoogleMapPlugin
为例
class _GoogleMapState extends State<GoogleMap> {
// 省略部分代码
@override
Widget build(BuildContext context) {
// 省略部分代码
// 判断当前设备是否 android 设备,或者 iOS 设备
if (defaultTargetPlatform == TargetPlatform.android) {
return AndroidView(
viewType: 'plugins.flutter.io/google_maps', // viewType 须要同原生端对应,来加载对应的 view
onPlatformViewCreated: onPlatformViewCreated,
// ....
);
} else if (defaultTargetPlatform == TargetPlatform.iOS) {
return UiKitView(
viewType: 'plugins.flutter.io/google_maps',
onPlatformViewCreated: onPlatformViewCreated,
// ....
);
}
return Text(
'$defaultTargetPlatform is not yet supported by the maps plugin');
}
复制代码
这边只贴出关键部分的代码,多余的代码省略,完整代码能够经过上述连接查看
接着看下 Android
端的代码
public class GoogleMapsPlugin implements Application.ActivityLifecycleCallbacks {
// 省略部分代码
public static void registerWith(Registrar registrar) {
if (registrar.activity() == null) {
// When a background flutter view tries to register the plugin, the registrar has no activity.
// We stop the registration process as this plugin is foreground only.
return;
}
final GoogleMapsPlugin plugin = new GoogleMapsPlugin(registrar);
registrar.activity().getApplication().registerActivityLifecycleCallbacks(plugin);
// 经过 registerViewFactory 方法注册相应的 PlatformViewFactory,
// 其中第一个参数就是 Flutter 端对应的 viewType 参数值
registrar.platformViewRegistry()
.registerViewFactory(
"plugins.flutter.io/google_maps", new GoogleMapFactory(plugin.state, registrar));
}
// 省略部分代码
}
复制代码
那么全部的显示工做都放到 GoogleMapFactory
这个类中了
public class GoogleMapFactory extends PlatformViewFactory {
// 省略部分代码
@SuppressWarnings("unchecked")
@Override
public PlatformView create(Context context, int id, Object args) {
Map<String, Object> params = (Map<String, Object>) args;
final GoogleMapBuilder builder = new GoogleMapBuilder();
// 省略属性设置代码
// 经过 `GoogleMapBuilder` 设置一些初始属性
return builder.build(id, context, mActivityState, mPluginRegistrar);
}
}
复制代码
GoogleMapFactory
继承 PlatformViewFactory
并重写 create
方法,返回一个 PlatformView
实例,这个实例经过 GoogleMapBuilder
进行初始化
// GoogleMapOptionsSink -> Receiver of GoogleMap configuration options.
class GoogleMapBuilder implements GoogleMapOptionsSink {
// 省略部分代码
GoogleMapController build( int id, Context context, AtomicInteger state, PluginRegistry.Registrar registrar) {
final GoogleMapController controller =
new GoogleMapController(id, context, state, registrar, options);
controller.init();
controller.setMyLocationEnabled(myLocationEnabled);
controller.setMyLocationButtonEnabled(myLocationButtonEnabled);
controller.setIndoorEnabled(indoorEnabled);
controller.setTrafficEnabled(trafficEnabled);
controller.setTrackCameraPosition(trackCameraPosition);
controller.setInitialMarkers(initialMarkers);
controller.setInitialPolygons(initialPolygons);
controller.setInitialPolylines(initialPolylines);
controller.setInitialCircles(initialCircles);
controller.setPadding(padding.top, padding.left, padding.bottom, padding.right);
return controller;
}
// 省略部分 set 方法代码
}
复制代码
GoogleMapBuilder
实现了GoogleMapOptionsSink
这个接口,主要用于接收一些地图属性参数,经过 build
方法最终返回的是一个 GoogleMapController
实例
final class GoogleMapController implements Application.ActivityLifecycleCallbacks, // 这里省略了一些地图处理的相关接口 MethodChannel.MethodCallHandler, PlatformView {
GoogleMapController(
int id,
Context context,
AtomicInteger activityState,
PluginRegistry.Registrar registrar,
GoogleMapOptions options) {
// 省略参数 set 代码
methodChannel =
new MethodChannel(registrar.messenger(), "plugins.flutter.io/google_maps_" + id);
methodChannel.setMethodCallHandler(this);
}
@Override
public View getView() {
return mapView;
}
@Override
public void onMethodCall(MethodCall call, MethodChannel.Result result) {
// 省略实现代码,switch .. case
}
@Override
public void dispose() {
if (disposed) {
return;
}
disposed = true;
methodChannel.setMethodCallHandler(null);
mapView.onDestroy();
registrar.activity().getApplication().unregisterActivityLifecycleCallbacks(this);
}
}
复制代码
GoogleMapController
这个类实现的接口比较多,这里主要看两个接口
MethodChannel.MethodCallHandler
对应实现的方法为 onMethodCall
方法,这里就是用于处理 Flutter
层调用原生的方法了,和前面介绍交互的一致PlatformView
对应实现的方法为 getView
和 dispose
方法,getView
返回一个 View
即为须要在 Flutter
层显示的控件了,dispose
方法用于处理一些生命周期相关的逻辑,销毁会形成内存泄漏的实例同时在初始化该类的时候,注册了相应的 MethodChannel
,用于两端的交互,那么在 Flutter
端是哪里注册的 channel
呢,答案是 controller
文件下的 GoogleMapController
类
class GoogleMapController {
GoogleMapController._(
this.channel,
CameraPosition initialCameraPosition,
this._googleMapState,
) : assert(channel != null) {
channel.setMethodCallHandler(_handleMethodCall);
}
static Future<GoogleMapController> init(
int id,
CameraPosition initialCameraPosition,
_GoogleMapState googleMapState,
) async {
assert(id != null);
final MethodChannel channel =
MethodChannel('plugins.flutter.io/google_maps_$id');
await channel.invokeMethod<void>('map#waitForMap');
return GoogleMapController._(
channel,
initialCameraPosition,
googleMapState,
);
}
@visibleForTesting
final MethodChannel channel;
// 省略无关代码
}
复制代码
当使用的时候,GoogleMap
只负责显示视图,属性操做经过 GoogleMapController
来进行设置,完美的分担相应的职责
iOS
端的 UiKitView
处理过程也相似,在使用过程当中,须要注意
view
是一个昂贵的操做,因此应当避免在 flutter
可以实现的状况下去使用它view
的绘制和其余任何 flutter widget
同样,view
的转换也一样使用AndroidView
须要 api 版本 20 及以上写个练手的小 demo,在 Flutter
层显示 Android
的 TextView
,至于功能,就作一个设置 Text
内容和文字大小
const _textType = "com.demo.plugin/textview"; // 用于注册 AndroidView
const _textMethodChannel = "com.demo.plugin/textview_"; // 用于注册 MethodChannel
// 参考 GoogleMap,经过 controller 来实现方法的交互,view 只负责展现
class TextController {
final MethodChannel _channel;
// 在构造函数注册 MethodChannel
TextController(int _id) : _channel = MethodChannel('$_textMethodChannel$_id');
// 设置文字方法
Future<void> setText(String text) {
assert(text != null);
return _channel.invokeMethod("text#setText", text);
}
// 设置文字大小方法
Future<void> setTextSize(double size) {
assert(size != null);
return _channel.invokeMethod("text#setTextSize", size);
}
}
// 用于给展现的 view 设置 controller
typedef void TextViewCreateWatcher(TextController controller);
// 只用于展现
class TextView extends StatefulWidget {
final TextViewCreateWatcher watcher;
TextView(this.watcher, {Key key}) : super(key: key);
@override
_TextViewState createState() => _TextViewState();
}
class _TextViewState extends State<TextView> {
@override
Widget build(BuildContext context) {
// 目前只作 AndroidView, UiKitView 有兴趣可自行搞定
return defaultTargetPlatform == TargetPlatform.android
? AndroidView(
viewType: _textType,
onPlatformViewCreated: _onPlatformViewCreated,
)
: Text('$defaultTargetPlatform not support TextView yet');
}
_onPlatformViewCreated(int id) => widget.watcher(TextController(id));
}
复制代码
// 须要同 flutter 端一致
private const val TextType = "com.demo.plugin/textview"
private const val TextChannel = "com.demo.plugin/textview_"
// 展现的 PlatformView
class FlutterTextView(context: Context?, messenger: BinaryMessenger, id: Int)
: PlatformView, MethodCallHandler {
private val textView = TextView(context).apply { gravity = Gravity.CENTER }
private val channel = MethodChannel(messenger, "$TextChannel$id")
init {
channel.setMethodCallHandler(this) // 注册交互的 MethodChannel
}
override fun getView(): View = textView // 最终返回的为 textView 实例
override fun dispose() {} // textview 无内存泄漏状况,因此该方法可空
override fun onMethodCall(call: MethodCall, result: Result) {
when (call.method) {
"text#setText" -> {
textView.text = call.arguments?.toString() ?: ""
result.success(null)
}
"text#setTextSize" -> {
// dart 的 double 直接转成 Float 会出错,经过 String 类型来过渡下便可
textView.textSize = "${call.arguments ?: 12}".toFloat()
result.success(null)
}
else -> result.notImplemented()
}
}
}
// 定义完 PlatformView,则能够实现 PlatformViewFactory
class TextViewFactory(private val messenger: BinaryMessenger)
: PlatformViewFactory(StandardMessageCodec.INSTANCE) {
override fun create(context: Context?, id: Int, `object`: Any?):
PlatformView = FlutterTextView(context, messenger, id) // 返回 PlatformView 便可
}
// 注册该 view
class ViewPlugin {
companion object {
@JvmStatic
fun registerWith(registrar: Registrar) {
registrar.platformViewRegistry()
.registerViewFactory(TextType, TextViewFactory(registrar.messenger()))
}
}
}
复制代码
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('AndroidView'),
),
body: TextView((controller) {
controller.setText("Hello Wrold!!");
controller.setTextSize(50.0);
}),
);
}
}
复制代码
最终将 Android
端的 TextView
显示到 Flutter
层,效果图就不贴了。固然了,这个例子没有一点实用性,只是做为一个简单的例子而已,当遇到 Flutter
缺乏原生须要的 View
时候,则能够经过该方法来实现,使用时候注意点参考上面提到的~