获取对支持操作栏中抽屉切换的引用

Get reference to drawer toggle in support actionbar

我将 ShowcaseView 库用于应用程序教程。我需要获得对导航抽屉切换按钮(又名 "burger button")的引用:

我把Toolbar当成Actionbar使用了,不知道怎么弄这个按钮。通常切换抽屉我用这个:

@Override
public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getItemId() == android.R.id.home) {
            toggleDrawer(Gravity.START);
        }
}

但是当我使用 Device Monitor 对屏幕进行快照时,没有 ID 为 "home" 的视图。

有什么建议吗?

这就是所谓的导航按钮,它实际上是一个嵌套在 Toolbar 中的 ImageButton。不幸的是,没有 public 方法或字段来获取对它的引用,因此我们不得不绕行。

对此有几种不同的方法,具有不同程度的有效性和审慎性。任你选。


迭代

如果您可以控制它,并且能够首先设置切换按钮,那么紧接着导航按钮应该是 Toolbar 的第一个(也可能是唯一的)ImageButton 子项.如果您确信它是,那么这可能是最直接的方法。

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

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

反光

这种方法的优点是绝对有把握。然而,它是反思,所以,你知道,无论你对此有何看法。

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
        }

按内容描述查找

这是通过为导航按钮的某些 属性 设置特殊值来完成任务的几种方法中的第一种。这个暂时把它的内容描述设置为一个唯一的值,然后用ViewGroup#findViewsWithText()的方法查找,然后恢复原来的描述。

这个和Find by Tag例子都使用了这个字符串资源,它的值可以是任何你喜欢的,真的:

<string name="toolbar_navigation_button_locator">ToolbarNavigationButtonLocator</string>

Java

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;
}

Kotlin

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
        return views.firstOrNull { it is ImageButton } as ImageButton?
    }

按标签查找

此方法利用了能够通过 toolbarNavigationButtonStyle 主题属性设置导航按钮样式的优势。在指定的样式中,我们将android:tag属性设置为我们唯一的定位符字符串,并在运行时使用View#findViewWithTag()方法来抓取它。

<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/toolbar_navigation_button_locator</item>
</style>

Java

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

Kotlin

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

按 ID 查找

使用与标记方法相同的样式技术,我们可以改为设置 android:id 属性,并可能使它与熟悉的 findViewById() 功能一起使用。然而,如果他们真的在那个按钮上设置了一个 ID——即使只是供内部使用——这很可能会失败,或者破坏 Toolbar.

中的某些东西

我们首先定义一个ID来分配按钮:

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

然后更改之前的主题设置以设置 ID 而不是标签:

<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>

为了完整起见:

Java

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

Kotlin

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

可绘制回调

这个利用了 ImageButton 将自己设置为其来源 DrawableCallback 这一事实。我们创建一个throwawayDrawable临时设置为导航图标,检查Callback对象是否是我们的ImageButton,并恢复原来的图标。

我想说,这更像是一种开箱即用的概念验证类型的东西。

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 if (callback is ImageButton) {
            callback
        } else {
            null
        }
    }

备注:

  • 导航按钮根据需要动态实例化。这意味着某些东西必须设置了一些导航按钮 属性 才能找到它,无论是主题设置还是您自己的代码。对于按内容描述查找按标签查找可绘制回调,这不是问题选项,因为它们首先设置这样的 属性。但是,对于 IterativeReflective 方法,您可能需要注意时间安排。

  • 此答案的先前修订假定某些设置可能使用 Toolbar 的徽标作为切换按钮,而不是导航按钮。这是极不可能的,因此将其提及移至此脚注,如果只是为了将知识保留在可访问的地方。

    • 徽标是 ImageView,并且适用 Iterative 选项中的相同建议。
    • View 的字段名称是 mLogoView,可以更改 Reflective 方法来查找它。
    • Toolbarclass提供了setLogoDescription()方法,可配合按内容描述查找方法查找logo相反。
    • 还有 setLogo() 方法与 Drawable Callback 技术一起使用。
    • 按标记查找 选项不适用于徽标 View

在您的 Xml 文件中,只需在 toolbar/Layout 的最左侧添加一个视图,然后在您的展示目标

中使用该视图