如何在 XML 中定义一个特定片段实例的 style/theme?

How can I define one specific fragment instance's style/theme in XML?

我在我的应用程序中创建了一个 UI 组件,该组件用于多个地方(一些亮,一些暗)并且需要 lifecycle-aware, so I've converted it into a fragment。我在我的活动的 XML 布局文件中专门使用 <fragment> 标签实例化了这个片段。

我正在努力寻找一种方法,使片段能够以适合其父元素设置的背景的颜色呈现自己的控件和文本。目前,无论我在父视图或 <fragment> 标签本身设置什么主题或样式,片段控件都显示为浅色背景:

我尝试将 <fragment> 上的 android:theme 设置为 @style/ThemeOverlay.AppCompat.Dark@style/ThemeOverlay.AppCompat.Dark.ActionBar 以尝试使深色背景上的文本颜色变浅,但没有运气。

我不想将这种颜色切换编程到片段本身,因为我觉得 theming/styling 框架肯定能够处理这个。

理想情况下,我还可以在稍后阶段将图标合并到该片段中,其颜色与文本相匹配。不过,我现在的首要任务是获得正确的文本颜色。

告诉片段的一个特定实例的正确方法是什么,“你在深色背景上而不是浅色背景上,”或“你需要以浅色而不是浅色呈现文本和控件黑暗的,”并相应地渲染它?

更新

通过在 <fragment> 标记上实现自定义属性并在我的片段代码中从中初始化它来实现。

上次更新

Chris Banes' article on Theme vs. Style 状态:

One thing to note is that android:theme in Lollipop propogates to all children declared in the layout:

<LinearLayout
    android:theme="@android:style/ThemeOverlay.Material.Dark">

    <!-- Anything here will also have a dark theme -->

</LinearLayout>

Your children can set their own theme if needed.

这显然不是在我使用片段的情况下发生的——似乎 <fragment> 标签的 android:theme 属性在 XML 在 inflation 期间根本没有被考虑,但应该被考虑。

我的片段onCreateView方法很标准:

@Override
public View onCreateView(
        @NonNull LayoutInflater inflater,
        @Nullable ViewGroup container,
        @Nullable Bundle savedInstanceState
) {
    return inflater.inflate(R.layout.fragment_task_search, container, false);
}

我想要么是 container 没有得到主题,要么是膨胀器没有将该信息“附加”到膨胀的子视图。我对 Android 开发不够熟练,无法确定是哪种情况(如果有的话)。

我通过在 <fragment> 标签的自定义属性中明确指定父容器的主题,并在片段初始化期间手动读取它,达到了预期的效果。

XML 布局文件使用 <fragment>

  1. 确保 XML 的根元素具有以下 xmlns:app 属性:

    xmlns:app="http://schemas.android.com/apk/res-auto"
    
  2. app:customTheme 的自定义属性添加到 <fragment> 标签:

    <fragment
        android:name="net.alexpeters.myapp.TaskSearchFragment"
        app:customTheme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" />
    

属性XML文件values/attrs.xml

添加如下所示的 declare-styleable 元素:

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <!-- ↓↓↓ -->
        <declare-styleable name="TaskSearchFragment">
            <attr name="customTheme" format="reference" />
        </declare-styleable>
        <!-- ↑↑↑ -->
    </resources>

Fragment 子类

  1. 添加实例变量以保存自定义主题 ID:

    private @StyleRes int themeResId;
    
  2. 添加一个常量来处理没有传入自定义主题的情况:

    private static final int NO_CUSTOM_THEME = 0;
    
  3. 重写 onInflate 方法如下:

    @Override
    public void onInflate(
            @NonNull Context context,
            AttributeSet attrs,
            Bundle savedInstanceState
    ) {
        super.onInflate(context, attrs, savedInstanceState);
        TypedArray a = context.obtainStyledAttributes(
                attrs,
                R.styleable.TaskSearchFragment
        );
        themeResId = a.getResourceId(
                R.styleable.TaskSearchFragment_customTheme,
                NO_CUSTOM_THEME
        );
        a.recycle();
    }
    
  4. onCreateView 方法添加一些逻辑以将自定义主题引用注入 inflater:

    @Nullable
    @Override
    public View onCreateView(
            @NonNull LayoutInflater inflater,
            @Nullable ViewGroup container,
            @Nullable Bundle savedInstanceState
    ) {
        // ↓↓↓
        if (themeResId != NO_CUSTOM_THEME) {
            inflater = inflater.cloneInContext(
                new ContextThemeWrapper(getActivity(), themeResId)
            );
        }
        // ↑↑↑
        return inflater.inflate(R.layout.fragment_task_search, container, false);
    }
    

我更愿意以某种方式推断父容器的主题,但我不知道这是否可能。

否则,我宁愿使用标准属性(即 android:theme)而不是自定义属性,但我也不知道这是否可行。