LayoutInflater 填充器解析

填充View最终都是调用LayoutInflaterpublic View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)三个参数方法

    /**
     * Inflate a new view hierarchy from the specified xml resource. Throws
     * {@link InflateException} if there is an error.
     *
     * @param resource ID for an XML layout resource to load (e.g.,
     *        <code>R.layout.main_page</code>)
     * @param root Optional view to be the parent of the generated hierarchy (if
     *        <em>attachToRoot</em> is true), or else simply an object that
     *        provides a set of LayoutParams values for root of the returned
     *        hierarchy (if <em>attachToRoot</em> is false.)
     * @param attachToRoot Whether the inflated hierarchy should be attached to
     *        the root parameter? If false, root is only used to create the
     *        correct subclass of LayoutParams for the root view in the XML.
     * @return The root View of the inflated hierarchy. If root was supplied and
     *         attachToRoot is true, this is root; otherwise it is the root of
     *         the inflated XML file.
     */
    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot){
           ··· ···
        return view;
    }

文档解释说明:

     /**
     * 只能填充特定格式的xml也就是得符合布局格式的xml,否则抛出异常InflateException。
     * @param resource 要填充的资源id
     * @param root 这个要和第三个参数有关系。
     *             若是attachToRoot为true,那么root的意义是,从resource填充成的view对象的父控件;
     *             若是attachToRoot为false,那么root的意义是,可以为resource生成的view对象的根布局提供一系LayoutParams参数的控件。
     *             若root为null,也就是我们不指定父控件,那么新生产的view对象的根布局的某些参数会失效,比如Layout_width和Layout_height会失效。
     * @param attachToRoot 是否附着在root上
     * @return 如果attachToRoo为false,那么返回resource填充的View;
     *         如果attachToRoo为true,那么返回root。
     */
    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot){
           ··· ···
        return view;
    }

常用填充View的方式有三种:

  1. View的静态方法,直接获取LayoutInflater进行填充
     /**
     * Inflate a view from an XML resource.  This convenience method wraps the {@link
     * LayoutInflater} class, which provides a full range of options for view inflation.
     *
     * @param context The Context object for your activity or application.
     * @param resource The resource ID to inflate
     * @param root A view group that will be the parent.  Used to properly inflate the
     * layout_* parameters.
     * @see LayoutInflater
     */
    public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
        LayoutInflater factory = LayoutInflater.from(context);
        return factory.inflate(resource, root);
    }
  1. LayoutInflater的静态方法获取LayoutInflater,然后调用Inflater填充
     /**
     * Obtains the LayoutInflater from the given context.
     */
    public static LayoutInflater from(Context context) {
        LayoutInflater LayoutInflater =
                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        if (LayoutInflater == null) {
            throw new AssertionError("LayoutInflater not found.");
        }
        return LayoutInflater;
    }
  1. 通过系统SystemService获取填充器,然后调用Inflater填充
LayoutInflater LayoutInflater = 
 (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)

#注意:
View的填充方式只有两个参数,也就是调用的inflate(resource, root, root != null)
而RecyclerViewonCreateViewHolder创建View填充时一定是inflate(resource, null)orinflate(resource, root, false)
root为空时view的根节点部分参数无效
否则会报
image.png

真正的填充

先创建XML解析器,用于解析XML:

public View LayoutInflater.inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
        final XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
}

接下来进入到Inflate:

  1. 先是校验XML的合法性;
  2. 然后找到正确的START_TAG,获取到节点name;
  3. 如果是Merge标签,则递归解析Merge;如果不是Merge,则进入到createViewFromTag()中创建出root View;
  4. 拿到root View后,则会递归解析子View节点,最终都是通过createViewFromTag()方法来创建的;
  5. createViewFromTag()待会再看,解析完View后,将XML中的root View挂载到DecorView上;
 public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            ...
            View result = root;
            ...
                int type;
                while ((type = parser.next()) != XmlPullParser.START_TAG &&  type != XmlPullParser.END_DOCUMENT) {
                    // Empty
                }
                if (type != XmlPullParser.START_TAG) {
                    throw new InflateException(parser.getPositionDescription()  + ": No start tag found!");
                }
                final String name = parser.getName();
                if (TAG_MERGE.equals(name)) {
                    if (root == null || !attachToRoot) {
                        throw new InflateException("<merge /> can be used only with a valid "  + "ViewGroup root and attachToRoot=true");
                    }
                    rInflate(parser, root, inflaterContext, attrs, false);
                } else {
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);
                    ViewGroup.LayoutParams params = null;
                    if (root != null) {
                        params = root.generateLayoutParams(attrs);
                        if (!attachToRoot) { 
                            temp.setLayoutParams(params);
                        }
                    }
                    if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }
                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                }
            return result;
        }
    } 

真正解析和创建出View的方法createViewFromTag():

  1. 如果是view,则拿出属性class对应的名称,用于后面创建View实例;
  2. 解析属性,设置主题
  3. 解析BlinkLayout
  4. 优先使用LayoutInflater.Factory2来解析;Factory2主要是在V7包中利用委托AppCompatDelegateImpl来解析View,将老的TextView等控件,替换成AppCompatTextView,因此,我们也可以这样模仿来做换肤、统一设置字体等功能
  5. 如果没有设置Factory2,那么将用Factory来进行解析;
  6. 如果再没有设置Factory,那么将用mPrivateFactory来创建;
  7. 如果没有设置mPrivateFactory,那么将通过节点名称进行反射创建;
  8. 反射创建时,判断了是否包含.,如果包含说明时自定义View,否则从系统默认的view来创建;
 View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr) {
        if (name.equals("view")) {
            name = attrs.getAttributeValue(null, "class");
        }

        if (!ignoreThemeAttr) {
            final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
            final int themeResId = ta.getResourceId(0, 0);
            if (themeResId != 0) {
                context = new ContextThemeWrapper(context, themeResId);
            }
            ta.recycle();
        }

        if (name.equals(TAG_1995)) {
            // Let's party like it's 1995!
            return new BlinkLayout(context, attrs);
        }
            View view;
            if (mFactory2 != null) {
                view = mFactory2.onCreateView(parent, name, context, attrs);
            } else if (mFactory != null) {
                view = mFactory.onCreateView(name, context, attrs);
            } else {
                view = null;
            }
            if (view == null && mPrivateFactory != null) {
                view = mPrivateFactory.onCreateView(parent, name, context, attrs);
            }

            if (view == null) {
                final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = context;
                try {
                    if (-1 == name.indexOf('.')) {
                        view = onCreateView(parent, name, attrs);
                    } else {
                        view = createView(name, null, attrs);
                    }
                } finally {
                    mConstructorArgs[0] = lastContext;
                }
            }
            return view;  
    }
  • Factory2
    Factory2中主要是在AppCompatActivity中使用,AppCompatActivity的创建是通过AppCompatDelegate委托来实现的,在onCreate中就创建了AppCompatDelegate,并且初始化了ViewFactory;
  • AppCompatDelegate
    功能主要是定义接口,并且创建实现类AppCompatDelegateImpl;
  • 在installViewFactory时,利用LayoutInflaterCompat来设置Factory,并且只接受AppCompatDelegateImpl,AppCompatDelegateImpl实现了Factory2;
  • AppCompatDelegateImpl
    class AppCompatDelegateImpl extends AppCompatDelegate implements MenuBuilder.Callback, LayoutInflater.Factory2
  • 在AppCompatDelegateImpl中,主要通过AppCompatViewInflater来进行createView的,AppCompatViewInflater是通过反射来创建的;
  • 反射创建时,区分了android.view和自定义View,但是最终都是通过反射了创建的;

看下流程

初始化AppCompatDelegate

protected void AppCompatActivity.onCreate(@Nullable Bundle savedInstanceState) {
        final AppCompatDelegate delegate = getDelegate();
        delegate.installViewFactory();
        ...
}
public AppCompatDelegate AppCompatActivity.getDelegate() {
        if (mDelegate == null) {
            mDelegate = AppCompatDelegate.create(this, this);
        }
        return mDelegate;
}
public static AppCompatDelegate AppCompatDelegate.create(Activity activity, AppCompatCallback callback) {
        return new AppCompatDelegateImpl(activity, activity.getWindow(), callback);
}

设置Factory

public void installViewFactory() {
    LayoutInflater layoutInflater = LayoutInflater.from(mContext);
    if (layoutInflater.getFactory() == null) {
        LayoutInflaterCompat.setFactory2(layoutInflater, this);
    } else {
        if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImpl)) {
        }
    }
}

创建AppCompatViewInflater:

public View AppCompatDelegateImpl.createView(View parent, final String name, @NonNull Context context, @NonNull AttributeSet attrs) {
        TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
        String viewInflaterClassName = a.getString(R.styleable.AppCompatTheme_viewInflaterClass);
        if ((viewInflaterClassName == null) || AppCompatViewInflater.class.getName().equals(viewInflaterClassName)) {
            mAppCompatViewInflater = new AppCompatViewInflater();
        } else {
            try {
                Class viewInflaterClass = Class.forName(viewInflaterClassName);
                mAppCompatViewInflater = (AppCompatViewInflater) viewInflaterClass.getDeclaredConstructor().newInstance();
            } catch (Throwable t) {
                mAppCompatViewInflater = new AppCompatViewInflater();
        }
        ...
        return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,IS_PRE_LOLLIPOP, true, VectorEnabledTintResources.shouldBeUsed() );
}

最终调用AppCompatViewInflater.createView

final View AppCompatViewInflater.createView(View parent, final String name, @NonNull Context context,
            @NonNull AttributeSet attrs, boolean inheritContext,
            boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
        final Context originalContext = context;

        // We can emulate Lollipop's android:theme attribute propagating down the view hierarchy
        // by using the parent's context
        if (inheritContext && parent != null) {
            context = parent.getContext();
        }
        if (readAndroidTheme || readAppTheme) {
            // We then apply the theme on the context, if specified
            context = themifyContext(context, attrs, readAndroidTheme, readAppTheme);
        }
        if (wrapContext) {
            context = TintContextWrapper.wrap(context);
        }

        View view = null;

        // We need to 'inject' our tint aware Views in place of the standard framework versions
        switch (name) {
            case "TextView":
                view = createTextView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "ImageView":
                view = createImageView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "Button":
                view = createButton(context, attrs);
                verifyNotNull(view, name);
                break;
            case "EditText":
                view = createEditText(context, attrs);
                verifyNotNull(view, name);
                break;
            case "Spinner":
                view = createSpinner(context, attrs);
                verifyNotNull(view, name);
                break;
            case "ImageButton":
                view = createImageButton(context, attrs);
                verifyNotNull(view, name);
                break;
            case "CheckBox":
                view = createCheckBox(context, attrs);
                verifyNotNull(view, name);
                break;
            case "RadioButton":
                view = createRadioButton(context, attrs);
                verifyNotNull(view, name);
                break;
            case "CheckedTextView":
                view = createCheckedTextView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "AutoCompleteTextView":
                view = createAutoCompleteTextView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "MultiAutoCompleteTextView":
                view = createMultiAutoCompleteTextView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "RatingBar":
                view = createRatingBar(context, attrs);
                verifyNotNull(view, name);
                break;
            case "SeekBar":
                view = createSeekBar(context, attrs);
                verifyNotNull(view, name);
                break;
            default:
                // The fallback that allows extending class to take over view inflation
                // for other tags. Note that we don't check that the result is not-null.
                // That allows the custom inflater path to fall back on the default one
                // later in this method.
                view = createView(context, name, attrs);
        }

        if (view == null && originalContext != context) {
            // If the original context does not equal our themed context, then we need to manually
            // inflate it using the name so that android:theme takes effect.
            view = createViewFromTag(context, name, attrs);
        }

        if (view != null) {
            // If we have created a view, check its android:onClick
            checkOnClickListener(view, attrs);
        }

        return view;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值