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

时间:2017-11-14 12:31:10

标签: android android-layout android-fragments android-theme android-styles

我在我的应用中创建了一个UI组件,用于多个地方(有些亮,有些暗),需要lifecycle-aware,所以我把它转换为fragment。我在活动的XML布局文件中使用<fragment>标记专门实例化此片段。

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

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

我尝试将android:theme <fragment>设置为@style/ThemeOverlay.AppCompat.Dark@style/ThemeOverlay.AppCompat.Dark.ActionBar,以尝试在深色背景上显示浅色文字,但是没有运气。

我不希望将此颜色切换编程到片段本身,因为我觉得主题/样式框架肯定能够处理这个。

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

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

更新

posted an answer通过在<fragment>标记上实现自定义属性并在我的片段代码中对其进行初始化来实现此目的。

以前的更新

Chris Banes' article on Theme vs. Style州:

  

需要注意的一点是Lollipop中的android:theme传播给布局中声明的所有孩子:

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

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

</LinearLayout>
     

如果需要,您的孩子可以设置自己的主题。

在使用片段的情况下,这显然是 - 似乎XML中的android:theme标记的<fragment>属性未被考虑在在通货膨胀期间,但应该是。

我的片段的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要么没有开始主题,要么inflater没有将这些信息“附加”到膨胀的子视图。我在Android开发方面不够精通,无法确定哪种情况(如果有的话)。

1 个答案:

答案 0 :(得分:0)

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

使用<fragment>

的XML布局文件
  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" />
    
  3. 属性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);
      }
      
    5. 我更倾向于以某种方式推断父容器的主题,但我不知道这是否可能。

      如果不这样做,我更倾向于使用标准属性(即android:theme)而不是自定义属性,但我也不知道这是否可行。