【Flutter 專題】130 圖解 DraggableScrollableSheet 可手勢滑動的選單欄

阿策小和尚發表於2021-07-04

    小菜發現在長期未登陸小米應用市場時,再次登陸會有可滑動的半屏底部選單,供使用者方便下載和推廣;而在 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 的手勢滑動過程還不夠熟悉,之後會對手勢進行進一步學習;如有錯誤,請多多指導!

來源: 阿策小和尚

相關文章