How do I use DrawerLayout to display over the ActionBar/Toolbar and under the status bar?

后端 未结 10 837
[愿得一人]
[愿得一人] 2020-11-22 04:05

I\'ve seen in the new material design Side Nav spec that you can display the drawer over the action bar and behind the status bar. How can I implement this?

相关标签:
10条回答
  • 2020-11-22 04:40

    Make it work, in values-v21 styles or theme xml needs to use this attribute:

    <item name="android:windowTranslucentStatus">true</item>

    That make the magic!

    0 讨论(0)
  • 2020-11-22 04:42

    The above all approaches are correct and may be working . I have created a working demo following the above guide and tested on 2.x to 5.x

    You can clone from Github

    The important thing to play around is in Main Activity

    toolbar = (Toolbar) findViewById(R.id.toolbar);
    res = this.getResources();
    
    this.setSupportActionBar(toolbar);
    ActionBar actionBar = getSupportActionBar();
    actionBar.setDisplayHomeAsUpEnabled(true);
    actionBar.setHomeButtonEnabled(true);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 
        ScrimInsetsFrameLayout scrimInsetsFrameLayout = (ScrimInsetsFrameLayout)
                findViewById(R.id.linearLayout);
        scrimInsetsFrameLayout.setOnInsetsCallback(this);
    } 
    

    and the call back

    @Override
    public void onInsetsChanged(Rect insets) {
        Toolbar toolbar = this.toolbar;
        ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams)
            toolbar.getLayoutParams();
        lp.topMargin = insets.top;
        int top = insets.top;
        insets.top += toolbar.getHeight();
        toolbar.setLayoutParams(lp);
        insets.top = top; // revert
    }
    

    Absolutely the Theme for V21 does the magic

    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <!-- API 21 theme customizations can go here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/accent_material_light</item>
        <item name="windowActionModeOverlay">true</item>
        <item name="android:windowDrawsSystemBarBackgrounds">true</item>
        <item name="android:statusBarColor">@android:color/transparent</item>
        <item name="android:windowTranslucentStatus">true</item>
    </style>
    

    and the ScrimInsetsFrameLayout

    Now this come more easy with new Design Support library

    compile 'com.android.support:design:22.2.0'
    

    clone from @Chris Banes https://github.com/chrisbanes/cheesesquare

    0 讨论(0)
  • 2020-11-22 04:45

    Instead of using the ScrimInsetsFrameLayout... Isn't it easier to just add a view with a fixed height of 24dp and a background of primaryColor?

    I understand that this involves adding a dummy view in the hierarchy, but it seems cleaner to me.

    I already tried it and it's working well.

    <android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_base_drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">
    
            <!-- THIS IS THE VIEW I'M TALKING ABOUT... -->
            <View
                android:layout_width="match_parent"
                android:layout_height="24dp"
                android:background="?attr/colorPrimary" />
    
            <android.support.v7.widget.Toolbar
                android:id="@+id/activity_base_toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                android:background="?attr/colorPrimary"
                android:elevation="2dp"
                android:theme="@style/ThemeOverlay.AppCompat.Dark" />
    
            <FrameLayout
                android:id="@+id/activity_base_content_frame_layout"
                android:layout_width="match_parent"
                android:layout_height="match_parent" />
    
        </LinearLayout>
    
        <fragment
            android:id="@+id/activity_base_drawer_fragment"
            android:name="com.myapp.drawer.ui.DrawerFragment"
            android:layout_width="240dp"
            android:layout_height="match_parent"
            android:layout_gravity="start"
            android:elevation="4dp"
            tools:layout="@layout/fragment_drawer" />
    
    </android.support.v4.widget.DrawerLayout>
    
    0 讨论(0)
  • 2020-11-22 04:48

    EDIT: The new Design Support Library supports this and the previous method is no longer required.

    This can now be achieved using the new Android Design Support Library.

    You can see the Cheesesquare sample app by Chris Banes which demos all the new features.


    Previous method:

    Since there is no complete solution posted, here is the way I achieved the desired result.

    First include a ScrimInsetsFrameLayout in your project.

    /*
    * 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.
    */
    
    /**
    * 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);
        }
    }
    

    Then create a styleable so that the insetForeground can be set.

    values/attrs.xml

    <declare-styleable name="ScrimInsetsView">
        <attr name="insetForeground" format="reference|color" />
    </declare-styleable>
    

    Update your activity's xml file and make sure android:fitsSystemWindows is set to true on both the DrawerLayout as well as the ScrimInsetsFrameLayout.

    layout/activity_main.xml

    <android.support.v4.widget.DrawerLayout 
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/drawerLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="true"
        tools:context=".MainActivity">
    
        <!-- The main content view -->
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">
    
            <!-- Your main content -->
    
        </LinearLayout>
    
        <!-- The navigation drawer -->
        <com.example.app.util.ScrimInsetsFrameLayout 
            xmlns:app="http://schemas.android.com/apk/res-auto"
            android:id="@+id/scrimInsetsFrameLayout"
            android:layout_width="320dp"
            android:layout_height="match_parent"
            android:layout_gravity="start"
            android:background="@color/white"
            android:elevation="10dp"
            android:fitsSystemWindows="true"
            app:insetForeground="#4000">
    
            <!-- Your drawer content -->
    
        </com.example.app.util.ScrimInsetsFrameLayout>
    
    </android.support.v4.widget.DrawerLayout>
    

    Inside the onCreate method of your activity set the status bar background color on the drawer layout.

    MainActivity.java

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    
        // ...
    
        mDrawerLayout = (DrawerLayout) findViewById(R.id.drawerLayout);
        mDrawerLayout.setStatusBarBackgroundColor(
            getResources().getColor(R.color.primary_dark));
    }
    

    Finally update your app's theme so that the DrawerLayout is behind the status bar.

    values-v21/styles.xml

    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <item name="android:windowDrawsSystemBarBackgrounds">true</item>
        <item name="android:statusBarColor">@android:color/transparent</item>
    </style>
    

    Result:

    0 讨论(0)
提交回复
热议问题