乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      最近 Flutter 爭氣了! Flutter 也可以做這么炫酷的動畫

       西北望msm66g9f 2019-05-13

      作者:BakerJQ

      鏈接:https:///post/5ca375f3e51d451a18362e2a

      在 2019 年的谷歌 I/O 大會上,開發(fā)團(tuán)隊(duì)發(fā)布了 Flutter for web 的首個技術(shù)預(yù)覽版,宣布 Flutter 正在為包括 Google Home Hub 在內(nèi)的 Google 智能顯示平臺提供支持,并通過結(jié)合 Chrome OS 為桌面級應(yīng)用程序提供支持邁出第一步。

      一張圖感受下

      本篇作者給大家展示了一個如何用Flutter做炫酷動畫!可能很多同學(xué)對 Flutter 還不了解,沒關(guān)系,可以通過全文類比于 Android 上制作動畫的區(qū)別與相似之處!

      前言

      這一段時間,F(xiàn)lutter的勢頭是越來越猛了,作為一個Android程序猿,我自然也是想要趕緊嘗試一把。在學(xué)習(xí)到動畫的這部分后,為了加深對Flutter動畫實(shí)現(xiàn)的理解,我決定把之前寫的一個卡片切換效果的開源小項(xiàng)目,用Flutter“翻譯”一遍。

      廢話不多說,先來看看效果吧:

      Android:

      IOS

      Github地址:

      https://github.com/BakerJQ/Flutter-InfiniteCards

      思路

      首先,關(guān)于卡片的層疊效果,在原Android項(xiàng)目中,是通過Scale差異以及TranslationY來體現(xiàn)的,F(xiàn)lutter可以繼續(xù)采用這種方式。

      其次,對于自定義卡片的內(nèi)容,原Android項(xiàng)目是通過Adapter實(shí)現(xiàn),對于Flutter,則可以采用IndexedWidgetBuilder實(shí)現(xiàn)。

      最后,就是自定義動效的實(shí)現(xiàn),原Android項(xiàng)目是通過一個0到1的ValueAnimator來定義動畫的展示過程,而Flutter中,正好有與之對應(yīng)的Animation和AnimationController,如此我們就可以直接自定義一個動畫過程中,具體的視圖展示方式。

      組件總覽

      由于卡片視圖需要根據(jù)動畫情況進(jìn)行渲染,所以顯然是一個StatefulWidget。

      同時,我們給出三種基本的動畫模式:

      enum AnimType {
        TO_FRONT,//被選中的卡片通過自定義動效移至第一,其他的卡片通過通用動效補(bǔ)位
        SWITCH,//選中的卡片和第一張卡片互換位置,并都是自定義動效
        TO_END,//第一張圖片通過自定義動效移至最后,其他卡片通過通用動效補(bǔ)位
      }

      并通過Helper和Controller來處理所有的動畫邏輯

      其中Controller由構(gòu)造方法傳入

      InfiniteCards({
        @required this.controller,
        this.width,
        this.height,
        this.background,
      });

      Helper在initState中進(jìn)行構(gòu)建,并初始化,同時將Helper綁定給Controller:

      @override
      void initState() {
        ...
        _helper = AnimHelper(
            controller: widget.controller,
            //傳入動畫更新監(jiān)聽,動畫時調(diào)用setState進(jìn)行實(shí)時渲染
            listenerForSetState: () {
              setState(() {});
            });
        _helper.init(this, context);
        if (widget.controller != null) {
            widget.controller.animHelper = _helper;
        }
      }

      而build過程中,則通過Helper返回具體的Widget列表,而Stack則是為了實(shí)現(xiàn)層疊效果。

      Widget build(BuildContext context) {
        ...
        return Container(
          ...
          child: Stack(
            children: _helper.getCardList(_width, _height),
          ),
        );
      }

      如此,基本的初始化等操作就算是完成了。下面我們來看看Controller和Helper都是怎么工作的。

      Controller

      我們先來看看Controller所包含的內(nèi)容:

      class InfiniteCardsController {
        //卡片構(gòu)造器
        IndexedWidgetBuilder _itemBuilder;
        //卡片個數(shù)
        int _itemCount;
        //動畫時長
        Duration _animDuration;
        //點(diǎn)擊卡片是否觸發(fā)切換動畫
        bool _clickItemToSwitch;
        //動畫Transform
        AnimTransform _transformToFront,_transformToBack,...;
        //排序Transform
        ZIndexTransform _zIndexTransformCommon,...;
        //動畫類型
        AnimType _animType;
        //曲線定義(類Android插值器)
        Curve _curve;
        //helper
        AnimHelper _animHelper;
        ...
        void anim(int index) {
          _animHelper.anim(index);
        }
        void reset(...) {
          ...
          //重設(shè)各參數(shù)
          setControllerParams();
          _animHelper.reset(); 
          ...
        }
      }

      由此可以看到,Controller基本上就是作為參數(shù)配置器和Helper的方法代理的存在。由此童鞋們肯定就知道了,對于動效的自定義和動效的觸發(fā)等操作,都是通過Controller來完成,demo如下:

      //構(gòu)建Controller
      _controller = InfiniteCardsController(
        itemBuilder: _renderItem,
        itemCount: 5,
        animType: AnimType.SWITCH,
      );
      //調(diào)用reset
      _controller.reset(
        itemCount: 4,
        animType: AnimType.TO_FRONT,
        transformToBack: _customToBackTransform,
      );
      //調(diào)用展示下一張卡片動畫
      _controller.reset(animType: AnimType.TO_END);
      _controller.next();

      關(guān)于具體的自定義,我們稍后再聊,咱們先來看看Helper。

      Helper

      Helper是整個動畫效果實(shí)現(xiàn)的核心類,我們先看幾個它所包含的核心成員:

      class AnimHelper {
        final InfiniteCardsController controller;
        //切換動畫
        AnimationController _animationController;
        Animation<double> _animation;
        //卡片列表
        List<CardItem> _cardList = new List();
        //需要向后切換的卡片,和需要向前切換的卡片
        CardItem _cardToBack, _cardToFront;
        //需要向后切換的卡片位置,和需要向前切換的卡片位置
        int _positionToBack, _positionToFront;
      }

      現(xiàn)在我們來看看,如果要觸發(fā)一個切換動畫,這些成員是如何相互配合的。

      當(dāng)選中一張卡片進(jìn)行切換時,這張卡片就是需要向前切換的卡片(ToFront),而第一張卡片,就是需要向后切換的卡片(ToBack)。

      void _cardAnim(int index, CardItem card) {
        //記錄要切換的卡片
        _cardToFront = card;
        _cardToBack = _cardList[0];
        _positionToBack = 0;
        _positionToFront = index;
        //觸發(fā)動畫
        _animationController.forward(from: 0.0);
      }

      由于設(shè)置了AnimationListener,在動畫過程中,setState就會被調(diào)用,如此就會觸發(fā)Widget的build,從而觸發(fā)Helper的getCardList方法。

      我們來看看在切換動畫的過程中,是如何返回卡片Widget列表的。

      List<Widget> getCardList(double width, double height) {
        for (int i = 0; i < controller.itemCount; i++) {
          ...
          if (_isSwitchAnim) {
            //處理切換動畫
            _switchTransform(width, height, i);
          }
          ...
        }
        //根據(jù)zIndex進(jìn)行排序渲染
        List<CardItem> copy = List.from(_cardList);
        copy.sort((card1, card2) {
          return card1.zIndex < card2.zIndex ? 1 : -1;
        });
        return copy.map((card) {
          return card.transformWidget;
        }).toList();
      }

      如上代碼所示,先進(jìn)行動畫處理,后根據(jù)zIndex進(jìn)行排序,因?yàn)橐WC在前面的后渲染。

      而動畫是如何處理的呢,以切換到前面的卡片為例:

      void _toFrontTransform(double width, double height, int fromPosition, int toPosition) {
          CardItem cardItem = _cardList[fromPosition];
          controller.zIndexTransformToFront(
              cardItem, _animation.value,
              _getCurveValue(_animation.value),
              width, height, fromPosition, toPosition);
          cardItem.transformWidget = controller.transformToFront(
              cardItem.widget, _animation.value,
              _getCurveValue(_animation.value),
              width, height, fromPosition, toPosition);
        }

      原來,正是在這一步,Helper通過Controller中配置的自定義動畫方法,得到了卡片的Widget。

      由此,動畫展示的基本流程就描述完了,下面我們進(jìn)入最關(guān)鍵的部分--如何自定義動畫。

      自定義動畫

      我們以通用動畫為例,來看看自定義動畫的主要流程。

      首先,AnimTransform為如下方法的定義:

      typedef AnimTransform = Transform Function(
          Widget item,//卡片原始Widget
          double fraction,//動畫執(zhí)行的系數(shù)
          double curveFraction,//曲線轉(zhuǎn)換后的系數(shù)
          double cardHeight,//整體高度
          double cardWidth,//整體寬度
          int fromPosition,//卡片開始位置
          int toPosition);//卡片要移動到的位置

      該方法返回的是一個Transform,專門用于處理視圖變換的Widget,而我們要做的,就是根據(jù)傳入的參數(shù),構(gòu)建相應(yīng)系數(shù)下的Widget。

      以DefaultCommonTransform為例:

      Transform _defaultCommonTransform(Widget item, 
          double fraction, double curveFraction, double cardHeight, double cardWidth, int fromPosition, int toPosition) 
        //需要跨越的卡片數(shù)量{
        int positionCount = fromPosition - toPosition;
        //以0.8做為第一張的縮放尺寸,每向后一張縮小0.1
        //(0.8 - 0.1 * fromPosition) = 當(dāng)前位置的縮放尺寸
        //(0.1 * fraction * positionCount) = 移動過程中需要改變的縮放尺寸 
        double scale = (0.8 - 0.1 * fromPosition) + (0.1 * fraction * positionCount);
        //在Y方向的偏移量,每向后一張,向上偏移卡片寬度的0.02
        //-cardHeight * (0.8 - scale) * 0.5 對卡片做整體居中處理
        double translationY = -cardHeight * (0.8 - scale) * 0.5 -
            cardHeight * (0.02 * fromPosition - 0.02 * fraction * positionCount);
        //返回縮放后,進(jìn)行Y方向偏移的Widget
        return Transform.translate(
          offset: Offset(0, translationY),
          child: Transform.scale(
            scale: scale,
            child: item,
          ),
        );
      }

      對于向第一位移動的選中卡片,也是同理,只不過是根據(jù)該卡片對應(yīng)的轉(zhuǎn)換器來進(jìn)行自定義動畫的轉(zhuǎn)換。

      最后的效果,就像演示圖中第一次點(diǎn)擊,圖片向前翻轉(zhuǎn)到第一位的效果一樣。

      總結(jié)

      由于Flutter采用的是聲明式的視圖構(gòu)建方式,在編碼初期,多少會受到原生編碼方式的思維影響,而覺得很難受。但是在熟悉了之后,就會發(fā)現(xiàn)其實(shí)很多思想都是共通的,比如Animation,比如插值器的概念等等。

      另外,研讀源碼仍然是最有效的解決問題的方式,比如相比Android中直接對ScrollView進(jìn)行animateTo操作,在Flutter中需要通過ScrollController進(jìn)行animateTo操作,正是這一點(diǎn)讓我找到了在Flutter中實(shí)現(xiàn)InfiniteCards效果的方法。

      更具體的Demo請前往Github的Flutter-InfiniteCards Repo,歡迎大家star和提issue。

      再次貼一下Github地址:

      https://github.com/BakerJQ/Flutter-InfiniteCards

      作者也實(shí)現(xiàn)了 Android 上相同效果,歡迎一起學(xué)習(xí):

      https://github.com/BakerJQ/Android-InfiniteCards


        本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報(bào)。
        轉(zhuǎn)藏 分享 獻(xiàn)花(0

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多