53

手把手教你在Android项目中接入Flutter,在Fl...

 4 years ago
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,可能你会遇到下面的这种问题

uiUrquY.png!web

这种问题明显是因为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.javaFlutterFragment.java 分别进行修改

修改FlutterFragment.java

原本的依赖如下

fIF3yij.png!web

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

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

修改Flutter.java

原本的依赖如下

67vaInB.png!web

将报错部分替换为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的计数器页面。实际效果如下:

b6Fbymq.png!web

那么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;
    });
  }

效果如下:

ayIrmez.png!web

获取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,
    );
  }

}

效果如下:

aMNBja6.png!web

一个超适合Flutter入门的Todo-List项目:

Todo-List-App

最后

如果你看到了这里,觉得文章写得不错就给个 呗?如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足。谢谢。

希望读到这的您能 转发分享关注一下 我,以后还会更新技术干货,谢谢您的支持!

Android架构师之路很漫长,一起共勉吧!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK