小菜發現在長期未登陸小米應用市場時,再次登陸會有可滑動的半屏底部選單,供使用者方便下載和推廣;而在 Flutter 中這個半屏底部選單並不是一個簡單的 BottomSheet 完成的,可以通過 DraggableScrollableSheet 根據手勢操作滑動固定位的選單欄完成;小菜簡單學習一下;
DraggableScrollableSheet
原始碼分析
const DraggableScrollableSheet({
Key key,
this.initialChildSize = 0.5, // 初始比例
this.minChildSize = 0.25, // 最小比例
this.maxChildSize = 1.0, // 最大比例
this.expand = true, // 是否填充滿
@required this.builder,
})
@overridep
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
_extent.availablePixels = widget.maxChildSize * constraints.biggest.height;
final Widget sheet = FractionallySizedBox(
heightFactor: _extent.currentExtent,
child: widget.builder(context, _scrollController),
alignment: Alignment.bottomCenter,
);
return widget.expand ? SizedBox.expand(child: sheet) : sheet;
},
);
}
複製程式碼
簡單分析原始碼 DraggableScrollableSheet 作為一個有狀態的 StatefulWidget 小元件,通過 FractionallySizedBox 以父 Widget 為基數,可設定寬高比例的容器構建子內容;
案例嘗試
1. builder
ScrollableWidgetBuilder 構造器作為必選欄位,用於在 DraggableScrollableSheet 中顯示可滑動的子內容;其中返回內容需為可滑動的 ScrollableWidget,例如 ListView / GridView / SingleChildScrollView 等;
_listWid(controller) => SingleChildScrollView(
controller: controller,
child: Column(children: [
Container(
height: 5.0, width: 25.0,
decoration: BoxDecoration(color: Colors.blue.withOpacity(0.8), borderRadius: BorderRadius.all(Radius.circular(16.0))),
margin: EdgeInsets.symmetric(vertical: 12.0)),
Padding(
padding: EdgeInsets.symmetric(horizontal: 12.0),
child: GridView.builder(
physics: ScrollPhysics(),
primary: false, shrinkWrap: true,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 5, mainAxisSpacing: 8.0, crossAxisSpacing: 12.0, childAspectRatio: 0.7),
itemCount: 12,
itemBuilder: (context, index) => GestureDetector(
child: Column(children: <Widget>[
PhysicalModel(
color: Colors.transparent, shape: BoxShape.circle,
clipBehavior: Clip.antiAlias, child: Image.asset('images/icon_hzw01.jpg')),
SizedBox(height: 4), Text('海賊王')
]),
onTap: () {}))),
ListView.builder(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: 15,
itemBuilder: (BuildContext context, index) => ListTile(title: Text('Current Item = ${(index + 1)}')))
]));
複製程式碼
2. initialChildSize
initialChildSize 用於顯示初始子 Widgets 所佔父 Widget 比例;同時,若返回的子 Widget 未提供 ScrollController,則 DraggableScrollableSheet 不會隨手勢進行滑動,小菜理解為 initialChildSize = minChildSize = maxChildSize;
_sheetWid02() => DraggableScrollableSheet(
initialChildSize: 0.66,
builder: (BuildContext context, ScrollController scrollController) =>
Container(
decoration: BoxDecoration(
color: Colors.grey.withOpacity(0.4),
borderRadius: BorderRadius.only(topLeft: Radius.circular(16.0), topRight: Radius.circular(16.0))),
child: _listWid(null)));
複製程式碼
3. minChildSize & maxChildSize
minChildSize & maxChildSize 分別對應子 Widgets 佔整體的最大最小比例,其中 initialChildSize 需要在兩者之間;
_sheetWid03() => DraggableScrollableSheet(
initialChildSize: 0.6,
minChildSize: 0.3,
maxChildSize: 0.9,
expand: true,
builder: (BuildContext context, ScrollController scrollController) =>
Container(
decoration: BoxDecoration(
color: Colors.grey.withOpacity(0.4),
borderRadius: BorderRadius.only(topLeft: Radius.circular(16.0), topRight: Radius.circular(16.0))),
child: _listWid(scrollController)));
複製程式碼
4. expand
expand 用於是否填充滿父 Widget,若 DraggableScrollableSheet 外層固定高度則不影響;若外層未對高度進行固定,expand 作用於是否填充滿父 Widget;構造的原始碼也是通過 SizedBox.expand 對父 Widget 進行填充判斷的;
return widget.expand ? SizedBox.expand(child: sheet) : sheet;
複製程式碼
小擴充套件
之前在分析 DraggableScrollableSheet 時其原始碼採用了 FractionallySizedBox 比例容器,小菜簡單瞭解一下,其原始碼非常簡單,通過設定 heightFactor & widthFactor 佔父 Widget 的比例即可,通過 alignment 設定所在父 widget 的對齊方式;
SizedBox.expand(child: _sizedBox())
_sizedBox() => FractionallySizedBox(
heightFactor: 0.5,
widthFactor: 0.5,
alignment: Alignment.center,
child: Container(
color: Colors.deepOrange.withOpacity(0.4),
child: ListView.builder(
itemCount: 15,
itemBuilder: (BuildContext context, index) => ListTile(title: Text('Current Item = ${(index + 1)}')))));
複製程式碼
小菜對 DraggableScrollableSheet 的手勢滑動過程還不夠熟悉,之後會對手勢進行進一步學習;如有錯誤,請多多指導!
來源: 阿策小和尚