0. 前言
Flare 是 2dimensions 提供的一个新的动画工具,动画很炫酷
,决定调研一下看看如何集成到项目中。
Flare目前支持flutter,所以工作有下面两部分
- 现有项目集成Flutter
- 原生调用Flutter方法
1. 集成Flutter到现有项目
https://github.com/flutter/flutter/wiki/Add-Flutter-to-existing-apps
1.1 创建Flutter module
我的Demo路径为 ~/workspace/FlareDemo
cd ~/worksapce
flutter create -t module keep_flare
这里
.android
和.ios
就是flutter项目里面的android和ios项目,只不过我们创建的是module,所以他们是隐藏文件夹,Flutter
就是我们需要的module
$ cd .android/
$ ./gradlew flutter:assembleDebug
通过上述命名获得module的aar文件,路径如下.android/Flutter/build/outputs/aar/flutter-debug.aar
1.2 Flutter module继承
FlareDemo/app/build.gradle
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
FlareDemo/settings.gradle
include ':app' // assumed existing content
setBinding(new Binding([gradle: this])) // new
evaluate(new File( // new
settingsDir.parentFile, // new
'keep_flare/.android/include_flutter.groovy' // new
)) // new
引入keep_flare项目下的Flutter module
FlareDemo/app/build.gradle
dependencies {
implementation project(':flutter')
}
- 注意事项
如果你的app使用了androidx,那么flutter module也需要使用androidx(默认情况下没有使用androidx)
keep_flare/pubspec.yaml
module:
androidX: true
androidPackage: com.example.keep_flare
iosBundleIdentifier: com.example.keepFlare
将这里修改成androidx: true
2.Flutter module(keep_flare)开发
2.1 通过FlutterView来调用
main.dart
void main() => runApp(_widgetForRoute(window.defaultRouteName));
Widget _widgetForRoute(String route) {
switch (route) {
case 'flare_teddy':
return TeddyView();
}
}
通过不同的routeName对应不同的View
2.2 添加Flare
- 添加依赖
dependencies:
flutter:
sdk: flutter
flare_flutter: ^1.5.4
flutter:
assets:
- assets/
从flare sample下载flr文件和
teddy_controller.dart
创建
teddy_view.dart
,里面添加MethodChannel
获得原生端发送的消息
关于MethodChannel
可以看这个文章
import 'package:flare_flutter/flare_actor.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
import 'package:keep_flare/teddy_controller.dart';
class TeddyView extends StatefulWidget {
@override
_TeddyViewState createState() => _TeddyViewState();
}
class _TeddyViewState extends State<TeddyView> {
TeddyController _teddyController;
MethodChannel _channel;
@override
void initState() {
_teddyController = TeddyController();
_initChannel();
super.initState();
}
_initChannel() {
this._channel = MethodChannel("com.gotokeep.keep.plugins.flare");
Future<dynamic> handler(MethodCall call) async {
print("========kt call flutter " + call.toString());
print(call.arguments.toString());
print(call.arguments.runtimeType.toString());
switch (call.method) {
case "lookAt":
_lookAt(call.arguments["dx"], call.arguments["dy"]);
break;
case "coverEyes":
_coverEyes(call.arguments["cover"]);
break;
case "setPassword":
_setPassword(call.arguments["password"]);
break;
case "submitPassword":
_submitPassword();
break;
}
}
this._channel.setMethodCallHandler(handler);
}
_lookAt(double x, double y) {
_teddyController.lookAt(Offset(x, y));
}
_setPassword(String value) {
_teddyController.setPassword(value);
}
_coverEyes(bool cover) {
_teddyController.coverEyes(cover);
}
_submitPassword() {
_teddyController.submitPassword();
}
@override
Widget build(BuildContext context) {
return Container(
color: Color(0xFFFFFFFF),
child: FlareActor(
"assets/Teddy.flr",
shouldClip: false,
alignment: Alignment.bottomCenter,
fit: BoxFit.contain,
controller: _teddyController,
));
}
}
3. Demo开发
-
FlarePlugin.kt
用于和Flutter交互
class FlarePlugin(flutterView: FlutterView) : MethodChannel.MethodCallHandler {
private var channel: MethodChannel
init {
channel = MethodChannel(flutterView, CHANNEL)
channel.setMethodCallHandler(this)
}
/**
* 发送flutter方法
*/
fun sendMessage(methodName: String, arguments: Map<String,Any>){
android.util.Log.e("liang", "call flutter methodName=$methodName, arguments=$arguments")
channel.invokeMethod(methodName, arguments)
}
/**
* 接收flutter端请求的方法
*/
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
android.util.Log.e("liang", "flutter call kt")
when (call.method) {
}
}
companion object {
private const val CHANNEL = "com.tesla1984.plugins.flare"
}
}
-
TeddyFlareView.kt
封装具体的TeddyView
里面的方法
package com.github.flaredemo
import androidx.fragment.app.FragmentActivity
import com.gotokeep.keep.fd.utils.FlarePlugin
import io.flutter.view.FlutterView
class TeddyFlareView(activity: FragmentActivity, route: String) {
var flutterView: FlutterView = Flutter.createView(activity, activity.lifecycle, route)
private var flarePlugin: FlarePlugin
init {
flarePlugin = FlarePlugin(flutterView)
}
fun setBackground(backgroudColor: Int) {
sendMessage(
"setBackground", mapOf(
"backgroundColor" to backgroudColor
)
)
}
fun coverEyes(cover: Boolean) {
sendMessage(
"coverEyes", mapOf("cover" to cover)
)
}
fun lookAt(dx: Double, dy: Double) {
sendMessage(
"lookAt", mapOf(
"dx" to dx,
"dy" to dy
)
)
}
fun setPassword(password: String) {
sendMessage(
"setPassword", mapOf(
"password" to password
)
)
}
fun submitPassword() {
sendMessage("submitPassword", emptyMap())
}
private fun sendMessage(methodName: String, arguments: Map<String, Any>) {
flarePlugin.sendMessage(methodName, arguments)
}
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initView()
}
private fun initView() {
val frameLayout = findViewById<FrameLayout>(R.id.frameLayout)
val flareViewHelper = TeddyFlareView(this, "flare_teddy")
frameLayout.addView(flareViewHelper.flutterView)
flareViewHelper.setBackground(0xFFE53935.toInt())
findViewById<EditText>(R.id.editText).let {
it.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
val pair = getLocation(it)
flareViewHelper.lookAt(pair.first, pair.second)
}
})
}
findViewById<EditText>(R.id.editText2).let {
it.setOnFocusChangeListener { _, hasFocus ->
flareViewHelper.coverEyes(hasFocus)
}
it.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
flareViewHelper.setPassword(s.toString())
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
}
})
}
findViewById<Button>(R.id.button).setOnClickListener {
flareViewHelper.submitPassword()
}
}
fun getLocation(editText: EditText): Pair<Double, Double> {
val pos = editText.selectionStart
val layout = editText.layout
val x = layout.getPrimaryHorizontal(pos).toDouble()
val location = IntArray(2)
editText.getLocationInWindow(location)
return Pair(x, location[1].toDouble())
}
}
最终效果
- 输入用户名字Teddy眼睛会跟着动
- 输入密码Teddy会闭上眼睛
- 输入密码错误,正确不同反应