亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

怎么使用Flutter實現58同城中的加載動畫

發布時間:2021-02-02 14:37:42 來源:億速云 閱讀:171 作者:小新 欄目:移動開發

這篇文章給大家分享的是有關怎么使用Flutter實現58同城中的加載動畫的內容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。

使用Flutter實現58同城中加載動畫的過程,先看一下加載動畫的效果:

怎么使用Flutter實現58同城中的加載動畫

動畫效果乍看比較復雜,難以看出端倪,其實我們可以先調慢動畫的速度,這樣能夠比較清晰地分析出動畫的流程。

動畫的流程

動畫由兩個圓弧的動效組成,兩個圓弧的起始點角度和掃過的弧度隨著時間規律變化。仔細觀察會發現,兩個圓弧的動效其實是一樣的,只不過起始位置是不一樣的。我們先看下外部大圓弧的運動規律。

大圓弧從x軸正方向開始運動,按照動畫的運動規律,可以將動畫分為三個階段:

第一階段:圓弧起點的在x軸正方向,終點的角度x軸正方向開始向下逐漸增大,直到終點到達y軸負方向位置,最終圓弧掃過的角度為180度。

第二階段:圓弧掃過的角度保持在180度,起點和終點一起順時針旋轉,直到旋轉180度后終點到達x軸正方向。

第三階段:圓弧的終點保持在x軸正方向,起點順時針旋轉,直到起點也到達x軸正方向,此時完成一個完整的動畫。接下來繼續重復動畫的第一階段,組成一個連貫的動畫。

分析完動畫的流程,思路就很清晰了,我們按照動畫流程把動畫拆分成三部分,通過對圓弧的起點、終點和掃過角度的變換,組合成一個完整的動畫,然后不斷地重復,最后就變成了一個加載中的動畫效果。

接下來開始寫代碼實現。

由于動畫是由一個圓弧不斷變化組成的,如果使用Android,我們很自然的想到可以使用Canvas來進行圓弧的繪制,然后根據時間的變化不停地重新繪制圓弧,從而實現動畫效果。那么在Flutter中是否也存在Canvas呢,答案是肯定的,Flutter和Android一樣,也存在Canvas。

Flutter中的Canvas

Flutter中使用 CustomPainter 類在Canvas上進行繪制,該類包含一個 paint() 方法,該方法提供了一個Canvas對象,可以用來繪制各種圖形。

 abstract class CustomPainter extends Listenable {

 void paint(Canvas canvas, Size size);

 }

不過在Flutter中一切皆是Widget,而承載Canvas功能的Widget是 CustomPaint 類。 CustomPaint 包含一個painter屬性,用來指定進行繪制的 CustomPainter,源碼如下:

 class CustomPaint extends SingleChildRenderObjectWidget {

 const CustomPaint({

 Key key,

 this.painter,

 });

 final CustomPainter painter;

 }

Flutter中的Canvas和Android類似,提供了一系列的API用來繪制點、線、圓形、正方形等,而且API很類似,對比一下Flutter與Android中Canvas的常見API(具體的參數列表請參考文檔和源碼,篇幅有限不再一一列出):


 
AndroidFlutter

drawPoint()

drawPoints()

drawPoints()

drawLine()

drawLines()

drawLine()
drawCircle()drawCircle()
橢圓drawOval()drawOval()
圓弧drawArc()drawArc()
矩形drawRect()drawRect()
PathdrawPath()drawPath()
圖片drawBitmap()drawImage()
文字drawText()drawParagraph()
變換

save()

restore()

save()

restore()

要繪制動畫中的圓弧,應該使用 drawArc() 方法來實現,這里需要注意的是drawArc()方法的參數:startAngle和sweepAngle的單位是弧度(180度等于π弧度)。

具體來看一下 Canvas.drawArc() 方法的參數列表:

 /// rect: 圓弧四周范圍所形成的矩形,在本篇中圓弧為圓形,可以使用Rect.fromCircle()確定圓弧的范圍

 /// startAngle: 圓弧起始點的角度,x軸正方向為0度,按順時針遞增,y軸負方向為90度,以此類推

 /// sweepAngle: 圓弧掃過的角度,即圓弧終點所在的角度為startAngle + sweepAngle

 /// useCenter: 如果為true,圓弧兩端會與圓心相連,形成一個扇形,本篇中應為false

 /// paint: 畫筆,下文中會進行簡單介紹

 void drawArc(Rect rect, double startAngle, double sweepAngle, bool useCenter, Paint paint)

在Canvas的一系列方法中會發現一個熟悉的名稱:Paint,與Android類似,Flutter中的Paint類也是用來描述畫筆的。

Paint類

Paint類位于 dart.ui 庫中,Paint類保存了畫筆的顏色、粗細、是否抗鋸齒、著色器等屬性。

下面簡單的介紹下幾個常用的屬性:

 Paint paint = Paint()

 ..color = Color(0xFFFF552E)

 ..strokeWidth = 2.0

 ..style = PaintingStyle.stroke

 ..isAntiAlias = true

 ..shader = LinearGradient(colors: []).createShader(rect)

 ..strokeCap = StrokeCap.round

 ..strokeJoin = StrokeJoin.bevel;

屬性說明:

  • color:Color類型,設置畫筆的顏色。

  • strokeWidth:double類型,設置畫筆的粗細。

  • style:PaintingStyle枚舉類型,設置畫筆的樣式, PaintingStyle.stroke 為描邊, PaintingStyle.fill 為填充。

  • isAntiAlias:bool類型,設置是否抗鋸齒,true為開啟抗鋸齒。

  • shader:Shader類型,著色器,一般用來繪制漸變效果,可以使用 LinearGradient、 RadialGradient、 SweepGradient 等。

  • strokeCap:StrokeCap枚舉類型,設置線條兩端點的樣式, StrokeCap.butt 為無(默認值), StrokeCap.round 為圓形, StrokeCap.square 為方形。

  • strokeJoin:StrokeJoin枚舉類型,設置線條交匯處的樣式, StrokeJoin.miter 為銳角, StrokeJoin.round 為圓弧, StrokeJoin.bevel 為斜角,可以參考下圖方便理解:

熟悉了Canvas和Paint的使用之后,就能夠繪制出加載動畫的圓弧了。當然,只是繪制出圓弧并沒有什么用,主要是怎么讓圓弧動起來。

Flutter中的動畫

想要讓圓弧動起來,我們需要使用到Flutter的動畫。下面先來介紹下Flutter中動畫的實現。

Flutter中的動畫相關的類主要有以下幾個:

 Animation:動畫的核心類,是一個抽象類。用來生成動畫執行過程中的插值,輸出的結果可以是線性或曲線的,Animation對象與UI渲染沒有任何關系。

 abstract class Animation<T> extends Listenable implements ValueListenable<T> {

  /// 添加動畫狀態的監聽

  void addStatusListener(AnimationStatusListener listener);


  /// 移除動畫狀態的監聽

  void removeStatusListener(AnimationStatusListener listener);


  /// 獲取當前動畫的狀態

  AnimationStatus get status;


  /// 獲取當前動畫的插值,執行動畫時需要根據該值進行UI繪制等

  T get value;

 }

    AnimationController:動畫的管理類,繼承自 Animation<double>。默認情況下在給定的時間范圍內線性生成從0.0到1.0的值。

    AnimationController對象需要傳遞一個vsync參數,它接收一個TickerProvider類型的對象,主要職責是創建Ticker。Flutter應用在啟動時會綁定一個SchedulerBinding,可以給每一次屏幕刷新添加回調,Ticker就是通過SchedulerBinding來添加屏幕刷新的回調,當屏幕刷新時,會通知到綁定的Ticker回調。假如動畫的UI不在當前屏幕,比如鎖屏時,鎖屏后屏幕停止刷新,不會通知SchedulerBinding,Ticker也就不會觸發,這樣就能夠防止屏幕外的動畫消耗不必要的資源。

 class AnimationController extends Animation<double>

  with AnimationEagerListenerMixin, AnimationLocalListenersMixin, AnimationLocalStatusListenersMixin {

  /// value:動畫的初始值,默認是lowerBound

  /// duration:動畫執行的時長

  /// lowerBound:動畫的最小值,默認值為0.0

  /// upperBound:動畫的最大值,默認值為1.0

  /// vsync:可以通過 `with SingleTickerProviderStateMixin` 傳入StatefulWidget對象

  AnimationController({

  double value,

  this.duration,

  this.lowerBound = 0.0,

  this.upperBound = 1.0,

  @required TickerProvider vsync,

  }) {

  _ticker = vsync.createTicker(_tick);

  }


  Ticker _ticker;


  /// Ticker的回調,每次屏幕刷新都會回調

  void _tick(Duration elapsed) {

  notifyListeners();

  }


  /// 開始播放動畫

  TickerFuture forward({ double from })


  /// 反向播放動畫

  TickerFuture reverse({ double from })


  /// 設置動畫重復執行

  TickerFuture repeat({ double min, double max, bool reverse = false, Duration period })


  /// 釋放動畫資源

  void dispose()

 }

    CurvedAnimation:非線性動畫類,繼承自 Animation<double>。CurvedAnimation可以使用curve屬性指定曲線函數Curve,類似Android動畫的插值器,Flutter中已經實現了許多常用的曲線,在Curves類中可以找到,比如Curves.linear、Curves.decelerate、Curves.ease。也可以繼承Curve類重寫 transform() 方法來實現自定義的曲線函數。

 class CurvedAnimation extends Animation<double>

  with AnimationWithParentMixin<double> {

  /// parent:指定AnimationController對象

  /// curve:指定動畫的曲線函數

  CurvedAnimation({

  @required this.parent,

  @required this.curve,

  })

 }


 abstract class Curve {

  /// 計算動畫執行中`t`點的插值,可以自定義曲線函數

  double transform(double t)

 }

    Tween:補間值的生成類,繼承自 Animatable<T>。

    由于AnimationController的值范圍默認為0.0到1.0,如果需要不同的范圍或數據類型,可以使用Tween指定動畫值的范圍。Tween不僅能返回double類型的值,還有IntTween、ColorTween、SizeTween等各種返回不同數據類型的子類。
    使用Tween對象需要調用 animate() 方法,傳入AnimationController對象,該方法會返回一個Animation,這樣就可以獲取到動畫的插值了。

 class Tween<T extends dynamic> extends Animatable<T> {

  /// begin:動畫的起始值

  /// end:動畫的結束值

  Tween({ this.begin, this.end });


  /// 可以把double類型的動畫插值轉換成任何類型的值

  T transform(double t)


  /// parent:傳入AnimationController對象

  /// 返回Animation對象,使用Animation.value獲取動畫當前的插值

  Animation<T> animate(Animation<double> parent)

 }

    AnimatedBuilder:用于構建動畫的Widget,將動畫和要執行動畫的Widget關聯起來,繼承關系為AnimatedBuilder → AnimatedWidget → StatefulWidget。

 class AnimatedBuilder extends AnimatedWidget {

  const AnimatedBuilder({

  @required Listenable animation,

  @required this.builder,

  });


  /// typedef TransitionBuilder = Widget Function(BuildContext context, Widget child);

  /// builder是一個函數,返回Widget對象

  final TransitionBuilder builder;


  @override

  Widget build(BuildContext context) {

  return builder(context, child);

  }

 }


 abstract class AnimatedWidget extends StatefulWidget {

  const AnimatedWidget({

  @required this.listenable,

  });


  @protected

  Widget build(BuildContext context);


  @override

  _AnimatedState createState() => _AnimatedState();

 }


 class _AnimatedState extends State<AnimatedWidget> {

  @override

  void initState() {

  super.initState();

  widget.listenable.addListener(_handleChange);

  }


  @override

  void dispose() {

  widget.listenable.removeListener(_handleChange);

  super.dispose();

  }


  void _handleChange() {

  setState(() { });

  }


  @override

  Widget build(BuildContext context) => widget.build(context);

 }

分析上面列出的源碼,AnimatedWidget是一個StatefulWidget。當AnimatedWidget關聯的_AnimatedState初始化時,會注冊動畫的監聽函數_handleChange,_handleChange監聽函數中又調用了setState()方法,即動畫插值每次改變時都會調用build()方法。_AnimatedState.build()方法中又調用了AnimatedWidget.build()方法,在AnimatedBuilder中實現了AnimatedWidget.build()方法:調用屬性builder生成Widget,最終實現了動畫與Widget的綁定。

加載動畫的實現

了解了Flutter的動畫后,再結合之前對加載動畫流程的分析,加載動畫可分成三個階段,我們可以依賴Tween類,指定值的范圍從0.0到3.0變化,當然也可以只使用AnimationController,指定lowerBound和upperBound的值分別為0.0和3.0。這里之所以不使用CurvedAnimation,是因為加載動畫的圓弧是線性變化的,不存在加速減速,沒有必要使用。

大圓弧能夠實現了,我們再來看內部的小圓弧,仔細觀察會發現小圓弧的變化規律與大圓弧完全一致,只不過小圓弧的起始位置在x軸負方向,與大圓弧正好相差180度,也就是π弧度。在繪制大圓弧的同時,可以很輕松的計算出小圓弧的起點的角度(即大圓弧起點的角度+π弧度)。

至此整個動畫的實現思路就清晰了:

  1. 自定義加載動畫的Widget,繼承自CustomPaint類。

  2. 使用AnimationController、Tween創建動畫,動畫的值范圍從0.0到3.0線性變化,并且設置動畫重復執行。動畫插值每遞增1.0代表動畫執行的一個階段。

  3. 繼承CustomPainter類,實現paint()方法繪制圓弧。根據動畫的插值判斷當前屬于動畫的哪個階段,再計算出圓弧的起點、掃過的角度,繪制出兩個圓弧。

下面是實現加載動畫的關鍵代碼:

 import 'dart:math';

 import 'package:flutter/material.dart';


 class WubaLoadingWidget extends StatefulWidget {

  @override

  _WubaLoadingWidgetState createState() => _WubaLoadingWidgetState();

 }


 class _WubaLoadingWidgetState extends State<WubaLoadingWidget>

  with SingleTickerProviderStateMixin {

  AnimationController _animationController;

  Animation<double> _animation;


  @override

  void initState() {

  super.initState();

  _animationController = new AnimationController(

   // 可以指定lowerBound、upperBound,使用AnimationController對象

   // lowerBound: 0.0,

   // upperBound: 3.0,

   vsync: this,

   duration: const Duration(milliseconds: 1500),

  );

  _animation = Tween(begin: 0.0, end: 3.0)

   .animate(_animationController);

  _animationController.forward(); // 執行動畫

  _animationController.repeat(); // 設置動畫循環執行

  }


  @override

  void dispose() {

  // 調用dispose()方法釋放動畫資源

  _animationController.dispose();

  super.dispose();

  }


  @override

  Widget build(BuildContext context) {

  return AnimatedBuilder(

   animation: _animationController,

   builder: (BuildContext context, Widget child) {

   return Container(

    child: CustomPaint(

    painter: _LoadingPaint(

     value: _animation.value,

    ),

    ),

   );

   },

  );

  }

 }


 class _LoadingPaint extends CustomPainter {

  final double value;

  final Paint _outerPaint; // 大圓弧的Paint

  final Paint _innerPaint; // 小圓弧的Paint


  _LoadingPaint({

  this.value,

  });


  @override

  void paint(Canvas canvas, Size size) {

  double startAngle = 0;

  double sweepAngle = 0;

  // 動畫的第一階段:圓弧起點為0度,終點的角度遞增

  if (value <= 1.0) {

   startAngle = 0;

   sweepAngle = value * pi;

  }

  // 動畫的第二階段:圓弧掃過的弧度為π弧度(180度),起點、終點一起順時針旋轉,一共旋轉π弧度

  else if (value <= 2.0) {

   startAngle = (value - 1) * pi;

   sweepAngle = pi;

  }

  // 動畫的第三階段:圓弧的終點不變,起點從x軸負方向開始順時針旋轉,直到起點也到達x軸正方向

  else {

   startAngle = pi + (value - 2) * pi;

   sweepAngle = (3 - value) * pi;

  }

  // 繪制外圈的大圓弧

  canvas.drawArc(outerRect, startAngle, sweepAngle, false, _outerPaint);

  // 繪制內圈的小圓弧

  canvas.drawArc(innerRect, startAngle + pi, sweepAngle, false, _innerPaint);

  }


  @override

  bool shouldRepaint(CustomPainter oldDelegate) {

  return true;

  }

 }

總結

Flutter的Canvas、Paint與Android的API非常類似,基本的思路也一致,對于Android同學比較容易掌握。

Flutter中動畫的實現相較于Android邏輯更加清晰簡單,方便易用。AnimatedBuilder類巧妙的將UI與動畫整合在一起,把UI和動畫職責分離,這種思路值得學習。Flutter中的動畫還有路由過渡動畫、Hero動畫、切換動畫組件AnimatedSwitcher等,有需要的同學可以查找相關資料。

感謝各位的閱讀!關于“怎么使用Flutter實現58同城中的加載動畫”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,讓大家可以學到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

常宁市| 新龙县| 宁河县| 岗巴县| 张北县| 丰台区| 新营市| 和平区| 永胜县| 新干县| 义乌市| 苏尼特右旗| 西吉县| 永吉县| 凭祥市| 区。| 德令哈市| 延边| 金华市| 商丘市| 七台河市| 诏安县| 余江县| 安图县| 松溪县| 山东| 札达县| 开封县| 丹东市| 伽师县| 平乐县| 浦东新区| 宜兰县| 阳信县| 清河县| 西林县| 台安县| 昌邑市| 北安市| 周至县| 久治县|