在flutter開發中,始終會有下面兩個沒法避免的問題:java
這篇文章,將會對上面兩種狀況,分別進行介紹android
在android項目中須要將flutter以module的形式接入git
進入當前android項目,在根目錄運行以下命令:github
flutter create -t module my_flutter
複製代碼
上面表示建立一個名爲 my_flutter
的flutter modulebash
以後運行markdown
cd my_flutter cd .android/ ./gradlew flutter:assembleDebug 複製代碼
同時,確保你的在你的android項目目錄下 app/build.gradle
,有添加以下代碼:app
android { compileSdkVersion 28 defaultConfig { ... } buildTypes { ... } //flutter相關聲明 compileOptions { sourceCompatibility 1.8 targetCompatibility 1.8 } } 複製代碼
接着,在 android項目 根目錄下的 settings.gradle
中添加以下代碼async
include ':app' setBinding(new Binding([gradle: this])) evaluate(new File( rootDir.path + '/my_flutter/.android/include_flutter.groovy' )) 複製代碼
最後,須要在android項目下的 app/build.gradle
中引入 my_flutteride
dependencies { ... //導入flutter implementation project(':flutter') } 複製代碼
到這裏,基本上就能夠開始接入flutter的內容了oop
不過這時候還有一個問題須要注意,若是你的android項目已經遷移到了androidx,可能你會遇到下面的這種問題
這種問題明顯是由於flutter建立moudle時,並未作到androidx的轉換,由於建立moudle的命令還不支持androidx
能夠查看下面這個issue
Generated Flutter Module Files Do Not Use 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 分別進行修改
本來的依賴以下
將報錯部分替換爲androidx的版本
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
複製代碼
本來的依賴以下
將報錯部分替換爲androidx的版本
import androidx.annotation.NonNull;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.OnLifecycleEvent;
複製代碼
那麼如今,androidx帶來的問題就解決了,下面就開始準備正式接入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項目,在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項目,用於測試這個例子
由於用到了kotin,因此使用如下命令
flutter create -a kotlin counter_native 複製代碼
項目建立好以後,就能夠開始了,在開始以前,咱們首先能夠了解如下如何在flutter中拿到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項目裏面,建立一個想要展現在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的視圖,須要經過 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項目: