阿里 Flutter-go 專案拆解筆記(七)
Flutter-go 專案地址是: https://github.com/alibaba/flutter-go
上文 我們分析了 第三個 Tab 頁面 ,主要分析了 元件的收藏的實現,EventBus,sqflite 的使用
這篇文章主要拆解 第四個Tab頁面(關於手冊) 。對應的 welcome_page.dart
檔案的路徑如下: 'package:flutter_go/views /welcome_page/welcome_page.dart';

UI 效果.PNG
下圖是整理後的 collection_page.dart
檔案主要的內容:

功能拆解.PNG
頁面切換實現
老實說,沒用 Flutter
做過專案,直接來閱讀原始碼還是有點吃力的,理解錯了歡迎指出。
在 fourth_page.dart
的佈局中,我們可以看到 children
是由 Page、PageReveal、PageIndicator、PageDragger
幾個 Widget
組成的。那麼我們就來分析這幾個的 Widget
的實現,瞭解他們的作用是什麼。
Page 元件分析
作用是:承載每個頁面。
在 Page
元件中使用了 Stack
元件,用於在右上角顯示 go GitHub
按鈕。
Page
元件的 children
有 Container、Positioned
, Container
用於展示 每個頁面 的內容 Positioned
主要顯示右上角 go GitHub
按鈕。
每個頁面的實現分析:
Transform
可以在其 子Widget
繪製時對其應用一個矩陣變換 (transformation)
。
這裡的實現主要就是在 children
集合中新增三個 Transform
元件,一個用於 頂部圖片 的動畫,一個用於 中間標題文字 的動畫,一個用於 描述文字 的動畫。
/// 這裡只貼出了頂部圖片的變換程式碼,更多程式碼請檢視原始碼 Transform( // 引數1:x 軸的移動方向,引數2:y 軸的移動方向,引數3:z 軸的移動方向 transform: Matrix4.translationValues( 0.0, 50.0 * (1.0 - percentVisible), 0.0), child: Padding( padding: EdgeInsets.only(top: 20.0, bottom: 10.0), /// 頂部圖片 child: Image.asset(viewModel.heroAssetPath, width: 160.0, height: 160.0), ), ), /// 標題的實現也是類似 /// 描述文字的實現同上
go GitHub按鈕的實現分析:
使用了 RaisedButton.icon
元件,該元件的作用是:可生成一個帶有 icon
的按鈕。而 半圓角的矩形邊框 是使用 RoundedRectangleBorder
實現的
/// 回到首頁按鈕,Github 按鈕 Widget creatButton( BuildContext context, String txt, IconData iconName, String type) { return RaisedButton.icon( onPressed: () async { if (type == 'start') { await SpUtil.getInstance() ..putBool(SharedPreferencesKeys.showWelcome, false); /// 跳轉首頁 _goHomePage(context); } else if (type == 'goGithub') { /// 進入 Flutter-goGitHub 首頁 Application.router.navigateTo(context, '${Routes.webViewPage}?title=${Uri.encodeComponent(txt)} Doc&&url=${Uri.encodeComponent("https://github.com/alibaba/flutter-go")}'); } }, elevation: 10.0, color: Colors.black26, // shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.horizontal(left: Radius.circular(20.0))), //如果不手動設定icon和text顏色,則預設使用foregroundColor顏色 icon: Icon(iconName, color: Colors.white, size: 14.0), label: Text( txt, maxLines: 1, style: TextStyle( color: Colors.white, fontSize: 14, fontWeight: FontWeight.w700), )); }
PageReveal 元件分析
PageReveal
作用是實現頁面 Page
的 裁剪 效果。在實現過程中繼承了 CustomClipper
, CustomClipper
重寫的方法 getClip
根據需要露出的百分比 revealPercent
對 child
進行裁剪,返回了 Rect
,但是在 CircleRevealClipper
的外層嵌套了 ClipOval
, ClipOval
是使用橢圓來剪輯其子物件的 Widget
。實現原始碼如下:
@override Widget build(BuildContext context) { return ClipOval( clipper: new CircleRevealClipper(revealPercent), // 這裡的 child 是傳入的 page child: child, ); } } class CircleRevealClipper extends CustomClipper<Rect>{ // 顯示的百分比 final double revealPercent; CircleRevealClipper( this.revealPercent ); @override Rect getClip(Size size) { final epicenter = new Offset(size.width / 2, size.height * 0.9); 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); } @override bool shouldReclip(CustomClipper<Rect> oldClipper) { return true; } }
PagerIndicator 元件分析
底部的指示器實現,在原始碼中可以看出指示器的實現也使用了 Matrix4.translationValues
動畫,指示器的實現主要是由 PageBubble Widget
集合組成。而 PageBubble
的實現如下:
@override Widget build(BuildContext context) { return new Container( width: 55.0, height: 65.0, child: new Center( child: new Container( // 寬度在(20.0,45.0)線性插值兩個數字之間變換 width: lerpDouble(20.0,45.0,viewModel.activePercent), // 高度在(20.0,45.0)線性插值兩個數字之間變換 height: lerpDouble(20.0,45.0,viewModel.activePercent), decoration: new BoxDecoration( shape: BoxShape.circle, // isHollow 是否顯示圓圈 // i > viewModel.activeIndex 從右向左滑動 返回 true // i == viewModel.activeIndex && viewModel.slideDirection == SlideDirection.leftToRight) 從左向右滑動 返回 true //bool isHollow = i > viewModel.activeIndex || (i == viewModel.activeIndex && viewModel.slideDirection == SlideDirection.leftToRight); // isHollow表示圓點對應的頁碼是否大於當前頁碼,如果大於的話顯示空心,否則顯示實心 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, ), ), ), ), ); }
PageDragger 元件分析
PageDragger
主要用來接收觸控事件,然後根據觸控事件來進行對應的操作。首先是在 FourthPage
的構造方法中建立了一個 Stream
流的事件監聽。關於 Stream
介紹可以檢視 官方文件 或者這篇博文 Flutter響應式程式設計 - Stream
FourthPage
的構造方法虛擬碼如下:
... slideUpdateStream = new StreamController<SlideUpdate>(); // 開始監聽 slideUpdateStream.stream.listen((SlideUpdate event) { ... } ...
在建立了 slideUpdateStream
之後將其傳遞個 PageDragger
構造方法,如下:
new PageDragger( canDragLeftToRight: activeIndex > 0, canDragRightToLeft: activeIndex < pages.length - 1, slideUpdateStream: this.slideUpdateStream, )
在構造方法中控制了左右滑動的邊界,而 slideUpdateStream
就是用於監聽觸控事件的。那麼在這裡是如何去監聽觸控事件的呢?
在 PageDragger
的 build
實現中可以看出是通過監聽了水平滑動來實現對應的操作
@override Widget build(BuildContext context) { // 水平觸控監聽 return GestureDetector( onHorizontalDragStart: onDragStart , onHorizontalDragUpdate: onDragUpdate , onHorizontalDragEnd: onDragEnd , ); }
我們可以看其中的一個方法 onDragUpdate
的實現:
// 正在拖拽 onDragUpdate(DragUpdateDetails details) { if (dragStart != null) { 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){ slidePercent = (dx / FULL_TRANSTITION_PX).abs().clamp(0.0, 1.0); } else { slidePercent = 0.0; } // 新增 stream 資料 widget.slideUpdateStream.add( new SlideUpdate( UpdateType.dragging, slideDirection, slidePercent )); } }
在上面的程式碼可以看到 slideUpdateStream
通過 add
方法添加了一個 SlideUpdate
物件。
所以 Stream
的使用方式可以分為如下步驟:
slideUpdateStream = new StreamController<SlideUpdate>(); slideUpdateStream.stream.listen((SlideUpdate event) {} slideUpdateStream.add(new SlideUpdate())
該效果實現總結
page PageReveal PageDragger
點選右上角 Github 跳轉
屬於跳轉詳情頁面,在下一篇跳轉詳情中介紹。