由于使用 MaterialComponents 的 ThemeEnforcement 错误,偶尔会出现 InflateException。怎么解决?

Occasional InflateException due to ThemeEnforcement errors using MaterialComponents. How to solve?

在花了一整天试图解决这个问题之后,我几乎可以肯定这是 Material 组件库中的一个错误。以下是我配置的相关部分(删除了不相关的元素):

styles.xml:

<style name="MaterialAppTheme" parent="Theme.MaterialComponents.Light.NoActionBar">
    <item name="colorPrimary">@color/colorPrimary</item>
    <item name="colorPrimaryVariant">@color/colorPrimary900</item>
    <item name="colorSecondary">@color/colorSecondary</item>
    <item name="colorSecondaryVariant">@color/colorSecondary900</item>
    <item name="android:statusBarColor">@android:color/transparent</item>
</style>

AndroidManifest:

<application
    android:name=".AndroidApplication"
    android:theme="@style/MaterialAppTheme">

主要活动:

<activity
        android:name=".MainActivity"
        android:label="@string/app_name"
        android:launchMode="singleTop"
        android:theme="@style/MaterialAppTheme">

依赖版本:

materialComponents: '1.1.0-alpha02',
constraintLayout  : '2.0.0-alpha3',
appCompat         : '1.1.0-alpha01'

我在登录页面添加 Material 按钮没有问题。问题从我的 MainActivity 开始,在我的第一个列表片段中。尝试显示以 MaterialCardView 作为每个项目的根的回收站视图项目时,出现 ThemeEnforcement 错误。当我尝试添加 MaterialTextButton 时也是如此。这是我的 MaterialCardView xml 声明的样子:

<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
                                               xmlns:card_view="http://schemas.android.com/apk/res-auto"
                                               xmlns:tools="http://schemas.android.com/tools"
                                               android:id="@+id/tripCard"
                                               style="@style/Widget.MaterialComponents.CardView"
                                               android:layout_width="match_parent"
                                               android:layout_height="wrap_content"
                                               android:layout_margin="8dp"
                                               card_view:cardBackgroundColor="@color/colorSurface"
                                               card_view:cardCornerRadius="@dimen/widget_corner_radius"
                                               card_view:cardElevation="@dimen/card_elevation">

在 MaterialComponents 库中设置断点时,我注意到它有时无法验证主题。以下是库的一些代码,显示了失败的地方(在 ThemeEnforcement.java 中):

    if (enforceMaterialTheme) {
  TypedValue isMaterialTheme = new TypedValue();
  boolean resolvedValue =
      context.getTheme().resolveAttribute(R.attr.isMaterialTheme, isMaterialTheme, true);

  if (!resolvedValue
      || (isMaterialTheme.type == TypedValue.TYPE_INT_BOOLEAN && isMaterialTheme.data == 0)) {
    // If we were unable to resolve isMaterialTheme boolean attribute, or isMaterialTheme is
    // false, check for Material Theme color attributes
    checkMaterialTheme(context);
  }
}

public static void checkMaterialTheme(Context context) {
    checkTheme(context, MATERIAL_CHECK_ATTRS, MATERIAL_THEME_NAME);
}

private static final int[] MATERIAL_CHECK_ATTRS = {R.attr.colorPrimaryVariant};

据我所知,Theme Enforcer 会尝试解析 isMaterialTheme 属性(它 有时 可以解析 尽管在这种情况下不是)然后它继续检查 material 主题颜色属性的存在(检查 colorPrimaryVariant)并再次失败(我也在我的应用程序主题中定义了它。)

我会继续提交错误,如果有人有解决方案或任何建议,我将不胜感激!

要跟进评论:使用从使用正确主题的 Context 检索到的 LayoutInflater 很重要。应用程序上下文不能满足此要求。

好消息是您不必担心将上下文注入适配器。这不是很明显,但适配器提供了您获得正确上下文所需的一切。

看看onCreateViewHolder()骨架:

@NonNull 
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    // ...
}

所有 View 实例都有一个 getContext() 方法,并且 parent 参数保证为非空...因此您可以从父级检索上下文并使用获取 LayoutInflater:

@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    LayoutInflater inflater = LayoutInflater.from(parent.getContext());
    View itemView = inflater.inflate(R.layout.my_item_view, parent, false);
    return new MyViewHolder(itemView);
}

同样,在 ViewHolder 中,您可以使用相同的技术来获取上下文对象。每个 ViewHolder 实例都有一个保证为非空的 itemView 字段,因此您可以从中检索上下文。

@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
    Context context = holder.itemView.getContext();
    // ...
}