Android setContentView流程[通俗易懂]

Android setContentView流程[通俗易懂]MainActivity继承Activity的流程MainActivity继承至Activityimportandroid.app.Activity;importandroid.os.Bundle;publicclassMainActivityextendsActivity{@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(saved

大家好,又见面了,我是你们的朋友全栈君。

更新于:2022-01-09
Android-30
implementation ‘androidx.appcompat:appcompat:1.2.0’
setContentView并没有将view添加到屏幕上,只是创建了DecorView,xml添加到DecorView而已。

文章目录

MainActivity 继承Activity的setContentView流程

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

1) MainActivity 继承至 Activity

import android.app.Activity;
import android.os.Bundle;

public class MainActivity extends Activity { 
   
    @Override
    protected void onCreate(Bundle savedInstanceState) { 
   
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

1.1) Activity#setContentView

/** * Set the activity content from a layout resource. The resource will be * inflated, adding all top-level views to the activity. * * @param layoutResID Resource ID to be inflated. * * @see #setContentView(android.view.View) * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams) */
public void setContentView(@LayoutRes int layoutResID) { 
   
    // getWindow() 是 抽象类Window对象,其子类为 PhoneWindow
    // 这里调用的是 PhoneWindow的setContentView
    getWindow().setContentView(layoutResID);
    
    initWindowDecorActionBar();
}

1.1.1) PhoneWindow#setContentView

@Override
public void setContentView(int layoutResID) { 
   
    // 初始化DecorView,并对 mContentParent 赋值 mContentParent就是一个View
    // mContentParent 是 R.layout.screen_simple资源文件 中 FramLayout 这个View
    // 后面会提到这里
    if (mContentParent == null) { 
   
        installDecor();
    } 
    ......
    // 将 layoutResID 即 R.layout.activity_main 填入到 mContentParent,渲染操作
    mLayoutInflater.inflate(layoutResID, mContentParent);
    ......
    // 设置标志位
    // 如:当调用requsetWindowFeature时,如果在setContentView之后调用
    // 就会抛出异常,因为这里设置了值
    mContentParentExplicitlySet = true;
}

1.1.1.1) PhoneWindow#installDecor

private void installDecor() { 
   
    ......
    // This is the top-level view of the window, containing the window decor.
    // mDecor 是一个 DecorView 即顶层的 View,它包含 window
    mDecor = generateDecor(-1);
	
    ......
    // This is the view in which the window contents are placed.It is either
    // mDecor itself, or a child of mDecor where the contents go.
    // mContentParent 是 ViewGroup 即 它是用来放置view的
    // 要么是mDecor本身,要么是mDecor的子代
    mContentParent = generateLayout(mDecor);
    ......
}

1.1.1.1.1) PhoneWindow#generateDecor

protected DecorView generateDecor(int featureId) { 
   
    ......
    // 初始化DecorView
    return new DecorView(context, featureId, this, getAttributes());
}

1.1.1.1.2) PhoneWindow#generateLayout

初始化 mContentParent
根据不同的主题选择不同的资源文件。

protected ViewGroup generateLayout(DecorView decor) { 
   
    ......
    // 根据不同的主题,选择不同的资源文件
    // 这里以 R.layout.screen_simple 文件为例说明
    layoutResource = R.layout.screen_simple;
	
    ......
    // 将 R.layout.screen_simple 添加到 DecorView 即顶层的 View
    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
	
    // 获取 id 为 ID_ANDROID_CONTENT 即 com.android.internal.R.id.content
    // contentParent 就是 R.layout.screen_simple资源文件 中 FramLayout 这个View
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    ......
	
    return contentParent;
}

1.1.1.1.2.1) screen_simple.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical">
    <ViewStub android:id="@+id/action_mode_bar_stub"
              android:inflatedId="@+id/action_mode_bar"
              android:layout="@layout/action_mode_bar"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:theme="?attr/actionBarTheme" />
    <FrameLayout
         android:id="@android:id/content"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:foregroundInsidePadding="false"
         android:foregroundGravity="fill_horizontal|top"
         android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

1.1.1.1.2.2) DecorView#onResourcesLoaded

void onResourcesLoaded(LayoutInflater inflater, int layoutResource) { 
   
    ......
    // 加载资源文件 如上方提到的 R.layout.screen_simple
    final View root = inflater.inflate(layoutResource, null);
        
    ......
    // 将 资源文件 加载到 DecorView 中
    addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    ......
}

1.1.1.2) LayoutInflater#inflate

渲染操作

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) { 
   
	
    ......
    return inflate(parser, root, attachToRoot);
    ......
}

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, 
	boolean attachToRoot) { 
   
	
    ......
    // 通过反射创建View
    final View temp = createViewFromTag(root, name, inflaterContext, attrs);
    ......
    ViewGroup.LayoutParams params = null;
    ......
    params = root.generateLayoutParams(attrs);
    ......
    // 创建子View
    rInflateChildren(parser, temp, attrs, true);
    ......
    root.addView(temp, params);
    ......
}

1.1.1.2.1) LayoutInflater#createViewFromTag

private View createViewFromTag(View parent, String name, Context context, 
	AttributeSet attrs) { 
   
    return createViewFromTag(parent, name, context, attrs, false);
}

View createViewFromTag(View parent, String name, Context context, 
	AttributeSet attrs, boolean ignoreThemeAttr) { 
   
    ......
    // 创建view
    View view = tryCreateView(parent, name, context, attrs);
    
    // 如果 view 为null,使用默认的创建 view 的方式创建
    // 也就是通过 AppCompatDelegateImpl 来创建 View
    if (view == null) { 
   
        final Object lastContext = mConstructorArgs[0];
        mConstructorArgs[0] = context;
        try { 
   
        	// 判断是否为sdk自己的
            if (-1 == name.indexOf('.')) { 
   
                view = onCreateView(context, parent, name, attrs);
            } else { 
   
                view = createView(context, name, null, attrs);
            }
        } finally { 
   
            mConstructorArgs[0] = lastContext;
        }
    }

    return view;
}

1.1.1.2.1.1) LayoutInflater#tryCreateView

创建View,当 mFactory2 不为空,就用 factory2 来创建view,否则就返回 view为null

当继承Activity时,并没有默认设置 factory2,当继承 AppCompatActivity 时,在 super.onCreate(savedInstanceState) 进行了设置 factory2

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

1.1.1.2.1.1.1) AppCompatDelegateImpl#onCreateView

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

1.1.1.2.1.1.1.1) AppCompatDelegateImpl#createView

从代码看,是调用 AppCompatViewInflater的createView方法

@Override
public View createView(View parent, final String name, 
	@NonNull Context context, @NonNull AttributeSet attrs) { 
   
    ......
    mAppCompatViewInflater = new AppCompatViewInflater();
    ......
    return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
            IS_PRE_LOLLIPOP,   
            true,              
            VectorEnabledTintResources.shouldBeUsed() 
    );
}

1.1.1.2.1.1.1.1.1) AppCompatViewInflater#createView

从下方代码可以看出,当创建的TextView等时,会进行替换操作,如TextView替换为AppCompatTextView

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;

    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;
        case "ToggleButton":
            view = createToggleButton(context, attrs);
            verifyNotNull(view, name);
            break;
        default:
            view = createView(context, name, attrs);
    }
    
    ......
    
    return view;
}

当为 TextView 的时候,就会被替换为 AppCompatTextView

@NonNull
protected AppCompatTextView createTextView(Context context, 
	AttributeSet attrs) { 
   
    return new AppCompatTextView(context, attrs);
}

1.1.1.2.1.2) LayoutInflater#onCreateView

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 { 
   
    // 这里是两个参数的,调用的是 onCreateView 的 onCreateView,进行重写了
    return onCreateView(name, attrs);
}

1.1.1.2.1.2.1) PhoneLayoutInflater#onCreateView

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

1.1.1.2.1.2.1.1) LayoutInflater#onCreateView

public final View createView(String name, String prefix, AttributeSet attrs)
	throws ClassNotFoundException, InflateException { 
   
    Context context = (Context) mConstructorArgs[0];
    if (context == null) { 
   
        context = mContext;
    }
    return createView(context, name, prefix, attrs);
}

1.1.1.2.1.2.1.1.1) LayoutInflater#createView

// 通过反射创建View
public final View createView(@NonNull Context viewContext, @NonNull String name, 
	@Nullable String prefix, @Nullable AttributeSet attrs) 
	throws ClassNotFoundException, InflateException { 
   

	clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
               mContext.getClassLoader()).asSubclass(View.class);

    constructor = clazz.getConstructor(mConstructorSignature);
    
    final View view = constructor.newInstance(args);
}

2) 为什么在 requestWindowFeature 在 setContentView之后调用,会报错,必须在之前调用才可以?

public class MainActivity extends Activity { 
   
    @Override
    protected void onCreate(Bundle savedInstanceState) { 
   
        super.onCreate(savedInstanceState);
// 在 setContentView 之前调用
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        
        setContentView(R.layout.activity_main);
        
// error
// requestWindowFeature(Window.FEATURE_NO_TITLE);
    }
}

报错内容:
在这里插入图片描述
查看requestWindowFeature的代码逻辑:

调用的是PhoneWindow的requestFeature方法,里面会判断mContentParentExplicitlySet是否已经设置过值了,如果为true,就会报错!

public boolean requestFeature(int featureId) { 
   
    if (mContentParentExplicitlySet) { 
   
        throw new AndroidRuntimeException(
        	"requestFeature() must be called before adding content");
    }
    ......
}

其实,在PhoneWindow#setContentView方法的最后一行,会设置 mContentParentExplicitlySet 为ture,所以在之后调用,就会报错了。

MainActivity 继承AppCompatActivity的setContentView流程

在这里插入图片描述

在这里插入图片描述

1) MainActivity 继承至 AppCompatActivity

public class MainActivity extends AppCompatActivity { 
   
    @Override
    protected void onCreate(Bundle savedInstanceState) { 
   
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

1.1) AppCompatActivity#onCreate

在onCreate中,会设置factory,去看 installViewFactory

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) { 
   
    final AppCompatDelegate delegate = getDelegate();
    delegate.installViewFactory();
    delegate.onCreate(savedInstanceState);
    super.onCreate(savedInstanceState);
}

1.1.1) AppCompatDelegateImpl#installViewFactory

进行设置 Factory2

如:当创建 TextView 的时候,会因为这里设置了 Factory2,而把 TextView 替换为 AppCompatTextView

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

1.2) AppCompatActivity#setContentView

调用的是AppCompatDelegateImpl实现类中的 setContentView

@Override
public void setContentView(@LayoutRes int layoutResID) { 
   
    initViewTreeOwners();
    getDelegate().setContentView(layoutResID);
}

1.2.1) AppCompatDelegateImpl#setContentView

@Override
public void setContentView(int resId) { 
   
    // 确保ActionBar的特有UI结构构建完毕
    ensureSubDecor();
    ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
    // 确保ContentView的所有Child全部被移除干净
    contentParent.removeAllViews();
    // 将画面的内容布局解析并添加到ContentView下
    LayoutInflater.from(mContext).inflate(resId, contentParent);
    mAppCompatWindowCallback.getWrapped().onContentChanged();
}

1.2.1.1) AppCompatDelegateImpl#ensureSubDecor

private ViewGroup createSubDecor() { 
   
    .....
	
    mWindow.getDecorView();  // 同MainActivity继承Activity的逻辑,看那个就行
	
    ......
	
    subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
	
    final ContentFrameLayout contentView = (ContentFrameLayout) 
    	subDecor.findViewById(R.id.action_bar_activity_content);
	
	final ViewGroup windowContentView = (ViewGroup) 
		mWindow.findViewById(android.R.id.content);
	
	......

    if (windowContentView != null) { 
   
       // There might be Views already added to the Window's content view 
       // so we need to
       // migrate them to our content view
       while (windowContentView.getChildCount() > 0) { 
   
           final View child = windowContentView.getChildAt(0);
           windowContentView.removeViewAt(0);
           contentView.addView(child);
       }

       // Change our content FrameLayout to use the android.R.id.content id.
       // Useful for fragments.
       // 把 android.R.id.content 这里,改为 NO_ID
       windowContentView.setId(View.NO_ID);  
       
       // R.id.action_bar_activity_content 改为 android.R.id.content
       // 就是做了下替换操作
       contentView.setId(android.R.id.content); 

       // The decorContent may have a foreground drawable 
       // set (windowContentOverlay).
       // Remove this as we handle it ourselves
       if (windowContentView instanceof FrameLayout) { 
   
           ((FrameLayout) windowContentView).setForeground(null);
       }
    }
   
    // Now set the Window's content view with the decor
    mWindow.setContentView(subDecor);
    ......
    return subDecor;
}

1.2.1.1.1) PhoneWindow#getDecorView

public final @NonNull View getDecorView() { 
   
    if (mDecor == null || mForceDecorInstall) { 
   
        // 同MainActivity继承Activity的逻辑,看那个就行
        installDecor();
    }
    return mDecor;
}

1.2.1.1.2) abc_screen_simple.xml

<androidx.appcompat.widget.FitWindowsLinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/action_bar_root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:fitsSystemWindows="true">

    <androidx.appcompat.widget.ViewStubCompat
        android:id="@+id/action_mode_bar_stub"
        android:inflatedId="@+id/action_mode_bar"
        android:layout="@layout/abc_action_mode_bar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <include layout="@layout/abc_screen_content_include" />

</androidx.appcompat.widget.FitWindowsLinearLayout>

1.2.1.1.3) abc_screen_content_include.xml

<merge xmlns:android="http://schemas.android.com/apk/res/android">

    <androidx.appcompat.widget.ContentFrameLayout
            android:id="@id/action_bar_activity_content"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:foregroundGravity="fill_horizontal|top"
            android:foreground="?android:attr/windowContentOverlay" />

</merge>

1.2.1.2) LayoutInflater#inflate

渲染操作

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) { 
   
    ......
    return inflate(parser, root, attachToRoot);
    ......
}

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, 
	boolean attachToRoot) { 
   
    ......
    // 通过反射创建View
    final View temp = createViewFromTag(root, name, inflaterContext, attrs);
    ......
    ViewGroup.LayoutParams params = null;
    ......
    params = root.generateLayoutParams(attrs);
    ......
    // 创建子View
    rInflateChildren(parser, temp, attrs, true);
    ......
    root.addView(temp, params);
    ......
}

1.2.1.2.1) LayoutInflater#createViewFromTag

private View createViewFromTag(View parent, String name, Context context, 
	AttributeSet attrs) { 
   
    return createViewFromTag(parent, name, context, attrs, false);
}

View createViewFromTag(View parent, String name, Context context, 
	AttributeSet attrs, boolean ignoreThemeAttr) { 
   
    ......
    
    // 创建view
    View view = tryCreateView(parent, name, context, attrs);
    
    // 如果 view 为null,使用默认的创建 view 的方式创建
    // 也就是通过 AppCompatDelegateImpl 来创建 View
    if (view == null) { 
   
        final Object lastContext = mConstructorArgs[0];
        mConstructorArgs[0] = context;
        try { 
   
        	// 判断是否为sdk自己的 
            if (-1 == name.indexOf('.')) { 
   
                // sdk的
                view = onCreateView(context, parent, name, attrs);
            } else { 
   
                // 自己的,或者封装的
                view = createView(context, name, null, attrs);
            }
        } finally { 
   
            mConstructorArgs[0] = lastContext;
        }
    }

    return view;
}

1.2.1.2.1.1) LayoutInflater#tryCreateView

创建View,当 mFactory2 不为空,就用 factory2 来创建view,否则就返回 view为null

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

1.2.1.2.1.1.1) AppCompatDelegateImpl#onCreateView

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

1.2.1.2.1.1.1.1) AppCompatDelegateImpl#createView

从代码看,是调用 AppCompatViewInflater的createView方法

@Override
public View createView(View parent, final String name, 
	@NonNull Context context, @NonNull AttributeSet attrs) { 
   
    ......
    mAppCompatViewInflater = new AppCompatViewInflater();
    ......
    return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
            IS_PRE_LOLLIPOP,   
            true,              
            VectorEnabledTintResources.shouldBeUsed() 
    );
}

1.2.1.2.1.1.1.1.1) AppCompatViewInflater#createView

从下方代码可以看出,当创建的TextView等时,会进行替换操作,如TextView替换为AppCompatTextView

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;

    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;
        case "ToggleButton":
            view = createToggleButton(context, attrs);
            verifyNotNull(view, name);
            break;
        default:
            view = createView(context, name, attrs);
    }
    
    ......
    
    return view;
}

当为 TextView 的时候,就会被替换为 AppCompatTextView

@NonNull
protected AppCompatTextView createTextView(Context context, 
	AttributeSet attrs) { 
   
    return new AppCompatTextView(context, attrs);
}

1.2.1.2.1.2) LayoutInflater#onCreateView

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 { 
   
    // 这里是两个参数的,调用的是 onCreateView 的 onCreateView,进行重写了
    return onCreateView(name, attrs);
}

1.2.1.2.1.2.1) PhoneLayoutInflater#onCreateView

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

1.2.1.2.1.2.1.1) LayoutInflater#onCreateView

public final View createView(String name, String prefix, AttributeSet attrs)
        throws ClassNotFoundException, InflateException { 
   
    Context context = (Context) mConstructorArgs[0];
    if (context == null) { 
   
        context = mContext;
    }
    return createView(context, name, prefix, attrs);
}

1.2.1.2.1.2.1.1.1) LayoutInflater#createView

// 通过反射创建View
public final View createView(@NonNull Context viewContext, @NonNull String name,
    @Nullable String prefix, @Nullable AttributeSet attrs)
    throws ClassNotFoundException, InflateException { 
   

	clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
               mContext.getClassLoader()).asSubclass(View.class);

    constructor = clazz.getConstructor(mConstructorSignature);
    
    final View view = constructor.newInstance(args);
}

继承Activity时,设置requestWindowFeature(Window.FEATURE_NO_TITLE)可以生效,当继承AppCompatActivity时,就无效了?

需要使用supportRequestWindowFeature,因为AppCompatActivity类里面会覆盖设置。

public class MainActivity extends AppCompatActivity { 
   
    @Override
    protected void onCreate(Bundle savedInstanceState) { 
   
        super.onCreate(savedInstanceState);

// requestWindowFeature(Window.FEATURE_NO_TITLE);

        supportRequestWindowFeature(Window.FEATURE_NO_TITLE);

        setContentView(R.layout.activity_main);
    }
}

当调用requestWindowFeature,设置的代码如下,其中使用的mLocalFeatures这个:

public boolean requestFeature(int featureId) { 
   
    final int flag = 1<<featureId;
    mFeatures |= flag;
    mLocalFeatures |= mContainer != null ? (flag&~mContainer.mFeatures) : flag;
    return (mFeatures&flag) != 0;
}

当调用supportRequestWindowFeature,设置的代码如下,使用的mWindowNoTitle,标志位改变了

@Override
public boolean requestWindowFeature(int featureId) { 
   
    featureId = sanitizeWindowFeatureId(featureId);

    if (mWindowNoTitle && featureId == FEATURE_SUPPORT_ACTION_BAR) { 
   
        return false; // Ignore. No title dominates.
    }
    if (mHasActionBar && featureId == Window.FEATURE_NO_TITLE) { 
   
        // Remove the action bar feature if we have no title. 
        // No title dominates.
        mHasActionBar = false;
    }

    switch (featureId) { 
   
        case FEATURE_SUPPORT_ACTION_BAR:
            throwFeatureRequestIfSubDecorInstalled();
            mHasActionBar = true;
            return true;
        case FEATURE_SUPPORT_ACTION_BAR_OVERLAY:
            throwFeatureRequestIfSubDecorInstalled();
            mOverlayActionBar = true;
            return true;
        case FEATURE_ACTION_MODE_OVERLAY:
            throwFeatureRequestIfSubDecorInstalled();
            mOverlayActionMode = true;
            return true;
        case Window.FEATURE_PROGRESS:
            throwFeatureRequestIfSubDecorInstalled();
            mFeatureProgress = true;
            return true;
        case Window.FEATURE_INDETERMINATE_PROGRESS:
            throwFeatureRequestIfSubDecorInstalled();
            mFeatureIndeterminateProgress = true;
            return true;
        case Window.FEATURE_NO_TITLE:
            throwFeatureRequestIfSubDecorInstalled();
            mWindowNoTitle = true;
            return true;
    }

    return mWindow.requestFeature(featureId);
}

打印TextView,却输出的不是TextView?

1) activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/test_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="hello world"
        />
    
</LinearLayout>

2) MainActivity.java

public class MainActivity extends AppCompatActivity { 
   
    private static final String TAG = "AAAAAAA";

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) { 
   
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        TextView textView1 = findViewById(R.id.test_tv);
        Log.d(TAG, "textView1: " + textView1);

        TextView textView2 = new TextView(this);
        Log.d(TAG, "textView2: " + textView2);
    }
}

3) 打印结果

D/AAAAAAA: textView1: com.google.android.material.textview.MaterialTextView{ 
   d43c607 V.ED..... ......ID 0,0-0,0 #7f080195 app:id/test_tv}
D/AAAAAAA: textView2: android.widget.TextView{ 
   c9ba334 V.ED..... ......ID 0,0-0,0}

4) 分析

    当继承 AppCompatActivity 时,Activity的 super.onCreate(savedInstanceState) 中会进行默认设置 factory2,然后在执行 LayoutInflater#createViewFromTag 方法时,其中的 tryCreateView 方法会使用到factory2
    当factory2不为空时,就会用factory2去创建View,这个view是把TextView替换为了 AppCompatTextView。

LayoutInflater.inflate中参数的作用

LayoutInflater的部分源码:

1)当root不为空,attachToRoot为true,会执行一次addView
2)当root不为空,attachToRoot为false,会获取到属性
3)当root为空,attachToRoot为false,会直接return,属性没有获取

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, 
	boolean attachToRoot) { 
   

	......

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

效果演示:

inflate_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="200dp"
    android:layout_height="200dp"
    android:background="@color/teal_200"
    android:gravity="center"
    android:orientation="vertical">

    <Button
        android:id="@+id/btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</LinearLayout>

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/activity_linear_layout">

</LinearLayout>

MainActivity

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.Window;
import android.widget.LinearLayout;

public class MainActivity extends AppCompatActivity { 
   

    @Override
    protected void onCreate(Bundle savedInstanceState) { 
   
        super.onCreate(savedInstanceState);
        supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);

        LinearLayout linear_layout = findViewById(R.id.activity_linear_layout);

        // 正常
        // LayoutInflater.from(this).inflate(R.layout.inflate_layout, 
        // linear_layout, true);


        // 报错
        // 当执行inflate的时候,已经addView了,当再次addView调用,会报错,
        // 一个View只能有一个parent
        // The specified child already has a parent. 
        // You must call removeView() on the child's parent first.
        // View view = LayoutInflater.from(this).inflate(
        // R.layout.inflate_layout, linear_layout, true);
        // linear_layout.addView(view);


        // 正常
        // 第三个参数为false,不会去addView,所以,当调用addView的时候,就没什么问题
        // View view = LayoutInflater.from(this).inflate(
        // R.layout.inflate_layout, linear_layout, false);
        // linear_layout.addView(view);


        // 能显示,但显示不正常,inflate_layout没有父容器了
        // inflate_layout的布局无效,由inflate_layout的内容即button的大小决定
        View view = LayoutInflater.from(this).inflate(
        	R.layout.inflate_layout, null, false);
        linear_layout.addView(view);
    }
}

正常的:
在这里插入图片描述
不带有布局参数的:
在这里插入图片描述

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/153268.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • 久坐提醒 android wear,这五个理由告诉你为什么需要一块智能手表![通俗易懂]

    久坐提醒 android wear,这五个理由告诉你为什么需要一块智能手表![通俗易懂]来源:驱动号作者:2019-06-14/17:15访问量:摘要智能化可穿戴设备现如今已经普及,如果你还没有一块智能手表,现在是时候戴一下了。智能手表与智能手机相比,带来的都是潜在性的好处。虽然智能手表并不能保证你一定会多运动,也不能保证你一定会少玩手机。但是它可以提供一种让生活更好的选择,这种选择可能就足以改变我们的生活。过去的几年,关于智能手表的讨论都是”要不要买一款智能手表”,而…

  • 小米nfc模拟加密门禁卡详细图文教程(实测可用)—————– IC ID CUID卡区别

    小米nfc模拟加密门禁卡详细图文教程(实测可用)—————– IC ID CUID卡区别现在小区虽然都加装了智能门,可以通过手机NFC功能开启或者使用钥匙开启,但是有些用户并不知道原来手机是可以当钥匙使用的。今天我们来学习使用小米nfc模拟加密门禁卡,这样手机就可以变成一把钥匙了。以下是小米nfc模拟加密门禁卡步骤。1、非加密卡直接使用小米钱包的门卡模拟功能即可,如果能直接模拟的就不是加密卡。2、NFC手机支持的频段一般为13.56Mhz卡片,如果是其他门禁卡,手机贴上根本没反应的不可以模拟。3、只能模拟卡片的ID,不支持储值消费等功能。部分门禁等系统只认证卡片ID,所以有可能通过

  • 解决:java.lang.NoSuchMethodException: tk.mybatis.mapper.provider.base.BaseSelectProvider

    解决:java.lang.NoSuchMethodException: tk.mybatis.mapper.provider.base.BaseSelectProvider控制台报错:java.lang.NoSuchMethodException:tk.mybatis.mapper.provider.base.BaseSelectProvider.&lt;init&gt;()浏览器访问:http://localhost:8081/category/list?pid=0解决办法:应该导入importtk.mybatis…

  • Scrapy爬虫框架,入门案例(非常详细)「建议收藏」

    Scrapy爬虫框架,入门案例(非常详细)「建议收藏」Scrapy,Python开发的一个快速、高层次的屏幕抓取和web抓取框架,用于抓取web站点并从页面中提取结构化的数据。Scrapy用途广泛,可以用于数据挖掘、监测和自动化测试.其最初是为了页面抓取(更确切来说,网络抓取)所设计的,后台也应用在获取API所返回的数据(例如AmazonAssociatesWebServices)或者通用的网络爬虫.Scrapy吸引人的地…

  • 静态网页设计作品_web静态网页模板

    静态网页设计作品_web静态网页模板作品汇报新的一年新的成就,经历了一星期的思考,新的作品终于完成啦,14张页面经历了多次的修改和揣摩,希望大家能够喜欢。作品主题是“春节”页面框架页面应用了导航栏下拉的形式,使导航栏更加简洁,让浏览者留下空间感,导航栏下拉形式能够让浏览者清晰的浏览页面,页面以红色为底色,更能突出春节这一主题,能够突出春节的喜庆首页首页运用表单和超链接,能够突出该页面的作用,表单运用border-radius属性,使页面更美观春节简介和起源禁烟令的实施和疫情的爆发,让中国春节减少了许多的年味,大部分人都快遗

  • 不是单组分组函数「建议收藏」

    不是单组分组函数「建议收藏」问题:一:SELECT tablespace_name, SUM(bytes) freeFROM dba_free_space不是单组分组函数原因: 1、如果程序中使用了分组函数,则有两种情况可以使用:程序中存在group by,并指定了分组条件,这样可以将分组条件一起查询出来改为:  SELECT tablespace_name, SUM(bytes) freeFROM dba_free_spa…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号