Flutter Go 源碼分析(六)

  1. PageReveal(用于操作nextPage執(zhí)行滑動(dòng)動(dòng)畫)
    PageReveal主要是利用ClipOval組件對(duì)該widget進(jìn)行圓形裁剪,從而和滑動(dòng)手勢(shì)關(guān)聯(lián)起來實(shí)現(xiàn)滑動(dòng)動(dòng)畫砍聊。
final double revealPercent;
  final Widget child;

  PageReveal({
    this.revealPercent,
    this.child
  });

  @override
  Widget build(BuildContext context) {
    return ClipOval(
      clipper: new CircleRevealClipper(revealPercent),//自定義剪裁路徑 
      child: child,
    );
  }

通過CircleRevealClipper繼承于CustomClipper重寫getClip方法自定義剪裁路徑:

Rect getClip(Size size) {

    final epicenter = new Offset(size.width / 2, size.height * 0.9);//剪裁中心點(diǎn)

    double theta = atan(epicenter.dy / epicenter.dx);
    final distanceToCorner = epicenter.dy / sin(theta);

    final radius = distanceToCorner * revealPercent;//圓形半徑
    final diameter = 2 * radius;//圓形的直徑

    return new Rect.fromLTWH(epicenter.dx - radius, epicenter.dy - radius, diameter, diameter);
  }
  1. PagerIndicator指示器
Widget build(BuildContext context) {

    List<PageBubble> bubbles = [];
    for(var i = 0; i < viewModel.pages.length; ++i ){
      final page = viewModel.pages[i];

      var percentActive;

      if(i == viewModel.activeIndex){//滑到當(dāng)前的index
        percentActive = 1.0 - viewModel.slidePercent;
      } else if (i == viewModel.activeIndex - 1 && viewModel.slideDirection == SlideDirection.leftToRight){//從左往右滑動(dòng) 而且是當(dāng)前index-1 
        percentActive = viewModel.slidePercent;
      } else if (i == viewModel.activeIndex + 1 && viewModel.slideDirection == SlideDirection.rightToLeft){//從右往左滑動(dòng) 而且是當(dāng)前index+1
        percentActive = viewModel.slidePercent;
      }else {//其他請(qǐng)求 不進(jìn)行變化
        percentActive = 0.0;
      }
      //isHollow 是否是未滑到的index 當(dāng)前index 的后面
      bool isHollow = i > viewModel.activeIndex || (i == viewModel.activeIndex && viewModel.slideDirection == SlideDirection.leftToRight);



      bubbles.add(
        new  PageBubble(//指示器原點(diǎn)
          viewModel: new PageBubbleViewModel(
              page.iconAssetPath,//icon imamge路徑
              page.color,//頁面顏色
              isHollow,//isHollow 是否是未滑到的index 當(dāng)前index 的后面
              percentActive,//滑動(dòng)百分百
          ),
        ),
      );
    }

    final bubbleWidth = 55.0 ;
    final baseTranslation = ((viewModel.pages.length * bubbleWidth) / 2) - (bubbleWidth / 2) ;
    var translation = baseTranslation - (viewModel.activeIndex * bubbleWidth);

    if (viewModel.slideDirection == SlideDirection.leftToRight){
        translation = bubbleWidth * viewModel.slidePercent + translation;
    }else if (viewModel.slideDirection == SlideDirection.rightToLeft){
        translation = bubbleWidth * viewModel.slidePercent - translation;
    }

    return new Column(
      children: <Widget>[
        new Expanded(child: new Container()),
        new Transform(
          transform: new Matrix4.translationValues(0, 0.0, 0.0),
          child: new Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: bubbles,
          ),
        ),
      ],
    );
  }
}
  • PageBubble指示圓點(diǎn)
Widget build(BuildContext context) {
    return new Container(
      width: 55.0,
      height: 65.0,
      child: new Center(
        child: new Container(
          width: lerpDouble(20.0,45.0,viewModel.activePercent),//根據(jù)比例縮放
          height: lerpDouble(20.0,45.0,viewModel.activePercent),//根據(jù)比例縮放
          decoration: new BoxDecoration(
            shape: BoxShape.circle,
            color: viewModel.isHollow
                ? const Color(0x88FFFFFF).withAlpha(0x88 * viewModel.activePercent.round())
                : const Color(0x88FFFFFF),
            border: new Border.all(
              color: viewModel.isHollow
                  ? const Color(0x88FFFFFF).withAlpha((0x88 * (1.0 - viewModel.activePercent)).round())
                  : Colors.transparent,
              width: 3.0,
            ),
          ),
          child: new Opacity(//透明度變化
            opacity: viewModel.activePercent,
            child: Image.asset(
              viewModel.iconAssetPath,
              color: viewModel.color,
            ),
          ),
        ),
      ),
    );
  }

代碼中我加了比較詳細(xì)的注釋舔腾,大家看代碼即可吟吝。

  • PageDragger(負(fù)責(zé)滑動(dòng)-->手勢(shì))
    一些初始化方法:
final canDragLeftToRight;
  final canDragRightToLeft;

  final StreamController<SlideUpdate> slideUpdateStream;


  PageDragger({
    this.canDragLeftToRight,//是否能從左往右滑動(dòng)
    this.canDragRightToLeft,//是否能從右往左滑動(dòng)
    this.slideUpdateStream,//監(jiān)聽器
  });

_PageDraggerState用來監(jiān)聽組件GestureDetector滑動(dòng)相關(guān)信息:

static const FULL_TRANSTITION_PX = 300.0;//滑動(dòng)最大距離

  Offset dragStart;//開始滑動(dòng)的點(diǎn)
  SlideDirection slideDirection;//滑動(dòng)方向
  double slidePercent = 0.0;//滑動(dòng)百分百

  onDragStart(DragStartDetails details){//滑動(dòng)開始
    dragStart = details.globalPosition;
  }

  onDragUpdate(DragUpdateDetails details) {//滑動(dòng)更新
    if (dragStart != null) {
      print(details.globalPosition);
      final newPosition = details.globalPosition;
      final dx = dragStart.dx - newPosition.dx;

      if (dx > 0 && widget.canDragRightToLeft) {
        slideDirection = SlideDirection.rightToLeft;
      } else if (dx < 0 && widget.canDragLeftToRight) {
        slideDirection = SlideDirection.leftToRight;
      } else {
        slideDirection = SlideDirection.none;
      }

      if (slideDirection != SlideDirection.none){//clamp :如果參數(shù)位于最小數(shù)值和最大數(shù)值之間的數(shù)值范圍內(nèi),則該函數(shù)將返回參數(shù)值。如果參數(shù)大于范圍瘾婿,該函數(shù)將返回最大數(shù)值恬惯。如果參數(shù)小于范圍向拆,該函數(shù)將返回最小數(shù)值,通過這個(gè)函數(shù)為變量的賦值設(shè)置了取值范圍
      slidePercent = (dx / FULL_TRANSTITION_PX).abs().clamp(0.0, 1.0);
      } else {
        slidePercent = 0.0;
      }
      widget.slideUpdateStream.add(//發(fā)送正在滑動(dòng)監(jiān)聽數(shù)據(jù)
          new SlideUpdate(
          UpdateType.dragging,
          slideDirection,
          slidePercent
      ));
    }
  }

  onDragEnd(DragEndDetails details){//滑動(dòng)結(jié)束
    widget.slideUpdateStream.add(//發(fā)送滑動(dòng)結(jié)束消息
      new SlideUpdate(
      UpdateType.doneDragging,
      SlideDirection.none,
      0.0,
      )
    );

    dragStart = null;
  }
  1. FourthPageState處理監(jiān)聽相關(guān)
StreamController<SlideUpdate> slideUpdateStream;////滑動(dòng)監(jiān)聽 controller
  AnimatedPageDragger animatedPageDragger;//動(dòng)畫執(zhí)行者

  int activeIndex = 0;//當(dāng)前序號(hào)
  SlideDirection slideDirection = SlideDirection.none;
  int nextPageIndex = 0;//下一個(gè)序號(hào)
  int waitingNextPageIndex = -1;

  double slidePercent = 0.0;//滑動(dòng)到下一頁的百分百

  FourthPageState() {
    slideUpdateStream = new StreamController<SlideUpdate>();//滑動(dòng)監(jiān)聽 controller

    slideUpdateStream.stream.listen((SlideUpdate event) {//監(jiān)聽
      if (mounted) {
        setState(() {
          if (event.updateType == UpdateType.dragging) {//手指滑動(dòng) 進(jìn)行滑動(dòng)動(dòng)畫
            slideDirection = event.direction;
            slidePercent = event.slidePercent;

            if (slideDirection == SlideDirection.leftToRight) {//從左往右滑動(dòng)
              nextPageIndex = activeIndex - 1;
            } else if (slideDirection == SlideDirection.rightToLeft) {//從右往右滑動(dòng)
              nextPageIndex = activeIndex + 1;
            } else {
              nextPageIndex = activeIndex;
            }
          } else if (event.updateType == UpdateType.doneDragging) {//手指滑動(dòng)完成 進(jìn)行下一步動(dòng)畫(跳轉(zhuǎn)下個(gè)頁面 或者返回 0.5界限)
            if (slidePercent > 0.5) {//跳轉(zhuǎn)下個(gè)頁面的動(dòng)畫
              animatedPageDragger = new AnimatedPageDragger(
                slideDirection: slideDirection,
                transitionGoal: TransitionGoal.open,
                slidePercent: slidePercent,
                slideUpdateStream: slideUpdateStream,
                vsync: this,
              );
            } else {//返回滑動(dòng)前的頁面動(dòng)畫
              animatedPageDragger = new AnimatedPageDragger(
                slideDirection: slideDirection,
                transitionGoal: TransitionGoal.close,
                slidePercent: slidePercent,
                slideUpdateStream: slideUpdateStream,
                vsync: this,
              );

              waitingNextPageIndex = activeIndex;
            }

            animatedPageDragger.run();//滑動(dòng)完 完成接下來的動(dòng)畫
          } else if (event.updateType == UpdateType.animating) {//動(dòng)畫進(jìn)行中 進(jìn)行百分百刷新
            slideDirection = event.direction;
            slidePercent = event.slidePercent;
          } else if (event.updateType == UpdateType.doneAnimating) {//動(dòng)畫完成  置空百分百數(shù)據(jù) 銷毀animatedPageDragger
            if (waitingNextPageIndex != -1) {
              nextPageIndex = waitingNextPageIndex;
              waitingNextPageIndex = -1;
            } else {
              activeIndex = nextPageIndex;
            }

            slideDirection = SlideDirection.none;
            slidePercent = 0.0;

            animatedPageDragger.dispose();//銷毀animatedPageDragger
          }
        });
      }
    });
  }

這里要說一下AnimatedPageDragger這個(gè)類酪耳,它是用來處理滑動(dòng)完成是進(jìn)行跳轉(zhuǎn)下個(gè)頁面或者返回上個(gè)頁面的動(dòng)畫執(zhí)行的浓恳。

static const PERCENT_PER_MILLISECOND = 0.005;//每毫秒的百分百

  final slideDirection;//滑動(dòng)方向
  final transitionGoal;//動(dòng)畫類型 open: 跳轉(zhuǎn)下個(gè)頁面   close: 返回當(dāng)前頁面

  AnimationController completionAnimationController;//動(dòng)畫監(jiān)聽controller

  AnimatedPageDragger({
      this.slideDirection,
      this.transitionGoal,
      slidePercent,
      StreamController<SlideUpdate> slideUpdateStream,
      TickerProvider vsync,
  }) {//一些初始化操作
    final startSlidePercent = slidePercent;
    var endSlidePercent;
    var duration;

    if ( transitionGoal == TransitionGoal.open){//如果是跳轉(zhuǎn)下個(gè)頁面
      endSlidePercent = 1.0;

      final slideRemaining = 1.0 - slidePercent;

      duration = new Duration(
        milliseconds: (slideRemaining / PERCENT_PER_MILLISECOND).round()
      );

    } else {//返回當(dāng)前頁面
      endSlidePercent = 0.0;
      duration = new Duration(
          milliseconds: (slidePercent / PERCENT_PER_MILLISECOND).round()
      );
    }

    completionAnimationController = new AnimationController(
        duration: duration,
        vsync: vsync
    )
    ..addListener((){//監(jiān)聽
       slidePercent = lerpDouble( //lerpDouble(begin.height, end.height, t), t是百分百 在 begin 和end之間
          startSlidePercent,
          endSlidePercent,
          completionAnimationController.value
      );

      slideUpdateStream.add(//發(fā)送動(dòng)畫執(zhí)行監(jiān)聽
        new SlideUpdate(
        UpdateType.animating,
        slideDirection,
        slidePercent,
        )
      );

    })

    ..addStatusListener((AnimationStatus status){//動(dòng)畫執(zhí)行狀態(tài)發(fā)生改變

      if(status == AnimationStatus.completed){
        slideUpdateStream.add(//發(fā)送動(dòng)畫完成監(jiān)聽
        new SlideUpdate(
          UpdateType.doneAnimating,
          slideDirection,
          endSlidePercent,
          )
        );
      }

    });

  }

全在注釋里。

結(jié)語

到這里碗暗,整個(gè)fluttergo項(xiàng)目的大體框架和實(shí)現(xiàn)思路基本都搞清楚了颈将,以上分析僅是我個(gè)人理解,如有不對(duì)的地方歡迎指正言疗。
最后再次感謝fluttergo項(xiàng)目開發(fā)人員的無私開源晴圾。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市噪奄,隨后出現(xiàn)的幾起案子死姚,更是在濱河造成了極大的恐慌人乓,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件都毒,死亡現(xiàn)場(chǎng)離奇詭異色罚,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)账劲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門戳护,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人瀑焦,你說我怎么就攤上這事姑尺。” “怎么了蝠猬?”我有些...
    開封第一講書人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵切蟋,是天一觀的道長。 經(jīng)常有香客問我榆芦,道長柄粹,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任匆绣,我火速辦了婚禮驻右,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘崎淳。我一直安慰自己堪夭,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開白布拣凹。 她就那樣靜靜地躺著森爽,像睡著了一般。 火紅的嫁衣襯著肌膚如雪嚣镜。 梳的紋絲不亂的頭發(fā)上爬迟,一...
    開封第一講書人閱讀 51,365評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音菊匿,去河邊找鬼付呕。 笑死,一個(gè)胖子當(dāng)著我的面吹牛跌捆,可吹牛的內(nèi)容都是我干的徽职。 我是一名探鬼主播,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼佩厚,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼姆钉!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤育韩,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后闺鲸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體筋讨,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年摸恍,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了悉罕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡立镶,死狀恐怖壁袄,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情媚媒,我是刑警寧澤嗜逻,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站缭召,受9級(jí)特大地震影響栈顷,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜嵌巷,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一萄凤、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧搪哪,春花似錦靡努、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至漓概,卻和暖如春行嗤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背垛耳。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來泰國打工栅屏, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人堂鲜。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓栈雳,卻偏偏與公主長得像,于是被迫代替她去往敵國和親缔莲。 傳聞我的和親對(duì)象是個(gè)殘疾皇子哥纫,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354