在 Android 開發(fā)中,有很多看起來 “約定俗成” 的代碼慎菲,有時候不妨停下來想一想深層次的含義嫁蛇。最近我在給 Fragment 填充布局時,寫到一句熟悉的代碼:
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.activity_main, container, false);
return view;
}
我又一次陷入了思索露该,其中的第三個參數(shù) false
到底是什么意思睬棚,此處能不能變?yōu)?true
。
探究
首頁我們還是通過函數(shù)本身的注釋來看各參數(shù)的作用,通過源碼查看如下:
/**
* Inflate a new view hierarchy from the specified xml resource. Throws
* {@link InflateException} if there is an error.
*
* @param resource ID for an XML layout resource to load (e.g.,
* <code>R.layout.main_page</code>)
* @param root Optional view to be the parent of the generated hierarchy (if
* <em>attachToRoot</em> is true), or else simply an object that
* provides a set of LayoutParams values for root of the returned
* hierarchy (if <em>attachToRoot</em> is false.)
* @param attachToRoot Whether the inflated hierarchy should be attached to
* the root parameter? If false, root is only used to create the
* correct subclass of LayoutParams for the root view in the XML.
* @return The root View of the inflated hierarchy. If root was supplied and
* attachToRoot is true, this is root; otherwise it is the root of
* the inflated XML file.
*/
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
...
}
上面的解釋比較官方抑党,但是不一定好理解包警。至少我就看了好幾遍,最后結(jié)合網(wǎng)上的總結(jié)才明白其含義新荤。
首先就 LayoutInflater.inflate()
這個函數(shù)而言揽趾,功能是將一段 XML 資源文件加載成為 View。所以通常用于將 XML 文件實例化為 View苛骨。然后獲取 View 上的組件最后操作之篱瞎。
對于這個函數(shù),我們通常容易引發(fā)歧義的地方在 attachToRoot
的值痒芝,當這個值為 true/false 時俐筋,是由顯著的區(qū)別的。
attachToRoot = true
當設(shè)置 attachToRoot = true
時, 表示 xml 實例化的 View 會被添加到第二個參數(shù) rootView 中严衬。 所以此時相當于處理了兩件事:
- 將 XML 實例化為 View澄者;
- 將實例化的 View 添加到非空的 rootView 中;
經(jīng)典的使用方法请琳,對于不為空的 container粱挡,
LayoutInflater.from(getContext()).inflate(R.layout.demo, container, true);
或者:
LayoutInflater.from(getContext()).inflate(R.layout.demo, container);
當 ViewGroup 不為空時,第三個 true 參數(shù)可以直接省略俄精。
attachToRoot = false
當設(shè)置 attachToRoot = false
時询筏,表示直接將 xml 實例化為 View,而不用將 View 添加到指定的 ViewGroup 中竖慧。如果需要將 View 加入到 ViewGroup 中嫌套,還需要手動的調(diào)用 ViewGroup.addView(view)
。
經(jīng)典寫法為:
LayoutInflater.from(getContext()).inflate(R.layout.demo, container, false);
注意:對于上面的第二個參數(shù) container圾旨,如果直接設(shè)置為 null踱讨,有可能導(dǎo)致 View 的位置或者大小等顯示不正確。所以上面代碼做了兩件事:
- 將 XML 實例化為 View;
- 根據(jù) ViewGroup 為子元素 View 設(shè)置正確的位置砍的、大小等 LayoutParams 屬性痹筛;
所以,實例化 View 時挨约,如果已經(jīng)明確知道父容器 ViewGroup味混,請直接傳遞 ViewGroup,避免出現(xiàn)子 View 顯示時的位置诫惭、大小等異常情形。
比較
對于非空的 ViewGroup continer蔓挖,以下兩句是等效的:
View buttonView = LayoutInflater.from(getContext()).inflate(R.layout.ugly_button, container, false);
container.addView(buttonView);
與
View buttonView = LayoutInflater.from(getContext()).inflate(R.layout.ugly_button, container, true);
應(yīng)用
通常將 XML 實例化為 View 涉及到以下常見情形:
ListView/RecyclerView 中 Adapter.getView
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
if (convertView == null) {
convertView = LayoutInflater.from(context).inflate(R.layout.ugly_button, parent, false);
viewHolder = new ViewHolder();
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
// ...
return convertView;
}
此處應(yīng)該設(shè)置 attachToRoot = true
因為 ListView 何時顯示夕土,以及如何顯示。應(yīng)該是由 ListView 決定的。Adapter.getView() 主要是根據(jù)指定的函數(shù)返回相應(yīng)的 View 實例怨绣。顯示邏輯則由 ListView 處理角溃,因此設(shè)置 attachToRoot = true
。
自定義 View
為了使得布局的層級足夠淺篮撑,我們在布局時通常使用 <merge></merge>
定義 layout 的根元素减细,然后將 layout 填充到自定義的 View 中。
res/layout/bottom_tab_item.xml:
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:id="@+id/tab_icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:paddingTop="2dp" />
<TextView
android:id="@+id/tab_label"
android:layout_marginTop="1dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14dp"
tools:text="Tab"/>
</LinearLayout>
</merge>
BottomTabItem.java:
public class BottomTabItem extends RelativeLayout {
...
public void init() {
LayoutInflater.from(getContext()).inflate(R.layout.bottom_tab_item, this, true);
}
...
}
對于自定義的 BottomTabItem赢笨,需要將定義的 XML 文件添加到其中未蝌。所以需要直接將 XML 實例化 View 作為子元素,添加到 BottonTabItem 中茧妒。
所以萧吠,對于自定義 View, 需要設(shè)置 attachToRoot = true
桐筏。
Fragment.onCreateView()
回顧我們最看我們最開始遇到的代碼塊:
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.activity_main, container, false);
return view;
}
onCreateView 函數(shù)直接反饋 View纸型,可以看出函數(shù)只需要拿到實例化的 View,至于 View 是如何添加到 Fragnent 中梅忌,則是底層 Fragment 的邏輯狰腌。
如果在 Fragment 中不恰到的處理這個 attchToRoot 參數(shù),會直接導(dǎo)致嚴重的崩潰性問題牧氮。而且通常出現(xiàn)這種問題時很不容易被發(fā)現(xiàn)琼腔。
所以,對于 Fragment 添加 layout蹋笼,需要設(shè)置 attachToRoot = false
展姐。
ListView headerView/footerView 等
對于 ListView.addHeaderView() / addFooterView() 也是我們經(jīng)常使用 LayoutInflator.inflate() 很多的場合。
從 ListView.addHeaderView(View view) 函數(shù)定義可以看到剖毯,ListView 需要將實例化的 View 添加作為 header/footer view圾笨,所以顯然需要的只是 View 的實例。添加或者刪除操作通過 ListView 內(nèi)部進行處理逊谋。
所以擂达,對于 ListView 添加 headerView, footerVuew 等,需要設(shè)置 attachToRoot = false
胶滋。
結(jié)語
知道 LayoutInflater.inflate 的細微知識點板鬓,可能會花費你一下午的時間。但是卻可以讓你對所寫的代碼更加自信究恤,并且能夠?qū)κ挛镎J識更加深刻俭令。何樂而不為呢?