Flutter 130: 圖解 DraggableScrollableSheet 可手勢滑動的菜單欄

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

來源: 阿策小和尚

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容