Open second drawerlayout as a sub drawerlayout over the first
It's unclear how exactly you wish to implement your drawer UI, so the following solution is rather generic, in that it should work with NavigationView
s, RecyclerView
s, or pretty much whatever type of View
s you'd like.
This solution uses a custom ViewSwitcher
subclass that acts as a DrawerLayout
's left drawer, and holds two child View
s, one being the main drawer View
, and the other being the second drawer that opens over it.
The DoubleDrawerView
class is a relatively simple ViewSwitcher
that loads its own Animation
s, and juggles them appropriately to give the effect of a second drawer opening and closing over the first. It tracks its own state so that it can be restored correctly after a device rotation, etc.
public class DoubleDrawerView extends ViewSwitcher {
private static final int NONE = -1;
private static final int MAIN_VIEW_INDEX = 0;
private static final int DRAWER_VIEW_INDEX = 1;
private Animation slideInAnimation, slideOutAnimation, noAnimation;
private boolean animating = false;
private Animation.AnimationListener listener = new Animation.AnimationListener() {
@Override
public void onAnimationEnd(Animation anim) {
animating = false;
}
@Override
public void onAnimationStart(Animation anim) {}
@Override
public void onAnimationRepeat(Animation anim) {}
};
public DoubleDrawerView(Context context) {
this(context, null);
}
public DoubleDrawerView(Context context, AttributeSet attrs) {
super(context, attrs);
slideInAnimation = AnimationUtils.loadAnimation(context, R.anim.slide_in_left);
slideOutAnimation = AnimationUtils.loadAnimation(context, R.anim.slide_out_left);
noAnimation = AnimationUtils.loadAnimation(context, R.anim.none);
noAnimation.setAnimationListener(listener);
}
public void openInnerDrawer() {
if (getDisplayedChild() != DRAWER_VIEW_INDEX) {
setChildAndAnimate(DRAWER_VIEW_INDEX, true);
}
}
public void closeInnerDrawer() {
if (getDisplayedChild() != MAIN_VIEW_INDEX) {
setChildAndAnimate(MAIN_VIEW_INDEX, true);
}
}
public boolean isInnerDrawerOpen() {
return getDisplayedChild() == DRAWER_VIEW_INDEX;
}
private void setChildAndAnimate(int whichChild, boolean doAnimate) {
if (doAnimate) {
setAnimationForChild(whichChild);
}
else {
setAnimationForChild(NONE);
}
animating = doAnimate;
setDisplayedChild(whichChild);
}
private void setAnimationForChild(int whichChild) {
if (whichChild == DRAWER_VIEW_INDEX) {
setInAnimation(slideInAnimation);
setOutAnimation(noAnimation);
}
else if (whichChild == MAIN_VIEW_INDEX) {
setInAnimation(noAnimation);
setOutAnimation(slideOutAnimation);
}
else {
setInAnimation(null);
setOutAnimation(null);
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (animating) {
return true;
}
else {
return super.onInterceptTouchEvent(ev);
}
}
@Override
protected Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
ss.whichChild = getDisplayedChild();
return ss;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
setChildAndAnimate(ss.whichChild, false);
}
private static class SavedState extends BaseSavedState {
int whichChild;
SavedState(Parcelable superState) {
super(superState);
}
private SavedState(Parcel in) {
super(in);
whichChild = in.readInt();
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeInt(whichChild);
}
public static final Parcelable.Creator<SavedState>
CREATOR = new Parcelable.Creator<SavedState>() {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
}
DoubleDrawerView
uses the following XML files for its Animation
s. These should be in your project's res/anim/
folder.
slide_in_left.xml
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromXDelta="-100%p" android:toXDelta="0"
android:duration="@android:integer/config_mediumAnimTime"/>
slide_out_left.xml
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromXDelta="0" android:toXDelta="-100%p"
android:duration="@android:integer/config_mediumAnimTime"/>
none.xml
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:fromAlpha="1.0" android:toAlpha="1.0"
android:duration="@android:integer/config_mediumAnimTime" />
This example's layout is a standard DrawerLayout
with a DoubleDrawerView
for its drawer, and two simple NavigationView
s therein. Do note that the main drawer View
must be listed first inside the DoubleDrawerView
, with the second, inner drawer View
after.
activity_main.xml
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.example.doubledrawer.DoubleDrawerView
android:id="@+id/double_drawer_view"
android:layout_width="240dp"
android:layout_height="match_parent"
android:layout_gravity="left">
<android.support.design.widget.NavigationView
android:id="@+id/main_navigation_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:menu="@menu/navigation_main" />
<android.support.design.widget.NavigationView
android:id="@+id/settings_navigation_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:menu="@menu/navigation_settings" />
</com.example.doubledrawer.DoubleDrawerView>
</android.support.v4.widget.DrawerLayout>
For the sake of a complete cut and paste example, some simple res/menu/
files for the NavigationView
s above.
navigation_main.xml
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<group
android:id="@+id/group_screens"
android:checkableBehavior="single">
<item
android:id="@+id/menu_screen_1"
android:title="Screen 1" />
<item
android:id="@+id/menu_screen_2"
android:title="Screen 2"/>
</group>
<item
android:id="@+id/menu_open_settings"
android:title="Open Settings" />
</menu>
navigation_settings.xml
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/menu_close_settings"
android:title="Back to Main" />
<group
android:id="@+id/group_settings">
<item
android:id="@+id/menu_setting_1"
android:title="Setting 1" />
<item
android:id="@+id/menu_setting_2"
android:title="Setting 2" />
</group>
</menu>
In the example Activity
, we just get references to the DoubleDrawerView
and NavigationView
s, and implement an OnNavigationItemSelectedListener
to open and close the inner drawer accordingly.
public class MainActivity extends AppCompatActivity
implements NavigationView.OnNavigationItemSelectedListener {
private DrawerLayout drawerLayout;
private DoubleDrawerView doubleDrawerView;
private NavigationView mainNavigationView, settingsNavigationView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
doubleDrawerView = (DoubleDrawerView) findViewById(R.id.double_drawer_view);
mainNavigationView = (NavigationView) findViewById(R.id.main_navigation_view);
settingsNavigationView = (NavigationView) findViewById(R.id.settings_navigation_view);
mainNavigationView.setNavigationItemSelectedListener(this);
settingsNavigationView.setNavigationItemSelectedListener(this);
drawerLayout.openDrawer(Gravity.LEFT);
}
@Override
public boolean onNavigationItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_open_settings:
doubleDrawerView.openInnerDrawer();
break;
case R.id.menu_close_settings:
doubleDrawerView.closeInnerDrawer();
break;
// Additional cases as needed
// This example simply Toasts the title for the extra sample items
default:
Toast.makeText(this, item.getTitle(), Toast.LENGTH_SHORT).show();
}
return true;
}
}
This solution uses two DrawerLayout
s, one nested in the other, in lieu of a custom View
. This might be slightly easier to implement, but it requires a bit more specialized code in the Activity
itself, and therefore will be more tightly coupled to whichever classes it's used in.
The Activity
's layout, activity_main.xml
.
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<android.support.v4.widget.DrawerLayout
android:id="@+id/inner_drawer_layout"
android:layout_width="240dp"
android:layout_height="match_parent"
android:layout_gravity="left">
<android.support.design.widget.NavigationView
android:id="@+id/main_navigation_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:menu="@menu/navigation_main" />
<android.support.design.widget.NavigationView
android:id="@+id/settings_navigation_view"
android:layout_width="240dp"
android:layout_height="match_parent"
android:layout_gravity="left"
app:menu="@menu/navigation_settings" />
</android.support.v4.widget.DrawerLayout>
</android.support.v4.widget.DrawerLayout>
The example NavigationView
s above use the same menu files as shown in my other answer here.
In the Activity
, we get references to both DrawerLayout
s, and set the scrim color and lock mode on the inner one at startup. We also need to handle the back button press ourselves, as the addition of a second DrawerLayout
messes with the first's handling of it. Upon opening and closing the inner drawer, we need to set the lock mode appropriately, to prevent dragging the inner drawer.
public class MainActivity extends AppCompatActivity
implements NavigationView.OnNavigationItemSelectedListener {
private DrawerLayout drawerLayout, innerDrawerLayout;
private NavigationView mainNavigationView, settingsNavigationView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
innerDrawerLayout = (DrawerLayout) findViewById(R.id.inner_drawer_layout);
mainNavigationView = (NavigationView) findViewById(R.id.main_navigation_view);
settingsNavigationView = (NavigationView) findViewById(R.id.settings_navigation_view);
mainNavigationView.setNavigationItemSelectedListener(this);
settingsNavigationView.setNavigationItemSelectedListener(this);
innerDrawerLayout.setScrimColor(Color.TRANSPARENT);
innerDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
drawerLayout.openDrawer(Gravity.LEFT);
}
@Override
public void onBackPressed() {
if (drawerLayout.isDrawerOpen(Gravity.LEFT)) {
drawerLayout.closeDrawer(Gravity.LEFT);
}
else {
super.onBackPressed();
}
}
private void openInnerDrawer() {
innerDrawerLayout.openDrawer(Gravity.LEFT);
innerDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_OPEN);
}
private void closeInnerDrawer() {
innerDrawerLayout.closeDrawer(Gravity.LEFT);
innerDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
}
@Override
public boolean onNavigationItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_open_settings:
openInnerDrawer();
break;
case R.id.menu_close_settings:
closeInnerDrawer();
break;
// Additional cases as needed
// This example simply Toasts the title for the extra sample items
default:
Toast.makeText(this, item.getTitle(), Toast.LENGTH_SHORT).show();
}
return true;
}
}