In the last articleAndroid LayoutInflater source codeIn View View, we said that there is a method createViewFromTag inside the View inflate, which will first try to create the View through the Factory.

    View view;
    if(mFactory2 ! View = mFactory2. OnCreateView (parent, name, context, attrs); }else if(mFactory ! View = mFactory. OnCreateView (name, context, attrs); }else {
        view = null;
    }
Copy the code

When the LayoutInflater Factory is empty, the LayoutInflater Factory is not empty.

Note: This article is based on Android 8.1.0.

1.LayoutInflater.FactoryIntroduction to the

Layoutinfler. Factory does not specify the layoutinfler. Factory method.

Hook you can supply that is called when inflating from a LayoutInflater. You can use this to customize the tag names available in your XML layout files.

LayoutInflater is a callback to View creation using LayoutInflater. The LayoutInflater.Factory can be used to modify tags that exist in XML.

Let’s look at the only way:

public abstract View onCreateView (String name, Context context, AttributeSet attrs)
Copy the code

So we know that if we set the LayoutInflater Factory, in the createViewFromTag method of the LayoutInflater the View is created using the onCreateView method of that Factory.

2. LayoutInflater.Factory function

What about the transformation cited above? For a simple example, if you write a TextView tag in XML and then determine in the onCreateView callback that if the name is a TextView, it can be changed to a Button, for example, replacing a control in batches. Examples are as follows:

Layout file < android. Support. The constraint. ConstraintLayout XMLNS: android ="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.liuzhaofutrue.teststart.MainActivity">

    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>
Copy the code

Let’s make a change in Java code:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        LayoutInflater.from(this).setFactory2(new LayoutInflater.Factory2() {
            @Override
            public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
                if(TextUtils.equals(name,"TextView")){
                    Button button = new Button(MainActivity.this);
                    button.setText("I replaced the TextView.");
                    button.setAllCaps(false);
                    return button;
                }
                return getDelegate().createView(parent, name, context, attrs);
            }

            @Override
            public View onCreateView(String name, Context context, AttributeSet attrs) {
                returnnull; }}); super.onCreate(savedInstanceState);setContentView(R.layout.activity_main); }}Copy the code

As you can see, what was supposed to be a TextView in the layout file has now been transformed into a Button.

Note: There is also a closely related class called Layoutinfler. Factory2, which differs from Layoutinfler. Factory:

  • LayoutInflater.Factory2 is API 11 added;
  • LayoutInflater.Factory2 inherits from LayoutInflater.Factory;
  • You can control the Parent that creates the View;

3, LayoutInflaterCompat

As we mentioned earlier, LayoutInflater.Factory2 was added to API 11, so LayoutInflaterCompat is a compatible class. Let’s look at its two most important methods:

    @Deprecated
    public static void setFactory(
            @NonNull LayoutInflater inflater, @NonNull LayoutInflaterFactory factory) {
        IMPL.setFactory(inflater, factory);
    }

    public static void setFactory2(
            @NonNull LayoutInflater inflater, @NonNull LayoutInflater.Factory2 factory) {
        IMPL.setFactory2(inflater, factory);
    }
Copy the code

You can see that setFactory has been marked out of date, and the setFactory2 method is preferred.

    static final LayoutInflaterCompatBaseImpl IMPL;
    static {
        if (Build.VERSION.SDK_INT >= 21) {
            IMPL = new LayoutInflaterCompatApi21Impl();
        } else {
            IMPL = new LayoutInflaterCompatBaseImpl();
        }
    }
    
    @RequiresApi(21)
    static class LayoutInflaterCompatApi21Impl extends LayoutInflaterCompatBaseImpl {
        @SuppressWarnings("deprecation")
        @Override
        public void setFactory(LayoutInflater inflater, LayoutInflaterFactory factory) { inflater.setFactory2(factory ! = null ? new Factory2Wrapper(factory) : null); } @Override public voidsetFactory2(LayoutInflater inflater, LayoutInflater.Factory2 factory) { inflater.setFactory2(factory); }}Copy the code

Here the setFactory call is actually the setFactory2 method that was called, wrapping LayoutInflaterFactory as a Factory2Wrapper.

4, LayoutInflater. SetFactory use note

If we take the LayoutInflater. SetFactory Noah to super. Behind the onCreate can? The program actually reported an error, let’s look at the Log:

    Process: com.example.teststart, PID: 24132
    java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.teststart/com.example.teststart.MainActivity}: java.lang.IllegalStateException: A factory has already been set on this LayoutInflater
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2876)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2941)
     Caused by: java.lang.IllegalStateException: A factory has already been set on this LayoutInflater
        at android.view.LayoutInflater.setFactory2(LayoutInflater.java:317)
        at com.example.teststart.MainActivity.onCreate(MainActivity.java:18)
        at android.app.Activity.performCreate(Activity.java:6765)
Copy the code

The LayoutInflater has been set to a Factory, and an error is reported when the LayoutInflater is set again. Let’s track the Layoutinfler.from (this).setFactory2 method:

    private boolean mFactorySet;
    public void setFactory2(Factory2 factory) {
        if (mFactorySet) {
            throw new IllegalStateException("A factory has already been set on this LayoutInflater");
        }
        if (factory == null) {
            throw new NullPointerException("Given factory can not be null");
        }
        mFactorySet = true;
        if (mFactory == null) {
            mFactory = mFactory2 = factory;
        } else{ mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2); }}Copy the code

You can see from the mFactorySet variable that the setFactory2 method can only be called once, and repeated Settings will throw an exception. So who sets Factory2?

Let’s look at the onCreate method for AppCompatActivity,

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        final AppCompatDelegate delegate = getDelegate();
        delegate.installViewFactory();
        delegate.onCreate(savedInstanceState);
        if(delegate.applyDayNight() && mThemeId ! = 0) { // If DayNight has been applied, we need to re-apply the themefor
            // the changes to take effect. On API 23+, we should bypass
            // setTheme(), which will no-op if the theme ID is identical to the
            // current theme ID.
            if (Build.VERSION.SDK_INT >= 23) {
                onApplyThemeResource(getTheme(), mThemeId, false);
            } else {
                setTheme(mThemeId);
            }
        }
        super.onCreate(savedInstanceState);
    }
Copy the code

Which will invoke the delegate. InstallViewFactory (); The installViewFactory method is called to AppCompatDelegateImplV9.

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

You can see:

  • If layoutInflater. GetFactory () is null, the AppCompatActivity will automatically set up a Factory2, no wonder we are super. OnCreate called after complains;
  • If we set Factory before super.onCreate, the system will not throw an exception when we set Factory again.

Note: If you’re smart enough, you can use reflection to change the mFactorySet in LayoutInflater to false so that you can set Factory after super.onCreate.

AppCompatActivity why setFactory

So why does AppCompatActivity automatically set a Factory? You follow the installViewFactory method of AppCompatDelegateImplV9 down to the onCreateView method, which will eventually call the createView method of AppCompatViewInflater.

    public final View createView(View parent, final String name, @NonNull Context context,
            @NonNull AttributeSet attrs, boolean inheritContext,
            boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
        View view = null;
        // We need to 'inject' our tint aware Views in place of the standard framework versions
        switch (name) {
            case "TextView":
                view = new AppCompatTextView(context, attrs);
                break;
            case "ImageView":
                view = new AppCompatImageView(context, attrs);
                break;
            case "Button":
                view = new AppCompatButton(context, attrs);
                break; . }return view;
    }
Copy the code

AppCompatActivity sets Factory to automatically convert widgets into compatible widgets (such as TextView to AppCompatTextView) so that they can work backwards in the new version. Some of the widgets that are new in older versions can be shown in older versions as well.

So if we set our own Factory, won’t we avoid the compatibility of the system? Actually compatible we can still preserved in the system, because the system is through AppCompatDelegate onCreateView ways to implement the widget compatible, We can call first when set up the Factory AppCompatDelegate. OnCreateView method, do we handle.

    LayoutInflater.from(this).setFactory2(new LayoutInflater.Factory2() { @Override public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {// call AppCompatDelegate's createView method getDelegate().createView(parent, name, context, attrs); // Let's do our customizationreturn null;
        }
    
        @Override
        public View onCreateView(String name, Context context, AttributeSet attrs) {
            returnnull; }});Copy the code

6, summary

  1. LayoutInflater Factory is a callback to View creation via LayoutInflater. LayoutInflater.Factory can be used to modify or customize the process of creating views.
  2. LayoutInflater. SetFactory use note: cannot be super. The onCreate after setting.
  3. AppCompatActivity why setFactory? Backward compatibility with effects in the new version.

AD time

Toutiao Android client team recruitment is in hot progress, all levels and fresh interns are needed, business growth is fast, daily work is high, the challenge is big, the treatment is powerful, you don’t miss it!

Bachelor degree or above, infrequent job-hopping (such as two jumps in two years), welcome to add my wechat details: KOBE8242011