最近要用 Flutter 重構一個 Native 頁面,效果如下:
隨着頁面滑動,圓形按鈕逐漸消失,返回按鈕逐漸呈現,同時AppBar的透明度在整個過程中,是隨着滑動距離線性變化的,而按鈕的變化分為兩段:圓形按鈕逐漸消失,返回按鈕逐漸呈現,整個過程可逆。
接下來介紹實現過程。
1.整體結構設計
通過觀察可知,listView 可以在 AppBar 底部滑動,常規的 Scaffold
widget 無法滿足這個需求,而 Stack
widget 可以實現組件的疊加,在這里通過 Stack
作為頁面的 root widget。通過監聽scrollView 的滑動距離,實時計算 appBar 和 按鈕 的透明度。
///首先聲明 全局變量
AppBarWidget appBar;
ScrollController scrollController; //scrollView的控制器
PositionedBtnWidget roundLeftBtn; //圓形返回按鈕
PositionedBtnWidget rectLeftBtn; //方形返回按鈕
在初始化方法里,給全局變量賦值:
@override
void initState() {
super.initState();
appBar = AppBarWidget();
scrollController = ScrollController();
roundLeftBtn = PositionedBtnWidget();
rectLeftBtn = PositionedBtnWidget();
}
整體UI結構使用 Scaffold
作為主框架,body
部分則是 Stack
,底部 TabBar使用 Scaffold 自帶屬性自定義搭建。為了適配 iPhoneX 底部,需要計算 安全區域高度。 MediaQuery.of(context).padding.bottom;
,CustomScrollView
的controller 繼承自 ChangeNotifier
,可監聽其位置變化。
///示意代碼
Scaffold(
body: Stack(
children: <Widget>[
///監聽滾動
NotificationListener(
onNotification: (notification) {
if (notification is ScrollUpdateNotification &&
notification.depth == 0) {
///滑動通知
scrollViewDidScrolled(notification.metrics.pixels);
}
///通知不再上傳
return true;
},
child: CustomScrollView(),
appBar,
rectLeftBtn,
roundLeftBtn,
],
),
bottomNavigationBar: Container(
color: Colors.orange,
height: bottomBarHeight,
child: Center(
child: Text('bottom bar'),
)));
2.其他部件的搭建
因為要實現透明度效果,這里使用 Opacity
widegt 來實現。控制 opacity 透明度的值,可實現透明度的變化。注意:在這里發現,Stack內的兩個組件,如果發生重疊,位於頂部的widget最先響應點擊事件。
///示例
Opacity(
opacity: opacity,
child: Container(
height: appBarHeight,
child: AppBar(
title: Text('app bar'),
backgroundColor: Colors.deepOrange,
),
),
);
在 Stack 內部,變動部件位置需要用到 Positioned
widegt, 點擊事件通過 IconButton
來實現
Positioned(
top: btnTop,
right: right,
left: left,
child: Opacity(
opacity: btnOpacity,
child: IconButton(
icon: Image.asset(image),
onPressed: () {
if (widget != null && widget.actionFunction != null) {
widget.actionFunction();
}
},
),
),
);
3.透明度計算
通過監聽 scrollview
的滑動距離,計算各個部件的透明度。
在這里 把完全透明到不透明 需要滑動的距離定為 80(單位邏輯像素 logical pixels
)
而按鈕 的變化分為兩段,每段滑動距離為整體的一半,也就是40邏輯像素。
具體計算方式如下:
double maxOffset = 80.0;
scrollViewDidScrolled(double offSet) {
//print('scroll offset ' + offSet.toString());
///appbar 透明度
double appBarOpacity = offSet / maxOffset;
double halfPace = maxOffset / 2.0;
///圓形按鈕透明度
double roundOpacity = (halfPace - offSet) / halfPace;
///方形按鈕透明度
double rectOpacity = (offSet - halfPace) / halfPace;
if (appBarOpacity < 0) {
appBarOpacity = 0.0;
} else if (appBarOpacity > 1) {
appBarOpacity = 1.0;
}
if (roundOpacity < 0) {
roundOpacity = 0.0;
} else if (roundOpacity > 1) {
roundOpacity = 1;
}
if (rectOpacity < 0) {
rectOpacity = 0.0;
} else if (rectOpacity > 1) {
rectOpacity = 1.0;
}
//print('roundOpacity $roundOpacity rectOpacity $rectOpacity');
///更新透明度
if (appBar != null && appBar.updateAppBarOpacity != null) {
appBar.updateAppBarOpacity(appBarOpacity);
}
if (roundLeftBtn != null && roundLeftBtn.updateOpacity != null) {
roundLeftBtn.updateOpacity(roundOpacity);
}
if (rectLeftBtn != null && rectLeftBtn.updateOpacity != null) {
rectLeftBtn.updateOpacity(rectOpacity);
}
}
代碼地址
Demo