如何使用 LayoutInflater 通过指向 MutableContextWrapper 的视图来扩充布局?

How to use a LayoutInflater to inflate a layout with views pointing to a MutableContextWrapper?

我有一个复杂的 "floating view" 层次结构,我在不同的活动之间移动。由于这些视图在活动之间共享,因此它们都应该有一个 MutableContextWrapper 以允许它们更改基本上下文。理论上我可以同时拥有多个浮动视图。

这是一个浮动视图的示例 XML:

<com.test.views.SampleFloatingView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/sample_floating_view"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

    <com.test.views.MinimizedImage
        android:layout_width="90dp"
        android:layout_height="90dp"
        android:scaleType="center"/>

</com.test.views.SampleFloatingView>

我正在使用 MutableContextWrapper 给这个 XML 充气并将其添加到将容纳它的 FrameLayout(这样我就可以将它拖过屏幕),但我没有得到结果我需要:

@NonNull
private FrameLayout createSampleFloatingView() {
    MutableContextWrapper mutableContextWrapper = new MutableContextWrapper(getActivity());
    final FrameLayout view = new FrameLayout(mutableContextWrapper);
    LayoutInflater layoutInflater = LayoutInflater.from(mutableContextWrapper);
    View floatingView = layoutInflater.inflate(R.layout.sample_floating_view, null, true).findViewById(R.id.sample_floating_view);
    setViewMutableContext(floatingView);
    view.addView(floatingView);
    return view;
}

private void setViewMutableContext(View view) {
    Context context = view.getContext();
    if (context instanceof MutableContextWrapper) {
        ((MutableContextWrapper) context).setBaseContext(getActivity());
    } else {
        throw new RuntimeException("View " + view + " doesn't have a MutableContextWrapper");
    }
    if (view instanceof ViewGroup) {
        int childCount = ((ViewGroup) view).getChildCount();
        for (int i = 0; i < childCount; i++) {
            setViewMutableContext(((ViewGroup) view).getChildAt(i));
        }
    }
}

问题是,虽然 FrameLayout 有一个 MutableContextWrapper 膨胀对象的上下文是创建视图的 Activity。这意味着上面的代码会崩溃,因为 SampleFloatingView 没有 MutableContextWrapper。如果我删除此代码,一旦我断开视图与原始 activity 的连接并将其移动到下一个

,稍后就会发生内存泄漏。

这个问题的一个明显的解决方案是手动创建整个层次结构并在构造函数中传递 MutableContextWrapper(就像我对 FrameLayout 所做的那样),但我宁愿避免它明显原因。

我的问题是是否有更好的方法可以让我 膨胀来自 XML 的视图并强制它们的上下文指向 MutableContextWrapper

我看到的问题的根本原因是默认 LayoutInflater 的工作方式(您从 LayoutInflater.from(context) 获得的那个)。它使用最后一个 activity 来膨胀视图,这就是我得到不想要的结果的原因。我不能说我对这个解决方案太兴奋了,但它似乎奏效了。你要做的是创建一个自定义 LayoutInflater 并设置它的 Factory。根据我阅读的一些文档,如果您使用 support/compat 视图,我使用的解决方案可能存在兼容性问题,但我认为您也可以在遇到这些问题时解决这些问题。

这是自定义代码 Factory & LayoutInflater 类:

@SuppressWarnings("rawtypes")
public static class MyLayoutInflaterFactory implements LayoutInflater.Factory2 {

    private static final Class<?>[] constructorSignature = new Class[] {Context.class, AttributeSet.class };

    final MutableContextWrapper mutableContextWrapper;

    public MyLayoutInflaterFactory(MutableContextWrapper mutableContextWrapper) {
        this.mutableContextWrapper = mutableContextWrapper;
    }

    @Override
    public View onCreateView(String name, Context context, AttributeSet attrs) {
        Constructor<? extends View> constructor = null;
        Class<? extends View> clazz = null;
        try {
            clazz = mutableContextWrapper.getClassLoader().loadClass(name).asSubclass(View.class);
            constructor = clazz.getConstructor(constructorSignature);
            constructor.setAccessible(true);
            return constructor.newInstance(mutableContextWrapper, attrs);
        } catch (Throwable t) {
            return null;
        }
    }

    @Override
    public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
        // Perhaps this can be done better
        return onCreateView(name, context, attrs);
    }
}

public static class MyLayoutInflater extends LayoutInflater {

    protected MyLayoutInflater(Context context) {
        super(context);
    }

    @Override
    public LayoutInflater cloneInContext(Context newContext) {
        return new MyLayoutInflater(newContext);
    }
}

以及我使用它来膨胀我的浮动视图的方式:

@NonNull
private FrameLayout createSampleFloatingView() {
    MutableContextWrapper mutableContextWrapper = new MutableContextWrapper(getActivity());
    FrameLayout view = new FrameLayout(mutableContextWrapper);
    LayoutInflater layoutInflater = new MyLayoutInflater(mutableContextWrapper);
    LayoutInflaterCompat.setFactory2(layoutInflater, new MyLayoutInflaterFactory(mutableContextWrapper));
    layoutInflater.inflate(R.layout.sample_floating_view, view, true);
    return view;
}