Skip to content

Instantly share code, notes, and snippets.

@rydmike
Created September 1, 2019 23:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rydmike/014817f7d19a924e7590ece6463c156e to your computer and use it in GitHub Desktop.
Save rydmike/014817f7d19a924e7590ece6463c156e to your computer and use it in GitHub Desktop.
Demo of Flutter Drawer issue on Android with transparent statusbar, as well as no impact of SafeArea(top: false) on Drawer content
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool _transparentAppBar = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget.title)),
drawer: SafeArea(top: false, child: Drawer(child: TestDrawer())),
body: Center(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Turn on/off transparent app bar'),
Switch(
onChanged: (value) {
setState(() {
_transparentAppBar = value;
SystemChrome.setSystemUIOverlayStyle(
SystemUiOverlayStyle.light.copyWith(
statusBarColor: _transparentAppBar
? Colors.transparent
: Colors.black26));
});
},
value: _transparentAppBar),
Text(
'When you make the statusbar transparent you get a "cleaner" '
'appbar look, similar to iOS design. \n\nWithout it there is a '
'scrim on top of the statusbar area.\n NOTE! When you do this, '
'the statusbar area of the Drawer will unfortunately look even '
'uglier than before.',
textAlign: TextAlign.center,
),
],
),
),
),
);
}
}
class TestDrawer extends StatelessWidget {
const TestDrawer({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
child: ListView(
shrinkWrap: true,
children: <Widget>[
Container(
height: 150,
color: Colors.blue,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'Cannot draw from top in Drawer! Not even with '
'SafeArea(top: false) wrapper. '
'If we could draw from the top, then with '
'transparent statusbar, we would get the background of '
'whatever we put at the top of of the drawer as '
'color/gradient/image in the statusbar area. '
'Then we also need to handle the statusbar '
'padding in the drawer manually.',
style: TextStyle(color: Colors.white),
),
)),
ListTile(leading: Icon(Icons.description), title: Text('Item 1')),
ListTile(leading: Icon(Icons.info), title: Text('Item 2')),
ListTile(leading: Icon(Icons.vpn_key), title: Text('Item 3')),
],
),
);
}
}
@rajeshzmoke
Copy link

rajeshzmoke commented Sep 2, 2019

class TestDrawer extends StatelessWidget {
  const TestDrawer({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      child: ListView(
 padding: EdgeInsets.only(top: 0),  <-------------- solves your problem
        shrinkWrap: true,
        children: <Widget>[
          Container(
              height: 150,
              color: Colors.blue,
              child: Padding(
                padding: const EdgeInsets.all(8.0),
                child: Text(
                  'Cannot draw from top in Drawer! Not even with '
                  'SafeArea(top: false) wrapper. '
                  'If we could draw from the top, then with '
                  'transparent statusbar, we would get the background of '
                  'whatever we put at the top of of the drawer as '
                  'color/gradient/image in the statusbar area. '
                  'Then we also need to handle the statusbar '
                  'padding in the drawer manually.',
                  style: TextStyle(color: Colors.white),
                ),
              )),
          ListTile(leading: Icon(Icons.description), title: Text('Item 1')),
          ListTile(leading: Icon(Icons.info), title: Text('Item 2')),
          ListTile(leading: Icon(Icons.vpn_key), title: Text('Item 3')),
        ],
      ),
    );
  }
}

@rydmike
Copy link
Author

rydmike commented Sep 2, 2019

Thanks Rajesh!

You are totally right, with this we can fix the ugly layout. The ListView implements BoxScrollView that has the EdgeInsetsGeometry padding logic built into it, which results in that we get a "statusbar" height amount of pixels before a ListView, interesting. I was not aware of this in the implementation, but as always looking at the Flutter framework code totally shows it, if you just suspect to look for it. I was using the Flutter inspector too try to find the reason for this behavior too, but missed it as the EdgeInsets in question get's hidden deep in the tree in a SliverPadding, when I knew where to look for it I found it there too.

With this, the desired implementation is an easy fix. Just add the statusbar hieght (MediaQuery.of(context).padding.top;) the top padding to the first Container used in the layout and add the same to the containers height and that does it.

By the way, I have seen this "issue" in so many Flutter drawer implementations, as pretty much all of the use the ListView. Usually it is just with the ugly grey scrim (coming from the black transparency scrim on the default drawer background) in the statusbar, not so many with the transparent statusbar though, but that is just a variation on this. Still pretty much all suffered from the same ugly effect. Thanks for providing the solution for this design issue, much appreciated! :)

@rydmike
Copy link
Author

rydmike commented Sep 2, 2019

Here is the same example, with the above fix applied. It fixes the ugly drawer in the statusbar area and adds the removed statusbar height back in the own layout. Worked great in my tests. Thanks again @rajeshzmoke for the solution.

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  bool _transparentAppBar = false;

  @override
  Widget build(BuildContext context) {
    double _statusBarHeight = MediaQuery.of(context).padding.top;
    print('Statusbar height in app: $_statusBarHeight');
    return Scaffold(
      appBar: AppBar(title: Text(widget.title)),
      drawer: SafeArea(top: false, child: Drawer(child: TestDrawer())),
      body: Center(
        child: Padding(
          padding: const EdgeInsets.all(16.0),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text('Turn on/off transparent app bar'),
              Switch(
                  onChanged: (value) {
                    setState(() {
                      _transparentAppBar = value;
                      SystemChrome.setSystemUIOverlayStyle(
                          SystemUiOverlayStyle.light.copyWith(
                              statusBarColor: _transparentAppBar
                                  ? Colors.transparent
                                  : Colors.black26));
                    });
                  },
                  value: _transparentAppBar),
              Text(
                'When you make the statusbar transparent you get a "cleaner" '
                'appbar look, similar to iOS design. \n\nWithout it there is a '
                'scrim on top of the statusbar area.\n NOTE! When you do this, '
                'the statusbar area of the Drawer will unfortunately look even '
                'uglier than before. Unless...',
                textAlign: TextAlign.center,
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class TestDrawer extends StatelessWidget {
  const TestDrawer({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    double _statusBarHeight = MediaQuery.of(context).padding.top;
    print('Statusbar height in drawer: $_statusBarHeight');

    return Container(
      child: ListView(
        padding: EdgeInsets.only(top: 0),
        shrinkWrap: true,
        children: <Widget>[
          Container(
              height: 150 + _statusBarHeight,
              color: Colors.blue,
              child: Padding(
                padding: EdgeInsets.fromLTRB(8, 8 + _statusBarHeight, 8, 8),
                child: Text(
                  'Hooray, the ListView padding: EdgeInsets.only(top: 0), '
                  'really solves this issue. Then we just add the height '
                  'of the statusbar to our own top container and to its top '
                  'padding for the same size container as before! ',
                  style: TextStyle(color: Colors.white),
                ),
              )),
          ListTile(leading: Icon(Icons.description), title: Text('Item 1')),
          ListTile(leading: Icon(Icons.info), title: Text('Item 2')),
          ListTile(leading: Icon(Icons.vpn_key), title: Text('Item 3')),
        ],
      ),
    );
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment