Flutter入門(35):Flutter 組件之 BottomSheet 詳解

1. 基本介紹

BottomSheet 是一種常見的上拉框,個人感覺 showModalBottomSheet 更為常用一點。

2. 示例代碼

代碼下載地址。如果對你有幫助的話記得給個關(guān)注,代碼會根據(jù)我的 Flutter 專題不斷更新。

3. 屬性介紹

BottomSheet 屬性 介紹
animationController 動畫控制器
enableDrag 是否可以拖動,默認(rèn)為 true
onDragStart 開始拖拽回調(diào),沒有找到具體使用場景,后續(xù)更新
onDragEnd 結(jié)束拖拽回調(diào),沒有找到具體使用場景,后續(xù)更新
backgroundColor 背景色
elevation 陰影高度
shape 形狀 BorderShape
clipBehavior 剪切方式
onClosing 關(guān)閉回調(diào)函數(shù)
builder 構(gòu)建函數(shù)

4. BottomSheet 詳解

BottomSheet 作為組件直接使用的時候比較少,比如配合 Scaffold 的子屬性使用,可以理解為展示在屏幕下方的一個組件。

import 'package:flutter/material.dart';

class FMBottomSheetVC extends StatefulWidget{
  @override
  FMBottomSheetState createState() => FMBottomSheetState();
}

class FMBottomSheetState extends State <FMBottomSheetVC>{
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return _scaffold(context);
  }

  Scaffold _scaffold(context){
    return Scaffold(
      appBar: AppBar(title: Text("BottomSheet"),),
      body: Center(
        // child: BottomSheetBtn(),
      ),
      bottomSheet: _bottomSheet(context),
      floatingActionButton: FloatingActionButton(
        child: Text("返回"),
        onPressed: (){
          Navigator.pop(context);
        },
      ),
    );
  }

  BottomSheet _bottomSheet(context){
    return BottomSheet(
      onClosing: (){
        print("closed");
      },

      builder: (context){
        return Container(
          height: 300,
          color: Colors.yellow,
          alignment: Alignment.centerLeft,
          child: Text("BottomSheet In Scaffold"),
        );
      },
    );
  }
}
BottomSheet.png

5. showModalBottomSheet 詳解

showModalBottomSheet 是一個直接調(diào)起 BottomSheet 的 api,使用頻率較高。

import 'package:flutter/material.dart';

class FMBottomSheetVC extends StatefulWidget{
  @override
  FMBottomSheetState createState() => FMBottomSheetState();
}

class FMBottomSheetState extends State <FMBottomSheetVC>{
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return _scaffold(context);
  }

  Scaffold _scaffold(context){
    return Scaffold(
      appBar: AppBar(title: Text("BottomSheet"),),
      body: Center(
        child: _raisedButton(context),
      ),
      // bottomSheet: _bottomSheet(context),
      // floatingActionButton: FloatingActionButton(
      //   child: Text("返回"),
      //   onPressed: (){
      //     Navigator.pop(context);
      //   },
      // ),
    );
  }

  BottomSheet _bottomSheet(context){
    return BottomSheet(
      onClosing: (){
        print("closed");
      },

      builder: (context){
        return Container(
          height: 300,
          color: Colors.yellow,
          alignment: Alignment.centerLeft,
          child: Text("BottomSheet In Scaffold"),
        );
      },
    );
  }

  RaisedButton _raisedButton(context){
    return RaisedButton(
      child: Text("showModalBottomSheet"),
      onPressed: (){
        showModalBottomSheet(
          context: context,
          builder: (context){
            return Container(
              width: 414,
              height: 300,
              color: Colors.red,
              alignment: Alignment.centerLeft,
              child: Text("showModalBottomSheet", style: TextStyle(color: Colors.white),),
            );
          },
        );
      },
    );
  }
}
showModalBottomSheet.gif

6. showBottomSheet 詳解

showBottomSheet 對新手可能不太友好,它的實際調(diào)用是 Scaffold.of(context).showBottomSheet,.of(context) 方法在當(dāng)前同一層級是拿不到 Scaffold Widget 的,所以會報錯,需要在封裝一層 class 進(jìn)行使用。

6.1 常規(guī)報錯

══╡ EXCEPTION CAUGHT BY GESTURE ╞═══════════════════════════════════════════════════════════════════
The following assertion was thrown while handling a gesture:
No Scaffold widget found.
FMBottomSheetVC widgets require a Scaffold widget ancestor.
The specific widget that could not find a Scaffold ancestor was:
  FMBottomSheetVC
The ancestors of this widget were:
  ...
  Semantics
  Builder
  RepaintBoundary-[GlobalKey#26d4e]
  IgnorePointer
  AnimatedBuilder
  ...
Typically, the Scaffold widget is introduced by the MaterialApp or WidgetsApp widget at the top of
your application widget tree.

When the exception was thrown, this was the stack:
#0      debugCheckHasScaffold.<anonymous closure> (package:flutter/src/material/debug.dart:114:7)
#1      debugCheckHasScaffold (package:flutter/src/material/debug.dart:125:4)
#2      showBottomSheet (package:flutter/src/material/bottom_sheet.dart:725:10)
#3      FMBottomSheetState._raisedButton.<anonymous closure> (package:FMStudyApp/Widgets/Material_components/bottomsheet.dart:53:9)
#4      _InkResponseState._handleTap (package:flutter/src/material/ink_well.dart:992:19)
#5      _InkResponseState.build.<anonymous closure> (package:flutter/src/material/ink_well.dart:1098:38)
#6      GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:184:24)
#7      TapGestureRecognizer.handleTapUp (package:flutter/src/gestures/tap.dart:524:11)
#8      BaseTapGestureRecognizer._checkUp (package:flutter/src/gestures/tap.dart:284:5)
#9      BaseTapGestureRecognizer.handlePrimaryPointer (package:flutter/src/gestures/tap.dart:219:7)
#10     PrimaryPointerGestureRecognizer.handleEvent (package:flutter/src/gestures/recognizer.dart:477:9)
#11     PointerRouter._dispatch (package:flutter/src/gestures/pointer_router.dart:78:12)
#12     PointerRouter._dispatchEventToRoutes.<anonymous closure> (package:flutter/src/gestures/pointer_router.dart:124:9)
#13     _LinkedHashMapMixin.forEach (dart:collection-patch/compact_hash.dart:377:8)
#14     PointerRouter._dispatchEventToRoutes (package:flutter/src/gestures/pointer_router.dart:122:18)
#15     PointerRouter.route (package:flutter/src/gestures/pointer_router.dart:108:7)
#16     GestureBinding.handleEvent (package:flutter/src/gestures/binding.dart:220:19)
#17     GestureBinding.dispatchEvent (package:flutter/src/gestures/binding.dart:200:22)
#18     GestureBinding._handlePointerEvent (package:flutter/src/gestures/binding.dart:158:7)
#19     GestureBinding._flushPointerEventQueue (package:flutter/src/gestures/binding.dart:104:7)
#20     GestureBinding._handlePointerDataPacket (package:flutter/src/gestures/binding.dart:88:7)
#24     _invoke1 (dart:ui/hooks.dart:267:10)
#25     _dispatchPointerDataPacket (dart:ui/hooks.dart:176:5)
(elided 3 frames from dart:async)

Handler: "onTap"
Recognizer:
  TapGestureRecognizer#1116e
════════════════════════════════════════════════════════════════════════════════════════════════════

════════ Exception caught by gesture ═══════════════════════════════════════════════════════════════
The following assertion was thrown while handling a gesture:
No Scaffold widget found.

FMBottomSheetVC widgets require a Scaffold widget ancestor.
The specific widget that could not find a Scaffold ancestor was: FMBottomSheetVC
  dependencies: [_InheritedTheme, _LocalizationsScope-[GlobalKey#da548]]
  state: FMBottomSheetState#0bb6b
The ancestors of this widget were: 
  : MaterialApp
    state: _MaterialAppState#e550a
  : MyApp
  ...

Typically, the Scaffold widget is introduced by the MaterialApp or WidgetsApp widget at the top of your application widget tree.

When the exception was thrown, this was the stack: 
#0      debugCheckHasScaffold.<anonymous closure> (package:flutter/src/material/debug.dart:114:7)
#1      debugCheckHasScaffold (package:flutter/src/material/debug.dart:125:4)
#2      showBottomSheet (package:flutter/src/material/bottom_sheet.dart:725:10)
#3      FMBottomSheetState._raisedButton.<anonymous closure> (package:FMStudyApp/Widgets/Material_components/bottomsheet.dart:53:9)
#4      _InkResponseState._handleTap (package:flutter/src/material/ink_well.dart:992:19)
...
Handler: "onTap"
Recognizer: TapGestureRecognizer#1116e
  debugOwner: GestureDetector
  state: possible
  won arena
  finalPosition: Offset(214.5, 497.0)
  finalLocalPosition: Offset(106.5, 17.0)
  button: 1
  sent tap down
════════════════════════════════════════════════════════════════════════════════════════════════════

No Scaffold widget found.
Typically, the Scaffold widget is introduced by the MaterialApp or WidgetsApp widget at the top of
your application widget tree.

6.2 解決方案

將 RaisedButton 單獨(dú)用一個 class 封裝一層即可。

import 'package:flutter/material.dart';

class FMBottomSheetVC extends StatefulWidget{
  @override
  FMBottomSheetState createState() => FMBottomSheetState();
}

class FMBottomSheetState extends State <FMBottomSheetVC>{
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    // return _materialApp(context);
    return _scaffold(context);
  }

  Scaffold _scaffold(context){
    return Scaffold(
      appBar: AppBar(title: Text("BottomSheet"),),
      body: Center(
        // child: _raisedButton(context),
        child: BottomSheetBtn(),
      ),
      // bottomSheet: _bottomSheet(context),
      // floatingActionButton: FloatingActionButton(
      //   child: Text("返回"),
      //   onPressed: (){
      //     Navigator.pop(context);
      //   },
      // ),
    );
  }

  BottomSheet _bottomSheet(context){
    return BottomSheet(
      onClosing: (){
        print("closed");
      },

      builder: (context){
        return Container(
          height: 300,
          color: Colors.yellow,
          alignment: Alignment.centerLeft,
          child: Text("BottomSheet In Scaffold"),
        );
      },
    );
  }

  RaisedButton _raisedButton(context){
    return RaisedButton(
      child: Text("showModalBottomSheet"),
      onPressed: (){
        showBottomSheet(
          context: context,
          builder: (context){
            return Container(
              width: 414,
              height: 300,
              color: Colors.red,
              alignment: Alignment.centerLeft,
              child: Text("showModalBottomSheet", style: TextStyle(color: Colors.white),),
            );
          },
        );
      },
    );
  }
}

class BottomSheetBtn extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return RaisedButton(
      child: Text("showBottomSheet"),
      onPressed: (){

        showBottomSheet(
          context: context,
          builder: (context){
            return Container(
              width: 414,
              height: 300,
              color: Colors.red,
              alignment: Alignment.centerLeft,
              child: Text("showBottomSheet", style: TextStyle(color: Colors.white),),
            );
          },
        );
      },
    );
  }
}

可以用以下方法收起上拉框

Navigator.pop(context);

showBottomSheet.gif

7. 技術(shù)小結(jié)

上拉框是一個很常用的控件,其實除了喚起,其他屬性和該組件并沒有太大關(guān)系,考驗的還是各種組件搭配使用的基本功。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容