首页 > 其他分享 >布局解析LayoutInflater分析

布局解析LayoutInflater分析

时间:2024-06-02 14:44:18浏览次数:26  
标签:解析 return LayoutInflater 布局 name attrs context null view

布局解析-LayoutInflater分析

一般添加布局或控件有两种方式,一种是直接new对应的View,然后通过addView方法添加到父控件中,一种是将布局写在layout的xml文件中,然后调用调用接口添加到父控件中,而这里就涉及到将xml布局转为View控件,一般都是使用LayoutInflater的inflate方法来讲布局xml转化为View对象的,可能有些地方如Activity设置布局调用setContentView来直接添加布局xml对应的资源id,但其内部实现中仍需要将资源转化为View对象,其也是使用LayoutInflater的inflate方法来进行转化的

1.LayoutInflater对象获取

代码中获取LayoutInflater对象的方式有多种,如在Activity中调用getLayoutInflater方法、或者调用LayoutInflater.from(context)方法等都可以获取到LayoutInflater对象,虽然方法多种,但其实殊途同归,这里以Activity的getLayoutInflater方法为例走读下代码(android10),Activity的getLayoutInflater方法代码如下:

public LayoutInflater getLayoutInflater() {
    return getWindow().getLayoutInflater();
}

显然,我们在代码中也可以这么调用来获取LayoutInflater对象,言归正传,继续先看下getWindow的逻辑

public Window getWindow() {
    return mWindow;
}

getWindow方法只是返回Activity的私有变量mWindow,在Activity的attach方法中找到了mWindow赋值的地方

mWindow = new PhoneWindow(this, window, activityConfigCallback);

显然,mWindow是一个PhoneWindow的对象,查看PhoneWindow中的getLayoutInflater方法代码:

public LayoutInflater getLayoutInflater() {
    return mLayoutInflater;
}

同样,查看mLayoutInflater赋值的地方

public PhoneWindow(Context context) {
    super(context);
    mLayoutInflater = LayoutInflater.from(context);
}

可以看到,在PhoneWIndow的构造方法中,调用LayoutInflater.from(context)方法获取LayoutInfater对象,查看其代码

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;
}

显然,这里是获取Context.LAYOUT_INFLATER_SERVICE=layout_inflater系统服务,查看下getSystemService源码,注意这里context实际是Activity的对象,而Activity的getSystemService代码如下

public Object getSystemService(@ServiceName @NonNull String name) {
    if (getBaseContext() == null) {
        throw new IllegalStateException(
                "System services not available to Activities before onCreate()");
    }

    if (WINDOW_SERVICE.equals(name)) {
        return mWindowManager;
    } else if (SEARCH_SERVICE.equals(name)) {
        ensureSearchManager();
        return mSearchManager;
    }
    return super.getSystemService(name);
}

显然,对于LAYOUT_INFLATER_SERVICE服务的获取还要查看其父类,Activity是继承ContextThemeWrapper的,查看下ContextThemeWrapper中getSystemService方法

public Object getSystemService(String name) {
    if (LAYOUT_INFLATER_SERVICE.equals(name)) {
        if (mInflater == null) {
            mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
        }
        return mInflater;
    }
    return getBaseContext().getSystemService(name);
}

这里代码逻辑很简单,如果是LAYOUT_INFLATER_SERVICE服务,则直接调用LayoutInflater.from方法来赋值返回,否则调用getBaseContext获取的context对象的getSystemService方法,这里关键在于getBaseContext方法

getBaseContext实现在ContextThemeWrapper的父类ContextWrapper中

public ContextWrapper(Context base) {
    mBase = base;
}

protected void attachBaseContext(Context base) {
    if (mBase != null) {
        throw new IllegalStateException("Base context already set");
    }
    mBase = base;
}

/**
 * @return the base context as set by the constructor or setBaseContext
 */
public Context getBaseContext() {
    return mBase;
}

而Activity并没有含Context参数的构造方法,那看下Activity中关于attachBaseContext方法调用

final void attach(Context context, ActivityThread aThread,
        Instrumentation instr, IBinder token, int ident,
        Application application, Intent intent, ActivityInfo info,
        CharSequence title, Activity parent, String id,
        NonConfigurationInstances lastNonConfigurationInstances,
        Configuration config, String referrer, IVoiceInteractor voiceInteractor,
        Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) {
    attachBaseContext(context);
    ...
}

protected void attachBaseContext(Context newBase) {
    super.attachBaseContext(newBase);
    if (newBase != null) {
        newBase.setAutofillClient(this);
        newBase.setContentCaptureOptions(getContentCaptureOptions());
    }
}

在Activity中虽有重写attachBaseContext方法,但其仍调用父类的attachBaseContext,查看代码其最后会调到ContextWrapper的attachBaseContext方法,也即会设置mBase,而在Activity的attach方法中有调用attachBaseContext方法,其调用主要在ActivityThread的performLaunchActivity方法中,即在启动Activity时会通过反射创建Activity对象然后调用其attach方法等,这里就不深究该逻辑,贴上部分关键attach调用关键代码

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ActivityInfo aInfo = r.activityInfo;
    if (r.packageInfo == null) {
        r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
                Context.CONTEXT_INCLUDE_CODE);
    }

    ComponentName component = r.intent.getComponent();
    if (component == null) {
        component = r.intent.resolveActivity(
            mInitialApplication.getPackageManager());
        r.intent.setComponent(component);
    }

    if (r.activityInfo.targetActivity != null) {
        component = new ComponentName(r.activityInfo.packageName,
                r.activityInfo.targetActivity);
    }

    ContextImpl appContext = createBaseContextForActivity(r);
    Activity activity = null;
    try {
        java.lang.ClassLoader cl = appContext.getClassLoader();
        activity = mInstrumentation.newActivity(
                cl, component.getClassName(), r.intent);
        StrictMode.incrementExpectedActivityCount(activity.getClass());
        r.intent.setExtrasClassLoader(cl);
        r.intent.prepareToEnterProcess();
        if (r.state != null) {
            r.state.setClassLoader(cl);
        }
    } catch (Exception e) {
        if (!mInstrumentation.onException(activity, e)) {
            throw new RuntimeException(
                "Unable to instantiate activity " + component
                + ": " + e.toString(), e);
        }
    }

    try {
        ...
        if (activity != null) {
            CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
            ...
            appContext.setOuterContext(activity);
            activity.attach(appContext, this, getInstrumentation(), r.token,
                    r.ident, app, r.intent, r.activityInfo, title, r.parent,
                    r.embeddedID, r.lastNonConfigurationInstances, config,
                    r.referrer, r.voiceInteractor, window, r.configCallback,
                    r.assistToken);

            ...
        }
        ...
    ...

    return activity;
}

这里可以看到,其调用Activity的attach方法传入的context对象为通过createBaseContextForActivity方法获取的

private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
    final int displayId;
    try {
        displayId = ActivityTaskManager.getService().getActivityDisplayId(r.token);
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }

    ContextImpl appContext = ContextImpl.createActivityContext(
            this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig);

    final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
    // For debugging purposes, if the activity's package name contains the value of
    // the "debug.use-second-display" system property as a substring, then show
    // its content on a secondary display if there is one.
    String pkgName = SystemProperties.get("debug.second-display.pkg");
    if (pkgName != null && !pkgName.isEmpty()
            && r.packageInfo.mPackageName.contains(pkgName)) {
        for (int id : dm.getDisplayIds()) {
            if (id != Display.DEFAULT_DISPLAY) {
                Display display =
                        dm.getCompatibleDisplay(id, appContext.getResources());
                appContext = (ContextImpl) appContext.createDisplayContext(display);
                break;
            }
        }
    }
    return appContext;
}

这里可以看到createBaseContextForActivity方法返回的是一个ContextImpl的对象

回到前面ContextThemeWrapper中getSystemService方法的地方

public Object getSystemService(String name) {
    if (LAYOUT_INFLATER_SERVICE.equals(name)) {
        if (mInflater == null) {
            mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
        }
        return mInflater;
    }
    return getBaseContext().getSystemService(name);
}

查看LayoutInflater的from方法

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;
}

显然,这里会调用到ContextImpl的getSystemService方法获取LayoutInflater对象

public Object getSystemService(String name) {
    return SystemServiceRegistry.getSystemService(this, name);
}

ContextImpl的getSystemService方法其实是获取在SystemServiceRegistry中注册的服务

static {
...
    registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
            new CachedServiceFetcher<LayoutInflater>() {
        @Override
        public LayoutInflater createService(ContextImpl ctx) {
            return new PhoneLayoutInflater(ctx.getOuterContext());
        }});
...
}

public static Object getSystemService(ContextImpl ctx, String name) {
    ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
    return fetcher != null ? fetcher.getService(ctx) : null;
}

private static <T> void registerService(String serviceName, Class<T> serviceClass,
        ServiceFetcher<T> serviceFetcher) {
    SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
    SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
}

显然,通过getSystemService方法获取的LAYOUT_INFLATER_SERVICE的服务对象其实是PhoneLayoutInflater对象,而前面ContextThemeWrapper中getSystemService方法中的cloneInContext方法,查看PhoneLayoutInflater中代码如下,可知其也只是重新创建了一个PhoneLayoutInflater对象

public LayoutInflater cloneInContext(Context newContext) {
    return new PhoneLayoutInflater(this, newContext);
}

当然,查看PhoneLayoutInflater的代码可以看到,其代码并不多,大部分逻辑还是在LayoutInflater中

2.inflate方法加载布局返回View对象

从应用常用的infalte方法开始看,查看infalte方法是定义在LayoutInflater中的

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
    return inflate(resource, root, root != null);
}

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
    final Resources res = getContext().getResources();
    if (DEBUG) {
        Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
              + Integer.toHexString(resource) + ")");
    }

    View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
    if (view != null) {
        return view;
    }
    XmlResourceParser parser = res.getLayout(resource);
    try {
        return inflate(parser, root, attachToRoot);
    } finally {
        parser.close();
    }
}

这里有两个地方有return view的地方,一个是tryInflatePrecompiled返回view,一个是inflate方法返回view

其中tryInflatePrecompiled方法相关部分如下,目前包括查看android10和android-12.0.0_r3的代码都是mUseCompiledView为false,即未使用相关逻辑,该处启动的话应该是对部分应用有个预编译的文件compiled_view.dex,然后在tryInflatePrecompiled方法中会通过该处获取的加载器获取对应的view。

View tryInflatePrecompiled(@LayoutRes int resource, Resources res, @Nullable ViewGroup root,
    boolean attachToRoot) {
    if (!mUseCompiledView) {
        return null;
    }
    ...
}

private void initPrecompiledViews() {
    // Precompiled layouts are not supported in this release.
    boolean enabled = false;
    initPrecompiledViews(enabled);
}

private void initPrecompiledViews(boolean enablePrecompiledViews) {
    mUseCompiledView = enablePrecompiledViews;

    if (!mUseCompiledView) {
        mPrecompiledClassLoader = null;
        return;
    }
    ...
}

/**
 * @hide for use by CTS tests
 */
@TestApi
public void setPrecompiledLayoutsEnabledForTesting(boolean enablePrecompiledLayouts) {
    initPrecompiledViews(enablePrecompiledLayouts);
}

目前代码看mUseCompiledView为false,即未使用,所以获取view是通过下面代码获取

    XmlResourceParser parser = res.getLayout(resource);
    try {
        return inflate(parser, root, attachToRoot);
    } finally {
        parser.close();
    }

这里关键查看inflate方法

    public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");

            final Context inflaterContext = mContext;
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            Context lastContext = (Context) mConstructorArgs[0];
            mConstructorArgs[0] = inflaterContext;
            View result = root;

            try {
                advanceToRootNode(parser);
                final String name = parser.getName();

                if (DEBUG) {
                    System.out.println("**************************");
                    System.out.println("Creating root view: "
                            + name);
                    System.out.println("**************************");
                }

                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 {
                    // Temp is the root view that was found in the xml
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                    ViewGroup.LayoutParams params = null;

                    if (root != null) {
                        if (DEBUG) {
                            System.out.println("Creating params from root: " +
                                    root);
                        }
                        // Create layout params that match root, if supplied
                        params = root.generateLayoutParams(attrs);
                        if (!attachToRoot) {
                            // Set the layout params for temp if we are not
                            // attaching. (If we are, we use addView, below)
                            temp.setLayoutParams(params);
                        }
                    }

                    if (DEBUG) {
                        System.out.println("-----> start inflating children");
                    }

                    // Inflate all children under temp against its context.
                    rInflateChildren(parser, temp, attrs, true);

                    if (DEBUG) {
                        System.out.println("-----> done inflating children");
                    }

                    // We are supposed to attach all the views we found (int temp)
                    // to root. Do that now.
                    if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }

                    // Decide whether to return the root that was passed in or the
                    // top view found in xml.
                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                }

            }...

            return result;
        }
    }

上面逻辑中主要区分了merge标签和其他标签,对于merge标签,其调用了rInflate方法,循环遍历标签然后解析控件添加到root即父控件中,其对标签处理差不多,这里主要看下非merge标签的,在非merge标签的代码中,关于生成控件的地方主要有如下两个代码调用

final View temp = createViewFromTag(root, name, inflaterContext, attrs);

 rInflateChildren(parser, temp, attrs, true);

其中createViewFromTag方法是根据tag标签属性来创建对应的view,在这里即是解析布局的根节点生成对应控件

rInflateChildren方法会调用到rInflate方法,其会遍历标签解析控件然后添加到父控件中,在这里即解析布局的根节点下的子节点添加到根节点生成的控件下

查看createViewFromTag方法的逻辑,其会调用到如下方法

    View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
            boolean ignoreThemeAttr) {
        if (name.equals("view")) {
            name = attrs.getAttributeValue(null, "class");
        }

        // Apply a theme wrapper, if allowed and one is specified.
        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();
        }

        try {
            View view = tryCreateView(parent, name, context, attrs);

            if (view == null) {
                final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = context;
                try {
                    if (-1 == name.indexOf('.')) {
                        view = onCreateView(context, parent, name, attrs);
                    } else {
                        view = createView(context, name, null, attrs);
                    }
                } finally {
                    mConstructorArgs[0] = lastContext;
                }
            }

            return view;
        }...
    }

这里有三个关键方法

View view = tryCreateView(parent, name, context, attrs);//优先调用,返回空时再根据条件调用另外两个方法
view = onCreateView(context, parent, name, attrs);//标签名中不包含'.'时调用
view = createView(context, name, null, attrs);//标签名中包含'.'时调用

2.1、tryCreateView

    public final View tryCreateView(@Nullable View parent, @NonNull String name,
        @NonNull Context context,
        @NonNull AttributeSet attrs) {
        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);
        }

        return view;
    }

上述代码中mFactory和mFactory2可通过setFactory和setFactory2方法进行设置,但一般情况下这两个变量为空,暂不细看,mPrivateFactory可通过setPrivateFactory方法设置,但该方法有hide标签,主要是用于框架层调用,目前查看代码主要有两个地方调用

一个是在Activity的attach方法中有调用如下代码

mWindow.getLayoutInflater().setPrivateFactory(this);

查看Activity实现的onCreateView方法:

public View onCreateView(@Nullable View parent, @NonNull String name,
        @NonNull Context context, @NonNull AttributeSet attrs) {
    if (!"fragment".equals(name)) {
        return onCreateView(name, context, attrs);
    }

    return mFragments.onCreateView(parent, name, context, attrs);
}

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

显然,根据上面代码,在标签不为fragment时,应用又没有重写该方法时,这里会返回空,如果标签为fragment,则会调用FragmentController的onCreateView方法

在Fragment中也有地方调用setPrivateFactory方法,这些应该是为了能够处理fragment相关标签,这里不细讲

2.2、createView

在标签中包含'.'时会调用createView方法来返回view(因为onCreateView会调用到createView,所以先分析createView方法)

view = createView(context, name, null, attrs);
public final View createView(@NonNull Context viewContext, @NonNull String name,
        @Nullable String prefix, @Nullable AttributeSet attrs)
        throws ClassNotFoundException, InflateException {
    Objects.requireNonNull(viewContext);
    Objects.requireNonNull(name);
    Constructor<? extends View> constructor = sConstructorMap.get(name);
    if (constructor != null && !verifyClassLoader(constructor)) {
        constructor = null;
        sConstructorMap.remove(name);
    }
    Class<? extends View> clazz = null;

    try {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);

        if (constructor == null) {
            // Class not found in the cache, see if it's real, and try to add it
            clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
                    mContext.getClassLoader()).asSubclass(View.class);

            if (mFilter != null && clazz != null) {
                boolean allowed = mFilter.onLoadClass(clazz);
                if (!allowed) {
                    failNotAllowed(name, prefix, viewContext, attrs);
                }
            }
            constructor = clazz.getConstructor(mConstructorSignature);
            constructor.setAccessible(true);
            sConstructorMap.put(name, constructor);
        } else {
            // If we have a filter, apply it to cached constructor
            if (mFilter != null) {
                // Have we seen this name before?
                Boolean allowedState = mFilterMap.get(name);
                if (allowedState == null) {
                    // New class -- remember whether it is allowed
                    clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
                            mContext.getClassLoader()).asSubclass(View.class);

                    boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
                    mFilterMap.put(name, allowed);
                    if (!allowed) {
                        failNotAllowed(name, prefix, viewContext, attrs);
                    }
                } else if (allowedState.equals(Boolean.FALSE)) {
                    failNotAllowed(name, prefix, viewContext, attrs);
                }
            }
        }

        Object lastContext = mConstructorArgs[0];
        mConstructorArgs[0] = viewContext;
        Object[] args = mConstructorArgs;
        args[1] = attrs;

        try {
            final View view = constructor.newInstance(args);
            if (view instanceof ViewStub) {
                // Use the same context when inflating ViewStub later.
                final ViewStub viewStub = (ViewStub) view;
                viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
            }
            return view;
        } finally {
            mConstructorArgs[0] = lastContext;
        }
    }...
}

代码看似有点多但逻辑其实并不复杂,这里主要是根据传入的name和prefix通过反射创建对应的对象

2.3、onCreateView

在标签名中不含'.'时会调用onCreateView方法来返回view

view = onCreateView(context, parent, name, attrs);
public View onCreateView(@NonNull Context viewContext, @Nullable View parent,
        @NonNull String name, @Nullable AttributeSet attrs)
        throws ClassNotFoundException {
    return onCreateView(parent, name, attrs);
}

protected View onCreateView(View parent, String name, AttributeSet attrs)
        throws ClassNotFoundException {
    return onCreateView(name, attrs);
}

protected View onCreateView(String name, AttributeSet attrs)
        throws ClassNotFoundException {
    return createView(name, "android.view.", attrs);
}

在上面LayoutInflater代码中可知其会调用到onCreateView的两个参数的方法,而在PhoneLayoutInflater中有重写该方法

private static final String[] sClassPrefixList = {
    "android.widget.",
    "android.webkit.",
    "android.app."
};

@Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
    for (String prefix : sClassPrefixList) {
        try {
            View view = createView(name, prefix, attrs);
            if (view != null) {
                return view;
            }
        } catch (ClassNotFoundException e) {
            // In this case we want to let the base class take a crack
            // at it.
        }
    }

    return super.onCreateView(name, attrs);
}

结合前面createView方法逻辑可知,onCreateView逻辑主要是尝试通过预置的几个prefix结合name来通过createView方法反射获取对应view,这里的几个prefix分别是

"android.widget."
"android.webkit."
"android.app."
"android.view."

根据上面逻辑是否说明在布局中标签写完整控件类名(包名+类名)会比简写加载效率要高那么一点?

2.4、rInflate

前面也提到过rInflate方法,根据前面的分析,createViewFromTag主要是用于单个控件类型的标签的加载,rInflateChildren用于加载标签下的子标签,而rInflate可以说是用于加载同一父标签下的同深度的平行标签,这里会遍历同一父标签下的标签,根据标签不同进行不同的处理

    void rInflate(XmlPullParser parser, View parent, Context context,
            AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {

        final int depth = parser.getDepth();
        int type;
        boolean pendingRequestFocus = false;

        while (((type = parser.next()) != XmlPullParser.END_TAG ||
                parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {

            if (type != XmlPullParser.START_TAG) {
                continue;
            }

            final String name = parser.getName();

            if (TAG_REQUEST_FOCUS.equals(name)) {
                pendingRequestFocus = true;
                consumeChildElements(parser);
            } else if (TAG_TAG.equals(name)) {
                parseViewTag(parser, parent, attrs);
            } else if (TAG_INCLUDE.equals(name)) {
                if (parser.getDepth() == 0) {
                    throw new InflateException("<include /> cannot be the root element");
                }
                parseInclude(parser, context, parent, attrs);
            } else if (TAG_MERGE.equals(name)) {
                throw new InflateException("<merge /> must be the root element");
            } else {
                final View view = createViewFromTag(parent, name, context, attrs);
                final ViewGroup viewGroup = (ViewGroup) parent;
                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
                rInflateChildren(parser, view, attrs, true);
                viewGroup.addView(view, params);
            }
        }

        if (pendingRequestFocus) {
            parent.restoreDefaultFocus();
        }

        if (finishInflate) {
            parent.onFinishInflate();
        }
    }

3.布局中一般节点标签

在上述代码中有一些标签的解析和处理,这里简单列举下

1、控件各种view:对应具体的View控件

2、ViewStub:ViewStub也是继承View的,不过相较于一般的view比较特殊,ViewStub和include标签一样可以引入一个布局,但其默认不会显示也不占用位置,在调用对应的接口后才会显示

3、merge:只能用于根标签,作用相当于一个虚拟控件,merge下的子标签对应的控件在加载时直接添加到父控件中

4、fragment:主要用于fragment的加载

5、requestFocus:可用于设置默认焦点,如一个布局中有多个EditText,在其中一个EditText中添加requestFocus标签后,进入该界面后默认含requestFocus标签的EditText拥有焦点

6、tag:tag标签只支持id和value值,添加后其会对标签对应父控件调用view.setTag(key, value)方法,其中key和value分别对应标签中的id和value值

7、include:引入布局

8、blink:其对应LayoutInflater中的BlinkLayout,不过目前应该是已经不使用了

标签:解析,return,LayoutInflater,布局,name,attrs,context,null,view
From: https://www.cnblogs.com/luoliang13/p/18227115

相关文章

  • 班级网页制作 HTML个人网页设计 我的班级网站设计与实现 大学生简单班级静态HTML网页
    ......
  • WSL2--DNS解析问题解决
    1.问题xurong@DESKTOP-SOE9MG1:~/.ssh$sudoaptupdateIgn:1http://security.ubuntu.com/ubuntunoble-securityInReleaseIgn:2http://archive.ubuntu.com/ubuntunobleInReleaseIgn:3http://archive.ubuntu.com/ubuntunoble-updatesInReleaseIgn:4http://archive......
  • 电机控制系列模块解析(26)—— 参数辨识
    一、离线辨识参数辨识分为:离线辨识和在线辨识。在现代电机控制领域,准确掌握电机的各项电气和机械参数对于实现高效、精准的控制至关重要。离线辨识作为电机参数测量的一种重要手段,主要在电机未接入实际运行系统时进行,通过特定的测试信号和算法,辨识出电机的关键参数。本文将介......
  • 【Django技术深潜】揭秘Django定时任务利器:django_apscheduler全面解析与实战
    在现代Web开发中,定时任务是不可或缺的一部分,无论是定期数据分析、定时发送邮件、还是系统维护脚本,都需要精准的定时调度。Django作为Python世界中强大的Web框架,其对定时任务的支持自然也是开发者关注的重点。本文将深入探讨Django定时任务解决方案,特别是聚焦于django_apscheduler......
  • 反转21克msvcr100.dll丢失怎么办?反转21克msvcr100.dll丢失问题的全面解析与解决之道
    《反转21克》是目前第一款以科幻为题材的互动影像作品。然而很多玩家都遇到了反转21克msvcr100.dll丢失的问题,其中msvcr100.dll是MicrosoftVisualC++2010RedistributablePackage的一部分,它提供了运行时库支持,下面一起来看看解决方法介绍吧!重新安装相关程序重新安装与ms......
  • 如何实现两栏布局,右侧自适应?三栏布局中间自适应呢?
     一、背景在日常布局中,无论是两栏布局还是三栏布局,使用的频率都非常高两栏布局两栏布局实现效果就是将页面分割成左右宽度不等的两列,宽度较小的列设置为固定宽度,剩余宽度由另一列撑满,比如 AntDesign 文档,蓝色区域为主要内容布局容器,侧边栏为次要内容布局容器这里称宽......
  • 深入解析力扣170题:两数之和 III - 数据结构设计(哈希表与双指针法详解及模拟面试问答)
    在本篇文章中,我们将详细解读力扣第170题“两数之和III-数据结构设计”。通过学习本篇文章,读者将掌握如何设计一个数据结构来支持两种操作,并了解相关的复杂度分析和模拟面试问答。每种方法都将配以详细的解释和ASCII图解,以便于理解。问题描述力扣第170题“两数之和III......
  • 反海淘商业模式案例分析 :Pandabuy淘宝代购集运系统解析丨1688代采集运系统
    反海淘商业模式是指通过代购、代采集运等方式,帮助海外消费者购买并运输国内商品的一种商业模式。这种模式可以帮助海外消费者解决购买国内商品的困难,同时也为国内商家提供了一个新的销售渠道。下面以Pandabuy淘宝代购集运系统和1688代采集运系统为例进行解析。Pandabuy淘宝代......
  • LeetCode 第15题:三数之和的解析
    大家好!本文我们将要探索的是LeetCode的第15题:三数之和。我们的目标是在一片数字的海洋中寻找三颗神奇的珍珠,它们的和为零。准备好了吗?让我们一同踏上这段充满挑战和乐趣的旅程吧!文章目录题目介绍解题思路思路1:暴力法思路2:双指针法思路3:哈希表法思路4:回溯法思......
  • LeetCode 第14题:最长公共前缀题目解析(进阶版)
    本文我们来探索LeetCode第14题——最长公共前缀题目解析(进阶版)。文章目录引言题目介绍解题思路思路1:水平扫描法思路2:垂直扫描法思路3:分治法思路4:二分查找法思路5:字典树(Trie)水平扫描法详细解析步骤1:初始化前缀步骤2:逐个比较示例讲解Java代码实现图......