在開發中UI佈局是我們都會遇到的問題,隨着UI越來越多,佈局的重複性、複雜度也會隨之增長。Android官方給了幾個優化的方法,但是網絡上的資料基本上都是對官方資料的翻譯,這些資料都特別的簡單,經常會出現問題而不知其所以然。這篇文章就是對這些問題的更詳細的說明,也歡迎大家多留言交流。
一、include
首先用得最多的應該是include,按照官方的意思,include就是爲了解決重複定義相同佈局的問題。例如你有五個界面,這五個界面的頂部都有佈局一模一樣的一個返回按鈕和一個文本控件,在不使用include的情況下你在每個界面都需要重新在xml裏面寫同樣的返回按鈕和文本控件的頂部欄,這樣的重複工作會相當的噁心。使用include標籤,我們只需要把這個會被多次使用的頂部欄獨立成一個xml文件,然後在需要使用的地方通過include標籤引入即可。其實就相當於C語言、C++中的include頭文件一樣,我們把一些常用的、底層的API封裝起來,然後複用,需要的時候引入它即可,而不必每次都自己寫一遍。示例如下 :
my_title_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:id="@+id/my_title_parent_id"
android:layout_height="wrap_content" >
<ImageButton
android:id="@+id/back_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_launcher" />
<TextView
android:id="@+id/title_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="20dp"
android:layout_toRightOf="@+id/back_btn"
android:gravity="center"
android:text="我的title"
android:textSize="18sp" />
</RelativeLayout>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
include佈局文件:
<?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" >
<include
android:id="@+id/my_title_ly"
android:layout_width="match_parent"
android:layout_height="wrap_content"
layout="@layout/my_title_layout" />
<!-- 代碼省略 -->
</LinearLayout>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
這樣我們就可以使用my_title_layout了。
注意事項
- 使用include最常見的問題就是findViewById查找不到目標控件,這個問題出現的前提是在include時設置了id,而在findViewById時卻用了被include進來的佈局的根元素id。例如上述例子中,include時設置了該佈局的id爲my_title_ly,而my_title_layout.xml中的根視圖的id爲my_title_parent_id。此時如果通過findViewById來找my_title_parent_id這個控件,然後再查找my_title_parent_id下的子控件則會拋出空指針。代碼如下 :
View titleView = findViewById(R.id.my_title_parent_id) ;
// 此時 titleView 爲空,找不到。此時空指針
TextView titleTextView = (TextView)titleView.findViewById(R.id.title_tv) ;
titleTextView.setText("new Title");
- 1
- 2
- 3
- 4
其正確的使用形式應該如下:
// 使用include時設置的id,即R.id.my_title_ly
View titleView = findViewById(R.id.my_title_ly) ;
// 通過titleView找子控件
TextView titleTextView = (TextView)titleView.findViewById(R.id.title_tv) ;
titleTextView.setText("new Title");
- 1
- 2
- 3
- 4
- 5
或者更簡單的直接查找它的子控件:
TextView titleTextView = (TextView)findViewById(R.id.title_tv) ;
titleTextView.setText("new Title");
- 1
- 2
那麼使用findViewById(R.id.my_title_parent_id)爲什麼會報空指針呢? 我們來分析它的源碼看看吧。對於佈局文件的解析,最終都會調用到LayoutInflater的inflate方法,該方法最終又會調用rInflate方法,我們看看這個方法。
/**
* Recursive method used to descend down the xml hierarchy and instantiate
* views, instantiate their children, and then call onFinishInflate().
*/
void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs,
boolean finishInflate) throws XmlPullParserException, IOException {
final int depth = parser.getDepth();
int type;
// 迭代xml中的所有元素,挨個解析
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
if (type != XmlPullParser.START_TAG) {
continue;
}
final String name = parser.getName();
if (TAG_REQUEST_FOCUS.equals(name)) {
parseRequestFocus(parser, parent);
} else if (TAG_INCLUDE.equals(name)) {// 如果xml中的節點是include節點,則調用parseInclude方法
if (parser.getDepth() == 0) {
throw new InflateException("<include /> cannot be the root element");
}
parseInclude(parser, parent, attrs);
} else if (TAG_MERGE.equals(name)) {
throw new InflateException("<merge /> must be the root element");
} else if (TAG_1995.equals(name)) {
final View view = new BlinkLayout(mContext, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
rInflate(parser, view, attrs, true);
viewGroup.addView(view, params);
} else {
final View view = createViewFromTag(parent, name, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
rInflate(parser, view, attrs, true);
viewGroup.addView(view, params);
}
}
if (finishInflate) parent.onFinishInflate();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
這個方法其實就是遍歷xml中的所有元素,然後挨個進行解析。例如解析到一個標籤,那麼就根據用戶設置的一些layout_width、layout_height、id等屬性來構造一個TextView對象,然後添加到父控件(ViewGroup類型)中。標籤也是一樣的,我們看到遇到include標籤時,會調用parseInclude函數,這就是對標籤的解析,我們看看吧。
private void parseInclude(XmlPullParser parser, View parent, AttributeSet attrs)
throws XmlPullParserException, IOException {
int type;
if (parent instanceof ViewGroup) {
final int layout = attrs.getAttributeResourceValue(null, "layout", 0);
if (layout == 0) {// include標籤中沒有設置layout屬性,會拋出異常
final String value = attrs.getAttributeValue(null, "layout");
if (value == null) {
throw new InflateException("You must specifiy a layout in the"
+ " include tag: <include layout=\"@layout/layoutID\" />");
} else {
throw new InflateException("You must specifiy a valid layout "
+ "reference. The layout ID " + value + " is not valid.");
}
} else {
final XmlResourceParser childParser =
getContext().getResources().getLayout(layout);
try {// 獲取屬性集,即在include標籤中設置的屬性
final AttributeSet childAttrs = Xml.asAttributeSet(childParser);
while ((type = childParser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty.
}
if (type != XmlPullParser.START_TAG) {
throw new InflateException(childParser.getPositionDescription() +
": No start tag found!");
}
// 1、解析include中的第一個元素
final String childName = childParser.getName();
// 如果第一個元素是merge標籤,那麼調用rInflate函數解析
if (TAG_MERGE.equals(childName)) {
// Inflate all children.
rInflate(childParser, parent, childAttrs, false);
} else {// 2、我們例子中的情況會走到這一步,首先根據include的屬性集創建被include進來的xml佈局的根view
// 這裏的根view對應爲my_title_layout.xml中的RelativeLayout
final View view = createViewFromTag(parent, childName, childAttrs);
final ViewGroup group = (ViewGroup) parent;// include標籤的parent view
ViewGroup.LayoutParams params = null;
try {// 獲3、取佈局屬性
params = group.generateLayoutParams(attrs);
} catch (RuntimeException e) {
params = group.generateLayoutParams(childAttrs);
} finally {
if (params != null) {// 被inlcude進來的根view設置佈局參數
view.setLayoutParams(params);
}
}
// 4、Inflate all children. 解析所有子控件
rInflate(childParser, view, childAttrs, true);
// Attempt to override the included layout's android:id with the
// one set on the <include /> tag itself.
TypedArray a = mContext.obtainStyledAttributes(attrs,
com.android.internal.R.styleable.View, 0, 0);
int id = a.getResourceId(com.android.internal.R.styleable.View_id, View.NO_ID);
// While we're at it, let's try to override android:visibility.
int visibility = a.getInt(com.android.internal.R.styleable.View_visibility, -1);
a.recycle();
// 5、將include中設置的id設置給根view,因此實際上my_title_layout.xml中的RelativeLayout的id會變成include標籤中的id,include不設置id,那麼也可以通過relative的找到.
if (id != View.NO_ID) {
view.setId(id);
}
switch (visibility) {
case 0:
view.setVisibility(View.VISIBLE);
break;
case 1:
view.setVisibility(View.INVISIBLE);
break;
case 2:
view.setVisibility(View.GONE);
break;
}
// 6、將根view添加到父控件中
group.addView(view);
}
} finally {
childParser.close();
}
}
} else {
throw new InflateException("<include /> can only be used inside of a ViewGroup");
}
final int currentDepth = parser.getDepth();
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > currentDepth) && type != XmlPullParser.END_DOCUMENT) {
// Empty
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
整個過程就是根據不同的標籤解析不同的元素,首先會解析include元素,然後再解析被include進來的佈局的root view元素。在我們的例子中對應的root view就是id爲my_title_parent_id的RelativeLayout,然後再解析root view下面的所有元素,這個過程是從上面註釋的2~4的過程,然後是設置佈局參數。我們注意看註釋5處,這裏就解釋了爲什麼include標籤和被引入的佈局的根元素都設置了id的情況下,通過被引入的根元素的id來查找子控件會找不到的情況。我們看到,註釋5處的會判斷include標籤的id如果不是View.NO_ID的話會把該id設置給被引入的佈局根元素的id,即此時在我們的例子中被引入的id爲my_title_parent_id的根元素RelativeLayout的id被設置成了include標籤中的id,即RelativeLayout的id被動態修改成了”my_title_ly”。因此此時我們再通過“my_title_parent_id”這個id來查找根元素就會找不到了!
所以結論就是: 如果include中設置了id,那麼就通過include的id來查找被include佈局根元素的View;如果include中沒有設置Id, 而被include的佈局的根元素設置了id,那麼通過該根元素的id來查找該view即可。拿到根元素後查找其子控件都是一樣的。
二、ViewStub
我們先看看官方的說明:
ViewStub is a lightweight view with no dimension and doesn’t draw anything or participate in the layout. As such, it’s cheap to inflate and cheap to leave in a view hierarchy. Each ViewStub simply needs to include the android:layout attribute to specify the layout to inflate.
其實ViewStub就是一個寬高都爲0的一個View,它默認是不可見的,只有通過調用setVisibility函數或者Inflate函數纔會將其要裝載的目標佈局給加載出來,從而達到延遲加載的效果,這個要被加載的佈局通過android:layout屬性來設置。例如我們通過一個ViewStub來惰性加載一個消息流的評論列表,因爲一個帖子可能並沒有評論,此時我可以不加載這個評論的ListView,只有當有評論時我才把它加載出來,這樣就去除了加載ListView帶來的資源消耗以及延時,示例如下 :
<ViewStub
android:id="@+id/stub_import"
android:inflatedId="@+id/stub_comm_lv"
android:layout="@layout/my_comment_layout"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom" /
- 1
- 2
- 3
- 4
- 5
- 6
- 7
my_comment_layout.xml如下:
<?xml version="1.0" encoding="utf-8"?>
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:id="@+id/my_comm_lv"
android:layout_height="match_parent" >
</ListView>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
在運行時,我們只需要控制id爲stub_import的ViewStub的可見性或者調用inflate()函數來控制是否加載這個評論列表即可。示例如下 :
public class MainActivity extends Activity {
public void onCreate(Bundle b){
// main.xml中包含上面的ViewStub
setContentView(R.layout.main);
// 方式1,獲取ViewStub,
ViewStub listStub = (ViewStub) findViewById(R.id.stub_import);
// 加載評論列表佈局
listStub.setVisibility(View.VISIBLE);
// 獲取到評論ListView,注意這裏是通過ViewStub的inflatedId來獲取
ListView commLv = findViewById(R.id.stub_comm_lv);
if ( listStub.getVisibility() == View.VISIBLE ) {
// 已經加載, 否則還沒有加載
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
通過setVisibility(View.VISIBILITY)來加載評論列表,此時你要獲取到評論ListView對象的話,則需要通過findViewById來查找,而這個id並不是就是ViewStub的id。
這是爲什麼呢 ?
我們先看ViewStub的部分代碼吧:
@SuppressWarnings({"UnusedDeclaration"})
public ViewStub(Context context, AttributeSet attrs, int defStyle) {
TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.ViewStub,
defStyle, 0);
// 獲取inflatedId屬性
mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID);
mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0);
a.recycle();
a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.View, defStyle, 0);
mID = a.getResourceId(R.styleable.View_id, NO_ID);
a.recycle();
initialize(context);
}
private void initialize(Context context) {
mContext = context;
setVisibility(GONE);// 設置不可教案
setWillNotDraw(true);// 設置不繪製
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(0, 0);// 寬高都爲0
}
@Override
public void setVisibility(int visibility) {
if (mInflatedViewRef != null) {// 如果已經加載過則只設置Visibility屬性
View view = mInflatedViewRef.get();
if (view != null) {
view.setVisibility(visibility);
} else {
throw new IllegalStateException("setVisibility called on un-referenced view");
}
} else {// 如果未加載,這加載目標佈局
super.setVisibility(visibility);
if (visibility == VISIBLE || visibility == INVISIBLE) {
inflate();// 調用inflate來加載目標佈局
}
}
}
/**
* Inflates the layout resource identified by {@link #getLayoutResource()}
* and replaces this StubbedView in its parent by the inflated layout resource.
*
* @return The inflated layout resource.
*
*/
public View inflate() {
final ViewParent viewParent = getParent();
if (viewParent != null && viewParent instanceof ViewGroup) {
if (mLayoutResource != 0) {
final ViewGroup parent = (ViewGroup) viewParent;// 獲取ViewStub的parent view,也是目標佈局根元素的parent view
final LayoutInflater factory = LayoutInflater.from(mContext);
final View view = factory.inflate(mLayoutResource, parent,
false);// 1、加載目標佈局
// 2、如果ViewStub的inflatedId不是NO_ID則把inflatedId設置爲目標佈局根元素的id,即評論ListView的id
if (mInflatedId != NO_ID) {
view.setId(mInflatedId);
}
final int index = parent.indexOfChild(this);
parent.removeViewInLayout(this);// 3、將ViewStub自身從parent中移除
final ViewGroup.LayoutParams layoutParams = getLayoutParams();
if (layoutParams != null) {
parent.addView(view, index, layoutParams);// 4、將目標佈局的根元素添加到parent中,有參數
} else {
parent.addView(view, index);// 4、將目標佈局的根元素添加到parent中
}
mInflatedViewRef = new WeakReference<View>(view);
if (mInflateListener != null) {
mInflateListener.onInflate(this, view);
}
return view;
} else {
throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
}
} else {
throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
可以看到,其實最終加載目標佈局的還是inflate()函數,在該函數中將加載目標佈局,獲取到根元素後,如果mInflatedId不爲NO_ID則把mInflatedId設置爲根元素的id,這也是爲什麼我們在獲取評論ListView時會使用findViewById(R.id.stub_comm_lv)來獲取,其中的stub_comm_lv就是ViewStub的inflatedId。當然如果你沒有設置inflatedId的話還是可以通過評論列表的id來獲取的,例如findViewById(R.id.my_comm_lv)。然後就是ViewStub從parent中移除、把目標佈局的根元素添加到parent中。最後會把目標佈局的根元素返回,因此我們在調用inflate()函數時可以直接獲得根元素,省掉了findViewById的過程。
還有一種方式加載目標佈局的就是直接調用ViewStub的inflate()方法,示例如下 :
public class MainActivity extends Activity {
// 把commLv2設置爲類的成員變量
ListView commLv2 = null;
//
public void onCreate(Bundle b){
// main.xml中包含上面的ViewStub
setContentView(R.layout.main);
// 方式二
ViewStub listStub2 = (ViewStub) findViewById(R.id.stub_import) ;
// 成員變量commLv2爲空則代表未加載
if ( commLv2 == null ) {
// 加載評論列表佈局, 並且獲取評論ListView,inflate函數直接返回ListView對象
commLv2 = (ListView)listStub2.inflate();
} else {
// ViewStub已經加載
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
注意事項
- 判斷是否已經加載過, 如果通過setVisibility來加載,那麼通過判斷可見性即可;如果通過inflate()來加載是不可以通過判斷可見性來處理的,而需要使用方式2來進行判斷。
- findViewById的問題,注意ViewStub中是否設置了inflatedId,如果設置了則需要通過inflatedId來查找目標佈局的根元素。
三、Merge
首先我們看官方的說明:
The tag helps eliminate redundant view groups in your view hierarchy when including one layout within another. For example, if your main layout is a vertical LinearLayout in which two consecutive views can be re-used in multiple layouts, then the re-usable layout in which you place the two views requires its own root view. However, using another LinearLayout as the root for the re-usable layout would result in a vertical LinearLayout inside a vertical LinearLayout. The nested LinearLayout serves no real purpose other than to slow down your UI performance.
其實就是減少在include佈局文件時的層級。標籤是這幾個標籤中最讓我費解的,大家可能想不到,標籤竟然會是一個Activity,裏面有一個LinearLayout對象。
/**
* Exercise <merge /> tag in XML files.
*/
public class Merge extends Activity {
private LinearLayout mLayout;
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
mLayout = new LinearLayout(this);
mLayout.setOrientation(LinearLayout.VERTICAL);
LayoutInflater.from(this).inflate(R.layout.merge_tag, mLayout);
setContentView(mLayout);
}
public ViewGroup getLayout() {
return mLayout;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
使用merge來組織子元素可以減少佈局的層級。例如我們在複用一個含有多個子控件的佈局時,肯定需要一個ViewGroup來管理,例如這樣 :
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<ImageView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:scaleType="center"
android:src="@drawable/golden_gate" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="20dip"
android:layout_gravity="center_horizontal|bottom"
android:padding="12dip"
android:background="#AA000000"
android:textColor="#ffffffff"
android:text="Golden Gate" />
</FrameLayout>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
將該佈局通過include引入時就會多引入了一個FrameLayout層級,此時結構如下 :
使用merge標籤就會消除上圖中藍色的FrameLayout層級。示例如下 :
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<ImageView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:scaleType="center"
android:src="@drawable/golden_gate" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="20dip"
android:layout_gravity="center_horizontal|bottom"
android:padding="12dip"
android:background="#AA000000"
android:textColor="#ffffffff"
android:text="Golden Gate" />
</merge>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
效果圖如下 :
那麼它是如何實現的呢,我們還是看源碼吧。相關的源碼也是在LayoutInflater的inflate()函數中。
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;
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();
// m如果是erge標籤,那麼調用rInflate進行解析
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");
}
// 解析merge標籤
rInflate(parser, root, attrs, false);
} else {
// 代碼省略
}
} catch (XmlPullParserException e) {
// 代碼省略
}
return result;
}
}
void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs,
boolean finishInflate) throws XmlPullParserException, IOException {
final int depth = parser.getDepth();
int type;
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
if (type != XmlPullParser.START_TAG) {
continue;
}
final String name = parser.getName();
if (TAG_REQUEST_FOCUS.equals(name)) {
parseRequestFocus(parser, parent);
} else if (TAG_INCLUDE.equals(name)) {
// 代碼省略
parseInclude(parser, parent, attrs);
} else if (TAG_MERGE.equals(name)) {
throw new InflateException("<merge /> must be the root element");
} else if (TAG_1995.equals(name)) {
final View view = new BlinkLayout(mContext, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
rInflate(parser, view, attrs, true);
viewGroup.addView(view, params);
} else { // 我們的例子會進入這裏
final View view = createViewFromTag(parent, name, attrs);
// 獲取merge標籤的parent
final ViewGroup viewGroup = (ViewGroup) parent;
// 獲取佈局參數
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
// 遞歸解析每個子元素
rInflate(parser, view, attrs, true);
// 將子元素直接添加到merge標籤的parent view中
viewGroup.addView(view, params);
}
}
if (finishInflate) parent.onFinishInflate();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
其實就是如果是merge標籤,那麼直接將其中的子元素添加到merge標籤parent中,這樣就保證了不會引入額外的層級。
在開發過程中,我們一定要儘量去深究一些常用技術點的本質,這樣才能避免出了問題不知如何解決的窘境。追根究底才能知道爲什麼是這樣,也是自我成長的必經之路。