为Fragment设置主题

132

我正在尝试为一个碎片设置主题。

在清单文件中设置主题无效:

android:theme="@android:style/Theme.Holo.Light"

从以前的博客看来,似乎我必须使用ContextThemeWrapper。有人可以给我一个编码示例吗?我找不到任何东西。

13个回答

219

在清单文件中设置主题通常用于Activity。

如果你想为Fragment设置主题,请在Fragment的onGetLayoutInflater()中添加以下代码:

override fun onGetLayoutInflater(savedInstanceState: Bundle?): LayoutInflater {
    val inflater = super.onGetLayoutInflater(savedInstanceState)
    val contextThemeWrapper: Context = ContextThemeWrapper(requireContext(), R.style.yourCustomTheme)
    return inflater.cloneInContext(contextThemeWrapper)
}

48
这对我没有用。该片段仍具有在清单文件中指定的相同主题。 - Giorgi
1
在清单文件中,您指定活动的主题,而不是片段。您正在使用片段还是FragmentActivity? - David
1
适用于我。在 Activity 中使用经典 Fragment。 - David
8
“在清单文件中设置主题仅适用于Activity”这种说法并不完全准确。如果您使用FragmentTransaction在运行时添加Fragment,该主题也会应用于片段。 - sebster
4
这似乎不适用于ActionBarSherlock库中的SherlockFragmentActivity。 - Etienne Lawlor
显示剩余6条评论

26

Fragment的主题来自它所在的Activity。每个片段都被分配了存在的Activity的主题。

主题应用于Fragment.onCreateView方法中,您的代码在其中创建视图,这些视图实际上是使用主题的对象。

在Fragment.onCreateView中,您会得到LayoutInflater参数,它会填充视图,并且它持有用于主题的Context,实际上就是Activity。因此,您填充的视图使用了Activity的主题。

要覆盖主题,您可以调用LayoutInflater.cloneInContext,文档中提到它可用于更改主题。您可以在此处使用ContextThemeWrapper,然后使用克隆的填充器来创建片段的视图。


正如Google文档所述:“...返回与给定上下文相关联的全新LayoutInflater对象...”- http://developer.android.com/reference/android/view/LayoutInflater.html#cloneInContext%28android.content.Context%29 - Chris
非常有帮助的补充,对David的代码解决方案表示感谢! - muetzenflo

24

应用单个样式,我只使用了

getContext().getTheme().applyStyle(styleId, true);

对我来说,在片段的onCreateView()中先于充气片段的根视图之前,它能够正常工作。


1
getContext() 的最小 API 版本为 23。 - vigilancer
@vigilancer 请查看以下答案,解决您的最小API问题:http://stackoverflow.com/questions/36736244/android-unable-to-set-getcontext-in-non-static-method-requires-api-level-23 - rm8x
1
很棒的解决方案。我将代码添加到onAttach(Context)中,这也适用于所有子片段的主题。 - crysxd
1
这可能会产生意想不到的后果,因为它修改了上下文(大多数情况下是活动)的主题。Activity 未来的膨胀(例如旋转后)将在所有地方使用新的主题。 - Irhala

12
我在片段的布局文件中使用android:theme = "@style/myTheme"来解决问题。例如,这将更改LinearLayout及其内容的样式,但不会影响LinearLayout之外的任何内容。因此,为了将整个片段装饰成任何样式,请将主题应用于其最外层父布局。
注意:如果您尚未找到解决方案,可以尝试一下。
           <LinearLayout 
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:theme = "@style/myTheme" >

            <TextView
                android:id="@+id/tc_buttom_text1"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Time elapsed"/>

            <TextView
                android:id="@+id/tc_buttom_text2"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="10dp"
                android:text="00:00:00 00"/>
        </LinearLayout>

1
这个快速解决方案更好,救了我的一天,谢谢! - Amos
简单的解决方案,它像魔法一样有效,谢谢。 - Mosericko
这是我多年开发中见过的最愚蠢的事情,但却非常有效!谢谢! - Steve Kamau

12
我也试图让我的片段对话框以不同的主题显示,并遵循这个解决方案。正如一些人在评论中提到的那样,我无法让它起作用,对话框仍然带有清单中指定的主题。问题是我在onCreateDialog方法中使用了AlertDialog.Builder来构建对话框,因此没有使用我链接中的答案中显示的onCreateView方法。而且当我实例化AlertDialog.Builder时,我使用了getActivity()来传递上下文,其实我应该使用已实例化的ConstextThemeWrapper

这是我的onCreateDialog代码:

@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
    // Create ContextThemeWrapper from the original Activity Context
    ContextThemeWrapper contextThemeWrapper = new ContextThemeWrapper(getActivity(), android.R.style.Theme_DeviceDefault_Light_Dialog);
    LayoutInflater inflater =   getActivity().getLayoutInflater().cloneInContext(contextThemeWrapper);
    // Now take note of the parameter passed into AlertDialog.Builder constructor
    AlertDialog.Builder builder = new AlertDialog.Builder(contextThemeWrapper);
    View view = inflater.inflate(R.layout.set_server_dialog, null);
    mEditText = (EditText) view.findViewById(R.id.txt_server);
    mEditText.requestFocus();  // Show soft keyboard automatically
    mEditText.setOnEditorActionListener(this);
    builder.setView(view);
    builder.setTitle(R.string.server_dialog);
    builder.setPositiveButton(android.R.string.ok, this);
    Dialog dialog = builder.create();
    dialog.setCanceledOnTouchOutside(false);
    return dialog;
}

我最初是这样实例化AlertDialog.Builder的:

AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());

我将其更改为:

AlertDialog.Builder builder = new AlertDialog.Builder(contextThemeWrapper);

这个更改后,片段对话框使用了正确的主题进行显示。因此,如果其他人也遇到类似的问题并正在使用AlertDialog.Builder,请检查传递给构建器的上下文。希望这可以帮助! :)


9
请确保在您的清单文件中设置了android:minSdkVersion="11"。这可能是David的示例无法正常工作的原因。
此外,为<application>标签设置android:theme="@android:style/Theme.Holo.Light"属性,而不是<activity>标签。
另一个可能存在的问题是使用ContextThemeWrapper()时获取上下文的方式。如果您使用类似getActivity().getApplicationContext()的东西,请将其替换为getActivity()
通常,Theme.Holo应该适用于与MainActivity链接的Fragments。
请注意,在想要为Fragment应用不同主题时使用ContextThemeWrapper会很有帮助。如果您提供从MainActivity添加Fragments的代码片段,可能会有所帮助。
一些有用的链接: Custom ListView in Fragment not adhering to parent theme

8
我尝试了David建议的解决方案,但并不适用于所有情况:
1.添加到堆栈中的第一个片段具有活动的主题而不是在onCreateView中定义的主题,但在我添加到堆栈的第二个片段上,正确的主题被应用到了该片段。

2.在正确显示主题的第二个片段中,我进行了以下操作:强制清除内存以关闭App,重新打开App,并在Activity与片段一起重新创建时,片段更改了错误的Activity主题,而不是在片段的onCreateView中设置的同一个主题。
为了修复此问题,我进行了小修改,将inflater.inflate中的container参数替换为null。
我不知道为什么在某些情况下inflater会使用容器视图的上下文。
注意 - 我正在使用android.support.v4.app.Fragment和android.support.v7.app.AppCompatActivity。
@Override 
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle   savedInstanceState) {

// create ContextThemeWrapper from the original Activity Context with the custom theme 
final Context contextThemeWrapper = new ContextThemeWrapper(getActivity(), R.style.yourCustomTheme);

// clone the inflater using the ContextThemeWrapper 
LayoutInflater localInflater = inflater.cloneInContext(contextThemeWrapper);

// inflate the layout using the cloned inflater, not default inflater 
return localInflater.inflate(R.layout.yourLayout, null, false);
} 

谢谢,你的解决方案帮了我大忙。我的 DialogFragment 主题与其他片段不同,这使得我使用提供的 inflater 时 EditText 变得奇怪。我找到了一些关于 ContextThemeWrapper 的帮助,但它们没有展示如何实现。你的解决方案对我很有效。非常感谢。 - Phuong Dao

7
我知道现在有些晚了,但这可能会帮助其他人,因为它对我很有用。您还可以尝试在片段的onCreateView函数中添加下面这行代码。
    inflater.context.setTheme(R.style.yourCustomTheme)

3
对我来说有效,但我想知道这种方法是否会对其他视图产生副作用。在我的情况下,上下文是MainActivity,我正在覆盖MainActivity的主题。这有意义吗? - andrea simeoni

2

创建一个Java类,然后在onCreate方法中使用你想要更改主题的布局。然后像平常一样在清单文件中提到它。


2
如果您只想为特定的片段应用样式,那么在调用片段的onCreateView()onAttach()之前添加以下代码即可。
getActivity().getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
getActivity().getWindow().setStatusBarColor(Color.TRANSPARENT);

如果您想设置透明状态栏,则将根布局的fitsSystemWindows属性设置为false

android:fitsSystemWindows="false"

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