此次和以前不同,咱們直接來看最終的動畫實現效果:git
(使用琦玉老師做爲例子,是由於他的畫風比較簡單,很是適合新手操做!!!)github
動畫演示完畢,接下來,就是實現過程啦。bash
聽,是引擎的聲音..ide
若是你對於Flare的一些基礎使用尚不熟悉,能夠先去了解一下這篇文章: 打開Flutter動畫的另外一種姿式——Flaresvg
首先,咱們須要將一拳超人畫出來,能夠先在繪圖軟件上生成svg,再導入flare的項目中;或者也能夠直接在flare項目中進行繪製。post
這裏爲了方便起見,我是經過前者的方式去實現的動畫
將琦玉老師創形成功後,我就能夠準備下一步操做了ui
【琦玉老師.svg】this
上面的動畫裏面,咱們能夠看到琦玉的臉部是隨着手指移動的,因此咱們須要將一塊兒跟隨移動的部位添加同一個約束spa
建立一個Node節點,將其display屬性換成target
而後咱們開始將多個臉部內容與這個節點約束在一塊兒,下面以左眼眶爲例:
其中咱們有對每一個約束的 Strength 進行調整,參數爲1時,被約束的控件位置會和節點位置保持一致,因此這裏會根據控件與節點的距離來設置不一樣的大小
當咱們全部約束都設置完成後,就能夠看到以下效果:
同時,咱們這裏將節點名字設置爲了 ctrl_eyes
以後咱們再建立三個很是簡單的動畫,動畫名爲idel、fail、success,其中,fail和success的效果以下:
接下來,咱們開始準備用代碼去控制這個動畫!
使用代碼去控制動畫纔是這篇文章的重中之重,在此以前,先確保項目中已經添加了flare的依賴
flare_flutter: ^1.5.2
複製代碼
上一篇文章中,咱們只是使用了flare提供的最基本的功能,如今要真正實現動畫與代碼的交互,就不得不介紹一下 FlareController
通常狀況下,咱們要經過繼承的方式去使用 FlareController ,由於它是一個抽象類,這個類中有三個須要重寫的方法:
initialize(FlutterActorArtboard artboard)
:這個方法會在動畫初始時調用,在整個FlareController的生命週期中,只會調用一次。其中的 artboard 參數表示畫板對象,能夠經過它獲取到全部節點,以及全部的動畫setViewTransform(Mat2D viewTransform)
: 這個方法用於進行矩陣座標的傳遞,其中的 viewTransform 參數表示flare畫板中的2d矩陣座標advance(FlutterActorArtboard artboard, double elapsed)
:這個方法會在每一幀都調用一次,操做動畫的主要邏輯就在這裏。其中 elapsed 參數表示消耗的時間官方給咱們提供了一個 FlareControls
類,這個類封裝好了一些基礎的方法,因此咱們實現的Controller繼承這個類便可
接下來,咱們來實現自定義的Controller,編寫一個 MyController 繼承 FlareControls
看一下其中的部分方法:
class MyController extends FlareControls{
//用於獲取ctrl_eyes節點
ActorNode _eyeControl;
// 存儲"約束臉部節點"座標
Vec2D _eyeOrigin = Vec2D();
Vec2D _eyeOriginLocal = Vec2D();
...
@override
void initialize(FlutterActorArtboard artboard) {
super.initialize(artboard);
_eyeControl = artboard.getNode("ctrl_eyes");
if (_eyeControl != null) {
_eyeControl.getWorldTranslation(_eyeOrigin);
Vec2D.copy(_eyeOriginLocal, _eyeControl.translation);
}
play("idle");
}
...
}
複製代碼
在 initialize 中獲取到了以前拖拽的臉部約束節點,而且進行了存儲。
// 用於存儲從flare轉換到flutter的矩陣
Mat2D _globalToFlareWorld = Mat2D();
@override
void setViewTransform(Mat2D viewTransform) {
super.setViewTransform(viewTransform);
Mat2D.invert(_globalToFlareWorld, viewTransform);
}
複製代碼
setViewTransform 方法中進行了矩陣座標的倒置。
其實關於矩陣座標的相關邏輯都是比較晦澀抽象的,這裏只要照搬便可,下面的 advance 方法一樣如此
// 在flutter中當前焦點所在的座標
Vec2D _caretGlobal = Vec2D();
// 在flare中當前焦點所在的座標
Vec2D _caretWorld = Vec2D();
//判斷是否正在輸入
bool _hasFocus = false;
String _password = "";
MyController({this.projectGaze = 100});
//這個參數用於縮放從輸入焦點到約束節點之間的距離
final double projectGaze;
@override
bool advance(FlutterActorArtboard artboard, double elapsed) {
super.advance(artboard, elapsed);
Vec2D targetTranslation;
if(_hasFocus){
// 獲取到flare中當前焦點所在的座標
Vec2D.transformMat2D(_caretWorld, _caretGlobal, _globalToFlareWorld);
//這裏是實現了動畫的"呼吸"效果,是爲了不動畫靜止不動,讓動畫更加有趣
_caretWorld[1] += sin(new DateTime.now().millisecondsSinceEpoch / 300.0) * 70.0;
// 計算矢量方向
Vec2D toCaret = Vec2D.subtract(Vec2D(), _caretWorld, _eyeOrigin);
//獲取比例,再進行縮放
Vec2D.normalize(toCaret, toCaret);
Vec2D.scale(toCaret, toCaret, projectGaze);
//用於計算"約束節點"到輸入焦點到距離
Mat2D toFaceTransform = Mat2D();
if (Mat2D.invert(toFaceTransform, _eyeControl.parent.worldTransform)) {
Vec2D.transformMat2(toCaret, toCaret, toFaceTransform);
targetTranslation = Vec2D.add(Vec2D(), toCaret, _eyeOriginLocal);
}
} else {
targetTranslation = Vec2D.clone(_eyeOriginLocal);
}
Vec2D diff =
Vec2D.subtract(Vec2D(), targetTranslation, _eyeControl.translation);
Vec2D frameTranslation = Vec2D.add(Vec2D(), _eyeControl.translation,
Vec2D.scale(diff, diff, min(1.0, elapsed * 5.0)));
_eyeControl.translation = frameTranslation;
return true;
}
複製代碼
advance 方法返回 true 表示每幀都進行刷新
實現完MyController以後,再搭配官方提供的 tracking_text_input.dart 與 input_helper.dart 就能夠實現 琦玉眼睛跟隨輸入框的效果了
Building an Interactive Login Screen with Flare & Flutter
www.2dimensions.com/a/homeman/f…
環形列表是我寫的一個dart插件,後續也許會出一篇開發dart packages的踩坑記錄