Files
unicorn-utterances/content/blog/draw-under-navbar-using-react-native/index.md
2020-04-15 21:01:46 -07:00

8.8 KiB

While working on my React Native mobile app, the super-talented designer for the project raised an interesting question to me:

"Are we able to draw under the navigation bar and status bar? Google officially recommends new apps to do so.

The idea of drawing under the navbar intrigued me. After lots of research, I was finally able to implement it in my app, but not without struggles. Let's walk through how to do it manually and what I ended up doing to solve the issue myself.

Feel free to follow along with the code samples, but if you're looking for looking for the easiest solution, you might want to read to the bottom to see how to easily integrate it into your app without all of the manual work.

The Wrong Way

After doing some initial research, I found myself presented with various StackOverflows and official documentation pointing towards a Window flag FLAG_LAYOUT_NO_LIMITS to, quote:

Window flag: allow window to extend outside of the screen.

This seemed perfect to me! Being able to draw content outside the edges of the screen would surely allow me to draw under the navbar, right? I looked for the MainActivity.java file that loads all of the project initially to make the configuration:

android > app > src > main > java > yourpackagepath > MainActivity.java

And added the respective flag to initialize once the app started:

import android.os.Bundle;
import android.view.Window;
import android.view.WindowManager;

// ...

@Override
protected void onCreate(Bundle savedInstanceState) {
    Window w = getWindow();
    w.setFlags(
        WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
        WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
    );
    super.onCreate(savedInstanceState);
}

Once this was done, I loaded my app and "et voilà"!

The FAB is placed under the navbar as expected=

"Success", I'd thought to myself. Clearly, since the FAB was now being drawn under the navbar, that was indication that the goal had been reached. However, I faced difficulties when trying to use the safe-area-context package in order to draw margins and padding to move the FAB above the navbar once again.

When I utilized the following code:

import {useSafeArea} from "react-native-safe-area-context";

// ...

const insets = useSafeArea();

// ...

return <Text style={styles.headingText}>Repositories {insets.bottom}</Text>

I was expecting to see the text to read out the height of the navbar. Then, I'd be able to use the bottom property to position the FAB properly.

The titlebar of the previous screenshot reads "Repositories 0"

However, as you can see, it returned a height of 0, which clearly wasn't the size of the navbar.

After some research, I found out that the safe-area-context package does not work properly when using this flag. It doesn't work because of the underlying APIs that the library uses for Android detection (InsetsAPI), does not support the FLAG_LAYOUT_NO_LIMITS. This was an automatic no-go for my app: I didn't want the contents of the app to be stuck under the navbar without a way to access it. I had to start over from the drawing board.

Translucent Bars

After even further research, I'd found myself with a potential alternative: Translucent bars! I knew that the ability to draw under navbars was often accompanied with translucent bars in previous versions of Android! If we revert changes to the MainActivity.java file back to how they were initially, and simply update our styles.xml file located at:

android > app > src > main > res > values > styles.xml

And added the translucent flags, maybe that would work:

<resources>
    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <!-- Customize your theme here. -->
        <item name="android:textColor">#000000</item>
        <!-- Add these two new items -->
        <item name="android:windowTranslucentStatus">true</item>
        <item name="android:windowTranslucentNavigation">true</item>
    </style>
</resources>

After making the changes and restarting my app, I was greeted with the following:

The correct number drawn but the bars aren't fully transparent

Fantastic! It's not only drawing under the navbar, but it's also registering the correct inset.bottom height we wanted to display in the titlebar! That said, I was still hoping for a fully transparent navbar. I knew it was possible. Maybe if I added explicit code to make the navbar transparent, that would work:

<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:windowTranslucentStatus">true</item>
<item name="android:windowTranslucentNavigation">true</item>

Unfortunately for me, there was nothing brought about by this testing.

Further Tests to no Avail

Before giving up on the styles.xml file, I tried two more flags that I thought might have helped.

 <!-- Boolean internal attribute to adjust view layout based on system windows such as the status bar.
    If true, adjusts the padding of this view to leave space for the system windows. API Level 1-->
<item name="android:fitsSystemWindows">true</item>
<!-- Flag indicating whether this Window is responsible for drawing the background for the system bars. API level 21-->
<item name="android:windowDrawsSystemBarBackgrounds">true</item>

Oddly enough, not only did this not solve the problem, but it made the bottom bar a solid color!

The bottom bar from before but now a solid color

Drats! From the start we go again.

The Right Way

After re-reading some of the resources I was looking at, I realized the answer was in the initial GitHub issue I was first looking into when FLAG_LAYOUT_NO_LIMIT didn't work. It suggested to use a View flag called SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION. After reading the documentation for the flag, I knew it was a step in the right direction:

View would like its window to be laid out as if it has requested SYSTEM_UI_FLAG_HIDE_NAVIGATION, even if it currently hasn't.

We'd also likely want to apply the SYSTEM_UI_FLAG_LAYOUT_STABLE flag as well:

When using other layout flags, we would like a stable view of the content insets given to fitSystemWindows

While this might be a bit confusing, it's essentially saying that not only would it draw under the navbar, but it would do so consistently, allowing us to draw under the navbar, just like we wanted!

For good measure, let's add in explicitly transparent navbars and status bar codes:

// MainActivity.java
import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import android.view.Window;

// ...

@Override
protected void onCreate(Bundle savedInstanceState) {
    Window w = getWindow();
    w.setStatusBarColor(Color.TRANSPARENT);
    w.setNavigationBarColor(Color.TRANSPARENT);
    w.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
    super.onCreate(savedInstanceState);
}

The bottom bar is now fully transparent

That's done it! Not only is the button being drawn under the navbar fully transparently, but the number at the top of the screen indicates that the Inset API is registering the height of the navbar still! This is exactly what we were hoping for!

If your bottom bar is still a solid color like the one here: The bottom bar in solid white

Then you've forgotten to remove the fitsSystemWindows flag that we added in our styles.xml flag previously. Once that (and the windowDrawsSystemBarBackgrounds flag) was removed, it worked for me

Other API versions

Extra Polish

The Easy Method