我之前遇到过 InflateException,“二进制 XML 文件中的类膨胀错误”,这通常是由于属性写入错误或加载大图像导致内存不足错误。但是,我认为这不是这种情况下的错误,因为我没有更改任何加载的 attr 或图像。我正在使用 Google 的 Blockly 库,当我加载 Blockly 的默认类时,在 xml 中效果很好:
<com.google.blockly.android.ui.CategoryTabs
android:id="@+id/category_tabs"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
但是,当我尝试加载扩展 CategoryTabs 的修改类时,我在第 37 行收到 InflateException,其中 37 是 ModifiedCategoryTabs 的开始标记:
<com.example.travisho.blockly.ModifiedCategoryTabs
android:id="@+id/category_tabs"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.travisho.blockly/com.example.travisho.blockly.BlocklyActivity}: android.view.InflateException: Binary XML file line #37: Error inflating class com.example.travisho.blockly.ModifiedCategoryTabs
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2366)
然后我的 ModifiedCategoryTabs 类很简单:
public class ModifiedCategoryTabs extends CategoryTabs{
public ModifiedCategoryTabs(Context context) {
super(context);
}
}
Blockly 中的 CategoryTabs 是:
public class CategoryTabs extends RecyclerView {
public static final String TAG = "CategoryTabs";
public static final int HORIZONTAL = OrientationHelper.HORIZONTAL;
public static final int VERTICAL = OrientationHelper.VERTICAL;
private final LinearLayoutManager mLayoutManager;
private final CategoryAdapter mAdapter;
protected final List<BlocklyCategory> mCategories = new ArrayList<>();
protected @Rotation.Enum int mLabelRotation = Rotation.NONE;
protected boolean mTapSelectedDeselects = false;
private LabelAdapter mLabelAdapter;
protected @Nullable CategorySelectorUI.Callback mCallback;
protected @Nullable
BlocklyCategory mCurrentCategory;
public CategoryTabs(Context context) {
this(context, null, 0);
}
public CategoryTabs(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CategoryTabs(Context context, AttributeSet attrs, int style) {
super(context, attrs, style);
mLayoutManager = new LinearLayoutManager(context);
setLayoutManager(mLayoutManager);
mAdapter = new CategoryAdapter();
setAdapter(mAdapter);
setLabelAdapter(new DefaultTabsAdapter());
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.BlocklyCategory,
0, 0);
try {
//noinspection ResourceType
mLabelRotation = a.getInt(R.styleable.BlocklyCategory_labelRotation, mLabelRotation);
int orientation = a.getInt(R.styleable.BlocklyCategory_scrollOrientation, VERTICAL);
mLayoutManager.setOrientation(orientation);
} finally {
a.recycle();
}
}
/**
* Sets the {@link Adapter} responsible for the label views.
*/
public void setLabelAdapter(LabelAdapter labelAdapter) {
mLabelAdapter = labelAdapter;
mAdapter.notifyDataSetChanged();
}
/**
* Sets the {@link CategorySelectorUI.Callback} used by this instance.
*
* @param callback The {@link CategorySelectorUI.Callback} for event handling.
*/
public void setCallback(@Nullable CategorySelectorUI.Callback callback) {
mCallback = callback;
}
/**
* Sets the orientation in which the tabs will accumulate, which is also the scroll direction
* when there are more tabs than space allows.
*
* @param orientation Either {@link #HORIZONTAL} or {@link #VERTICAL}.
*/
public void setOrientation(int orientation) {
mLayoutManager.setOrientation(orientation);
}
/**
* Sets the {@link Rotation} direction constant for the tab labels.
*
* @param labelRotation The {@link Rotation} direction constant for the tab labels.
*/
public void setLabelRotation(@Rotation.Enum int labelRotation) {
mLabelRotation = labelRotation;
mAdapter.notifyDataSetChanged();
}
/**
* Sets whether the selected tab will deselect when clicked again.
*
* @param tapSelectedDeselects If {@code true}, selected tab will deselect when clicked again.
*/
public void setTapSelectedDeselects(boolean tapSelectedDeselects) {
mTapSelectedDeselects = tapSelectedDeselects;
}
/**
* Sets the list of {@link BlocklyCategory}s used to populate the tab labels.
*
* @param categories The list of {@link BlocklyCategory}s used to populate the tab labels.
*/
public void setCategories(List<BlocklyCategory> categories) {
mCategories.clear();
mCategories.addAll(categories);
mAdapter.notifyDataSetChanged();
}
/**
* Sets the currently selected tab. If the tab is not a member of the assigned categories, no
* tab will render selected.
*
* @param category
*/
public void setSelectedCategory(@Nullable BlocklyCategory category) {
if (mCurrentCategory == category) {
return;
}
if (mCurrentCategory != null) {
// Deselect the old tab.
TabLabelHolder vh = getTabLabelHolder(mCurrentCategory);
if (vh != null && mLabelAdapter != null) { // Tab might not be rendered or visible yet.
// Update style. Don't use notifyItemChanged(..), due to a resulting UI flash.
mLabelAdapter.onSelectionChanged(
vh.mLabel, vh.mCategory, vh.getAdapterPosition(), false);
}
}
mCurrentCategory = category;
if (mCurrentCategory != null && mLabelAdapter != null) {
// Select the new tab.
TabLabelHolder vh = getTabLabelHolder(mCurrentCategory);
if (vh != null) { // Tab might not be rendered or visible yet.
// Update style. Don't use notifyItemChanged(..), due to a resulting UI flash.
mLabelAdapter.onSelectionChanged(
vh.mLabel, vh.mCategory, vh.getAdapterPosition(), true);
}
}
}
/**
* @return The currently highlighted category or null.
*/
public BlocklyCategory getSelectedCategory() {
return mCurrentCategory;
}
public int getTabCount() {
return mCategories.size();
}
private void onCategoryClicked(BlocklyCategory category) {
if (mCallback != null) {
mCallback.onCategoryClicked(category);
}
}
private TabLabelHolder getTabLabelHolder(BlocklyCategory category) {
int childCount = getChildCount();
for (int i = 0; i < childCount; ++i) {
View child = getChildAt(i);
TabLabelHolder vh = (TabLabelHolder) child.getTag();
if (vh != null && vh.mCategory == category) {
return vh;
}
}
return null;
}
private class CategoryAdapter extends RecyclerView.Adapter<TabLabelHolder> {
@Override
public int getItemCount() {
return getTabCount();
}
@Override
public TabLabelHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (mLabelAdapter == null) {
throw new IllegalStateException("No LabelAdapter assigned.");
}
return new TabLabelHolder(mLabelAdapter.onCreateLabel());
}
@Override
public void onBindViewHolder(TabLabelHolder holder, int tabPosition) {
final BlocklyCategory category = mCategories.get(tabPosition);
boolean isSelected = (category == mCurrentCategory);
// These may throw a NPE, but that is an illegal state checked above.
mLabelAdapter.onBindLabel(holder.mLabel, category, tabPosition);
mLabelAdapter.onSelectionChanged(holder.mLabel, category, tabPosition, isSelected);
holder.mCategory = category;
holder.mRotator.setChildRotation(mLabelRotation);
holder.mRotator.setTag(holder); // For getTabLabelHolder() and deselection
holder.mLabel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View label) {
onCategoryClicked(category);
}
});
}
@Override
public void onViewRecycled(TabLabelHolder holder) {
holder.mRotator.setTag(null); // Remove reference to holder.
holder.mCategory = null;
holder.mLabel.setOnClickListener(null);
}
}
/** Manages TextView labels derived from {@link R.layout#default_toolbox_tab}. */
protected class DefaultTabsAdapter extends CategoryTabs.LabelAdapter {
@Override
public View onCreateLabel() {
return (TextView) LayoutInflater.from(getContext())
.inflate(R.layout.default_toolbox_tab, null);
}
/**
* Assigns the category name to the {@link TextView}. Tabs without labels will be assigned
* the text {@link R.string#blockly_toolbox_default_category_name} ("Blocks" in English).
*
* @param labelView The view used as the label.
* @param category The {@link BlocklyCategory}.
* @param position The ordering position of the tab.
*/
@Override
public void onBindLabel(View labelView, BlocklyCategory category, int position) {
String labelText = category.getCategoryName();
if (TextUtils.isEmpty(labelText)) {
labelText = getContext().getString(R.string.blockly_toolbox_default_category_name);
}
((TextView) labelView).setText(labelText);
}
}
public abstract static class LabelAdapter {
/**
* Create a label view for a tab. This view will later be assigned an
* {@link View.OnClickListener} to handle tab selection and deselection.
*/
public abstract View onCreateLabel();
/**
* Bind a {@link BlocklyCategory} to a label view, with any appropriate styling.
*
* @param labelView The tab's label view.
* @param category The category to bind to.
* @param position The position of the category in the list of tabs.
*/
public abstract void onBindLabel(View labelView, BlocklyCategory category, int position);
/**
* Called when a label is bound or when clicking results in a selection change. Responsible
* for updating the view to reflect the new state, including applying the category name.
* <p/>
* By default, it calls {@link View#setSelected(boolean)}. Many views and/or styles will
* handle this appropriately.
*
* @param labelView The tab's label view.
* @param category The category to bind to.
* @param position The position of the category in the list of tabs.
* @param isSelected the new selection state.
*/
public void onSelectionChanged(
View labelView, BlocklyCategory category, int position, boolean isSelected) {
labelView.setSelected(isSelected);
}
}
/**
* ViewHolder for the display name of a category in the toolbox.
*/
private static class TabLabelHolder extends RecyclerView.ViewHolder {
public final RotatedViewGroup mRotator;
public final View mLabel;
public BlocklyCategory mCategory;
TabLabelHolder(View label) {
super(new RotatedViewGroup(label.getContext()));
mRotator = (RotatedViewGroup) itemView;
mLabel = label;
mRotator.addView(mLabel);
}
}}
记住它在膨胀 CategoryTabs 时有效,但在膨胀 ModifiedCategoryTabs 时无效,这可能是因为 ModifiedCategoryTabs 没有直接继承 RecyclerView 吗?我认为在这两种情况下,膨胀都应该起作用,因为 ModifiedCategoryTabs 是 CategoryTabs 的子类,其中 ModifiedCategoryTabs 不会覆盖 CategoryTabs 中的任何内容。