Android navigation drawer toggle icon to right
In your android manifest add this line:
android:supportsRtl="true"
to your application, like so:
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
Then in your onCreate method, add this line:
getWindow().getDecorView().setLayoutDirection(View.LAYOUT_DIRECTION_RTL);
WARNING::: This only works for SdkVersion 17+, so if your application targets a lower minimum SDK, you will have to create a custom menu and override the OnCreateOptions method(Unless there's another way which I'm not aware of, which is definitely possible).
https://developer.android.com/guide/topics/manifest/application-element.html#supportsrtl
There's really no (practical) way to make ActionBarDrawerToggle
do that, as it is always set on the start/left-side navigation button. However, that class is basically just a DrawerListener
that manages a specialized Drawable
, and wires an ImageButton
to the DrawerLayout
. We can put together something similar for an end/right-side drawer with an ImageButton
that we can place on that same side in a Toolbar
(which is required for this example).
public class EndDrawerToggle implements DrawerLayout.DrawerListener {
private final DrawerLayout drawerLayout;
private final AppCompatImageButton toggleButton;
private final int openDrawerContentDescRes;
private final int closeDrawerContentDescRes;
private DrawerArrowDrawable arrowDrawable;
public EndDrawerToggle(DrawerLayout drawerLayout, Toolbar toolbar,
int openDrawerContentDescRes, int closeDrawerContentDescRes) {
this.drawerLayout = drawerLayout;
this.openDrawerContentDescRes = openDrawerContentDescRes;
this.closeDrawerContentDescRes = closeDrawerContentDescRes;
toggleButton = new AppCompatImageButton(toolbar.getContext(), null,
R.attr.toolbarNavigationButtonStyle);
toolbar.addView(toggleButton, new Toolbar.LayoutParams(GravityCompat.END));
toggleButton.setOnClickListener(v -> toggle());
loadDrawerArrowDrawable();
}
public void syncState() {
if (drawerLayout.isDrawerOpen(GravityCompat.END)) {
setPosition(1f);
} else {
setPosition(0f);
}
}
public void onConfigurationChanged(Configuration newConfig) {
loadDrawerArrowDrawable();
syncState();
}
@Override
public void onDrawerSlide(@NonNull View drawerView, float slideOffset) {
setPosition(Math.min(1f, Math.max(0f, slideOffset)));
}
@Override
public void onDrawerOpened(@NonNull View drawerView) {
setPosition(1f);
}
@Override
public void onDrawerClosed(@NonNull View drawerView) {
setPosition(0f);
}
@Override
public void onDrawerStateChanged(int newState) {}
private void loadDrawerArrowDrawable() {
arrowDrawable = new DrawerArrowDrawable(toggleButton.getContext());
arrowDrawable.setDirection(DrawerArrowDrawable.ARROW_DIRECTION_END);
toggleButton.setImageDrawable(arrowDrawable);
}
private void toggle() {
final int drawerLockMode = drawerLayout.getDrawerLockMode(GravityCompat.END);
if (drawerLayout.isDrawerVisible(GravityCompat.END)
&& (drawerLockMode != DrawerLayout.LOCK_MODE_LOCKED_OPEN)) {
drawerLayout.closeDrawer(GravityCompat.END);
} else if (drawerLockMode != DrawerLayout.LOCK_MODE_LOCKED_CLOSED) {
drawerLayout.openDrawer(GravityCompat.END);
}
}
private void setPosition(float position) {
if (position == 1f) {
arrowDrawable.setVerticalMirror(true);
setContentDescription(closeDrawerContentDescRes);
} else if (position == 0f) {
arrowDrawable.setVerticalMirror(false);
setContentDescription(openDrawerContentDescRes);
}
arrowDrawable.setProgress(position);
}
private void setContentDescription(int resId) {
toggleButton.setContentDescription(toggleButton.getContext().getText(resId));
}
}
The EndDrawerToggle
class works exactly the same way as ActionBarDrawerToggle
does when used with a Toolbar
(except the constructor call doesn't need an Activity
argument): first instantiate the toggle, then add it as a DrawerListener
, and sync it in the Activity
's onPostCreate()
method. If you're already overriding the Activity
's onConfigurationChanged()
method, you'll want to call the toggle's corresponding method there, like you would for an ActionBarDrawerToggle
.
private EndDrawerToggle drawerToggle;
public void initNavigationDrawer() {
...
drawerLayout = (DrawerLayout) findViewById(R.id.drawer);
drawerToggle = new EndDrawerToggle(drawerLayout,
toolbar,
R.string.drawer_open,
R.string.drawer_close);
drawerLayout.addDrawerListener(drawerToggle);
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
drawerToggle.syncState();
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
drawerToggle.onConfigurationChanged(newConfig);
}
If you have two drawers and need to use ActionBarDrawerToggle
and EndDrawerToggle
simultaneously, it is possible, but we'll need to handle intercepting and dispatching drawer motion events to the correct toggle.
If you prefer fewer classes, you could subclass ActionBarDrawerToggle
and merge EndDrawerToggle
's functionality into it, dispatching each DrawerListener
method call either to the super
class, or to the local end toggle code.
However, composition is arguably much cleaner here, and it will let us use EndDrawerToggle
as is. This example is a DrawerListener
that relays syncState()
and onConfigurationChanged()
calls to each toggle, but dispatches the listener method calls only to the appropriate one, depending on which drawer is moving.
public class DualDrawerToggle implements DrawerLayout.DrawerListener {
private final DrawerLayout drawerLayout;
private final Toolbar toolbar;
private final ActionBarDrawerToggle actionBarDrawerToggle;
private final EndDrawerToggle endDrawerToggle;
public DualDrawerToggle(Activity activity, DrawerLayout drawerLayout, Toolbar toolbar,
int startDrawerOpenContDescRes, int startDrawerCloseContDescRes,
int endDrawerOpenContDescRes, int endDrawerCloseContDescRes) {
this.drawerLayout = drawerLayout;
this.toolbar = toolbar;
this.actionBarDrawerToggle =
new ActionBarDrawerToggle(activity, drawerLayout, toolbar,
startDrawerOpenContDescRes, startDrawerCloseContDescRes);
this.endDrawerToggle =
new EndDrawerToggle(drawerLayout, toolbar,
endDrawerOpenContDescRes, endDrawerCloseContDescRes);
}
public void syncState() {
actionBarDrawerToggle.syncState();
endDrawerToggle.syncState();
}
public void onConfigurationChanged(Configuration newConfig) {
actionBarDrawerToggle.onConfigurationChanged(newConfig);
// Fixes bug in ABDT, which only reloads the up nav indicator, for some reason.
final DrawerArrowDrawable dad = new DrawerArrowDrawable(toolbar.getContext());
actionBarDrawerToggle.setDrawerArrowDrawable(dad);
endDrawerToggle.onConfigurationChanged(newConfig);
}
@Override
public void onDrawerSlide(@NonNull View drawerView, float slideOffset) {
if (isStartDrawerView(drawerView, drawerLayout.getLayoutDirection())) {
actionBarDrawerToggle.onDrawerSlide(drawerView, slideOffset);
} else {
endDrawerToggle.onDrawerSlide(drawerView, slideOffset);
}
}
@Override
public void onDrawerOpened(@NonNull View drawerView) {
if (isStartDrawerView(drawerView, drawerLayout.getLayoutDirection())) {
actionBarDrawerToggle.onDrawerOpened(drawerView);
} else {
endDrawerToggle.onDrawerOpened(drawerView);
}
}
@Override
public void onDrawerClosed(@NonNull View drawerView) {
if (isStartDrawerView(drawerView, drawerLayout.getLayoutDirection())) {
actionBarDrawerToggle.onDrawerClosed(drawerView);
} else {
endDrawerToggle.onDrawerClosed(drawerView);
}
}
@Override
public void onDrawerStateChanged(int newState) {}
@SuppressLint("RtlHardcoded")
static boolean isStartDrawerView(View drawerView, int layoutDirection) {
final int gravity = ((DrawerLayout.LayoutParams) drawerView.getLayoutParams()).gravity;
final int horizontalGravity = gravity & GravityCompat.RELATIVE_HORIZONTAL_GRAVITY_MASK;
if ((horizontalGravity & GravityCompat.RELATIVE_LAYOUT_DIRECTION) > 0) {
return horizontalGravity == GravityCompat.START;
} else {
if (layoutDirection == View.LAYOUT_DIRECTION_RTL) {
return horizontalGravity == Gravity.RIGHT;
} else {
return horizontalGravity == Gravity.LEFT;
}
}
}
}
Again, DualDrawerToggle
works exactly the same way as ActionBarDrawerToggle
does when used with a Toolbar
, except for the extra content description resource IDs in the constructor.
Do note that the one DualDrawerToggle
creates and manages both other toggles internally. You don't need to set up any other toggle instances in your Activity
; just the DualDrawerToggle
.
Notes:
If you read through the code, you'll see that
EndDrawerToggle
– and therefore alsoDualDrawerToggle
– can be readily adapted to put a toggle on pretty much anything that can display aDrawable
and register clicks; e.g., aFloatingActionButton
, an options menu item, aTextView
's compound drawable, etc. Simply replace theAppCompatImageButton
with your target UI component, which can be passed in the constructor in place of theToolbar
, since the toggle won't be added to that anymore.Additionally, this could be modified to work with the start/left-aligned drawer, too – completely replacing
ActionBarDrawerToggle
– so that its toggle could be placed on those various components, as well.The
AppCompatImageButton
used here is a regular child of theToolbar
. If you're using a menu on theToolbar
, that menu will take precedence in the layout, and push the toggle inward. To keep it on the outside, you can modify the class as described above to set the toggle on an action menu item. I've an example of that in my answer here.That menu item example might also be useful if your design requires you use the decor-supplied
ActionBar
in lieu of your ownToolbar
. ThoughActionBarDrawerToggle
can work with a decor-suppliedActionBar
, this example can not, as is.