There are several different approaches to this, of varying degrees of effectiveness and prudence. Take your pick.
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 method.
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;
}
val Toolbar.navigationButton: ImageButton?
get() {
children.forEach {
if (it is ImageButton) {
return it
}
}
return null
}
Reflective
This method has the advantage of absolute certainty. However, it is reflection, so, ya know, whatever your thoughts are on that.
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;
}
}
val Toolbar.navigationButton: ImageButton?
get() {
Toolbar::class.java
.getDeclaredField("mNavButtonView").apply {
isAccessible = true
return get(this@navigationButton) as ImageButton?
}
}
Find by Content Description
This is the first of three 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 following Find by Tag example both use this string resource, the value of which can be whatever you like, really:
<string name="toolbar_navigation_button_locator">ToolbarNavigationButtonLocator</string>
static ImageButton getNavigationButton(Toolbar toolbar) {
final CharSequence originalDescription =
toolbar.getNavigationContentDescription();
final CharSequence locator =
toolbar.getResources()
.getText(R.string.toolbar_navigation_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;
}
val Toolbar.navigationButton: ImageButton?
get() {
val originalDescription = navigationContentDescription
val locator =
resources.getText(R.string.toolbar_navigation_button_locator)
navigationContentDescription = locator
val views = ArrayList<View>()
findViewsWithText(
views,
locator,
View.FIND_VIEWS_WITH_CONTENT_DESCRIPTION
)
navigationContentDescription = originalDescription
views.forEach { v ->
if (v is ImageButton) {
return v
}
}
return null
}
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/Toolbar.Button.Navigation.Tagged</item>
</style>
<style name="Toolbar.Button.Navigation.Tagged" parent="Widget.AppCompat.Toolbar.Button.Navigation">
<item name="android:tag">@string/toolbar_navigation_button_locator</item>
</style>
static ImageButton getNavigationButton(Toolbar toolbar) {
final CharSequence tag =
toolbar.getResources()
.getText(R.string.toolbar_navigation_button_locator);
return toolbar.findViewWithTag(tag);
}
val Toolbar.navigationButton: ImageButton?
get() {
return run {
findViewWithTag(
resources
.getText(R.string.toolbar_navigation_button_locator)
)
}
}
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.
This one's more of an outside-the-box, proof-of-concept-type thing, I'd say.
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;
}
}
val Toolbar.navigationButton: ImageButton?
get() {
val originalIcon: Drawable? = navigationIcon
val temporaryDrawable = ColorDrawable(0)
navigationIcon = temporaryDrawable
val callback: Any? = temporaryDrawable.callback
navigationIcon = originalIcon
return if (callback is ImageButton) {
callback
} else {
null
}
}