0. 前言
Flare 是 2dimensions 提供的一個(gè)新的動(dòng)畫工具,動(dòng)畫很炫酷
,決定調(diào)研一下看看如何集成到項(xiàng)目中。
Flare目前支持flutter,所以工作有下面兩部分
- 現(xiàn)有項(xiàng)目集成Flutter
- 原生調(diào)用Flutter方法
1. 集成Flutter到現(xiàn)有項(xiàng)目
https://github.com/flutter/flutter/wiki/Add-Flutter-to-existing-apps
1.1 創(chuàng)建Flutter module
我的Demo路徑為 ~/workspace/FlareDemo
cd ~/worksapce
flutter create -t module keep_flare

image.png
這里
.android和.ios就是flutter項(xiàng)目里面的android和ios項(xiàng)目,只不過(guò)我們創(chuàng)建的是module,所以他們是隱藏文件夾,Flutter就是我們需要的module
$ cd .android/
$ ./gradlew flutter:assembleDebug
通過(guò)上述命名獲得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項(xiàng)目下的Flutter module
FlareDemo/app/build.gradle
dependencies {
implementation project(':flutter')
}
- 注意事項(xiàng)
如果你的app使用了androidx,那么flutter module也需要使用androidx(默認(rèn)情況下沒(méi)有使用androidx)
keep_flare/pubspec.yaml
module:
androidX: true
androidPackage: com.example.keep_flare
iosBundleIdentifier: com.example.keepFlare
將這里修改成androidx: true
2.Flutter module(keep_flare)開(kāi)發(fā)
2.1 通過(guò)FlutterView來(lái)調(diào)用
main.dart
void main() => runApp(_widgetForRoute(window.defaultRouteName));
Widget _widgetForRoute(String route) {
switch (route) {
case 'flare_teddy':
return TeddyView();
}
}
通過(guò)不同的routeName對(duì)應(yīng)不同的View
2.2 添加Flare
- 添加依賴
dependencies:
flutter:
sdk: flutter
flare_flutter: ^1.5.4
flutter:
assets:
- assets/
從flare sample下載flr文件和
teddy_controller.dart創(chuàng)建
teddy_view.dart,里面添加MethodChannel獲得原生端發(fā)送的消息
關(guān)于MethodChannel可以看這個(gè)文章
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開(kāi)發(fā)
-
FlarePlugin.kt用于和Flutter交互
class FlarePlugin(flutterView: FlutterView) : MethodChannel.MethodCallHandler {
private var channel: MethodChannel
init {
channel = MethodChannel(flutterView, CHANNEL)
channel.setMethodCallHandler(this)
}
/**
* 發(fā)送flutter方法
*/
fun sendMessage(methodName: String, arguments: Map<String,Any>){
android.util.Log.e("liang", "call flutter methodName=$methodName, arguments=$arguments")
channel.invokeMethod(methodName, arguments)
}
/**
* 接收f(shuō)lutter端請(qǐng)求的方法
*/
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眼睛會(huì)跟著動(dòng)
- 輸入密碼Teddy會(huì)閉上眼睛
- 輸入密碼錯(cuò)誤,正確不同反應(yīng)

has-gif.gif