亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Android中LayoutInflater的知識點有哪些

發布時間:2022-03-28 09:20:49 來源:億速云 閱讀:189 作者:iii 欄目:開發技術

這篇文章主要介紹“Android中LayoutInflater的知識點有哪些”的相關知識,小編通過實際案例向大家展示操作過程,操作方法簡單快捷,實用性強,希望這篇“Android中LayoutInflater的知識點有哪些”文章能幫助大家解決問題。

首先概括一下LayoutInflater是用來做什么的。

我們都知道,在開發Android應用程序的時候,編寫布局基本都是通過xml文件來編寫的。當然你也完全可以在代碼中純手寫布局,但是寫過的人都清楚,這樣編寫布局會非常麻煩。

那么通過xml編寫的布局文件是如何轉換成Android中的一個View對象從而顯示在應用程序當中的呢?這就是LayoutInflater的作用了。

簡單來說,LayoutInflater的工作就是將使用xml文件編寫的布局轉換成Android里的View對象,并且這也是Android中將xml布局轉換成View的唯一方式。

可能有些朋友會說,不對啊,我平時也沒怎么用過LayoutInflater,xml布局轉換成View不是調用Activity里的setContentView()方法就可以了嗎?

這是因為Android SDK在上層給我們做了一些很好的封裝,讓開發工作變得更加簡單。如果你打開setContentView()方法的源碼去了解一下,就會發現它的底層同樣也是使用的LayoutInflater:

@Override
public void setContentView(int resId) {
    ensureSubDecor();
    ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
    contentParent.removeAllViews();
    LayoutInflater.from(mContext).inflate(resId, contentParent);
    mAppCompatWindowCallback.getWrapped().onContentChanged();
}

那么LayoutInflater又是如何將一個xml布局轉換成一個View對象的呢?

這當然是一個非常復雜的過程,但是如果簡要概括的話,最重要的無非就是兩步:

  • 通過解析器來將xml文件中的內容解析出來。

  • 使用反射將解析出來的元素創建成View對象。

這里我不想在文章中帶著大家一步步追源碼,這樣文章看起來可能會又累又枯燥,因此我就只貼出一些我認為比較關鍵的代碼。

解析xml文件內容的代碼片段:

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

可以看到,這里獲取到了一個XmlResourceParser對象,用于對xml文件進行解析。由于具體的解析規則過于復雜,我們就不跟進去看了。

使用反射創建View對象的代碼片段:

public final View createView(@NonNull Context viewContext, @NonNull String name,
            @Nullable String prefix, @Nullable AttributeSet attrs)
            throws ClassNotFoundException, InflateException {
    ...
    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);
        constructor = clazz.getConstructor(mConstructorSignature);
        constructor.setAccessible(true);
        sConstructorMap.put(name, constructor);
    }
    ...
    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;
    }
    ...
}

看到這里,我們就將LayoutInflater大體的工作原理基本了解了。

但是正如前面所說,本篇文章并不是要帶著大家去讀源碼的,而是想要從用法層面對LayoutInflater有些新的理解。

那么LayoutInflater最常見的用法如下:

View view = LayoutInflater.from(context).inflate(resourceId, parent, false);

這段代碼的意思是,首先調用LayoutInflater的from()方法去獲取一個LayoutInflater的實例,然后再調用它的inflate()方法去解析并加載一個布局,從而轉換成一個View對象并返回。

然而我認為這段代碼對于新手來說卻及其不友好,甚至對于很多的老手來說也是。

我們來看一下inflate()方法的參數定義:

public View inflate(int resource, 
                    @Nullable ViewGroup root, 
                    boolean attachToRoot) {
    ...
}

inflate()方法接收3個參數,第一個參數resource還比較好理解,就是我們要解析加載的xml文件的資源id。第二個參數root,和第三個參數attachToRoot是什么意思?可能即使不少做過多年Android開發的程序員也未必能解釋得清楚。

而這段代碼在我們使用RecyclerView,或者使用Fragment時都是一定會用到的。我在寫《第一行代碼》時由于在很早的章節就要講RecyclerView的用法,但是卻又感覺很難向初學者解釋清楚LayoutInflater的相關內容,所以我一直都覺得這塊內容沒有講好。只能先用死記硬背的方式,暫時就記著這部分代碼必須這么寫。

而今天,我希望能將LayoutInflater真正講講清楚。

我們知道,Android的布局結構是一種樹狀結構。每個布局都可以包含若干個子布局,每個子布局又可以繼續包含子布局,以此構建出任意樣式的View呈現給用戶。

因此,我們大致可以明白,每個布局它都是要有一個父布局的。

這也是inflate()方法第二個參數root的作用,就是給當前要解析加載的xml布局指定一個父布局。

那么一個布局可不可以沒有父布局呢?當然也是可以的,這也是為什么root參數被標為@Nullable的原因。

但是如果我們inflate出來了一個沒有父布局的布局,又該如何去展示它呢?那自然是沒有辦法去展示的,所以只能后面再用addView的方式將它添加到某個現有的布局下面。又或者你inflate出來的布局就是個頂層布局,所以它不需要有父布局。但是這些場景都比較少見,因此大多數情況下,我們在使用LayoutInflater的inflate()方法時都是要指定父布局的。

另外,如果不為inflate出來的布局指定父布局,還會出現另外一種問題,我們通過一個例子來講解一下。

這里我們定義一個button_layout.xml布局文件,代碼如下所示:

<?xml version="1.0" encoding="utf-8"?>
<Button xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Button" />

這個布局文件非常簡單,里面只有一個按鈕。

接下來我們使用LayoutInflater來加載這個布局文件,并將它添加到一個現有的布局當中:

public class MainActivity extends Activity {
 
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		LinearLayout mainLayout = (LinearLayout) findViewById(R.id.main_layout);
		View buttonLayout = LayoutInflater.from(this).inflate(R.layout.button_layout, null);
		mainLayout.addView(buttonLayout);
	}
 
}

可以看到,這里我們并沒有給button_layout指定父布局,而是傳入了一個null。當第二個參數傳入null時,第三個參數就沒有意義了,因此可以不用指定。

但是前面也說了,一個布局如果沒有父布局的話沒辦法顯示出來呀,所以我們又使用了addView()方法將它添加到了一個現有布局當中。

代碼就是這么簡單,現在我們可以運行一下程序,效果如下圖所示:

Android中LayoutInflater的知識點有哪些

看上去好像沒啥問題,按鈕已經可以正常顯示出來了,說明button_layout.xml這個布局確實成功加載出來并且添加到現有的布局當中了。

但是如果你嘗試去調整一下按鈕的大小,你會發現不管你如何調整,按鈕的大小都是不會變的:

<?xml version="1.0" encoding="utf-8"?>
<Button xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="300dp"
    android:layout_height="100dp"
    android:text="Button" />

這里我們將按鈕的寬高指定成了300dp,高度指定成了100dp,重新運行程序界面毫無變化。

為什么會出現這樣的情況呢?

其實這里不管你將Button的layout_width和layout_height的值修改成多少,都不會有任何效果的,因為這兩個值現在已經完全失去了作用。平時我們經常使用layout_width和layout_height來設置View的大小,并且一直都能正常工作,就好像這兩個屬性確實是用于設置View的大小的。

而實際上則不然,它們其實是用于設置View在布局中的大小的,也就是說,首先View必須存在于一個布局中才行。這也是為什么這兩個屬性叫作layout_width和layout_height,而不是width和height。

而我們因為在使用LayoutInflater加載button_layout.xml這個布局時并沒有為它指定父布局,因此這里layout_width和layout_height屬性就都失去了作用。更準確點來講,所有以layout_開頭的屬性都會失去作用。

現在我們將代碼進行如下修改:

public class MainActivity extends Activity {
 
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		LinearLayout mainLayout = (LinearLayout) findViewById(R.id.main_layout);
		View buttonLayout = LayoutInflater.from(this).inflate(R.layout.button_layout, mainLayout, false);
		mainLayout.addView(buttonLayout);
	}
 
}

可以看到,這里將inflate()方法的第二個參數指定成了mainLayout。也就是說,我們為button_layout.xml這個布局指定了一個父布局。這樣的話,layout_width和layout_height屬性就可以生效了。

重新運行程序,效果如下圖所示:

Android中LayoutInflater的知識點有哪些

到這里為止,我們就將inflate()方法的第二個參數root的作用解釋得非常清楚了。那么還有一個問題就是,第三個參數attachToRoot又是什么意思呢?

注意觀察上述代碼,我們將第二個參數指定成mainLayout的同時,將第三個參數指定成了false。如果你嘗試將第三個參數指定成true,然后重新運行代碼,程序將會直接崩潰。崩潰信息如下:

Android中LayoutInflater的知識點有哪些

這個崩潰信息是在說,我們正在添加一個子View,但是這個子View已經有父布局了,需要讓父布局先調用removeView()移除子View后才能添加。

為什么修改第三個參數之后會出現這樣的錯誤呢?我們現在就來分析一下。

首先關注一下第三個參數的名字是什么,attachToRoot。從字面意思上看,是在問我們是否要添加到root上面。那么root是什么呢?再次觀察inflate()方法的定義,你會發現第二個參數不就是root嗎?

public View inflate(int resource, @Nullable ViewGroup root, boolean attachToRoot) {
    ...
}

也就是說,attachToRoot的意思,就是在問我們要不要將當前加載的xml布局添加到第二個參數傳入的父布局上面。如果傳入true,那么就意味著會添加,傳入false就表示不會添加。

所以在剛才的代碼當中,我們一開始在inflate()方法的第三個參數中傳入false,那么button_layout.xml布局是不會被添加到mainLayout當中的,我們后面就可以手動調用addView()方法將它添加到mainLayout當中。

而如果將第三個參數改成true,就表示button_layout.xml布局已經自動被添加到mainLayout當中了,此時再去調用一遍addView()方法,發現button_layout.xml已經有父布局了,自然就會拋出上面的異常。

經過這樣的解釋之后,你是否就對inflate()方法中的每一個參數的作用都理解清楚了呢?

其實理解到了這里,我們可以回過頭來再去看一看過去寫的代碼。比如說大家肯定都用過Fragment,在Fragment中加載一個布局我們通常都會這么寫:

public class MyFragment extends Fragment {

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, 
                             @Nullable ViewGroup container, 
                             @Nullable Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_layout, container, false);
    }
}

不知道你過去有沒有想過,為什么這里inflate()方法的最后一個參數一定要傳入false?

那么現在可以想一想了。觀察一下Fragment的相關源碼,你會發現它會將我們在onCreateView()方法中返回的View添加到一個Container當中:

void addViewToContainer() {
    // Ensure that our new Fragment is placed in the right index
    // based on its relative position to Fragments already in the
    // same container
    int index = mFragmentStore.findFragmentIndexInContainer(mFragment);
    mFragment.mContainer.addView(mFragment.mView, index);
}

這個情況和我們剛才的例子非常類似,也就是說,后續Fragment自己會有一個addView的操作,如果我們將inflate()方法的第三個參數傳入true,那么就會直接將inflate出來的布局添加到父布局當中。這樣后面再次addView的時候就會發現它已經有一個父布局了,從而拋出與上面同樣的崩潰信息。

關于“Android中LayoutInflater的知識點有哪些”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識,可以關注億速云行業資訊頻道,小編每天都會為大家更新不同的知識點。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

遂平县| 五河县| 台州市| 裕民县| 铜山县| 莆田市| 兴安县| 亳州市| 崇义县| 景东| 田林县| 太和县| 喀喇沁旗| 精河县| 丹江口市| 金山区| 兰考县| 阿荣旗| 水富县| 凤阳县| 万载县| 湘阴县| 五华县| 广西| 乌拉特中旗| 长沙县| 新安县| 洞头县| 高安市| 达拉特旗| 保德县| 商南县| 读书| 施秉县| 潮安县| 建瓯市| 维西| 开封县| 麻江县| 铜陵市| 麟游县|