A Flutter guide to visual overlap: padding, viewPadding, and viewInsets
Flutter is designed to run on a variety of devices. And to run on multiple devices is to adapt to each device’s quirks and features.
One type of device’s quirk that we developers need to pay attention to is the system UIs that overlap our apps. It might be a status bar, a notch, or a bottom indicator.
Fortunately, Flutter supports this out of the box with MediaQuery
. You can get the status bar height with MediaQuery.of(context).padding.top
, the keyboard height with MediaQuery.of(context).viewInsets.bottom
, etc.
However, MediaQuery
itself also has some quirks. For example, it has padding
but also a viewPadding
property. And sometimes they return EdgeInsets.zero
even though there are visible obscured system UI.
So in this article, I will attempt to clear things up for those who ran into these little quirks.
For the rest of the article, whenever I refer to padding
, viewPadding
, or viewInsets
, it is the properties of MediaQuery
that I’m talking about.
Categorization
First, we should divide the system obscure UIs into 2 categories:
Partially obscured UIs
These are status bars, bottom indicators. We should account for these UIs in our app to create an immersive experience.
Completely obscured UIs
These usually are keyboards. We should adjust our layout to avoid these UIs when they show up.
Notice that when the keyboard appears, the bottom indicator section turns from partially to completely obscured UI.
How Flutter handles this: viewInsets
and viewPadding
Flutter’s MediaQuery
separates these into 2 properties:
viewInsets
→ the space needed for completely obscured UIs.
viewPadding
→ the space needed for partially obscured UIs.
These 2 are independent of each other. When the keyboard appears, viewInsets.bottom
changes from 0 to 336, but viewPadding.bottom
stays at 34 although we can see that the keyboard consumes the bottom indicator space that our app occupied.
So consider the case above where the completely obscured UI consumed the partially obscured UI, Flutter has a property that accounts for the consumption and it is nonother than padding
!
padding
→ The parts of the display that are still partially obscured by system UI after accounting for the ones that are completely obscured. It is calculated like this:
padding
= max(0,viewPadding
-viewInsets
)
We can see that viewInsets
is rather straightforward, but padding
and viewPadding
can sometimes be used interchangeably, other times it is unwise to do so.
Choose your padding wisely: when to use viewPadding and padding
Layout 1
We decide we want our button to stay behind the keyboard.
- We need to set our
Scaffold.resizeToAvoidBottomInset = false
to prevent ourScaffold
from resizing. - For this case, we should use
viewPadding
because by then our button is irrelevant to the flow, and usingpadding
might cause our layout to jump down.
Layout 2
We decide we want our button to transform and stay on top when the keyboard appears.
- We need to check whether the keyboard is visible using
viewInsets.bottom > 0
- For this case, we should use
padding
because Flutter will account forviewInsets
and reducepadding
to 0. Our button will stay on top nicely without any further checking.
Why is my viewPadding / padding/ viewInsets is all zeroes?
I previously said that we can check if viewInsets.bottom > 0
to determine if the keyboard is visible. This is only partially true.
Calling MediaQuery.of(context).viewInsets
in the wrong place and we will only get EdgeInsets.zero
even though the keyboard is clearly showing.
What do I mean by wrong place?
In Flutter, a Widget
, usually a Scaffold
, can consume viewPadding
, padding
or viewInsets
and modify the MediaQuery
which then is passed down to it’s child.
The idea is that if the parent took care of the obscured UIs, then the child shouldn’t worry about them anymore.
If Scaffold
has an AppBar
, it will consume viewPadding.top
.
If Scaffold
has a BottomNavigationBar
, it will consume viewPadding.bottom
.
If Scaffold has resizeToAvoidBottomInset = true
, it will consume viewInsets.bottom
.
So if you want to check for the keyboard visibility, check them outside of the Scaffold
, use an InheritedWidget
to pass the data down if needed.
Preparing for Android Q: systemGestureInsets
On a side note, MediaQuery
also has a property called systemGestureInsets
. This is for Android Q’s gesture navigation. You can find out more about this here.
Wrapping things up
viewPadding
is for partially obscured UIsviewInsets
is for completely obscured UIspadding
is derived fromviewPadding
andviewInsets
and should be used only when necessary.- Some
Widgets
can consume these properties and passing down a modifiedMediaQuery
to their child.