編輯:關於Android編程
在看inflate()方法時,我們隨便看下如何獲得 LayoutInflater ,獲得LayoutInflater 實例有三種方式
LayoutInflater inflater =
getLayoutInflater();//調用Activity的getLayoutInflater()
LayoutInflater inflater = LayoutInflater.from(context);
LayoutInflater inflater = (LayoutInflater)context.getSystemService
(Context.LAYOUT_INFLATER_SERVICE);
其實,這三種方式本質是相同的,從源碼中可以看出:
getLayoutInflater()
Activity 的 getLayoutInflater() 方法是調用 PhoneWindow 的getLayoutInflater()方法,看一下該源代碼:
public PhoneWindow(Context context)
{
super(context);
mLayoutInflater = LayoutInflater.from(context);
}
可以看出它其實是調用
LayoutInflater.from(Context context)方法。
LayoutInflater.from(Context context)
public static LayoutInflater from(Context context)
{
LayoutInflater LayoutInflater = (LayoutInflater) context.getSystemService
(Context.LAYOUT_INFLATER_SERVICE);
if (LayoutInflater == null)
{
throw new AssertionError("LayoutInflater not found.");
}
return LayoutInflater;
}
可以看出它其實調用 context.getSystemService()。
所以這三種方式最終本質是都是調用的Context.getSystemService()。
接下來我們來看下inflate()方法,
inflate()
LayoutInflater的inflate方法一共有四種
public View inflate(int, ViewGroup)
public View inflate(XmlPullParser, ViewGroup)
public View inflate(int, ViewGroup, boolean)
public View inflate(XmlPullParser, ViewGroup, boolean)
查看源碼我們會發現
inflate(int, ViewGroup)調用的是
inflate(int, ViewGroup, boolean)方法
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
而
inflate(int, ViewGroup, boolean)調用的是
inflate(XmlPullParser, ViewGroup, boolean)方法
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
if (DEBUG) {
Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
+ Integer.toHexString(resource) + ")");
}
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
再看
View inflate(XmlPullParser, ViewGroup)我們會發現它調用的也是
inflate(XmlPullParser, ViewGroup, boolean)方法
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
if (DEBUG) {
Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
+ Integer.toHexString(resource) + ")");
}
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
所以呢?這四個方法都是
public View inflate(XmlPullParser, ViewGroup, boolean)方法,那其他三個我們就不看了,我們來分析這個方法。
看下別人對這個方法的參數是怎樣描述的:
返回值View:
返回的值是View指向的根節點。大家可能會有個疑問,第二個參數root不就是根結點嗎?那返回根結點的話,不就是我們傳進去的root嗎?這可不一定,大家知道返回根結點的VIEW之後,繼續往下看參數的具體講解。
第一個參數XmlPullParser:
也就說根據其他幾個方法傳進來的xml布局文件在這裡會被用傳進來的parser進行解析 第二個參數root:
表示根結點,它的作用主要取決於第三個參數
第三個參數attachToRoot:
表示是否將轉換後的VIEW直接添加在根結點上,如果是TRUE,那麼在轉換出來VIEW之後,內部直接會調用root.addView()來將其添加到root結點上,然後返回根結點,當然是我們傳進去的ROOT結點。如果設為FALSE,那只會將XML布局轉換成對應的VIEW,不會將其添加的我們傳進去的root結點上。
第三個參數可能這樣說比較難理解,我們來舉個例子:
1 . 創建一個activity_root.xml文件,一個垂直的線性布局id為root,只有一個TextView
" data-snippet-id="ext.80283f5a3bd6b5a922acb5a05793b5bf" data-snippet-saved="false" data-codota-status="done">
2 . 然後再建一個布局:add_layout.xml,也是一個垂直的線性布局背景顏色是紅色,裡面有個TextView
" data-snippet-id="ext.300890aa6fce25bc88f605b58eba0e99" data-snippet-saved="false" data-codota-status="done">
我們先來試試TRUE這個參數
public class InflateDomeActivity extends Activity{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
//顯示activity_root布局
setContentView(R.layout.activity_root);
//通過LayoutInflater.from(Context context);來獲取LayoutInflater的實例
LayoutInflater layoutInflater = LayoutInflater.from(this);
//獲取根結點的控件實例
LinearLayout linearLayout = (LinearLayout)findViewById(R.id.root);
//將activity_add加載到activity_root布局中
layoutInflater.inflate(R.layout.activity_add,linearLayout,true);
}
}
效果:
那如果將TRUE換為FALSE呢?
public class InflateDomeActivity extends Activity{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_root);
LayoutInflater layoutInflater = LayoutInflater.from(this);
LinearLayout linearLayout = (LinearLayout)findViewById(R.id.root);
layoutInflater.inflate(R.layout.activity_add,linearLayout,false);
}
}
<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwcmUgY2xhc3M9"brush:java;">
效果:
可以看到,我們的主布局沒有任何變化,也就是說add_layout.xml的布局沒有被添加到activity_mian.xml中;
我們開頭就講過,如果attachToRoot設為false,那轉換後的布局是不會被添加到root中的,會作為結果返回。
其實attachToRoot設為TRUE的代碼與下面的代碼是等價的:public class InflateDomeActivity extends Activity{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_root);
LayoutInflater layoutInflater = LayoutInflater.from(this);
LinearLayout linearLayout = (LinearLayout)findViewById(R.id.root);
View view = layoutInflater.inflate(R.layout.activity_add, linearLayout, false);
linearLayout.addView(view);
}
}
透過源碼分析LayoutInflater.inflate()的過程
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot)方法源碼:
can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// 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;
}
}
} catch (XmlPullParserException e) {
InflateException ex = new InflateException(e.getMessage());
ex.initCause(e);
throw ex;
} catch (Exception e) {
InflateException ex = new InflateException(
parser.getPositionDescription()
+ ": " + e.getMessage());
ex.initCause(e);
throw ex;
} finally {
// Don't retain static reference on context.
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
}
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
return result;
}
}" data-snippet-id="ext.438ac77f5aa684def78e1a328ada9568" data-snippet-saved="false" data-codota-status="done"> public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
final Context inflaterContext = mContext;
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = inflaterContext;
View result = root;
try {
// Look for the root node.
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
if (type != XmlPullParser.START_TAG) {
throw new InflateException(parser.getPositionDescription()
+ ": No start tag found!");
}
final String name = parser.getName();
if (DEBUG) {
System.out.println("**************************");
System.out.println("Creating root view: "
+ name);
System.out.println("**************************");
}
if (TAG_MERGE.equals(name)) {
if (root == null || !attachToRoot) {
throw new InflateException(" can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// 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;
}
}
} catch (XmlPullParserException e) {
InflateException ex = new InflateException(e.getMessage());
ex.initCause(e);
throw ex;
} catch (Exception e) {
InflateException ex = new InflateException(
parser.getPositionDescription()
+ ": " + e.getMessage());
ex.initCause(e);
throw ex;
} finally {
// Don't retain static reference on context.
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
}
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
return result;
}
}
我們一點點來分析:
再源碼的基礎上我們保留下一些核心代碼來分析下
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
//第一步:初始化
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context)mConstructorArgs[0];
mConstructorArgs[0] = mContext;
//注意這裡,在初始化時,result表示要返回的視圖,默認是返回root
View result = root;
…………
final String name = parser.getName();
//第二步:創建XML對應的空白VIEW:temp
if (TAG_MERGE.equals(name)) {
//如果是merge標簽:拋出異常 (因為merge標簽能夠將該標簽中的所有控件直接連在上一級布局上面,從而減少布局層級,假如一個線性布局替換為merge標簽,那麼原線性布局下的多個控件將直接連在上一層結構上,也就是如果加載進來的root根節點是root的話,那麼將來無法知道布局的根節點是什麼)
…………
} else {
View temp;
if (TAG_1995.equals(name)) {
temp = new BlinkLayout(mContext, attrs);
} else {
temp = createViewFromTag(root, name, attrs);
}
//第三步:從根結點中,獲取布局參數,設置到temp中
ViewGroup.LayoutParams params = null;
if (root != null) {
// 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中的子控件
rInflate(parser, temp, attrs, true);
//第五步:如果root不為空,而且attachToRoot設為TRUE,則將其視圖通過addView添加到root中
if (root != null && attachToRoot) {
root.addView(temp, params);
}
//第六步:如果root為空,或者attachToRoot設為FALSE,那麼就將TEMP視圖做為result返回
if (root == null || !attachToRoot) {
result = temp;
}
}
return result;
}
}
我們來逐步分析一下:
第一步:一進來是初始化部分:
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context)mConstructorArgs[0];
mConstructorArgs[0] = mContext;
//注意這裡,在初始化時,result表示要返回的視圖,默認是返回root
View result = root;
通過XML文件獲取布局中各個控件的屬性集:AttributeSet,注意:這裡通過XML只能知道布局裡每個控件的布局參數。那整個LAYOUT的布局參數呢,雖然我們在XML的根結點的布局可能寫的是layout_width:fill_parent,layout_height:fill_parent。但這只是很籠統的,系統要確實計算出它的寬高數來。這個寬高數的計算就是通過root中的屬性來得出來的,下面代碼中會提到。
一個很重要的部分在於最後一句話!!!
[java] view plain copy
View result = root;
result表示最後返回的視圖VIEW,所以這表示在默認情況下,返回root的視圖!!注意這只是在默認情況下,下面會對result進行賦值的!
第二步:創建XML對應的空白視圖temp
[java] view plain copy
//第二步:創建XML對應的空白VIEW:temp
if (TAG_MERGE.equals(name)) {
//如果是merge標簽,拋出異常
…………
} else {
View temp;
if (TAG_1995.equals(name)) {
temp = new BlinkLayout(mContext, attrs);
} else {
temp = createViewFromTag(root, name, attrs);
}
在這裡首先判斷XML的根結點是不是merge標簽,大家知道我們的merge標簽的主要作用是用來將merge標簽下的所有控件合並到其上層布局上,也就是說merge標簽下是沒有根控件的!因為merge標簽下的控件都是並列關系,所以如果merge標簽使用的inflate函數,那我們根本沒有辦法給它返回根視圖,所以肯定是要拋出異常的
如果不是merge標簽,就創建一個空白視圖,返回給temp,這裡的temp就是我們XML所對應的布局!
第三步:獲取root的布局參數,設置到temp中
//第三步:從根結點中,獲取布局參數,設置到temp中
ViewGroup.LayoutParams params = null;
if (root != null) {
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
temp.setLayoutParams(params);
}
}
在這裡看到,首先獲取到了ROOT的布局參數賦值為params,然後當attachToRoot為FALSE時,將參數設置到temp中;
第四步:初始化temp中的子控件
[java] view plain copy
//第四步:初始化temp中的子控件
rInflate(parser, temp, attrs, true);
//rInflate()其實是一個遞歸函數,用來遞歸建立temp下的所有控件的視圖
第五步:如果root不為空,而且attachToRoot設為TRUE,則將其視圖通過addView添加到root中
if (root != null && attachToRoot) {
root.addView(temp, params);
}
在這裡,就是當root不為空、attachToRoot為TRUE時,將構建好的temp視圖添加到root中,注意這裡的參數仍然從root中獲取的布局參數params!!!所以,無論attachToRoot值為如何,temp的布局參數都是從ROOT裡獲取的!!!!
第六步:如果root為空,或者attachToRoot設為FALSE,那麼就將TEMP視圖做為result返回
if (root == null || !attachToRoot) {
result = temp;
}
從這裡可以看到,如果root為空,獲取attachToRoot為FALSE,就會將temp做為結果返回!
到這裡整個過程就分析結束了,下面我們總結一下:
root的最基本作用,就是給我們傳進去的Layout提供布局參數信息
如果attachToRoot為TRUE,那麼會將Layout產生的布局視圖添加到root中,返回root,如果attachToRoot為FALSE,那麼會將Layout產生的布局視圖直接返回
如果做一個如下圖的Dialog,首先要定義樣式: stateUnchanged|adjustResize @null @null
在上一篇文章中,我們分析了Android系統進程間通信機制Binder中的Server在啟動過程使用Service Ma
之前寫的第一篇Fragment實例,和大多數人一開始學的一樣,都是通過FragmentTransaction的replace方法來實現,replace方法相
效果如下: 此圖片不會動,但實際上是會快速跳動的。 之前看到有支付寶的效果非常牛逼。就是進去看到余額呼噜噜的直接上躥下跳到具體數字,效果帥,