如何使用LayoutInflater来扩展具有指向MutableContextWrapper的视图的布局?

时间:2018-04-19 19:19:39

标签: java android android-layout android-inflate mutable-context-wrapper

我有一个复杂的“浮动视图”层次结构,我在不同的活动之间移动。由于这些视图是跨活动共享的,因此它们都应该有一个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。如果我删除此代码,一旦我断开视图与原始活动的连接并将其移动到下一个,就会发生内存泄漏。

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

我的问题是,是否有更好的方法可以让我从XML中扩充视图并强制其上下文指向MutableContextWrapper

1 个答案:

答案 0 :(得分:0)

我看到的问题的根本原因是默认LayoutInflater的工作方式(从LayoutInflater.from(context)获得的方式)。它使用最后一个活动来膨胀视图,这就是我得到不良结果的原因。我不能说我对解决方案太激动了,但它似乎正在发挥作用。您要做的是创建自定义LayoutInflater并设置其Factory。根据我读到的一些文档,我使用的解决方案可能存在兼容性问题,以防您使用支持/ compat视图,但我认为您可以解决这些问题,以防您遇到它。

以下是自定义Factory&amp;的代码。 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;
}