LayoutInflater.SetFactory()学习(2)

参考1
参考2
完整github代码

一:LayoutInflaterFactory的用途:

自行创建自定义的View,而不是让系统去创建,可以避免系统的反射过程,提升性能;
在XML使用自定义View的时候,可以不声明全限定名称;
更换系统View为自己定义View,这正是Appcompat库替换默认的系统View的方式。

二:使用LayoutInflaterFactory的方式

(1)自定义LayoutInflaterFactory

通过继承Support Library的LayoutInflaterFactory,在Activity里面设置自定义的LayoutInflaterFactory

@Override public void onCreate(Bundle savedInstanceState) {
    LayoutInflaterCompat.setFactory(getLayoutInflater(), new MyLayoutInflaterFactory(this));
    super.onCreate(savedInstanceState);
    ...
}

注意:super.onCreate(savedInstanceState);之前设置自定义LayoutInflaterFactory;否则自定义的LayoutInflaterFactory不会生效。
LayoutInflater factories的最大限制是一个factory只能绑定一个LayoutInflater,因为support library已经绑定自己的factory;设置自定义的LayoutInflaterFactory可能会带来一些问题,比如无法从XML文件中加载Fragment,无法加载v21包里面的属性;

官方文档:
如果使用自定义的Factory,可以忽略调用 installViewFactory 方法,然后直接调用 createView() 方法 返回兼容的View;

LayoutInflater.SetFactory()学习(2)_第1张图片
document

也就是说自定义LayoutInflaterFactory负责调用

AppCompatDelegate#createView(android.view.View, String, android.content.Context, android.util.AttributeSet

下面这个方法可以克服一个LayoutInflater只能设置一个LayoutInflaterFactory的缺陷;它会创建一个新的LayoutInflater实例,然后绑定你可以给它绑定自定义的Factory;它是通过合并自定义factory和support library的 factory 来实现的;如果调用这个方法来创建LayoutInflater实例,需要在Activity中重写getLayoutInflater()getSystemService(String)来返回自己的LayoutInflater

LayoutInflater#cloneInContext(Context)
(2)使用Activity作为LayoutInflaterFactory

所有的LayoutInflater都设置了一个默认的LayoutInflaterFactory,Activity默认实现了Factory和Factory2;
这样在Activity中就允许 override下面的两个方法来处理自定义view的加载。

View onCreateView(View, String, Context, AttributeSet);
View onCreateView(String, Context, AttributeSet);

三:LayoutInflater Factory创建方法

(1)一个在XML文件中的移除自定义View全限定名称的 Factory

移除自定义View全限定名称的好处:(1)如果自定义View被重构(refactor)了,不用在XML文件中修改;(2)增强可读性
一个Factory的例子

public class MyLayoutInflaterFactory implements LayoutInflaterFactory {
    @Override
    public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
        if (TextUtils.equals(name, "DebugDrawerLayout")) {
            return new DebugDrawerLayout(context, attrs);
        } else if (TextUtils.equals(name, "ScrimInsetsFrameLayout") {
            return new ScrimInsetsFrameLayout(context, attrs);
        }
        // and so on...
    }
}

上面代码的问题: 如果按照上面的写法可能要列出所有的自定义View,然后一个个判断;
改进为使用反射的方式创建;

public class MyLayoutInflaterFactory implements LayoutInflaterFactory {
    private static final String CUSTOM_VIEWS_PACKAGE = "com.example.ui.customviews.";

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

    @Override
    public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
        Constructor constructor = null;
         //通过classLoader来加载类(全限定名称);
        Class clazz = context.getClassLoader()
                .loadClass(CUSTOM_VIEWS_PACKAGE + name).asSubclass(View.class);
          //获取构造函数
        constructor = clazz.getConstructor(constructorSignature);
        constructor.setAccessible(true);
        return constructor.newInstance(context, attrs);
    }
}

上面代码的缺陷是没有反射cache;

四:如何在自定义LayoutInflaterFactory中保留support library的特性;

public class CustomViewsLayoutInflaterFactory implements LayoutInflaterFactory {
    private AppCompatDelegate appCompatDelegate;

    public CustomViewsLayoutInflaterFactory(AppCompatDelegate appCompatDelegate) {
        this.appCompatDelegate = appCompatDelegate;
    }

    @Override
    public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
        View result = null;

        // todo: your custom inflation code here!

        if (result == null) {
            // Get themed views from AppCompat
            result = appCompatDelegate.createView(parent, name, context, attrs);
        }
        return result;
    }
}

你可能感兴趣的:(LayoutInflater.SetFactory()学习(2))