Flare動畫進階——建立可互動的一拳超人動畫

安卓小哥發表於2019-07-04

開頭

這次和之前不一樣,我們直接來看最終的動畫實現效果:

image

image

(使用琦玉老師作為例子,是因為他的畫風比較簡單,非常適合新手操作!!!)

動畫演示完畢,接下來,就是實現過程啦。

聽,是引擎的聲音..

動畫部分

如果你對於Flare的一些基礎使用尚不熟悉,可以先去了解一下這篇文章: 開啟Flutter動畫的另一種姿勢——Flare

繪製圖形

首先,我們需要將一拳超人畫出來,可以先在繪圖軟體上生成svg,再匯入flare的專案中;或者也可以直接在flare專案中進行繪製。

這裡為了方便起見,我是通過前者的方式去實現的

image

將琦玉老師創造成功後,我就可以準備下一步操作了

【琦玉老師.svg】

新增約束

上面的動畫裡面,我們可以看到琦玉的臉部是隨著手指移動的,所以我們需要將一起跟隨移動的部位新增同一個約束

建立一個Node節點,將其display屬性換成target

image

然後我們開始將多個臉部內容與這個節點約束在一起,下面以左眼眶為例:

image

其中我們有對每個約束的 Strength 進行調整,引數為1時,被約束的控制元件位置會和節點位置保持一致,所以這裡會根據控制元件與節點的距離來設定不同的大小

image

當我們所有約束都設定完成後,就可以看到如下效果:

image

同時,我們這裡將節點名字設定為了 ctrl_eyes

之後我們再建立三個非常簡單的動畫,動畫名為idel、fail、success,其中,fail和success的效果如下:

image

image

接下來,我們開始準備用程式碼去控制這個動畫!

程式碼部分

使用程式碼去控制動畫才是這篇文章的重中之重,在此之前,先確保專案中已經新增了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.dartinput_helper.dart 就可以實現 琦玉眼睛跟隨輸入框的效果了

附錄

一、參考文章

Building an Interactive Login Screen with Flare & Flutter

二、Demo地址

【one_punch_demo】

三、Flare動畫地址

www.2dimensions.com/a/homeman/f…

四、環形列表控制元件

【circle_list】

環形列表是我寫的一個dart外掛,後續也許會出一篇開發dart packages的踩坑記錄

相關文章