Flutter-go 項目地址是:https://github.com/alibaba/flutter-go
上文 我們分析了 第三個 Tab 頁面,主要分析了 組件的收藏的實現,EventBus,sqflite 的使用
這篇文章主要拆解 第四個Tab頁面(關於手冊)。對應的welcome_page.dart
文件的路徑如下:'package:flutter_go/views /welcome_page/welcome_page.dart';
下圖是整理後的collection_page.dart
文件主要的內容:
頁面切換實現
老實說,沒用
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-go GitHub 首頁
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 跳轉
屬於跳轉詳情頁面,在下一篇跳轉詳情中介紹。