I have my main view with 3 Button
s in a vertical LinearLayout
on top right of the screen. Each button shows some Fragment
that is hosted inside a DrawerLayout
. Like this:
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:
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:
Is there any way to avoid DrawerLayout's shadow to affect a particular view? Or perhaps to cancel it?
DrawerLayout
applies that shadow - the scrim - to its child View
s 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 ImageButton
s' 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");
}
}
}