thanhbinh84
thanhbinh84

Reputation: 18464

Flutter - How to set showModalBottomSheet to full height but below status bar?

I am able to make bottomSheet to full height by using showModalBottomSheet(...) and set the property isScrollControlled:true.

However, the bottom sheet is over the status bar, and that is not what I expect.

enter image description here

Is it possible to make it below the status bar?

Upvotes: 19

Views: 14992

Answers (9)

Luke Hutchison
Luke Hutchison

Reputation: 9230

As AnasSafi's answer stated, use useSafeArea: true to solve this problem. However there are many other tricky issues to solve when creating a bottom sheet. Here is my complete working bottom sheet creator function, which will hopefully help someone.

import 'package:flutter/material.dart';

Future Function() openBottomSheet(BuildContext context, Widget child,
    {String? title}) {
  return () => showModalBottomSheet(
        // Don't let bottom sheet extend past the top of the safe area
        useSafeArea: true,
        context: context,
        // Round the top of the bottom sheet
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(12.0),
        ),
        // Add scrollbar when necessary
        isScrollControlled: true,
        builder: (context) => ScrollableWidget(
          child: Container(
            // Move bottom sheet above on-screen keyboard, if keyboard is open
            padding: EdgeInsets.only(
                bottom: MediaQuery.of(context).viewInsets.bottom),
            child: Stack(
              children: [
                // Add back button at top left of bottom sheet (since it's
                // not obvious that sheet can be swiped down to close it)
                const BackButton(),
                Padding(
                  // Pad the main widget (larger padding on the top to leave
                  // space for the back button)
                  padding: const EdgeInsets.fromLTRB(20.0, 35.0, 20.0, 25.0),
                  child: Column(
                    children: [
                      // Make content full-width, so that main widget is
                      // centered even if it doesn't expand
                      Row(
                        mainAxisSize: MainAxisSize.max,
                        children: const [SizedBox(height: 0)],
                      ),
                      // Add title text to top of bottom sheet, if provided
                      title == null
                          ? Container()
                          : Column(
                              children: [
                                Text(
                                  title,
                                  style: const TextStyle(
                                      fontWeight: FontWeight.bold,
                                      fontSize: 18),
                                  textAlign: TextAlign.center,
                                ),
                                const SizedBox(height: 8),
                              ],
                            ),
                      // Add the main widget
                      child,
                    ],
                  ),
                ),
              ],
            ),
          ),
        ),
      );
}

/// A scrollable widget with a scrollbar that is shown when necessary
class ScrollableWidget extends StatefulWidget {
  const ScrollableWidget({super.key, required this.child});

  final Widget child;

  @override
  State<ScrollableWidget> createState() => _ScrollableWidgetState();
}

class _ScrollableWidgetState extends State<ScrollableWidget> {
  final controller = ScrollController();

  @override
  Widget build(BuildContext context) {
    return Scrollbar(
      thumbVisibility: true,
      thickness: 15,
      radius: const Radius.circular(8),
      controller: controller,
      child: SingleChildScrollView(
        // Same controller must be used on Scrollbar and SingleChildScrollView
        controller: controller,
        child: widget.child,
      ),
    );
  }

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }
}

Upvotes: 0

AnasSafi
AnasSafi

Reputation: 6294

Since 20 Jul 2022, you can set useSafeArea parameter to true to show modal under status bar, you can find details here.

showModalBottomSheet<void>(
  useSafeArea: true, // <<<<< use this
  // ....
);

Note: Now it available on master channel only, and later will be available on stable channel.

Upvotes: 19

Gajendra Pandeya
Gajendra Pandeya

Reputation: 106

The best and the easiest way is to subtract the total height of device from status bar height. You can get statusBarHeight by MediaQuery.of(context).viewPadding.top and whole device height by MediaQuery.of(context).size.height

Upvotes: 2

Atul Parmar
Atul Parmar

Reputation: 11

You can use the constraints parameter and set the height you want

showModalBottomSheet(
      context: context,
      isScrollControlled: true,
      shape: const RoundedRectangleBorder(
        borderRadius: BorderRadius.only(
          topLeft: Radius.circular(35),
          topRight: Radius.circular(35),
        ),
      ),
      constraints: BoxConstraints(maxHeight: 
           MediaQuery.of(context).size.height - 50),
      builder: (context) {
         //continue coding..
      }

Upvotes: 1

Huy Nguyen
Huy Nguyen

Reputation: 1109

I think this solution is the most suitable for now: (use FractionallySizedBox)

showModalBottomSheet(
  context: context,
  enableDrag: true,
  isScrollControlled: true,
  builder: (context) => FractionallySizedBox(
    heightFactor: 0.9,
    child: Container()
  )
);

Upvotes: 2

Chris
Chris

Reputation: 2044

Just like @Roddy R's answer but a little simpler

showModalBottomSheet(
            context: context,
            isScrollControlled: true,
            shape: RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(20))),
            builder: (ctx) => Padding(
              padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top),
              child: YourCustomBottomSheet(),
            ),
          ),

Thanks Roddy!

Upvotes: 0

Roddy R
Roddy R

Reputation: 1470

No need to create a new class. Simply assign a variable to the padding on the build of your view and use it when opening the dialog.

class MyHomeScreenState extends State<MyHomeScreen> {
  double? topPadding;
  
  @override
  Widget build(BuildContext context) {
    topPadding = MediaQuery.of(context).padding.top;
  //...
  }
showModalBottomSheet(
        context: context,
        useRootNavigator: true,
        isScrollControlled: true,
        builder: (context) {
          return Container(
            height: MediaQuery.of(context).size.height -
                topPadding!,
            color: Colors.green,
          );
});

If you look at SafeArea source code this is exactly what is happening. Make sure that your build is at a 'root' level on the widget tree since descendant widgets might not have a top padding because they are not underneath the edges.

Upvotes: 8

SUDESH KUMARA
SUDESH KUMARA

Reputation: 1322

Another easy workaround with rounded borders

showModalBottomSheet(
    context: context,
    isScrollControlled: true,
    backgroundColor: Colors.transparent,
    builder: (context) => Container(
         height: MediaQuery.of(context).size.height * 0.965,
         decoration: new BoxDecoration(
              color: Colors.white,
              borderRadius: new BorderRadius.only(
                 topLeft: const Radius.circular(16.0),
                 topRight: const Radius.circular(16.0),
              ),
          ),
          child: Center(
              child: Text("Your content goes here"),
    ),
),

);

enter image description here

Upvotes: 2

user10539074
user10539074

Reputation:

as an option you can modify bottom sheet 1. create new file custom_bottom_sheet.dart 2. copy-paste all code from bottom_sheet class into your custom class 3. modify buildPage() method like this

  @override
  Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
    final BottomSheetThemeData sheetTheme = theme?.bottomSheetTheme ?? Theme.of(context).bottomSheetTheme;
    Widget bottomSheet = SafeArea(
      child: _ModalBottomSheet<T>(
        route: this,
        backgroundColor: backgroundColor ?? sheetTheme?.modalBackgroundColor ?? sheetTheme?.backgroundColor,
        elevation: elevation ?? sheetTheme?.modalElevation ?? sheetTheme?.elevation,
        shape: shape,
        clipBehavior: clipBehavior,
        isScrollControlled: isScrollControlled,
        enableDrag: enableDrag,
      ),
    );
    if (theme != null) bottomSheet = Theme(data: theme, child: bottomSheet);
    return bottomSheet;
  }
  1. use your class
    import 'package:flutter/material.dart';
    import 'custom_bottom_sheet.dart' as bs;

    void main() => runApp(MyApp());

    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: Scaffold(
            body: SafeArea(child: Page()),
          ),
        );
      }
    }

    class Page extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Center(
          child: RaisedButton(
            child: Text('show'),
            onPressed: () {
              bs.showModalBottomSheet(
                context: context,
                isScrollControlled: true,
                builder: (context) => Container(color: Colors.red),
              );
            },
          ),
        );
      }
    }

here is modified bottom_sheet class https://pastebin.com/5U7fsqCw

also I think you need to add property

barrierColor: Colors.white.withOpacity(0),

to prevent status bar dimming

Upvotes: 15

Related Questions