如何在XML中定义特定片段实例的样式/主题?

3
我在应用程序中创建了一个 UI 组件,该组件在多个位置使用(有些是浅色的,有些是暗色的),需要lifecycle-aware,因此我将其转换为fragment。我使用 <fragment> 标签在我的活动 XML 布局文件中独占实例化此片段。
我正在努力寻找一种方法,使片段能够以适合其父元素设置的背景颜色呈现自己的控件和文本。目前,无论我在父视图或<fragment>标记本身上设置什么主题或样式,片段控件都会显示为在浅色背景上:

the fragment as rendered on a light background the fragment as rendered on a dark background

我曾尝试在<fragment>上设置android:theme,并分别使用@style/ThemeOverlay.AppCompat.Dark@style/ThemeOverlay.AppCompat.Dark.ActionBar,以期望在深色背景下使文字呈浅色,但没有成功。

我希望不必在片段本身中编写此颜色切换程序,因为我认为主题/样式框架肯定能够处理这个问题。

理想情况下,我还可以在稍后的阶段将一个图标合并到此片段中,并使其颜色与文本匹配。但我现在的重点是获取正确的文本颜色。

如何告诉一个特定的片段实例,“你处于深色背景而不是浅色背景”,或“你需要以浅色而不是深色渲染你的文本和控件”,并使其相应地呈现?

更新

我已经发布了一个答案,它通过在<fragment>标签上实现自定义属性,并在我的片段代码中进行初始化来实现。

之前的更新

Chris Banes关于主题与样式的文章指出:

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.

这在我使用片段时明显没有发生 - XML中标签的android:theme属性似乎在膨胀过程中根本没有被考虑,但应该被考虑。
我的片段的onCreateView方法非常标准:
@Override
public View onCreateView(
        @NonNull LayoutInflater inflater,
        @Nullable ViewGroup container,
        @Nullable Bundle savedInstanceState
) {
    return inflater.inflate(R.layout.fragment_task_search, container, false);
}

我猜测要么是容器本身没有主题,要么是充气器没有将该信息“附加”到充气的子视图上。我对Android开发不够熟练,无法确定两者中的哪一个(如果有的话)。


当您将 android:theme="@style/ThemeOverlay.AppCompat.Dark" 应用于 <fragment> 时,输出是什么? - azizbekian
在我的 <fragment> 中,该属性完全被忽略,文本保持黑色。而在附近的某个随机的 <EditText> 上,文本如预期般变为白色。 - Alex Peters
在屏幕截图中,第一行是一个片段,第二行是一个标准的EditText,它们都应用了android:theme吗? - azizbekian
上面展示的两个EditText(技术上是AutoCompleteTextView)都是该片段的实例。浅色背景上的一个没有显式应用主题,因为它已经按预期呈现。深色背景上的一个主题被显式应用,但被忽略了。 - Alex Peters
你说的 <fragment> 标签是什么意思?你是如何管理你的片段的? - Eselfar
我在我的活动的XML布局文件中使用<fragment>标签,在需要的地方创建此片段的实例,并让Android框架执行其余所需的管理。片段实例与其父活动一起创建、启动、停止和销毁,符合预期。唯一的问题是缺乏从包含视图传播主题的功能。 - Alex Peters
1个回答

0

我通过在<fragment>标签上的自定义属性中显式指定父容器的主题,并在片段初始化期间手动读取它来实现所需的效果。

使用<fragment>的XML布局文件

确保XML的根元素具有以下xmlns:app属性:
xmlns:app="http://schemas.android.com/apk/res-auto"
<fragment>标签中添加自定义属性app:customTheme
<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)而不是自定义属性,但我也不知道这是否可能。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接