開頭
這次和之前不一樣,我們直接來看最終的動畫實現效果:
(使用琦玉老師作為例子,是因為他的畫風比較簡單,非常適合新手操作!!!)
動畫演示完畢,接下來,就是實現過程啦。
聽,是引擎的聲音..
動畫部分
如果你對於Flare的一些基礎使用尚不熟悉,可以先去了解一下這篇文章: 開啟Flutter動畫的另一種姿勢——Flare
繪製圖形
首先,我們需要將一拳超人畫出來,可以先在繪圖軟體上生成svg,再匯入flare的專案中;或者也可以直接在flare專案中進行繪製。
這裡為了方便起見,我是通過前者的方式去實現的
將琦玉老師創造成功後,我就可以準備下一步操作了
新增約束
上面的動畫裡面,我們可以看到琦玉的臉部是隨著手指移動的,所以我們需要將一起跟隨移動的部位新增同一個約束
建立一個Node節點,將其display屬性換成target
然後我們開始將多個臉部內容與這個節點約束在一起,下面以左眼眶為例:
其中我們有對每個約束的 Strength 進行調整,引數為1時,被約束的控制元件位置會和節點位置保持一致,所以這裡會根據控制元件與節點的距離來設定不同的大小
當我們所有約束都設定完成後,就可以看到如下效果:
同時,我們這裡將節點名字設定為了 ctrl_eyes
之後我們再建立三個非常簡單的動畫,動畫名為idel、fail、success,其中,fail和success的效果如下:
接下來,我們開始準備用程式碼去控制這個動畫!
程式碼部分
使用程式碼去控制動畫才是這篇文章的重中之重,在此之前,先確保專案中已經新增了flare的依賴
flare_flutter: ^1.5.2
複製程式碼
上一篇文章中,我們只是使用了flare提供的最基本的功能,現在要真正實現動畫與程式碼的互動,就不得不介紹一下 FlareController
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
二、Demo地址
三、Flare動畫地址
www.2dimensions.com/a/homeman/f…
四、環形列表控制元件
環形列表是我寫的一個dart外掛,後續也許會出一篇開發dart packages的踩坑記錄