Android中LayoutInflater.Factory的使用

  • 内容
  • 评论
  • 相关

上一篇文章我们分析了系统如何在新版本中给老控件添加新功能,总结一句话就是悄悄换成了新控件。这篇文章我们来看看,我们如何来把这种功能为我所用。

既然系统能够偷天换日,那么我们能不能呢?看下面的代码:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        LayoutInflater.Factory2 factory2 = new LayoutInflater.Factory2() {
            @Override
            public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
                if ("TextView".equals(name)) {
                    return new Button(MainActivity.this, attrs);
                }
                return null;
            }

            @Override
            public View onCreateView(String name, Context context, AttributeSet attrs) {
                return null;
            }
        };
        LayoutInflater.from(this).setFactory2(factory2);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

setFactory2是在SDK>11时候添加的,如果你是基于11以上的就使用setFactory2,否则就使用setFactory ,两者功能基本一致。当然你不想考虑兼容问题可以直接使用LayoutInflaterCompat.setFactory()

可以看到,我们在调用父类onCreate前,先设置了Factory。这样带来的效果是什么呢?可能现在已经不能说很神奇了,因为通过上一篇文章的介绍,你应该已经猜到了,我们activity_main中默认的TextView控件被替换成了Button。

现在我们可以畅想一下这个功能能给我们带来的好处:

  • 在XML使用自定义View的时候,可以不声明全限定名称,增加了可读性;
  • 自行创建自定义的View,而不是让系统去创建,可以避免系统的反射过程,提升性能;
  • 如果自定义View被重构(refactor)了,不用在XML文件中修改;
  • 给系统的控件添加我们自己的功能;

由上可以联想到,如果我们自己加一些属性,换肤,改字体,甚至能够得到其他任何你能想到的功能。完美!

不过如果你再仔细些就能在Logcat中看到如下的输出:

是不是有点熟悉!是的,上篇文章说了,系统会自己设置factory,初始化时会有如下的代码:

    public void installViewFactory() {
        LayoutInflater layoutInflater = LayoutInflater.from(this.mContext);
        if (layoutInflater.getFactory() == null) {
            LayoutInflaterCompat.setFactory2(layoutInflater, this);
        } else if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImpl)) {
            Log.i("AppCompatDelegate", "The Activity's LayoutInflater already has a Factory installed so we can not install AppCompat's");
        }

    }

说明了什么,说明如果我们自己在系统之前就设置了factory,那么系统的就无法起作用了。这带来的后果就是,我们的新功能加上了,但是系统的新功能却不起作用了。

如何解决这个问题呢?

系统的factory无非就是使用AppCompatDelegateImpl中的createView来进行控件的替换,所以说,如果我们在设置了自己的factory后帮系统调用一下createView是不是就可以了。幸运的是这个createView是个public的方法,所以我们把上面的factory代码稍加修改就可以了:

LayoutInflater.Factory2 factory2 = new LayoutInflater.Factory2() {
            @Override
            public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
                View view = null;
                if ("TextView".equals(name)) {
                    view = new Button(MainActivity.this, attrs);
                }
                if (view == null) {
                    view = getDelegate().createView(parent, name, context, attrs);
                }
                return view;
            }

            @Override
            public View onCreateView(String name, Context context, AttributeSet attrs) {
                return null;
            }
        };

如上图所示,如果控件name不符合我们的要求,那么还是会用getDelegate().createView去创建这个View,这样的话,相当于我们两个factory都执行到了。当然了,现在的super.onCreate(savedInstanceState);依然会打印那个log,不过已经没什么影响了。

评论

0条评论

发表评论

电子邮件地址不会被公开。 必填项已用*标注