開頭
在flutter開發(fā)中肥橙,始終會(huì)有下面兩個(gè)無(wú)法避免的問(wèn)題:
- 原生項(xiàng)目往flutter遷移,就需要在原生項(xiàng)目中接入flutter
- flutter項(xiàng)目中要使用到一些比較成熟的應(yīng)用秸侣,就無(wú)法避免去用到原生的各種成熟庫(kù)存筏,比如音視頻之類的
這篇文章,將會(huì)對(duì)上面兩種情況味榛,分別進(jìn)行介紹
在Android中接入flutter界面
在android項(xiàng)目中需要將flutter以module的形式接入
創(chuàng)建flutter module
進(jìn)入當(dāng)前android項(xiàng)目椭坚,在根目錄運(yùn)行如下命令:
flutter create -t module my_flutter
上面表示創(chuàng)建一個(gè)名為 my_flutter 的flutter module
之后運(yùn)行
cd my_flutter
cd .android/
./gradlew flutter:assembleDebug
同時(shí),確保你的在你的android項(xiàng)目目錄下 app/build.gradle ,有添加如下代碼:
android {
compileSdkVersion 28
defaultConfig {
...
}
buildTypes {
...
}
//flutter相關(guān)聲明
compileOptions {
sourceCompatibility 1.8
targetCompatibility 1.8
}
}
接著搏色,在 android項(xiàng)目 根目錄下的 settings.gradle 中添加如下代碼
include ':app'
setBinding(new Binding([gradle: this]))
evaluate(new File(
rootDir.path + '/my_flutter/.android/include_flutter.groovy'
))
最后善茎,需要在android項(xiàng)目下的 app/build.gradle 中引入 my_flutter
dependencies {
...
//導(dǎo)入flutter
implementation project(':flutter')
}
到這里,基本上就可以開始接入flutter的內(nèi)容了
不過(guò)這時(shí)候還有一個(gè)問(wèn)題需要注意频轿,如果你的android項(xiàng)目已經(jīng)遷移到了androidx垂涯,可能你會(huì)遇到下面的這種問(wèn)題
這種問(wèn)題明顯是因?yàn)閒lutter創(chuàng)建moudle時(shí)航邢,并未做到androidx的轉(zhuǎn)換耕赘,因?yàn)閯?chuàng)建moudle的命令還不支持androidx
下面就開始解決這個(gè)問(wèn)題
解決androidx帶來(lái)的問(wèn)題
首先,如果你原先的android項(xiàng)目已經(jīng)遷移到了androidx膳殷,那么在根目錄下的 grale.properties 一定有如下內(nèi)容
# 表示使用 androidx
android.useAndroidX=true
# 表示將第三方庫(kù)遷移到 androidx
android.enableJetifier=true
下面進(jìn)入到 my_flutter 目錄下操骡,在 你的android項(xiàng)目/my_flutter/.android/Flutter/build.gradle 中對(duì)庫(kù)的依賴部分進(jìn)行修改
如果默認(rèn)的內(nèi)容如下:
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上點(diǎn)擊完 Sync Now 同步之后
再進(jìn)入下面的目錄 你的android項(xiàng)目/my_flutter/.android/Flutter/src/main/java/io/flutter/facade/ 目錄下,對(duì) Flutter.java和 FlutterFragment.java 分別進(jìn)行修改
修改FlutterFragment.java
原本的依賴如下
將報(bào)錯(cuò)部分替換為androidx的版本
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
修改Flutter.java
原本的依賴如下
將報(bào)錯(cuò)部分替換為androidx的版本
import androidx.annotation.NonNull;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.OnLifecycleEvent;
那么現(xiàn)在册招,androidx帶來(lái)的問(wèn)題就解決了,下面就開始準(zhǔn)備正式接入Flutter
在flutter中編輯入口
進(jìn)入 my_flutter 目錄中的lib目錄考榨,可以看到會(huì)有系統(tǒng)自帶的 main.dart 文件跨细,這是一個(gè)默認(rèn)的計(jì)數(shù)器頁(yè)面,我們修改一部分:
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),
);
}
}
將入口更換為通過(guò)“route1" 命名進(jìn)入進(jìn)入
接下來(lái)就是在android中進(jìn)行操作了
在android中接入flutter
進(jìn)入到android項(xiàng)目河质,在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)
}
從上面的代碼可以看到,我們通過(guò)一個(gè)按鈕的點(diǎn)擊事件去展示了flutter的計(jì)數(shù)器頁(yè)面掀鹅。實(shí)際效果如下:
那么android接入flutter就結(jié)束了散休,下面是在flutter中接入android
在Flutter中接入android界面
我們可以新建一個(gè)flutter項(xiàng)目,用于測(cè)試這個(gè)例子
因?yàn)橛玫搅薻otin乐尊,所以使用以下命令
flutter create -a kotlin counter_native
項(xiàng)目創(chuàng)建好之后戚丸,就可以開始了,在開始之前,我們首先可以了解以下如何在flutter中拿到android中的數(shù)據(jù)
獲取android數(shù)據(jù)
關(guān)于如何去獲取數(shù)據(jù)限府,主要還是使用 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 的結(jié)果回調(diào)中夺颤,我們進(jìn)行了篩選,如果方法名是 getCounterData就直接返回100
接下來(lái)在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的數(shù)據(jù)就說(shuō)到這里胁勺,下面就是去獲取android的頁(yè)面了
獲取android的布局
相較于數(shù)據(jù)而言世澜,拿到android的布局就要復(fù)雜的多
創(chuàng)建android視圖
在android項(xiàng)目里面,創(chuàng)建一個(gè)想要展示在flutter中的布局署穗,這里寥裂,我們結(jié)合xml文件來(lái)創(chuàng)建布局,不過(guò)使用xml的方式案疲,會(huì)出現(xiàn)R文件找不到的情況封恰,這時(shí)候編譯器會(huì)報(bào)錯(cuò),暫時(shí)不用去管:
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 = "當(dāng)前Android的Text數(shù)值是:${counterData.counterData}"
result.success(counterData.counterData)
}
"decreaseNumber" -> {
counterData.counterData--
myText.text = "當(dāng)前Android的Text數(shù)值是:${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("出錯(cuò)", "size無(wú)法再小了褐啡!", 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("出錯(cuò)", "size無(wú)法再大了诺舔!", null)
}
}
"setText" -> {
myText.text = (methodCall.arguments as String)
result.success(myText.text)
}
else -> {
result.notImplemented();
}
}
}
}
上面的 CounterData 類是用于存儲(chǔ)數(shù)據(jù)創(chuàng)建的一個(gè)類:
class CounterData(var counterData: Int = 0) {
}
接下來(lái),我們創(chuàng)建一個(gè) 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)
}
}
最后創(chuàng)建一個(gè) CounterViewPlugin.kt 文件春贸,它用于注冊(cè)視圖混萝,相當(dāng)于初始化入口
class CounterViewPlugin{
fun registerWith(registrar: Registrar) {
registrar.platformViewRegistry().registerViewFactory("samples.flutter.io/counter_view", CounterViewFactory(registrar.messenger()))
}
}
創(chuàng)建完成后,在MainActivity中進(jìn)行視圖注冊(cè):
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
CounterViewPlugin().registerWith(flutterView.pluginRegistry.registrarFor("CounterViewPlugin"))
...
}
接下來(lái)萍恕,就是在flutter中需要做的一些事情了
在flutter中獲取android視圖
在flutter里面,想要拿到android的視圖车要,需要通過(guò) AndroidView 去獲取
Widget build(BuildContext context) {
if (Platform.isAndroid) {
return AndroidView(
viewType: 'samples.flutter.io/counter_view',
onPlatformViewCreated: _onPlatformViewCreated,
);
}
return Text(
'$defaultTargetPlatform 還不支持這個(gè)布局');
}
在 onPlatformViewCreated 方法中允粤,我們需要?jiǎng)?chuàng)建 MethodChannel ,用于調(diào)用android中編寫的方法翼岁,我們可以封裝一個(gè)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,
);
}
}
效果如下:
最后給大家分享一份移動(dòng)架構(gòu)大綱类垫,包含了移動(dòng)架構(gòu)師需要掌握的所有的技術(shù)體系,大家可以對(duì)比一下自己不足或者欠缺的地方有方向的去學(xué)習(xí)提升琅坡;
需要高清架構(gòu)圖以及圖中視頻資料的可以加入我的技術(shù)交流群:825106898私聊群主小姐姐免費(fèi)獲取