手把手教你在Android项目中接入Flutter,在Fl...
source link: https://www.tuicool.com/articles/nqMNzqJ
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
在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,可能你会遇到下面的这种问题
这种问题明显是因为flutter创建moudle时,并未做到androidx的转换,因为创建moudle的命令还不支持androidx
可以查看下面这个issue
Generated Flutter Module Files Do Not Use 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
原本的依赖如下
将报错部分替换为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的计数器页面。实际效果如下:
那么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; }); }
效果如下:
获取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, ); } }
效果如下:
一个超适合Flutter入门的Todo-List项目:
最后
如果你看到了这里,觉得文章写得不错就给个 赞 呗?如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足。谢谢。
希望读到这的您能 转发分享 和 关注一下 我,以后还会更新技术干货,谢谢您的支持!
Android架构师之路很漫长,一起共勉吧!
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK