Search code examples
androidandroid-layoutdrawerlayout

How move View alongside DrawerLayout's content whit the same Z


I have my main view with 3 Buttons in a vertical LinearLayout on top right of the screen. Each button shows some Fragment that is hosted inside a DrawerLayout. Like this:

enter image description here

When I click any button, I want the DrawerLayout to be shown from the right and this button to be translated alongside it. Like this:

enter image description here

I have managed to move the Button with the drawer but my problem is that the drawers's shadow affects the button as well. I want it to be so bright as the drawer's content (same hight) but I also want the other buttons to remain behind the drawer.

This is my activity_main.xml file:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">

    <android.support.v4.widget.DrawerLayout
        android:id="@+id/drawer_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

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

        <LinearLayout ... >
            <ImageButton ... />
            <ImageButton ... />
            <ImageButton ... />
        </LinearLayout>

        <FrameLayout
            android:id="@+id/fragment_secondary_content"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_gravity="right"
            tools:context="..."/>

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

</android.support.design.widget.CoordinatorLayout>

With the buttons inside DrawerLayout, the drawer moves above them but the one that's being translated gets dark.

On the other hand if I put the buttons outside DrawerLayout, the button that moves looks all right but the drawer is below the other buttons. Like:

enter image description here

Is there any way to avoid DrawerLayout's shadow to affect a particular view? Or perhaps to cancel it?


Solution

  • DrawerLayout applies that shadow - the scrim - to its child Views in its drawChild() method. It determines which children to apply it to by checking that a given child is not a drawer View. It ultimately does this by checking the gravity of the child's LayoutParams.

    Knowing this, we can create a custom DrawerLayout subclass to override drawChild(), and temporarily change the gravity on the child that we don't want the scrim applied to. Since no layout is happening during the draw, this won't affect the actual placement of the View.

    public class CustomDrawerLayout extends DrawerLayout {
    
        private int noScrimId;
    
        public CustomDrawerLayout(Context context, AttributeSet attrs) {
            super(context, attrs);
    
            // Get the ID for the no-scrim View.
            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomDrawerLayout);
            noScrimId = a.getResourceId(R.styleable.CustomDrawerLayout_noScrimView, View.NO_ID);
            a.recycle();
        }
    
        @Override
        protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
            // Is this the child we want?
            final boolean isNoScrimView = child.getId() == noScrimId;
    
            // If yes, temporarily tag it as a drawer.
            if (isNoScrimView) {
                ((LayoutParams) child.getLayoutParams()).gravity = Gravity.LEFT;
            }
    
            // Let the super class do the draw, and save the return.
            final boolean res = super.drawChild(canvas, child, drawingTime);
    
            // Reset the gravity if we changed it.
            if (isNoScrimView) {
                ((LayoutParams) child.getLayoutParams()).gravity = Gravity.NO_GRAVITY;
            }
    
            return res;
        }
    }
    

    This example uses a custom attribute so that the "no-scrim" View can be specified in the layout. If you don't want to use that attribute, you can remove the TypedArray processing from the constructor, and just hardcode an ID. If you do want to use the custom attribute, you need to define it in your project, which you can do by sticking the following file in the values/ folder, or adding to the one that might already be there.

    attrs.xml

    <resources>
        <declare-styleable name="CustomDrawerLayout">
            <attr name="noScrimView" format="reference" />
        </declare-styleable>
    </resources>
    

    CustomDrawerLayout is a drop-in replacement, and you can use it in your layouts just like you would the regular class. For example, if your ImageButtons' LinearLayout has the ID button_menu:

    <com.mycompany.myapp.CustomDrawerLayout
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/drawer_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:noScrimView="@+id/button_menu">
    

    Please note that if you should happen to set a drawer View as the noScrimView, you're gonna have a bad time. Also, the noScrimView must be a direct child of the DrawerLayout, as the scrim effect is applied only to those.

    For simplicity's sake, I've omitted any checks from the sample, but if you wish to include some, doing them in the onMeasure() method would be in line with how DrawerLayout handles its own checks.

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    
        final View v = findViewById(noScrimId);
        if (v != null) {
            if (!(v.getLayoutParams() instanceof LayoutParams)) {
                throw new IllegalStateException("noScrimView must be a child of DrawerLayout");
            }
            if (((LayoutParams) v.getLayoutParams()).gravity != Gravity.NO_GRAVITY) {
                throw new IllegalStateException("noScrimView cannot be a drawer View");
            }
        }
        else {
            if (noScrimId != View.NO_ID) {
                Log.d(getClass().getSimpleName(), "noScrimView not found");
            }
        }
    }