『Flutter-繪製篇』實現夢幻的晴晚流星效果

前言

前不久,利用週末時間學習並完成一個簡單的 Flutter 項目 - 簡悅天氣簡約不簡單,豐富不復雜,這是一款簡約風格的 flutter 天氣項目,提供實時、多日、24 小時、颱風路徑、語音播報以及生活指數等服務,支持定位、刪除、搜索等操作。

下圖爲主頁效果,點擊下載 進行體驗:

項目中運用了大量的自定義繪製 widget,首頁豐富的 自定義 chart 效果和炫酷的天氣背景動效。天氣背景動效在不同的天氣氣象下展示不同的效果。目前一共實現了 14 種類別,其中有,晴、晴晚、多雲、陰天、小中大雨、小中大雪、霧、霾、浮塵以及雷暴。背景動效一共分爲三層:

  • 背景顏色層。從上到下的漸變效果
  • 雲層。只有一種圖片,對其位移、數量、染色做不同變化達到不同效果
  • 信息層。包括雨雪、雷暴和晴晚流星效果

之前在 『Flutter-繪製篇』實現炫酷的雨雪特效 一文中介紹過雨雪的實現細節,今天我們聊一聊如何實現 夢幻的晴晚流星 效果,其實實現到不難,主要是實現的思路和方法。

話不多說,先看一下動態效果圖(爲了更好預覽多樣的背景動效,在右上角關於頁面加了入口,可以實時切換天氣類型,查看動態效果,感興趣的下載體驗):

陪你去看流星雨落在這地球上,讓你的淚落在我肩膀,在我肩膀~

仔細觀察不難發現,主要有兩部分組成:

  • 不斷閃爍的星星效果
  • 轉瞬即逝的流星效果

所以接下來會圍繞上面兩部分進行詳細講解。

晴晚

Flutter 同 Android 的繪製有很多相似之處,提供了 canvas 和 paint 類作爲畫板和畫筆,可以繪製基礎的圖形文字和圖片,同樣有很多 api 爲簡單的圖形添加漸變、高斯模糊等炫酷的特效 。

初始化素材和參數

晴晚由羣星組成,首先繪製一顆星星,簡單實現,就不使用圖片,直接通過 canvas 繪製。Flutter 提供了 MaskFilter.blur(_style, _sigma),並通過 _paint.maskFilter 可以圖形設置模糊,從而營造星星的發光效果。

BlurStyle 一共有四種類別,分別爲:normal、solid、outer和outter,對應下面的效果:

_sigma 模糊係數越大越模糊,在 normal 下分別設置 0、1、3、7 效果:

有了星星,接下來就需要創建星星,並賦予其屬性,讓其展示在屏幕上。

class _StarParam {
  double x;
  double y;
  double alpha = 0.0;
  double scale;

  _StarParam();

  void init() {
    alpha = Random().nextDouble();
    scale = Random().nextDouble() * 0.1 + 0.6;
    x = Random().nextDouble() * 1.wp / scale;
    y = Random().nextDouble() * 0.3.hp / scale;
  }
}

除了座標 x,y 屬性外,alpha 用於後面做動畫, scale 用於模擬遠近的效果,我們創建 100 個隨機分佈在 1*width0.3*height 的區域內。

繪製

有參數後,直接開始繪製,只需要調用 canvas.drawCircle() 即可:

  void drawStar(_StarParam param, Canvas canvas) {
    if (param == null) {
      return;
    }
    canvas.save();
    var identity = ColorFilter.matrix(<double>[
      1, 0, 0, 0, 0,
      0, 1, 0, 0, 0,
      0, 0, 1, 0, 0,
      0, 0, 0, param.alpha, 0,
    ]);
    _paint.colorFilter = identity;
    canvas.scale(param.scale);
    canvas.drawCircle(Offset(param.x, param.y), 3, _paint);
    canvas.restore();
  }

實現效果如下:

動畫

接下來就是讓星星“動”起來。有點類似之前實現雨雪的邏輯,創建一個持續運行的動畫,在動畫回調中不斷的 setState(),對於星星對象,用一個屬性 alpha 字段,不斷自增或自減來達到閃爍的效果。

  void move() {
    if (reverse == true) {
      alpha -= 0.01;
      if (alpha < 0) {
        reset();
      }
    } else {
      alpha += 0.01;
      if (alpha > 1.2) {
        reverse = true;
      }
    }
  }

這裏,當 alpha 值達到閾值時,將 reverse 字段置爲 true,此時開始自減,做消失動畫,因爲很多屬性都是隨機,最終就營造出羣星閃爍的效果。

流星

初始化素材和參數

一開始準備從各種圖片素材網上試着找一下流星的資源,結果要麼效果不滿意,要麼就是要各種收費和關注等等。轉念一想,還不如自己實現來的快,無非就是一條漸變細長形圓角矩形。

圓角矩形通過 canvas.drawRRect() 可以實現,爲了實現流星的拖尾效果,借用 paint#shader 的屬性,達到漸變的效果。

  var gradient = ui.Gradient.linear(
    const Offset(0, 0),
    Offset(_meteorWidth, 0),
    <Color>[const Color(0xFFFFFFFF), const Color(0x00FFFFFF)],
  );
  _meteorPaint.shader = gradient;

我們隨機創建4組流星。

繪製

爲了營造更真實的流星劃過效果,水平效果是不滿足的,需要對其進行角度翻轉。

一開始,打算通過 y/x=tan(Θ) 方式進行繪製,後來考慮到要做動畫,各種動態計算,乾脆直接使用 canvas.rotate()canvas.translate() 的方法,更加方便,更加的好理解。

  void drawMeteor(_MeteorParam param, Canvas canvas) {
    canvas.save();
    var gradient = ui.Gradient.linear(
      const Offset(0, 0),
      Offset(_meteorWidth, 0),
      <Color>[const Color(0xFFFFFFFF), const Color(0x00FFFFFF)],
    );
    _meteorPaint.shader = gradient;
    canvas.rotate(pi * param.radians);
    canvas.translate(param.translateX, tan(pi * 0.1) *_meteorWidth + param.translateY);
    canvas.drawRRect(
        RRect.fromLTRBAndCorners(0, 0, _meteorWidth, _meteorHeight,
            topLeft: _radius,
            topRight: _radius,
            bottomRight: _radius,
            bottomLeft: _radius),
        _meteorPaint);
    canvas.restore();
  }

這裏 rotate 和 translate 的順序不能寫反,需要先旋轉再平移,因爲旋轉的點在左側端,如果先平移再旋轉,會導致運動過程中,流星不再一條直線上運行。效果如下:

動畫

複用晴晚的 AnimationController,通過不斷的改變 translateX 值,來完成劃過的動畫效果。

class _MeteorParam {
  double translateX;
  double translateY;
  double radians;
  void reset() {
    translateX = 1.0.wp + Random().nextDouble() * 20.0.wp;
    radians = -Random().nextDouble() * 0.07 - 0.05;
    translateY = Random().nextDouble() * 0.5.hp;
  }

  void move() {
    translateX -= 20;
    if (translateX <= -1.0.wp) {
      reset();
    }
  }
}

這裏初始化 1.0.wp + Random().nextDouble() * 20.0.wp,使其初始化位置分佈在 [1, 20] * width 的位置,而在的動畫過程中不斷 translateX -= 20,當滑過一個屏幕則重新初始化位置。

到這,夢幻的晴晚和流星效果就實現了,預告一下下期-炫酷的雷暴效果。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章