亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

如何使用Flutter實現(xiàn)58同城中的加載動畫詳解

 更新時間:2019年10月14日 08:32:43   作者:吳振  
這篇文章主要給大家介紹了關(guān)于如何使用Flutter實現(xiàn)58同城中加載動畫詳?shù)南嚓P(guān)資料,文中通過示例代碼介紹的非常詳細,對大家學習或者使用Flutter具有一定的參考學習價值,需要的朋友們下面來一起學習學習吧

前言

在應(yīng)用中執(zhí)行耗時操作時,為了避免界面長時間等待造成假死的現(xiàn)象,往往會添加一個加載中的動畫來提醒用戶,在58同城中也不例外,而且我們并沒有使用系統(tǒng)默認的加載動畫,而是制作了一個具有58特色的加載動畫。

在本篇文章中,給大家分享下筆者使用Flutter實現(xiàn)58同城中加載動畫的過程。先看一下加載動畫的效果:

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

動畫的流程

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

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

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

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

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

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

接下來開始寫代碼實現(xiàn)。

由于動畫是由一個圓弧不斷變化組成的,如果使用Android,我們很自然的想到可以使用Canvas來進行圓弧的繪制,然后根據(jù)時間的變化不停地重新繪制圓弧,從而實現(xiàn)動畫效果。那么在Flutter中是否也存在Canvas呢,答案是肯定的,F(xiàn)lutter和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(具體的參數(shù)列表請參考文檔和源碼,篇幅有限不再一一列出):


Android Flutter

drawPoint()

drawPoints()

drawPoints()

drawLine()

drawLines()

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

save()

restore()

save()

restore()


 

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

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

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

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

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

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

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

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

在Canvas的一系列方法中會發(fā)現(xiàn)一個熟悉的名稱:Paint,與Android類似,F(xiàn)lutter中的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類型,設(shè)置畫筆的顏色。
  • strokeWidth:double類型,設(shè)置畫筆的粗細。
  • style:PaintingStyle枚舉類型,設(shè)置畫筆的樣式, PaintingStyle.stroke 為描邊, PaintingStyle.fill 為填充。
  • isAntiAlias:bool類型,設(shè)置是否抗鋸齒,true為開啟抗鋸齒。
  • shader:Shader類型,著色器,一般用來繪制漸變效果,可以使用 LinearGradient、 RadialGradient、 SweepGradient 等。
  • strokeCap:StrokeCap枚舉類型,設(shè)置線條兩端點的樣式, StrokeCap.butt 為無(默認值), StrokeCap.round 為圓形, StrokeCap.square 為方形。
  • strokeJoin:StrokeJoin枚舉類型,設(shè)置線條交匯處的樣式, StrokeJoin.miter 為銳角, StrokeJoin.round 為圓弧, StrokeJoin.bevel 為斜角,可以參考下圖方便理解:

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

Flutter中的動畫

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

Flutter中的動畫相關(guān)的類主要有以下幾個:

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

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

  /// 添加動畫狀態(tài)的監(jiān)聽

  void addStatusListener(AnimationStatusListener listener);


  /// 移除動畫狀態(tài)的監(jiān)聽

  void removeStatusListener(AnimationStatusListener listener);


  /// 獲取當前動畫的狀態(tài)

  AnimationStatus get status;


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

  T get value;

 }

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

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

 class AnimationController extends Animation<double>

  with AnimationEagerListenerMixin, AnimationLocalListenersMixin, AnimationLocalStatusListenersMixin {

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

  /// duration:動畫執(zhí)行的時長

  /// 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的回調(diào),每次屏幕刷新都會回調(diào)

  void _tick(Duration elapsed) {

  notifyListeners();

  }


  /// 開始播放動畫

  TickerFuture forward({ double from })


  /// 反向播放動畫

  TickerFuture reverse({ double from })


  /// 設(shè)置動畫重復執(zhí)行

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


  /// 釋放動畫資源

  void dispose()

 }

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

 class CurvedAnimation extends Animation<double>

  with AnimationWithParentMixin<double> {

  /// parent:指定AnimationController對象

  /// curve:指定動畫的曲線函數(shù)

  CurvedAnimation({

  @required this.parent,

  @required this.curve,

  })

 }


 abstract class Curve {

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

  double transform(double t)

 }

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

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

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

  /// begin:動畫的起始值

  /// end:動畫的結(jié)束值

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


  /// 可以把double類型的動畫插值轉(zhuǎn)換成任何類型的值

  T transform(double t)


  /// parent:傳入AnimationController對象

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

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

 }

    AnimatedBuilder:用于構(gòu)建動畫的Widget,將動畫和要執(zhí)行動畫的Widget關(guān)聯(lián)起來,繼承關(guān)系為AnimatedBuilder → AnimatedWidget → StatefulWidget。

 class AnimatedBuilder extends AnimatedWidget {

  const AnimatedBuilder({

  @required Listenable animation,

  @required this.builder,

  });


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

  /// builder是一個函數(shù),返回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關(guān)聯(lián)的_AnimatedState初始化時,會注冊動畫的監(jiān)聽函數(shù)_handleChange,_handleChange監(jiān)聽函數(shù)中又調(diào)用了setState()方法,即動畫插值每次改變時都會調(diào)用build()方法。_AnimatedState.build()方法中又調(diào)用了AnimatedWidget.build()方法,在AnimatedBuilder中實現(xiàn)了AnimatedWidget.build()方法:調(diào)用屬性builder生成Widget,最終實現(xiàn)了動畫與Widget的綁定。

加載動畫的實現(xiàn)

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

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

至此整個動畫的實現(xiàn)思路就清晰了:

  1. 自定義加載動畫的Widget,繼承自CustomPaint類。
  2. 使用AnimationController、Tween創(chuàng)建動畫,動畫的值范圍從0.0到3.0線性變化,并且設(shè)置動畫重復執(zhí)行。動畫插值每遞增1.0代表動畫執(zhí)行的一個階段。
  3. 繼承CustomPainter類,實現(xiàn)paint()方法繪制圓弧。根據(jù)動畫的插值判斷當前屬于動畫的哪個階段,再計算出圓弧的起點、掃過的角度,繪制出兩個圓弧。

下面是實現(xiàn)加載動畫的關(guān)鍵代碼:

 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(); // 執(zhí)行動畫

  _animationController.repeat(); // 設(shè)置動畫循環(huán)執(zhí)行

  }


  @override

  void dispose() {

  // 調(diào)用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度),起點、終點一起順時針旋轉(zhuǎn),一共旋轉(zhuǎn)π弧度

  else if (value <= 2.0) {

   startAngle = (value - 1) * pi;

   sweepAngle = pi;

  }

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

  else {

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

   sweepAngle = (3 - value) * pi;

  }

  // 繪制外圈的大圓弧

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

  // 繪制內(nèi)圈的小圓弧

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

  }


  @override

  bool shouldRepaint(CustomPainter oldDelegate) {

  return true;

  }

 }

總結(jié)

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

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

如果大家需要定制一些個性化的加載動畫,推薦一個GitHub的開源項目:flutter_spinkit,這個插件提供了很多種常用的加載動畫效果。

好了,以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對腳本之家的支持。

相關(guān)文章

最新評論