文章目錄
Activity 中的 setContentView
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
setContentView 方法如下所示,調用的是 window 中的 setContentView,但是 window 中的只是一個抽象方法:
public abstract void setContentView(View view, ViewGroup.LayoutParams params);
而在 window 類的最開始也說了,window 唯一的實現類是 PhoneWindow。所以這裏調用的是 PhoneWindow 中的 setContentView,如下:
@Override
public void setContentView(int layoutResID) {
//父容器如果爲 null 則進行創建
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
//將傳入的資源Id 加載到 mContentParent 上
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
// return new DecorView(context, featureId, this, getAttributes());
//最終會創建一個 DecorView 賦值給 mDecor
mDecor = generateDecor(-1);
//.....
} else {
mDecor.setWindow(this);
}
//mContentParent是一個 VeiwGroup
if (mContentParent == null) {
//最終會根據當前窗口樣式加載一個資源文件,並且加載到 mDecor 中,
//並返回資源文件中 id 爲: @android:id/content 的ViewGroup,類型爲 FrameLayout
mContentParent = generateLayout(mDecor);
}
}
protected ViewGroup generateLayout(DecorView decor) {
// 獲取自當前主題的數據
TypedArray a = getWindowStyle();
//.......
// 裝飾 decor,判斷當前窗口的屬性,將符合條件的佈局添加到 decor
int layoutResource;
int features = getLocalFeatures();
else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
&& (features & (1 << FEATURE_ACTION_BAR)) == 0) {
layoutResource = R.layout.screen_progress;
} else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
//加載資源
layoutResource = R.layout.screen_custom_title;
} else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
layoutResource = res.resourceId;
layoutResource = R.layout.screen_title;
} else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
layoutResource = R.layout.screen_simple_overlay_action_mode;
} else {
layoutResource = R.layout.screen_simple;
}
mDecor.startChanging();
//調用 mDecor 的方法,將剛纔找到的系統佈局文件加載到 DecorView 中
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
// ID_ANDROID_CONTENT 這個ID 就是資源文件中的 Id
//點開findViewById ,就可以看到裏面用的 View 是 DecorView
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
//......
mDecor.finishChanging();
return contentParent;
}
在 setContentView 方法中,調用了 installDecor(),下面分析一下這個方法。
首先會創建一個 DecorView ,這是一個 繼承子 Fragment 的View 。也就是說 Window 類中包含了一個 DecorView。
接着就會判斷 mContentParent 是否爲 null,如果爲 null,就會調用 generateLayout 去創建,mContentParent 是一個 ViewGroup。在 generateLayout中會調用系統的資源,判斷系統當前的窗口模式。然後加載對應的佈局。最終就會將這個資源文件加載到 DecorView 中。並且會調用 findViewById 找到一個 ViewGroup,並返回,點開findViewById ,就可以看到裏面用的 View 是 DecorView 。至於加載的是那個 id,如下所示:
一般情況下,加載的資源layout中都有會 framelayout 這個 View,並且可以看到 id 爲 @android:id/content。你可以複製佈局名稱然後全局搜索查看一下這個佈局。
佈局如下:
<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>
到這裏,installDecor()方法中比較重要的地方已經看完了。梳理一下:
在 setContentView() 中創建 DecorView ,接着根據系統窗口的類型獲取到一個資源 layout。接着講這個資源文件 加載到 DecorView 中,並 通過findViewById 獲取了 資源文件中 id 爲 @android:id/content 的控件,將其強轉爲 ViewGroup 並返回。
在 setContentView 方法中,調用完 installDecor()方法後,往下還有非常重要的一句話
mLayoutInflater.inflate(layoutResID, mContentParent);
這個 layoutResID 就是 調用 setContentView 時傳入的。這裏將這個資源加載到了 mContentParent 上面,通過上面的分析我們可以知道 contentParent 就是 DecorView 中 id 爲 @android:id/content 的 Framelayout 佈局。
最終大致的邏輯如上圖
我們可以做一個測試,看一下我們分析的有沒有問題:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// setContentView(layout())
val view = LayoutInflater.from(this).inflate(layout(), null)
val decorView = window.decorView
val frameLayout = decorView.findViewById<FrameLayout>(android.R.id.content)
frameLayout.addView(view)
}
這是一個 activity 的 onCreate 方法,我註釋掉了 setContentView 方法,並直接加載了一個佈局。然後調用 window 中的 decorView。獲取到其中的 frameLayout,然後將 佈局添加進去。
最終運行顯示的效果是正常的。
下面給一張圖,清楚的展示了佈局加載的流程
AppCompatActivity 中的 setContentView
其實相比於 Activity 的 setContentView 還是有一些區別。主要是爲了兼容低版本的一些東西。
接下來就看一下源碼吧
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
getDelegate().setContentView(view, params);
}
@NonNull
public AppCompatDelegate getDelegate() {
if (mDelegate == null) {
mDelegate = AppCompatDelegate.create(this, this);
}
return mDelegate;
}
public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {
return new AppCompatDelegateImpl(activity, activity.getWindow(), callback);
}
這裏調用的 setContentView 也是一個抽象方法,最終調用的是 AppCompatDelegateImpl 中的
@Override
public void setContentView(View v) {
ensureSubDecor();
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
contentParent.addView(v);
mOriginalWindowCallback.onContentChanged();
}
private void ensureSubDecor() {
if (!mSubDecorInstalled) {
mSubDecor = createSubDecor();
}
}
private ViewGroup createSubDecor() {
TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
final LayoutInflater inflater = LayoutInflater.from(mContext);
//空的 ViewGroup
ViewGroup subDecor = null;
//根據一系列的判斷,最後加載一個 layout 到 ViewGroup 上
if (!mWindowNoTitle) {
subDecor = (ViewGroup) inflater.inflate(
R.layout.abc_dialog_title_material, null);
} else if (mHasActionBar) {
subDecor = (ViewGroup) LayoutInflater.from(themedContext)
.inflate(R.layout.abc_screen_toolbar, null);
} else {
if (mOverlayActionMode) {
subDecor = (ViewGroup) inflater.inflate(
R.layout.abc_screen_simple_overlay_action_mode, null);
} else {
subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
}
}
//獲取 subDecor 中的id
final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
R.id.action_bar_activity_content);
//這裏獲取的是 Window 中的 DecorView
final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
if (windowContentView != null) {
//設置一個 空的 Id
windowContentView.setId(View.NO_ID);
//給 contentView 設置一個新的 id
contentView.setId(android.R.id.content);
}
//最終還是調用了 window 中的 setContentView 方法
// Now set the Window's content view with the decor
mWindow.setContentView(subDecor);
return subDecor;
}
看流程,可以發現最終還是調用的 window 中的 setContentView,但是在這裏有多了一層
他首先會創建一個ViewGroup,然後根據一系列的判斷添加一個layout。最後調用 window 的 setContentView 。接着就會將我們傳入的佈局 添加到這個 ViewGroup 中,相比於 Activity。他中間多了一個 ViewGroup。
AppCompatActivity 的兼容性
一個小例子:新建一個 activity,在佈局中創建一個 ImageView。
<ImageView
android:id="@+id/test_iv"
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_marginTop="50dp"
android:src="@drawable/image"/>
接着讓 activity 首先繼承 Activity ,最後繼承 AppCompatActivity,然後打印 ImageView
android.widget.ImageView
androidx.appcompat.widget.AppCompatImageView
發現如果是繼承自 AppcompatActivity,則 iamgeView 最終創建的是 AppCompatImageView ,具有兼容性的 ImageView。這個是爲啥呢,下面分析一下源碼:
源碼分析:
首先在 AppCompatActivity 的 onCreate 方法中 調用了一個非常重要的方法,如下:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
final AppCompatDelegate delegate = getDelegate();
//安裝View 的工廠,這裏的 delegate 就是 AppCompatDelegateImpl
delegate.installViewFactory();
delegate.onCreate(savedInstanceState);
super.onCreate(savedInstanceState);
}
AppCompatDelegateImpl 實現了一個接口 LayoutInflater.Factory2
@Override
public void installViewFactory() {
LayoutInflater layoutInflater = LayoutInflater.from(mContext);
if (layoutInflater.getFactory() == null) {
//把 LayoutInflater 的 Factory 設置爲 this,也就是說創建 View 就會掉自己的 onCreateView 方法
//如果沒看懂就看一下 LayoutInflater 的源碼,LayoutInflater.from(mContext) 其實是一個單例
//如果設置了 Factory,那麼每次創建 View 時都會先執行 onCreateView 方法
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");
}
}
}
LayoutInflater.from(mContext) 其實是一個單例。他會將 LayoutInflater 的 Factory 設置爲 this。
當我們在使用這種 : LayoutInflater.from(this).inflate(layout(), parent) 代碼時,就會調用到 AppCompatDelegateImpl 類中 實現 LayoutInflater.Factory2的 onCreateView 方法,
public abstract class LayoutInflater {
@UnsupportedAppUsage(trackingBug = 122360734)
@Nullable
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!
}
View view;
if (mFactory2 != null) {
//這裏,LayoutInflater.from(this).inflate(layout(), null),如果使用 AppCompatActivity ,就會給 mFactory2 設置一個值,最終這裏就會調用到 AppCompatDelegateImpl 中的 onCreateView 中。
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;
}
}
通過上面可以看到 在使用 LayoutInflater.from(this).inflate(layout(), null) 時,是如何調用到 AppCompatDelegateImpl 中的 onCreateView 中的。
接着看一下 onCreateView
/**
* From {@link LayoutInflater.Factory2}.
*/
@Override
public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
return createView(parent, name, context, attrs);
}
@Override
public View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs) {
//.......
return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext, IS_PRE_LOLLIPOP, true, VectorEnabledTintResources.shouldBeUsed()
);
}
final View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs, boolean inheritContext,
boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
final Context originalContext = context;
//.......
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;
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);
}
return view;
}
//替換後的 AppComptImageView
@NonNull
protected AppCompatImageView createImageView(Context context, AttributeSet attrs) {
return new AppCompatImageView(context, attrs);
}
最終將替換後的 View 進行返回
這裏比較重要的是 View 的創建首先會走 mFactory2,然後纔會走 mFactory,只要不會 null,就會執行 Factory 的 onCreateView 方法。否則最後就會走 系統的 createView 方法。
LayoutInflater
主要用來實例化我我們的 layout 佈局
使用的方式
View.inflate(this,R.layout.activity_main,null) //1
LayoutInflater.from(this).inflate(R.layout.activity_main,null) //2
LayoutInflater.from(this).inflate(R.layout.activity_main,null,false) //3
//1
public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
LayoutInflater factory = LayoutInflater.from(context);
return factory.inflate(resource, root);
}
//2
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
//3
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
//......
}
從源碼中可以看到,第一種調用的是第二種,第二種調用的是第三種,根據有沒有傳入根佈局來傳入第三個參數。所以我們只需要看第三個方法就 ok
看一下 LayoutInflater.from()
public static LayoutInflater from(Context context) {
LayoutInflater LayoutInflater =
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
return LayoutInflater;
}
############################# ContextImpl ############
@Override
public Object getSystemService(String name) {
return SystemServiceRegistry.getSystemService(this, name);
}
//一個靜態的 Map
private static final Map<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
new ArrayMap<String, ServiceFetcher<?>>();
public static Object getSystemService(ContextImpl ctx, String name) {
ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
return fetcher != null ? fetcher.getService(ctx) : null;
}
可以看到這裏拿到的是一個系統的服務
接着往下看就可以看到這個服務是從一個 靜態的 Map 中獲取的。那麼這個 Map 是怎麼初始化的呢?
ContextImpl 中有一個靜態代碼塊,專門用來註冊各種服務,LAYOUT_INFLATER_SERVICE 也是其中的一個。
由此我們可以得知 LayoutInflater.from(this) 是一個系統服務,並且他是一個單例。
接着看一下是怎樣實例化 View 的
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
//獲取資源文件
final Resources res = getContext().getResources();
//XmlResourceParser 的解析器
XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
//...........
//保存傳進來的 Viwe
View result = root;
try {
advanceToRootNode(parser);
final String name = parser.getName();
//如果是 merge 標籤 就調用 rInflate,否則執行 else
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");
}
//這裏直接加載界面,忽略 marge 標記,直接傳入 root 進 rInflate 進行加載子 View
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// Temp是在xml中找到的根視圖,創建 View
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
// root 如果不爲空,則設置 layoutParams
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);
}
}
//先獲取到了temp,再把temp當做root傳進去rInflateChildren,進行加載temp後面的子view
rInflateChildren(parser, temp, attrs, true);
//把 View 添加到 root 佈局並設置佈局參數
if (root != null && attachToRoot) {
root.addView(temp, params);
}
//...
}
} catch (XmlPullParserException e) {
//...
}
return result;
}
}
//創建 View
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
//......
try {
//創建 View
View view = tryCreateView(parent, name, context, attrs);
//如果沒有創建成果
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
//判斷 name 是否爲 全類名,最終創建反射創建 View
//如果不是全類別,就需要進行拼接
if (-1 == name.indexOf('.')) {
view = onCreateView(context, parent, name, attrs);
} else {
view = createView(context, name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
} catch (InflateException e) {
//....
}
}
public final View tryCreateView(@Nullable View parent, @NonNull String name,
@NonNull Context context,
@NonNull AttributeSet attrs) {
View view;
//mFactory2 如果不爲 空,則直接調用 mFactory2 的 onCreateView
// AppCompatActivity 中就設置了 mFactory2
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;
}
在 AppCompatDelegateImp 中 爲什麼能走自己的 onCreateView 方法,就是因爲他設置了 mFactory ,所以纔可以攔截 View 的創建
如果說 mFactory 都等於 空,最後會自己創建 view,如果不爲空,則 View 的創建會被攔截,去執行對應 mFactory 中的方法
接着我們看下沒有使用 mFactory 的 View 創建
//默認的 View 創建流程
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(String name, AttributeSet attrs)
throws ClassNotFoundException {
//添加全類名
return createView(name, "android.view.", attrs);
}
public final View createView(@NonNull Context viewContext, @NonNull String name,
@Nullable String prefix, @Nullable AttributeSet attrs)
throws ClassNotFoundException, InflateException {
//從緩存中獲取
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);
//拿到構造函數mConstructorSignature = new Class[] {Context.class, AttributeSet.class};
//拿到爲兩個參數的構造函數
constructor = clazz.getConstructor(mConstructorSignature);
constructor.setAccessible(true);
sConstructorMap.put(name, constructor);
} else {
//.......
}
Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = viewContext;
Object[] args = mConstructorArgs;
args[1] = attrs;
try {
//反射創建 View
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;
}
}catch (Exception e) {
//......
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
大致看完了源碼,需要知道一些幾個問題
1,如果獲取 LayoutInflater
通過獲取系統的服務,並且是一個單例
2,如果使用 LayoutInflater
三種使用方式,在開頭說過了
3,佈局是如果被實例化的
最終佈局是通過反射進行實例化的
4,mFactory 的作用
攔截 View 的創建,使 View 的創建走自定義的流程,如 AppCompatView 的 setContentView 中。
5,xml 和 直接 new 出來的有啥區別
佈局文件中的VIew 創建調用的是兩個參數的構造,而直接 new 的是通過一個參數的構造。並且 xml 中定義的佈局最終是通過反射進行創建的,所以儘量不要多重嵌套
攔截 View 的創建
按照上面的分析可以知道,如果要攔截 View 的創建,就需要給 LayoutInflater 設置 Factory 。
override fun onCreate(savedInstanceState: Bundle?) {
intercept()
super.onCreate(savedInstanceState)
}
private fun intercept() {
val layoutInflater = LayoutInflater.from(this)
LayoutInflaterCompat.setFactory2(layoutInflater, object : LayoutInflater.Factory2 {
override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? {
return onCreateView(null, name, context, attrs)
}
override fun onCreateView(
parent: View?, name: String, context: Context, attrs: AttributeSet
): View? {
Log.e("BaseSkinActivity", "攔截到 View 的創建")
//攔截 View 的創建
return if (name == "Button") {
val button = Button(this@BaseSkinActivity)
button.id = R.id.test_btn
button.text = "攔截"
return button
} else null
}
})
}
上面給 LayoutInflater 設置了一個 Factory,攔截了 VIew 的創建
在 onCreateView 中,判斷如果是 Button,就修改他顯示的內容。
最終的結果就是攔截成功了。
到這裏整片文章就弄完了,如果有問題還請指出!,對你有幫助的還請點個贊!謝謝
參考自 紅橙Darren 的視頻