20

I'm trying to make my navigation drawer go under the status bar. I've read extensively about the ScrimInsetsFrameLayout view and I tried implementing it, but for some reason it won't go under.

Here's the code that I used/wrote.

XML DrawerLayout:

<android.support.v4.widget.DrawerLayout
    android:id="@+id/drawer_layout"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <FrameLayout
            android:id="@+id/content_frame"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

        <include layout="@layout/toolbar" />

    </FrameLayout>

    <com.andrewq.planets.util.ScrimInsetsFrameLayout
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/linearLayout"
        android:layout_width="304dp"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:fitsSystemWindows="true"
        app:insetForeground="#4000">

        <ListView
            android:id="@+id/left_drawer"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:choiceMode="singleChoice" />
    </com.andrewq.planets.util.ScrimInsetsFrameLayout>


</android.support.v4.widget.DrawerLayout>

ScrimInsetsFrameLayout.java:

package com.andrewq.planets.util;

/*
* Copyright 2014 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.widget.FrameLayout;

import com.andrewq.planets.R;

/**
 * A layout that draws something in the insets passed to {@link #fitSystemWindows(Rect)}, i.e. the area above UI chrome
 * (status and navigation bars, overlay action bars).
 */
public class ScrimInsetsFrameLayout extends FrameLayout {
    private Drawable mInsetForeground;

    private Rect mInsets;
    private Rect mTempRect = new Rect();
    private OnInsetsCallback mOnInsetsCallback;

    public ScrimInsetsFrameLayout(Context context) {
        super(context);
        init(context, null, 0);
    }

    public ScrimInsetsFrameLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs, 0);
    }

    public ScrimInsetsFrameLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(context, attrs, defStyle);
    }

    private void init(Context context, AttributeSet attrs, int defStyle) {
        final TypedArray a = context.obtainStyledAttributes(attrs,
                R.styleable.ScrimInsetsView, defStyle, 0);
        if (a == null) {
            return;
        }
        mInsetForeground = a.getDrawable(R.styleable.ScrimInsetsView_insetForeground);
        a.recycle();

        setWillNotDraw(true);
    }

    @Override
    protected boolean fitSystemWindows(Rect insets) {
        mInsets = new Rect(insets);
        setWillNotDraw(mInsetForeground == null);
        ViewCompat.postInvalidateOnAnimation(this);
        if (mOnInsetsCallback != null) {
            mOnInsetsCallback.onInsetsChanged(insets);
        }
        return true; // consume insets
    }

    @Override
    public void draw(Canvas canvas) {
        super.draw(canvas);

        int width = getWidth();
        int height = getHeight();
        if (mInsets != null && mInsetForeground != null) {
            int sc = canvas.save();
            canvas.translate(getScrollX(), getScrollY());

            // Top
            mTempRect.set(0, 0, width, mInsets.top);
            mInsetForeground.setBounds(mTempRect);
            mInsetForeground.draw(canvas);

            // Bottom
            mTempRect.set(0, height - mInsets.bottom, width, height);
            mInsetForeground.setBounds(mTempRect);
            mInsetForeground.draw(canvas);

            // Left
            mTempRect.set(0, mInsets.top, mInsets.left, height - mInsets.bottom);
            mInsetForeground.setBounds(mTempRect);
            mInsetForeground.draw(canvas);

            // Right
            mTempRect.set(width - mInsets.right, mInsets.top, width, height - mInsets.bottom);
            mInsetForeground.setBounds(mTempRect);
            mInsetForeground.draw(canvas);

            canvas.restoreToCount(sc);
        }
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        if (mInsetForeground != null) {
            mInsetForeground.setCallback(this);
        }
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        if (mInsetForeground != null) {
            mInsetForeground.setCallback(null);
        }
    }

    /**
     * Allows the calling container to specify a callback for custom processing when insets change (i.e. when
     * {@link #fitSystemWindows(Rect)} is called. This is useful for setting padding on UI elements based on
     * UI chrome insets (e.g. a Google Map or a ListView). When using with ListView or GridView, remember to set
     * clipToPadding to false.
     */
    public void setOnInsetsCallback(OnInsetsCallback onInsetsCallback) {
        mOnInsetsCallback = onInsetsCallback;
    }

    public static interface OnInsetsCallback {
        public void onInsetsChanged(Rect insets);
    }
}

And finally, here's my styles.xml for values-v21:

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <style name="AppThemeNavDrawer" parent="Theme.AppCompat.NoActionBar">
        <item name="colorAccent">#F8F8F8</item>
        <item name="android:windowTranslucentStatus">true</item>

        <item name="windowActionBar">false</item>
        <item name="windowActionModeOverlay">true</item>
        <item name="android:windowDrawsSystemBarBackgrounds">true</item>
        <item name="android:statusBarColor">@android:color/transparent</item>
    </style>
</resources>

I've looked at the 2014 I/O app source code as well as this question, and I don't know what is so different.

Here's a screenshot of what I have so far minus the drawer under the status bar: Screenshot

I've got everything else working perfectly and this is the last thing I need to do. Help is greatly appreciated!

Edit:

To clarify, I want to have the image be tinted under the status bar just like in most of the Google Apps and Google Now.

Community
  • 1
  • 1
Andrew Quebe
  • 2,263
  • 5
  • 25
  • 53

5 Answers5

56

adding android:fitsSystemWindows="false" to DrawerLayout will solve the problem

Moaz H
  • 776
  • 1
  • 6
  • 5
26

There are different approaches to get to the desired result. You can enable translucent via style or via code.

I've created a MaterialDrawer (which follows the Android Material Design Guidelines) which implements all of this and handles everything for you. Read more here: https://github.com/mikepenz/MaterialDrawer/

If you want to create it on your own you always have to decide which is the lowest api you want to support and/or if you have to split up your styles.

So to enable translucentStatusbar you have to be at least on API v19 or you create a separat style for v19+ values-v19 This will look somehow like this

<style name="YourTheme.TranslucentStatus" parent="Theme.AppCompat.Light.NoActionBar">
    <item name="android:windowTranslucentStatus">true</item>
</style>

So now this will move your complete layout below the statusbar. In almost all cases you will now want to add the padding on the top of the drawer content and your normal view content.

You can do this by adding 24dp padding.

This is not a really nice implementation. So there's a different approach by using the ScrimInsetsLayout which is used in the Google IO 2014 app. https://github.com/google/iosched/blob/master/android/src/main/java/com/google/samples/apps/iosched/ui/widget/ScrimInsetsFrameLayout.java

This will be your contents layout and you can set the color for the statusbar on it. You can find a detailed instruction, on how you can use it, here: https://stackoverflow.com/a/26932228

It requires some time to get used to the styles and / or the ScrimInsetsLayout.

EDIT:

A more complex sample on how you can handle this programmatically:

if (Build.VERSION.SDK_INT >= 19 && Build.VERSION.SDK_INT < 21) {
    //enable translucent statusbar via flags
    setTranslucentStatusFlag(true);
}
if (Build.VERSION.SDK_INT >= 19) {
    mActivity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
}
if (Build.VERSION.SDK_INT >= 21) {
    //we don't need the translucent flag this is handled by the theme
    setTranslucentStatusFlag(false);
    //set the statusbarcolor transparent to remove the black shadow
    mActivity.getWindow().setStatusBarColor(Color.TRANSPARENT);
}

//add a padding to the content of the drawer (25dp on devices starting with api v19)
mDrawerContentRoot.setPadding(0, mActivity.getResources().getDimensionPixelSize(R.dimen.tool_bar_top_padding), 0, 0);

// define the statusBarColor
mDrawerContentRoot.setInsetForeground(mStatusBarColor);


private void setTranslucentStatusFlag(boolean on) {
    if (Build.VERSION.SDK_INT >= 19) {
        Window win = mActivity.getWindow();
        WindowManager.LayoutParams winParams = win.getAttributes();
        final int bits = WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
        if (on) {
            winParams.flags |= bits;
        } else {
            winParams.flags &= ~bits;
        }
        win.setAttributes(winParams);
    }
}

EDIT2:

The complete solution to fix this issue was to clean up all the layouts which were in the project. some combination of the layouts and styles were causing the troubles.

The complete changes can be found in this pull request: https://github.com/Andrew-Quebe/Planets-Gradle/commit/83e28c09253af6e807b6f4e94baca8fbca3fc7c8

Community
  • 1
  • 1
mikepenz
  • 12,708
  • 14
  • 77
  • 117
  • How would I implement the ScrimInsetsView in my XML code? Did I do it right with my attempt? Also, currently, I'm working on making the drawer go under for Lollipop devices only...for now. My theme for v21 already has the `true` in it. – Andrew Quebe Mar 26 '15 at 16:27
  • As far as i can tell everything looks fine. I'll take a closer look at your source @github – mikepenz Mar 27 '15 at 06:39
  • I have fixed your issue @github. was a huge commit. The thing were many issues with your layouts and styles. – mikepenz Mar 27 '15 at 15:25
  • @mikepenz Where did you get `setInsetForeground`? I can't find it anywhere. – Mark13426 Oct 06 '16 at 00:45
  • 1
    @Mark13426 this answer is from 2015. things may changed since then – mikepenz Oct 06 '16 at 06:15
4

You need to make a new styles.xml and put that file in style-v19 folder because the status bar translucent method is not available for pre kitkat devices.

<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar" >
    <item name="android:windowTranslucentStatus">true</item>
</style>

After that you will see the your Application is under the Status bar But you need to give padding to toolBar for the Exact implementation.Create a dimen-v19 and add

<dimen name="ToolBarPaddingTop">24dp</dimen>

Use it in ToolBar

<android.support.v7.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@color/accent_material_light"
    android:theme="@style/ThemeOverlay.AppCompat.Dark"
    android:fitsSystemWindows="true"
    android:paddingTop="@dimen/ToolBarPaddingTop">

</android.support.v7.widget.Toolbar>
Hemant Shori
  • 2,463
  • 1
  • 22
  • 20
  • I already have a values-v19 folder and it has styling specific to Kitkat. I don't see how this is going to make a difference. – Andrew Quebe Mar 26 '15 at 14:54
  • I just tried what you said and all it does is add 24dp worth of padding to the top of the toolbar...the drawer doesn't go under at all. – Andrew Quebe Mar 26 '15 at 15:02
3

This is what worked for me:

  • Set android:windowTranslucentStatus to true in your default style in the v21 folder. This will make your statusbar almost transparent from Lollipop.
  • Define the color of your Toolbar with the android:colorPrimary parameter in your style.
  • Do NOT set android:fitsSystemWindows="true" in your layout.
  • Add a 24dp padding to the top of your toolbar AND to the top of your drawer as well, but only from Lollyipop. The best way to do this is to add a new dimension to your dimens files. In the values folder set it to 0, in the values-v21 set it to 24 dp, and use it on your Toolbar and Drawer.
BSMP
  • 4,596
  • 8
  • 33
  • 44
Analizer
  • 1,594
  • 16
  • 30
1

I believe you need to add android:fitsSystemWindows="true" to the android.support.v4.widget.DrawerLayout tag as well.

Aditya Vyas-Lakhan
  • 13,409
  • 16
  • 61
  • 96
Krypton Nite
  • 54
  • 2
  • 6