前言
本人接觸Flutter不到一個(gè)月蒂秘,深深感受到了這個(gè)平臺(tái)的威力,于是不斷學(xué)習(xí)姻僧,F(xiàn)lutter官方Example中的flutter_gallery有一個(gè)非常好看的動(dòng)畫卡片式的Tab導(dǎo)航,很好的展示了Flutter各個(gè)widget的功能
其中的animation內(nèi)容琴儿,展示的是一個(gè)帶有動(dòng)畫和拖拽功能的
可展開的
卡片式Tab導(dǎo)航,非常漂亮造成,但是其實(shí)現(xiàn)沒有抽象出一個(gè)可供第三方使用的Widget出來显熏,而且其頁面內(nèi)容的定制性不夠友好晒屎,滑動(dòng)的時(shí)候也有bug喘蟆,我在他的基礎(chǔ)上進(jìn)行了優(yōu)化
官方展示了一個(gè)非常好的開源示例鼓鲁,我改造了一下蕴轨,也不敢獨(dú)自享用,現(xiàn)在分享給大家橙弱,歡迎大家多多交流
外觀
實(shí)現(xiàn)
這里是我的代碼: GitHub/Realank
想使用這個(gè)控件非常簡單,首先定義頁面數(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('第一頁'))),
new CardSection(
title: 'Second Page',
leftColor: _mariner,
rightColor: _mySin,
contentWidget: Center(child: new Text('第二頁'))),
new CardSection(
title: 'Third Page',
leftColor: _mySin,
rightColor: _tomato,
contentWidget: Center(child: new Text('第三頁'))),
new CardSection(
title: 'Forth Page',
leftColor: _tomato,
rightColor: Colors.blue,
contentWidget: Center(child: new Text('第四頁'))),
new CardSection(
title: 'Fifth Page',
leftColor: Colors.blue,
rightColor: _mediumPurple,
contentWidget: Center(child: new Text('第五頁'))),
];
然后創(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,
),
),
),
);
}
}
大功告成
原理
知其然還要知其所以然棘脐,下面來說說這個(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)題龙致,左邊顏色和右邊顏色(為了顯示過渡顏色效果),以及子控件(這個(gè)是我改進(jìn)的目代,這樣可以別人使用的時(shí)候隨意添加控件)
然后在widgets.dart中定義了幾個(gè)widget:
- SectionCard : 標(biāo)題卡片
- SectionTitle : 標(biāo)題
- SectionIndicator : 標(biāo)題下的裝飾線
最后在cardNavigation.dart中就是布局這些內(nèi)容啦屈梁,這里面代碼很復(fù)雜,其思路倒是不難:
- 定義全屏展示tab的高度maxHeight在讶,以及打開tab后煞抬,tab顯示在頂部的高度minHeight
- 在用戶拖動(dòng)tab卡片的時(shí)候构哺,根據(jù)卡片的位置于minHeight和maxHeight的比例此疹,計(jì)算出動(dòng)畫進(jìn)度(0.0-1.0)
- 在_AllSectionsLayout中,定義了全屏顯示tab時(shí)遮婶,卡片的columnCardRect湖笨,以及打開tab后旗扑,tab顯示在頂部時(shí)候的rowCardRectt
- 計(jì)算出這兩個(gè)rect在動(dòng)畫進(jìn)度0-1過程中的中間態(tài)的rect尺寸,賦值給每一個(gè)卡片臀防,這樣卡片就有中間狀態(tài)的外觀了。
- 當(dāng)用戶點(diǎn)擊了tab區(qū)域边败,就會(huì)觸發(fā)_maybeScroll方法,這個(gè)方法判斷當(dāng)前的tab是全屏的還是打開后的
- 當(dāng)tab是全屏的笑窜,就展開對(duì)應(yīng)的tab頁
- 當(dāng)tab已經(jīng)是打開的致燥,就判斷點(diǎn)擊的位置排截,在tab欄的左側(cè)嫌蚤,就往左翻頁,反之亦然脱吱。