介紹
預(yù)覽圖
image
分析
效果非常簡單,在切換的時候,對應(yīng)的文字要縮小/放大。
我們來實現(xiàn)這個自定義tabbar
實現(xiàn)
首先我們定義一個類
class CustomTabBar extends WidgetState with SingleTickerProviderStateMixin
因為動畫的原因,所以需要混入
SingleTickerProviderStateMixin
CustomTabBar
首先需要定義兩個回調(diào)
//因為我用到的MVVM,所以需要將tabbar的vm傳出,方便外層控制tabbar
typedef TabBarController = void Function(TabBarViewModel controller);
//當(dāng)我們點擊tab的item的時候,需要將對應(yīng)index傳出 ,外層可以切換pageView
typedef TabClick = void Function(int index);
兩個變量用于控制文字放大的系數(shù)閾值
final double min = 1.0;
final double max = 1.2;
///動畫
AnimationController controller;
Animation animation;
接下來我們看一下變量的初始化
@override
void initState() {
super.initState();
//動畫很快 只有50ms
controller = AnimationController(duration: Duration(milliseconds: 50),vsync: this);
//動畫控制文字的放大和縮小
animation = Tween<double>(begin: min,end: max).animate(controller);
controller.addListener(() {
//對動畫進行監(jiān)聽,
//并調(diào)用updateFactor()方法
if(!parentVM.isResetting){
parentVM.updateFactor(animation.value);
}
});
controller.addStatusListener((status) {
if(status == AnimationStatus.completed){
//當(dāng)動畫執(zhí)行完畢后,我們重置動畫,這里的重置使我們自己的方法
//而非直接調(diào)用controller的
parentVM.resetController();
}
});
}
我們先來看一下 updateFactor()和resetController()方法
//這個變量用于字體的放大和縮小
double textScaleFactor = 1.2;
updateFactor(double newV){
//我們將動畫的value傳進來更新textScaleFactor
//下面的表達式,可以確保 這個放大系數(shù) 在1-1.2之間
textScaleFactor = newV > textScaleFactor ? newV.clamp(1.0, 1.2) : textScaleFactor;
notifyListeners();
}
//這里用于界定controller的reset,
//避免controller reset時,縮小了字體,所以加此變量
bool isResetting = false;
void resetController(){
//從上面的代碼可以看到,(監(jiān)聽動畫那部分)
//reset=true的時候,將不會觸發(fā)頁面的刷新
isResetting = true;
controller.reset();
//重置完成后將狀態(tài)置為false
isResetting = false;
}
接下來我們看一下布局
return Container(
color: Colors.white,
height: getWidthPx(80),
child: buildTab(),
);
static const double txtSize = 36;
Widget buildTab() {
return Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
wrap(TabBarItem(parentVM, '我的', 0,textSize: getSp(txtSize)).generateWidget(),0),
wrap(TabBarItem(parentVM, '發(fā)現(xiàn)', 1,textSize: getSp(txtSize)).generateWidget(),1),
wrap(TabBarItem(parentVM, '云村', 2,textSize: getSp(txtSize)).generateWidget(),2),
wrap(TabBarItem(parentVM, '視頻', 3,textSize: getSp(txtSize)).generateWidget(),3),
],
);
}
Widget wrap(Widget child,int index){
return GestureDetector(
onTap: (){
tabClick(index);
},
child: Container(
alignment: Alignment.bottomCenter,
width: getWidthPx(110),
child: child,
),
);
}
代碼很簡單,基本的4個tab item 橫向布局,這里的item是我們自定義的,將在后面介紹
我們主要看一下這個方法,他將會觸發(fā)動畫
tabClick(index);//這個就是我們的回調(diào),最終會將item的index傳到外層頁面
我們看一下外層頁面的動畫觸發(fā)
頁面的動畫觸發(fā)
下方代碼,是頁面的布局,這里簡稱homePage,這個自定義tab 會與一個pageview綁定
buildTab() {
return Container(
height: getWidthPx(80),
//color: Colors.greenAccent,
child: CustomTabBar((controller){
tabController = controller;
},(index){
//tab click
pageController.jumpToPage(index);
}).generateWidget(),
);
}
///pageview的 代碼(刪減版)
PageView(
controller: pageController,
onPageChanged: (index){
tabController.switchPage(index);
pageIndex = index;
},
...)
我們按照上節(jié)的回調(diào)來過一遍流程,當(dāng)回調(diào)觸發(fā)的時候,將會發(fā)出pageview的切換
(index){
//tab click
pageController.jumpToPage(index);
}
而pageview切換完成后,又會觸發(fā)它自己的回調(diào)
onPageChanged: (index){
//page view的回調(diào)又觸發(fā)了 tab的switchPage(index)方法
tabController.switchPage(index);
pageIndex = index;
}
還記得tabcontroller嗎? 它實際是自定義tab的 VM可以,我們來看一下它的switchPage(index)方法
switchPage(int index){
//我們將tab的 index配置為和 pageview 相匹配
pageIndex = index;
//下面
record();
//刷新一下界面
notifyListeners();
//執(zhí)行放大動畫
controller.forward();
}
//這個方法我們是用來記錄tab 的 item index
//因為目標(biāo)的index要放大,而前一個item則要縮小,
//它相當(dāng)于一個切換歷史記錄
//始終只記錄兩個值
List<int> indexRecord = [];
void record() {
if(indexRecord.length == 3) indexRecord.removeAt(0);
indexRecord.add(pageIndex);
}
這個pageindex和對應(yīng)的歷史記錄聯(lián)合起來,就可以控制item的縮小和放大了,我們看一下item的實現(xiàn).
tabbar item
說明我將寫在注釋里
class TabBarItem extends WidgetState {
//外層的vm,這個vm獲取的方法很多,我這個構(gòu)造函數(shù)傳參方便
final TabBarViewModel parentVM;
final String text;//文案
final double textSize;//字體大小
final index;//每個item的 標(biāo)識 一般是 0,1,2,3
TabBarItem(this.parentVM,this.text,this.index,{this.textSize = 20})
:assert(parentVM!=null),assert(text.isNotEmpty);
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
//這里的表達式比較繞
//簡單講,它是根據(jù)咱們上面講的 pageIndex和 recordList歷史記錄
//來獲得當(dāng)前的currentIndex和 上一個 preIndex
// currentIndex 我們會放大
// preIndex 我們會對應(yīng)縮小
return Text(text,
//我們通過textScaleFactor來對 字體進行放大
textScaleFactor:(index == parentVM.pageIndex
?parentVM.textScaleFactor :
(index == parentVM.getLastIndex())
? parentVM.textScaleFactor : parentVM.min) ,
style: TextStyle(fontSize: textSize,
color: index == parentVM.pageIndex?Colors.black:Colors.grey),);
}
}
至此整個功能就開發(fā)完畢了,謝謝大家閱覽
如有不足之處,歡迎指出 :)
Demo
內(nèi)部搜索即可