【问题标题】:How to find a spinner's displayed drop down views如何找到微调器显示的下拉视图
【发布时间】:2020-04-09 22:04:48
【问题描述】:

我公司的应用程序支持在运行时动态更改主题,而无需重新启动当前的 Activity。它通过遍历视图树并将样式应用于配置为支持它的每个视图来实现此目的。然而,我遇到的问题是,在查看视图树时,永远找不到为 Spinners 显示的下拉视图。这是从 Activity 中查找每个 View 的代码:

void applyTheme(final int resourceId) {
    setTheme(resourceId);

    // Apply the theme to the activity's view.
    styleView(findViewById(android.R.id.content));

    // Apply the theme to any dialog fragments.
    final List<Fragment> fragments = getSupportFragmentManager().getFragments();
    for (final Fragment fragment : fragments) {
        if (fragment instanceof DialogFragment) {
            final Dialog dialog = ((DialogFragment)fragment).getDialog();
            if (dialog != null) {
                final Window window = dialog.getWindow();
                if (window != null) {
                    styleView(window.getDecorView());
                }
            }
        }
    }
}

void styleView(final View view) {
    // Apply styling here.
    ...

    // Recursively find all children and apply the theme to them.
    if (view instanceof ViewGroup) {
        final ViewGroup viewGroup = (ViewGroup)view;
        for (int i = 0; i < viewGroup.getChildCount(); i++) {
            styleView(viewGroup.getChildAt(i));
        }
    }
}

无论父 Spinner 定义在 Activity、Fragment 还是 DialogFragment 中,都找不到下拉视图。如果下拉视图对用户可见,是否有任何方法可以实际检索对它们的引用?

【问题讨论】:

  • Spinner 的下拉菜单实际上是一个PopupWindow,其中包含一个ListViewPopupWindow 是一个与 Activity 完全独立的窗口,因此它的 View 层次结构不附加到 Activity 的,这就是为什么您在遍历中没有得到那些 Views .然而,即使你可以,我认为它不会很好地工作。首先,PopupWindow 和其中的ListView 每次显示时都会重新创建,因此直接对这些Views 进行的任何修改都不会持续存在。其次,我会假设您的主题切换是由某处的某个点击事件触发的。
  • 如果是这样,那么我认为下拉菜单在发生这种情况时不会可见,因为单击下拉菜单或其他任何地方会导致它立即关闭。考虑到这两种情况,我认为一个可行的选择是简单地在Spinner 上设置一个新的Adapter 实例,并在Activity 周围传递一个ContextThemeWrapper,并使用适当的样式。或者,您可以创建一个自定义的 Adapter 类,在其中设置每个项目 View 的样式,类似于您现在的样式。在这种情况下,您不一定需要每次都创建一个新实例。
  • 用户既可以选择特定主题,也可以选择根据一天中的时间自动更改主题。在后者的情况下,当切换发生时,微调器可能会打开,使下拉菜单的主题与微调器实际所在的视图不同。微调器为下拉菜单返回的视图实际上是一个自定义视图,其构造函数适用样式,所以只要我可以更新已经实例化的视图,那么我不需要让适配器应用样式。
  • 啊,好吧,那我明白为什么要担心了。好吧,真的没有像你一样干净,非hacky的方法来获取那些Views,因为PopupWindowListView都没有公开曝光。不过,自定义Adapter 仍然是一个可行的选项,因为您可以在主题开关上调用notifyDataSetChanged(),并使用getView()getDropDownView() 中的当前样式实例化新的下拉列表Views,具体取决于是否为下拉项设置了单独的布局。
  • 哦,很酷。 :-) 除了给出一个非常广泛的解决方案大纲之外,我并没有真正做任何事情。当您找到适合您的东西时,请随时自己发布答案。我敢肯定,一个具体的例子对将来的某个人会很有帮助,现在每个人都在做暗/亮,DayNight 主题的东西。不过谢谢。我真的很感激这个提议。很高兴我能给你一些建议。干杯!

标签: java android android-view android-spinner


【解决方案1】:

This answer 为我提供了一种查找所有视图的方法,包括 PopupWindows 的视图。不幸的是,它利用了反射并且不能保证在所有 API 版本上都可以工作,但它似乎可以在我们的应用程序支持的 API 上工作(我在 API 19 和 28 上进行了测试)。这是applyTheme()更改的结果:

private Object wmgInstance = null;
private Method getViewRootNames = null;
private Method getRootView = null;

private void applyTheme(final int resourceId)
{
    setTheme(resourceId);

    try
    {
        // Find all possible root views (the Activity, any PopupWindows, and Dialogs). This logic should work with APIs 17-28.
        if (wmgInstance == null)
        {
            final Class wmgClass = Class.forName("android.view.WindowManagerGlobal");
            wmgInstance = wmgClass.getMethod("getInstance").invoke(null, (Object[])null);

            getViewRootNames = wmgClass.getMethod("getViewRootNames");
            getRootView = wmgClass.getMethod("getRootView", String.class);
        }

        final String[] rootViewNames = (String[])getViewRootNames.invoke(wmgInstance, (Object[])null);

        for (final String viewName : rootViewNames)
        {
            final View rootView = (View)getRootView.invoke(wmgInstance, viewName);
            styleView(rootView);
        }
    }
    catch (Exception e)
    {
        // Log the exception.
    }
}

这个逻辑成功地允许我更新 Spinner 的下拉视图,当它们在主题更改期间可见时。但是,仍然存在一个问题,如果 Spinner 的下拉视图是在主题更改发生之前创建的,但下拉菜单在主题更改时关闭,那么重新打开 Spinner 会显示使用旧主题的视图。我处理这个问题的方式只是在我适配器的getDropDownView() 中的视图上调用styleView()

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-06-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-14
    • 1970-01-01
    相关资源
    最近更新 更多