原文地址:http://www.lxweimin.com/p/de7f651170be
一、簡述
LayoutInflater直譯為 布局填充器,它是用來創建布局視圖的,常用inflate()將一個xml布局文件轉換成一個View,下面先介紹下獲取LayoutInflater的三種方式 和 創建View的兩種方式。
- 1、獲取LayoutInflater的三種方式
LayoutInflater inflater = getLayoutInflater(); //調用Activity的getLayoutInflater()
LayoutInflater inflater =(LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
LayoutInflater inflater = LayoutInflater.from(context);
其實不管是哪種方式,最后都是通過方式2獲取到LayoutInflater的,如圖:
- 2、創建View的兩種方式
View.inflate();
LayoutInflater.from(context).inflate();
二、源碼分析
上面兩種創建View的方式都是開發中常用的,那兩者有什么關系嗎?下面對View.inflate()進行方法調用分析:
-
1、View.inflate()最終調用方法探究
1)按住Ctrl+鼠標左鍵查看View.inflate()方法
可以看到View.inflate()就是調用了LayoutInflater.from(context).inflate()。
好,到這一步要明確,不管我們研究哪種方式,實際上都研究方式2,即LayoutInflater.from(context).inflate()。
2)按住Ctrl+鼠標左鍵查看LayoutInflater.from(context).inflate(resource, root)方法。
嗯?LayoutInflater.from(context).inflate(resource, root)再調用了自己的重載inflate(resource, root, root != null)。
3)按住Ctrl+鼠標左鍵查看LayoutInflater.from(context).inflate(resource, root).inflate(resource, root, root != null)方法。
嗯??LayoutInflater.from(context).inflate(resource, root).inflate(resource, root, root != null)再再調用了自己的重載inflate(parser, root, attachToRoot)。
4)按住Ctrl+鼠標左鍵查看LayoutInflater.from(context).inflate(resource, root).inflate(resource, root, root != null).inflate(parser, root, attachToRoot)方法。
這下總算是到頭了,不過代碼太長,這里就截了一半的圖(這不是重點)。
好,重點來了,到這步我們可以明白一點,View.inflate()整個方法調用鏈如下:
View.inflate() = LayoutInflater.from(context)
.inflate(resource, root)
.inflate(resource, root, root != null)
.inflate(parser, root, attachToRoot)
-
2、LayoutInflater的inflate(parser, root, attachToRoot)做了什么?
由于代碼太長,不方便截圖,下面貼出代碼中的重點代碼:
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
...
省略代碼~
...
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);
}
}
...
省略代碼~
...
// 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方法中有以下四步操作:
1.通過使用XmlPullParser parser將xml布局文件轉換成視圖temp。
2.判斷ViewGroup root對象是否為null,來決定要不要給temp設置LayoutParams。
3.判斷boolean attachToRoot是否為true,來決定是否要把temp順便加到ViewGroup root中。
4.最后返回視圖temp。
到這里就把創建視圖的流程分析完了,接下來是比較 View.inflate()和 LayoutInflater.from(context).inflate()的區別。
- 3、View.inflate()和 LayoutInflater.from(context).inflate()的區別
1)View.inflate()第三個參數的解析:
開發中常常會對第三個參數(ViewGroup root)傳入null吧,通過上面對最終inflate方法的分析,可以知道,如果ViewGroup root取值為null,則得到的視圖temp不會被設置LayoutParams。下面做個試驗:
View itemView = View.inflate(parent.getContext(), android.R.layout.simple_list_item_1, null);
ViewGroup.LayoutParams params = itemView.getLayoutParams();
Log.e("CSDN_LQR", "params == null : " + (params == null));
打印結果如下:
同理,將第三個參數傳入一個確實存在的ViewGroup時,結果就是視圖temp能獲取到LayoutParams,有興趣的可以自己試試。
2)LayoutInflater.from(context).inflate()的優勢:
下面的場景分析將體現出LayoutInflater.from(context).inflate()的靈活性。
如果是在RecyclerView或ListView中使用View.inflate()創建布局視圖,又想對創建出來的布局視圖進行高度等參數設置時,會有什么瓶頸呢?
下面貼出我之前寫過的一段用于瀑布流適配器的代碼:
public class MyStaggeredAdapter extends RecyclerView.Adapter<MyStaggeredAdapter.MyViewHolder> {
private List<String> mData;
private Random mRandom = new Random();
public MyStaggeredAdapter(List<String> data) {
mData = data;
}
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//這里使用的是安卓自帶的文本控件布局
View itemView = View.inflate(parent.getContext(), android.R.layout.simple_list_item_1, null);
return new MyViewHolder(itemView);
}
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
//為實現瀑布流效果,需要對條目高度進行設置(讓各個條目的高度不同)
ViewGroup.LayoutParams params = holder.mTv.getLayoutParams();
params.height = mRandom.nextInt(200) + 200;
holder.mTv.setLayoutParams(params);
holder.mTv.setBackgroundColor(Color.argb(255, 180 + mRandom.nextInt(60) + 30, 180 + mRandom.nextInt(60) + 30, 180 + mRandom.nextInt(60) + 30));
holder.mTv.setText(mData.get(position));
}
@Override
public int getItemCount() {
return mData.size();
}
class MyViewHolder extends RecyclerView.ViewHolder {
TextView mTv;
public MyViewHolder(View itemView) {
super(itemView);
mTv = (TextView) itemView.findViewById(android.R.id.text1);
}
}
}
經過上面對View.inflate()的第三個參數解析之后,這代碼的問題一眼就能看出來了吧,沒錯,就是ViewGroup.LayoutParams params = holder.mTv.getLayoutParams();這行代碼獲取到的LayoutParams為空,不信?走一個。
接下來理所當然的要讓得到的LayoutParams不為空啦,所以將onCreateViewHolder()的代碼修改如下:
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//這里使用的是安卓自帶的文本控件布局
View itemView = View.inflate(parent.getContext(), android.R.layout.simple_list_item_1, parent);
return new MyViewHolder(itemView);
}
傳入的ViewGroup parent不為null,所以肯定獲取的LayoutParams不為空,但是又有一個問題,看報錯。
為什么會報這樣的錯呢?回看最終inflate()的四個步驟:
1.通過使用XmlPullParser parser將xml布局文件轉換成視圖temp。
2.判斷ViewGroup root對象是否為null,來決定要不要給temp設置LayoutParams。
3.判斷boolean attachToRoot是否為true,來決定是否要把temp順便加到ViewGroup root中。
4.最后返回視圖temp。
步驟2讓條目獲取的LayoutParams不為空沒錯,但是步驟3出問題了,當使用View.inflate(parent.getContext(), android.R.layout.simple_list_item_1, parent)傳入parent后,boolean attachToRoot的取值就是為true,所以創建出來的條目會順便添加到ViewGroup中(這里的ViewGroup就是RecyclerView),而RecyclerView本身就會自動將條目添加到自身,這樣就添加了兩次,故報錯。那為什么attachToRoot的取值是true呢?再看View.inflate()的整個方法調用鏈:
View.inflate() =
LayoutInflater.from(context)
.inflate(resource, root)
.inflate(resource, root, root != null)
.inflate(parser, root, attachToRoot)
boolean attachToRoot的取值取決于root(也就是parent)是否為空,這就是View.inflate()的瓶頸,它沒法靈活的指定boolean attachToRoot的取值。這里我就是只是想讓創建出來的視圖能得到LayoutParams,但不添加到ViewGroup中,這樣的要求可以通過LayoutInflater.from(context).inflate()來實現。所以下面將onCreateViewHolder()的代碼修改如下:
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext()).inflate(android.R.layout.simple_list_item_1,parent,false);
ViewGroup.LayoutParams params = itemView.getLayoutParams();
Log.e("CSDN_LQR", "params == null : " + (params == null));
return new MyViewHolder(itemView);
}
代碼中LayoutInflater.from(parent.getContext()).inflate(android.R.layout.simple_list_item_1,parent,false)傳入了parent(即ViewGroup不為null),所以創建出來的視圖可以得到LayoutParams,同時又指定attachToRoot的取值為false,即不添加到ViewGroup中。到這里,上面重覆添加子控件的問題就解決了,總結一下吧:
- View.inflate()第三個參數若不為null,則創建出來的視圖一定能獲得LayoutParams,反之,不一定。(下面會解釋)
- LayoutInflater.from(context).inflate()可以靈活的指定傳入的ViewGroup是否為空來決定創建出來的視圖能否獲得LayoutParams,同時又可以指定attachToRoot的取值來決定創建出來的視圖是否要添加到ViewGroup中。
三、小細節
上面已經將LayoutInflater的源碼分析完畢,現在還有一個小問題,其實跟本文主題沒多大關系,當作拓展來看吧。
前面說到,View.inflate()第三個參數若不為null,則創建出來的視圖一定能獲得LayoutParams,反之,不一定。這話怎么理解?
也就是說,即使View.inflate()第三個參數為null,創建出來的視圖也有可能獲得LayoutParams咯?是的,說到底,這個LayoutParams的有無,實際取決于條目本身是否有父控件,且看上面用到的simple_list_item_1布局:
發現了吧,就一個TextView,沒有父控件,那如果我給它加個父控件,同時使用最開始的方式也能順利得到LayoutParams呢?代碼如下:
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = View.inflate(parent.getContext(), R.layout.item_layout, null);
return new MyViewHolder(itemView);
}
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
ViewGroup.LayoutParams params = holder.mTv.getLayoutParams();
Log.e("CSDN_LQR", "params == null : " + (params == null));
...
控件設置
...
}
item_layout的布局代碼如下:
<?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:orientation="vertical">
<TextView
android:id="@android:id/text1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:minHeight="?android:attr/listPreferredItemHeightSmall"
android:textAppearance="?android:attr/textAppearanceListItemSmall"/>
</LinearLayout>
運行,果然可以獲得LayoutParams,打印結果如下: