前言
本人接觸Flutter不到一個(gè)月,深深感受到了這個(gè)平臺(tái)的威力,于是不斷學(xué)習(xí),F(xiàn)lutter官方Example中的flutter_gallery有一個(gè)非常好看的動(dòng)畫(huà)卡片式的Tab導(dǎo)航,很好的展示了Flutter各個(gè)widget的功能
其中的animation內(nèi)容,展示的是一個(gè)帶有動(dòng)畫(huà)和拖拽功能的 可展開(kāi)的卡片式Tab導(dǎo)航,非常漂亮,但是其實(shí)現(xiàn)沒(méi)有抽象出一個(gè)可供第三方使用的Widget出來(lái),而且其頁(yè)面內(nèi)容的定制性不夠友好,滑動(dòng)的時(shí)候也有bug,我在他的基礎(chǔ)上進(jìn)行了優(yōu)化
官方展示了一個(gè)非常好的開(kāi)源示例,我改造了一下,也不敢獨(dú)自享用,現(xiàn)在分享給大家,歡迎大家多多交流
外觀(guān)

實(shí)現(xiàn)
這里是我的代碼: GitHub/Realank
想使用這個(gè)控件非常簡(jiǎn)單,首先定義頁(yè)面數(shù)據(jù):
const Color _mariner = const Color(0xFF3B5F8F);
const Color _mediumPurple = const Color(0xFF8266D4);
const Color _tomato = const Color(0xFFF95B57);
const Color _mySin = const Color(0xFFF3A646);
List<CardSection> allSections = <CardSection>[
new CardSection(
title: 'First Page',
leftColor: _mediumPurple,
rightColor: _mariner,
contentWidget: Center(child: new Text('第一頁(yè)'))),
new CardSection(
title: 'Second Page',
leftColor: _mariner,
rightColor: _mySin,
contentWidget: Center(child: new Text('第二頁(yè)'))),
new CardSection(
title: 'Third Page',
leftColor: _mySin,
rightColor: _tomato,
contentWidget: Center(child: new Text('第三頁(yè)'))),
new CardSection(
title: 'Forth Page',
leftColor: _tomato,
rightColor: Colors.blue,
contentWidget: Center(child: new Text('第四頁(yè)'))),
new CardSection(
title: 'Fifth Page',
leftColor: Colors.blue,
rightColor: _mediumPurple,
contentWidget: Center(child: new Text('第五頁(yè)'))),
];
然后創(chuàng)建這個(gè)控件:
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
home: new Scaffold(
body: Center(
child: new AnimateTabNavigation(
sectionList: allSections,
),
),
),
);
}
}
大功告成
原理
知其然還要知其所以然,下面來(lái)說(shuō)說(shuō)這個(gè)控件的實(shí)現(xiàn)原理
首先,在sections.dart里定義了數(shù)據(jù)結(jié)構(gòu):
class CardSection {
CardSection({this.title, this.leftColor, this.rightColor, this.contentWidget});
final String title;
final Color leftColor;
final Color rightColor;
final Widget contentWidget;
@override
bool operator ==(Object other) {
if (other is! CardSection) return false;
final CardSection otherSection = other;
return title == otherSection.title;
}
@override
int get hashCode => title.hashCode;
}
它定義了其中一個(gè)卡片的標(biāo)題,左邊顏色和右邊顏色(為了顯示過(guò)渡顏色效果),以及子控件(這個(gè)是我改進(jìn)的,這樣可以別人使用的時(shí)候隨意添加控件)
然后在widgets.dart中定義了幾個(gè)widget:
- SectionCard : 標(biāo)題卡片
- SectionTitle : 標(biāo)題
- SectionIndicator : 標(biāo)題下的裝飾線(xiàn)
最后在cardNavigation.dart中就是布局這些內(nèi)容啦,這里面代碼很復(fù)雜,其思路倒是不難:
- 定義全屏展示tab的高度maxHeight,以及打開(kāi)tab后,tab顯示在頂部的高度minHeight
- 在用戶(hù)拖動(dòng)tab卡片的時(shí)候,根據(jù)卡片的位置于minHeight和maxHeight的比例,計(jì)算出動(dòng)畫(huà)進(jìn)度(0.0-1.0)
- 在_AllSectionsLayout中,定義了全屏顯示tab時(shí),卡片的columnCardRect,以及打開(kāi)tab后,tab顯示在頂部時(shí)候的rowCardRectt
- 計(jì)算出這兩個(gè)rect在動(dòng)畫(huà)進(jìn)度0-1過(guò)程中的中間態(tài)的rect尺寸,賦值給每一個(gè)卡片,這樣卡片就有中間狀態(tài)的外觀(guān)了。
- 當(dāng)用戶(hù)點(diǎn)擊了tab區(qū)域,就會(huì)觸發(fā)_maybeScroll方法,這個(gè)方法判斷當(dāng)前的tab是全屏的還是打開(kāi)后的
- 當(dāng)tab是全屏的,就展開(kāi)對(duì)應(yīng)的tab頁(yè)
- 當(dāng)tab已經(jīng)是打開(kāi)的,就判斷點(diǎn)擊的位置,在tab欄的左側(cè),就往左翻頁(yè),反之亦然。