相信你們對於這種需求確定不陌生:git
側滑出菜單,在Flutter 當中,這種需求怎麼實現?github
看一下實現的效果:微信
老套路,先分析一下需求:less
需求明瞭之後就能夠寫代碼了。ide
最基本的,菜單要能滑的出來,咱們思考一下,如何能在屏幕外面放置 Widget,而且還能滑動?函數
基本上不到一分鐘,相信你們都能想出來答案:ScrollView,沒錯,也就只有 ScrollView 知足咱們的需求。測試
說幹就幹:ui
SingleChildScrollView(
physics: ClampingScrollPhysics(),
scrollDirection: Axis.horizontal,
controller: _controller,
child: Row(children: children),
)
---------------
// 第一個 Widget,寬度爲屏幕的寬度
SizedBox(
width: screenWidth,
child: child,
),
複製代碼
這個效果就須要監聽滑動距離和手勢了。this
若是滑動距離大於全部 menu 寬度的 1/4,那就全都滑出來,若是不到的話,就回滾回去。spa
那就要判斷一下手是否已經離開屏幕,在這個時候判斷距離。
原本想着套一個 Gesture,可是發現不行,問了一下大佬們,用了 Listener
。
代碼以下:
Listener(
onPointerUp: (d) {
if (_controller.offset < (screenWidth / 5) * menu.length / 4) {
_controller.animateTo(0, duration: Duration(milliseconds: 100), curve: Curves.linear);
} else {
_controller.animateTo(menu.length * (screenWidth / 5), duration: Duration(milliseconds: 100), curve: Curves.linear);
}
},
child: SingleChildScrollView(
physics: ClampingScrollPhysics(),
scrollDirection: Axis.horizontal,
controller: _controller,
child: Row(children: children),
),
)
複製代碼
很簡單,就是在 手擡起 的時候判斷了一下距離,而後調用了 animteTo
方法。
這個其實很簡單,讓「用戶」來傳入就行了,
我只須要控制 menu 的寬度。
因而我把 Container 的參數都扒了下來,封裝成了一個組件 SlideMenuItem
:
class SlideMenuItem extends StatelessWidget {
SlideMenuItem({
Key key,
@required this.child,
this.alignment,
this.padding,
Color color,
Decoration decoration,
this.foregroundDecoration,
this.height,
BoxConstraints constraints,
this.margin,
this.transform,
@required this.onTap,
}) : assert(child != null),
assert(margin == null || margin.isNonNegative),
assert(padding == null || padding.isNonNegative),
assert(decoration == null || decoration.debugAssertIsValid()),
assert(constraints == null || constraints.debugAssertIsValid()),
assert(
color == null || decoration == null,
'Cannot provide both a color and a decoration\n'
'The color argument is just a shorthand for "decoration: new BoxDecoration(color: color)".'),
decoration =
decoration ?? (color != null ? BoxDecoration(color: color) : null),
constraints = (height != null)
? constraints?.tighten(height: height) ??
BoxConstraints.tightFor(height: height)
: constraints,
super(key: key);
final BoxConstraints constraints;
final Decoration decoration;
final AlignmentGeometry alignment;
final EdgeInsets padding;
final Decoration foregroundDecoration;
final EdgeInsets margin;
final Matrix4 transform;
final Widget child;
final double height;
final GestureTapCallback onTap;
@override
Widget build(BuildContext context) {
return Container(
child: child,
alignment: alignment,
constraints: constraints,
decoration: decoration,
padding: padding,
width: screenWidth / 5,
height: height,
foregroundDecoration: foregroundDecoration,
margin: margin,
transform: transform,
);
}
}
複製代碼
這麼長的代碼,其實就 「width」 和 「onTap」 是本身寫的。
這裏有個小問題:把 Menu 單獨封裝成了一個組件,那如何在點擊 menu 的時候把 menu 收回去?
基於這個問題,在建立整個 SlideItem
的時候,經過構造函數把每個 menu 都添加上了 GestureDetector
,而後在 onTap() 回調中調用 menu 的 onTap() 方法,再調用 dismiss()
方法來收回 menu。
代碼以下:
addAll(menu
.map((w) => GestureDetector(
child: w,
onTap: (){
w.onTap();
dismiss();
},
))
.toList());
複製代碼
也就是 菜單展開時,點擊了 item 的話,要先收回菜單。QQ 就是如此。
這裏有一個知識點,咱們設置的點擊事件默認是不會命中透明組件的,因此要給第一個默認佔滿屏幕寬度的 Widget 加上一個屬性:behavior: HitTestBehavior.opaque
。
完整的代碼以下:
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: (){
if(_controller.offset != 0){
dismiss();
}else{
onTap();
}
},
child: SizedBox(
width: screenWidth,
child: child,
),
)
複製代碼
其中 behavior 有三個值:
引用羣裏一大佬的話:
不要把問題複雜化。
其實對於這種效果,咱們仔細的想一分鐘,幾乎都能想出來解決方案的。並且實現起來也很簡單。
原本想封裝成一個 ListView 的,後來感受沒什麼必要,單獨封裝成一個 Item 也足夠用了。
代碼已傳到GitHub:github.com/wanglu1209/…
另我我的建立了一個「Flutter 交流羣」,能夠添加我我的微信 「17610912320」來入羣。
推薦閱讀:
Flutter | 1.9 全新組件 ToggleButtons
Flutter | WReorderList 一個能夠指定兩個item互換位置的組件
Flutter | 如何實現一個水波紋擴散效果的 Widget