Get reference to drawer toggle in support actionbar

That is what’s called the navigation button, and it’s actually an ImageButton nested inside the Toolbar. Unfortunately, there is no public method or field by which to get reference to it, so we have to go roundabout.

There are several different approaches to this, of varying degrees of effectiveness and prudence. Take your pick.

Navigation OnClickListener

If this hadn’t occurred to you yet, I would point out the that View parameter in the navigation OnClickListener‘s method will be the navigation button itself. If you’re setting that listener yourself, and you don’t actually need access to the button until the user clicks it, there’s nothing special you need to do, really.

  • Java

    toolbar.setNavigationOnClickListener(v -> {
        ImageButton navigationButton = (ImageButton) v;
        …
    });
    
  • Kotlin

    toolbar.setNavigationOnClickListener {
        val navigationButton = it as ImageButton
        …
    }
    

Iterative

If you have control over it and are able to set the toggle first thing, then directly afterward the navigation button should be the first (and possibly only) ImageButton child of the Toolbar. If you’re confident that it is, then this is probably the most straightforward of the remaining methods.

  • Java

    static ImageButton getNavigationButton(Toolbar toolbar) {
        for (int i = 0; i < toolbar.getChildCount(); ++i) {
            final View child = toolbar.getChildAt(i);
            if (child instanceof ImageButton) {
                return (ImageButton) child;
            }
        }
        return null;
    }
    
  • Kotlin

    import androidx.core.view.children
    
    val Toolbar.navigationButton: ImageButton?
        get() = children.firstOrNull { it is ImageButton } as ImageButton?
    

Reflective

This method has the advantage of absolute certainty. However, it is reflection, so ya know, whatever your thoughts are on that.

  • Java

    static ImageButton getNavigationButton(Toolbar toolbar) {
        try {
            final Field mNavButtonView =
                    Toolbar.class.getDeclaredField("mNavButtonView");
            mNavButtonView.setAccessible(true);
            return (ImageButton) mNavButtonView.get(toolbar);
        } catch (Exception e) {
            return null;
        }
    }
    
  • Kotlin

    val Toolbar.navigationButton: ImageButton?
        get() = try {
            Toolbar::class.java
                .getDeclaredField("mNavButtonView").apply {
                    isAccessible = true
                }.get(this) as ImageButton?
        } catch (e: Exception) {
            null
        }
    

Find by Content Description

This is the first of several methods that accomplish the task by setting some property of the navigation button with a special value. This one temporarily sets its content description to a unique value, and utilizes the ViewGroup#findViewsWithText() method to look for it before restoring the original description.

This and the Find by Tag example both use this string resource, which can be whatever unique value you like, really:

<string name="nav_button_locator">ToolbarNavigationButtonLocator</string>
  • Java

    static ImageButton getNavigationButton(Toolbar toolbar) {
        final CharSequence originalDescription =
                toolbar.getNavigationContentDescription();
        final CharSequence locator =
                toolbar.getResources()
                        .getText(R.string.nav_button_locator);
        toolbar.setNavigationContentDescription(locator);
        final ArrayList<View> views = new ArrayList<>();
        toolbar.findViewsWithText(
                views,
                locator,
                View.FIND_VIEWS_WITH_CONTENT_DESCRIPTION);
        toolbar.setNavigationContentDescription(originalDescription);
        for (View view : views) {
            if (view instanceof ImageButton) {
                return (ImageButton) view;
            }
        }
        return null;
    }
    
  • Kotlin

    val Toolbar.navigationButton: ImageButton?
        get() {
            val originalDescription = navigationContentDescription
            val locator =
                resources.getText(R.string.nav_button_locator)
            navigationContentDescription = locator
            val views = ArrayList<View>()
            findViewsWithText(
                views,
                locator,
                View.FIND_VIEWS_WITH_CONTENT_DESCRIPTION
            )
            navigationContentDescription = originalDescription
            return views.firstOrNull { it is ImageButton } as ImageButton?
        }
    

Find by Tag

This method takes advantage of being able to style the navigation button through the toolbarNavigationButtonStyle theme attribute. In the specified style, we set the android:tag attribute to our unique locator string, and use the View#findViewWithTag() method to grab it at runtime.

<style name="Theme.YourApp" parent="...">
    ...
    <item name="toolbarNavigationButtonStyle">@style/Widget.App.Button.Navigation</item>
</style>

<style name="Widget.App.Button.Navigation" parent="Widget.AppCompat.Toolbar.Button.Navigation">
    <item name="android:tag">@string/nav_button_locator</item>
</style>
  • Java

    static ImageButton getNavigationButton(Toolbar toolbar) {
        final CharSequence tag =
            toolbar.getResources().getText(R.string.nav_button_locator);
        return toolbar.findViewWithTag(tag);
    }
    
  • Kotlin

    val Toolbar.navigationButton: ImageButton?
        get() = findViewWithTag(resources.getText(R.string.nav_button_locator))
    

Find by ID

Using the same styling technique as the tag method, we can instead set the android:id attribute, and possibly get this working with the familiar findViewById() functionality. However, if they ever do set an ID on that button – even if just for internal use – this will very likely fail or break something in the Toolbar.

We first define an ID to assign the button:

<item name="navigation_button" type="id" />

Then alter the previous theme setup to set the ID rather than the tag:

<style name="Theme.YourApp" parent="...">
    ...
    <item name="toolbarNavigationButtonStyle">@style/Widget.App.Button.Navigation</item>
</style>

<style name="Widget.App.Button.Navigation" parent="Widget.AppCompat.Toolbar.Button.Navigation">
    <item name="android:id">@id/navigation_button</item>
</style>

Just for completeness’ sake:

  • Java

    static ImageButton getNavigationButton(Toolbar toolbar) {
        return toolbar.findViewById(R.id.navigation_button);
    }
    
  • Kotlin

    val Toolbar.navigationButton: ImageButton?
        get() = findViewById(R.id.navigation_button)
    

Drawable Callback

This one takes advantage of the fact that an ImageButton will set itself as its source Drawable‘s Callback. We create a throwaway Drawable to temporarily set as the navigation icon, check if the Callback object is our ImageButton, and restore the original icon. We set our own Drawable unconditionally here, rather than fiddling with checking for an existing one first.

  • Java

    static ImageButton getNavigationButton(Toolbar toolbar) {
        final Drawable originalIcon = toolbar.getNavigationIcon();
        final ColorDrawable temporaryDrawable = new ColorDrawable(0);
        toolbar.setNavigationIcon(temporaryDrawable);
        Object callback = temporaryDrawable.getCallback();
        toolbar.setNavigationIcon(originalIcon);
        if (callback instanceof ImageButton) {
            return (ImageButton) callback;
        }
        else {
            return null;
        }
    }
    
  • Kotlin

    val Toolbar.navigationButton: ImageButton?
        get() {
            val originalIcon: Drawable? = navigationIcon
            val temporaryDrawable = ColorDrawable(0)
            navigationIcon = temporaryDrawable
            val callback: Any? = temporaryDrawable.callback
            navigationIcon = originalIcon
            return callback as? ImageButton
        }
    

Notes:

  • The navigation button is instantiated dynamically on demand. This means that something must have set some navigation button property before you can find it, whether that something is a theme setting or some code of your own. This isn’t a problem for the Find by Content Description and Drawable Callback options, as they begin by setting such a property. For the other methods, however, you might need to take care with your timing.

  • Previous revisions of this answer assumed that some setups might be using the Toolbar‘s logo for the toggle, in lieu of the navigation button. This is highly unlikely, so its mention is moved to this footnote, if only to retain the knowledge somewhere accessible.

    • The Navigation OnClickListener option is not workable with the logo View.
    • The logo is an ImageView, and the same advice in the Iterative option applies.
    • The field name for the View is mLogoView, and the Reflective method can be altered to look for that.
    • The Toolbar class offers the setLogoDescription() method, which can be used with the Find by Content Description method to find the logo instead.
    • There is also the setLogo() method to utilize with the Drawable Callback technique.
    • The Find by Tag option is not workable with the logo View.

Leave a Comment