16
votes

My requirement is shown in the picture below My navigation drawer should be opened from the right side. I have implemented this. My navigation drawer open from right to left. But the problem is toggle icon is always on the left side. How can I set toggle icon to the right?

I have checked the following SO questions, but none of them came to any help:

Change toggle button image Icon In Navigation Drawer right to left

Drawer Toggle in right Drawer

enter link description here

enter image description here

Here is what I have tried:

code for my layout activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/drawer"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:openDrawer="end">

    <android.support.design.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="true"
        tools:context="com.example.nav.MainActivity"
        android:foregroundGravity="right">

        <android.support.design.widget.AppBarLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="end"
            android:theme="@style/AppTheme.AppBarOverlay">

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                android:background="?attr/colorPrimary"
                android:layout_gravity="right"
                app:popupTheme="@style/AppTheme.PopupOverlay"
                android:foregroundGravity="right"
                android:textAlignment="viewEnd"
                android:touchscreenBlocksFocus="false" />

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

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

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

    <android.support.design.widget.NavigationView
        android:id="@+id/navigation_view"
        android:layout_height="match_parent"
        android:layout_width="wrap_content"
        android:layout_gravity="end"
        app:headerLayout="@layout/nav_header"
        app:menu="@menu/menu_navigation"
        android:textAlignment="viewEnd" />


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

Code for my activity

public class MainActivity extends AppCompatActivity {
    private DrawerLayout drawerLayout;
    private Toolbar toolbar;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        initNavigationDrawer();

    }

    @TargetApi(Build.VERSION_CODES.M)
    public void initNavigationDrawer() {

        NavigationView navigationView = (NavigationView)findViewById(R.id.navigation_view);
        navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
            @Override
            public boolean onNavigationItemSelected(MenuItem menuItem) {

                int id = menuItem.getItemId();

                switch (id){
                    case R.id.home:
                        Toast.makeText(getApplicationContext(),"Home",Toast.LENGTH_SHORT).show();
                        drawerLayout.closeDrawers();
                        break;
                    case R.id.settings:
                        Toast.makeText(getApplicationContext(),"Settings",Toast.LENGTH_SHORT).show();
                        break;
                    case R.id.trash:
                        Toast.makeText(getApplicationContext(),"Trash",Toast.LENGTH_SHORT).show();
                        drawerLayout.closeDrawers();
                        break;
                    case R.id.logout:
                        finish();

                }
                return true;
            }
        });
        drawerLayout = (DrawerLayout)findViewById(R.id.drawer);

        ActionBarDrawerToggle actionBarDrawerToggle = new ActionBarDrawerToggle(this,drawerLayout,toolbar,R.string.drawer_open,R.string.drawer_close){

            @Override
            public void onDrawerClosed(View v){
                super.onDrawerClosed(v);
            }

            @Override
            public void onDrawerOpened(View v) {
                super.onDrawerOpened(v);
            }

            @Override
            public boolean onOptionsItemSelected(MenuItem item) {
                if (item != null && item.getItemId() == android.R.id.home) {
                    if (drawerLayout.isDrawerOpen(Gravity.RIGHT)) {
                        drawerLayout.closeDrawer(Gravity.RIGHT);
                    }
                    else {
                        drawerLayout.openDrawer(Gravity.RIGHT);
                    }
                }
                return false;
            }
        };
        drawerLayout.addDrawerListener(actionBarDrawerToggle);
        actionBarDrawerToggle.syncState();

        toolbar.setNavigationOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                if (drawerLayout.isDrawerOpen(Gravity.RIGHT)) {
                    drawerLayout.closeDrawer(Gravity.RIGHT);
                } else {
                    drawerLayout.openDrawer(Gravity.RIGHT);
                }
            }
        });
    }

}
2

2 Answers

44
votes

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 also DualDrawerToggle – can be readily adapted to put a toggle on pretty much anything that can display a Drawable and register clicks; e.g., a FloatingActionButton, an options menu item, a TextView's compound drawable, etc. Simply replace the AppCompatImageButton with your target UI component, which can be passed in the constructor in place of the Toolbar, 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 the Toolbar. If you're using a menu on the Toolbar, 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 own Toolbar. Though ActionBarDrawerToggle can work with a decor-supplied ActionBar, this example can not, as is.

1
votes

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