先扯兩句
上次寫的部分主要還是一些封裝的抽象方法,這部分只是單純的為我這種懶漢提供了便利罷了,而本次寫的內(nèi)容呢,則是對(duì)Title的封裝,不過(guò)這篇是我自己寫的title封裝,并沒有使用Toolbar,也不是閑得沒事干,之前使用Toolbar時(shí)UI要求title下邊加上一條1px的分割線,結(jié)果Toolbar的左側(cè)出現(xiàn)了16dp左右的空白無(wú)法處理,沒找到解決方案就放棄了Toolbar的使用。過(guò)些時(shí)間我會(huì)好好研究一下Toolbar,畢竟除去這點(diǎn)還是不錯(cuò)的,畢竟自己封裝,對(duì)于我這種懶漢來(lái)說(shuō)還是太麻煩了不是。而關(guān)于上述的情況,如果大家誰(shuí)知道如何解決方法也歡迎分享,小老兒不勝感激。
好了,閑言少敘,老規(guī)矩還是先上我的Git。
MyBaseApplication (https://github.com/BanShouWeng/MyBaseApplication)
并給大家展示個(gè)神器,叫Android知識(shí)點(diǎn)——目錄,好了,閑言少敘,下面進(jìn)入正題。
正文
時(shí)間已經(jīng)過(guò)去好久了,不知道大家還記得我之前的封裝嗎?好吧,反正我是忘得差不多了,只能重新查了查之前的博客。
首先是創(chuàng)建一個(gè)title_layout.xml存放title布局:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="@dimen/title_height">
<RelativeLayout
android:id="@+id/base_bg"
android:layout_width="match_parent"
android:layout_height="@dimen/title_height"
android:background="@color/blue">
<ImageView
android:id="@+id/base_back"
android:layout_width="50dp"
android:layout_height="50dp"
android:padding="@dimen/size_13"
android:src="@mipmap/back"
android:tint="@android:color/white" />
<TextView
android:id="@+id/base_title"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:gravity="center"
android:text="@string/title"
android:textColor="@android:color/white"
android:textSize="@dimen/size_20" />
<ImageView
android:id="@+id/base_right_icon2"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_toLeftOf="@+id/base_right_icon1"
android:contentDescription="@string/second_function_key"
android:padding="@dimen/size_13"
android:src="@mipmap/add"
android:tint="@android:color/white"
android:visibility="gone" />
<ImageView
android:id="@+id/base_right_icon1"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_alignParentRight="true"
android:contentDescription="@string/first_function_key"
android:padding="@dimen/size_13"
android:src="@mipmap/more"
android:tint="@android:color/white"
android:visibility="gone" />
<TextView
android:id="@+id/base_right_text"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_alignParentRight="true"
android:gravity="center"
android:text="@string/make_sure"
android:textColor="@android:color/white"
android:textSize="@dimen/size_17"
android:visibility="gone" />
</RelativeLayout>
</RelativeLayout>
隨后在BaseActivity的布局文件activity_base.xml中添加如下布局:
<include layout="@layout/title_layout"/>
就完成title的添加,不過(guò)在項(xiàng)目中,有些時(shí)候并不需要title,或者是需要根據(jù)UI設(shè)計(jì),去創(chuàng)建一些比較復(fù)雜的title,這個(gè)時(shí)候,就需要把這個(gè)刻板的title隱藏起來(lái)。因此這里便給title添加了一個(gè)id
<include
android:id="@+id/base_title_layout"
layout="@layout/title_layout"/>
隨后在BaseActivity中添加了如下方法:
/**
* 隱藏頭布局
*/
public void hideTitle() {
if (baseTitle == null) {
baseTitle = getView(R.id.base_title_layout);
}
baseTitle.setVisibility(View.GONE);
}
這樣就實(shí)現(xiàn)了上述的要求,可是雖然這里加了隱藏,但是大家都知道,實(shí)際上這個(gè)title的布局資源缺已經(jīng)加載了,所以在這些頁(yè)面中,如果依舊這么使用的話,就會(huì)造成資源的浪費(fèi)。因此,在這次修改中,我將這個(gè)部分也做了修改,而使用到的控件就是ViewStub。
(其中g(shù)etView方法看過(guò)我之前博客的應(yīng)該知道,就是封裝的findViewById方法,大家可以直接使用findViewById替換,只是需要強(qiáng)轉(zhuǎn)一下類型即可)
布局優(yōu)化
說(shuō)起ViewStub就不得不先說(shuō)說(shuō)重用布局文件(也就是傳說(shuō)中的布局優(yōu)化),其實(shí)網(wǎng)上可以查到大量的文章,而我卻是其中理解的相對(duì)淺顯的,所以如果大家想要具體了解的話,郭霖郭神的 Android最佳性能實(shí)踐(四)——布局優(yōu)化技巧
,而我這里呢,而我呢,則根據(jù)自己的應(yīng)用簡(jiǎn)單說(shuō)兩句就好了(無(wú)奈我這話癆的毛病,大家就忍耐一下這段“狗尾續(xù)貂”吧)
include
使用
include一看就知道是英語(yǔ)單詞,所以想知道他是做什么的,最簡(jiǎn)單的就是查查翻譯
除了那個(gè)包住感覺有點(diǎn)不靠譜以外,其他的意思還都差不多,那就簡(jiǎn)單了,我就理解為它的作用就是將關(guān)聯(lián)布局內(nèi)的所有控件都包括在當(dāng)前include的位置。而既然可以把其他布局引用過(guò)來(lái),那樣自然也就復(fù)用了控件,從而優(yōu)化了布局代碼。
就比如上述我之前的封裝,就是單獨(dú)封裝了title布局,然后在BaseActivity以及BaseFragment中進(jìn)行了復(fù)用。而因?yàn)檫@個(gè)布局文件已經(jīng)包含在了這個(gè)Iinclude標(biāo)簽下,也就相當(dāng)于其中的所有控件都在當(dāng)前的這個(gè)頁(yè)面中。因此,在使用其中的控件時(shí),就與當(dāng)前布局中的其他控件一樣,直接根據(jù)ID獲取即可。當(dāng)然,在我封裝的這個(gè)框架里,直接調(diào)用getView即可。
xml:
<include
android:id="@+id/base_title_layout"
layout="@layout/title_layout"/>
java:
/**
* 隱藏頭布局
*/
public void hideTitle() {
if (baseTitle == null) {
baseTitle = getView(R.id.base_title_layout);
}
baseTitle.setVisibility(View.GONE);
}
注意事項(xiàng)
有些東西就是這樣,在提供方便的同時(shí),自然就會(huì)出現(xiàn)一些隱患,而在使用include的時(shí)候,自然也有需要注意的地方。
- include在沒有設(shè)置約束參數(shù)的時(shí)候,會(huì)自動(dòng)根據(jù)layout引入的去定義。而當(dāng)我們需要更改的時(shí)候,則需要復(fù)寫這些約束參數(shù)即可,這也就是為什么我們常用的控件必須填寫“android:layout_width”與“android:layout_height”兩個(gè)參數(shù),而當(dāng)使用include的時(shí)候則不需要,因?yàn)閕nclude復(fù)用布局的最外層布局已經(jīng)夠約束好了其長(zhǎng)寬,至于有什么可以設(shè)置,郭神的博客中已經(jīng)說(shuō)明了:
非layout屬性則無(wú)法在<include>標(biāo)簽當(dāng)中進(jìn)行覆寫。另外需要注意的是,如果我們想要在<include>標(biāo)簽當(dāng)中覆寫layout屬性,必須要將layout_width和layout_height這兩個(gè)屬性也進(jìn)行覆寫,否則覆寫效果將不會(huì)生效。
- 在同一個(gè)布局中,大家應(yīng)該都會(huì)出現(xiàn)過(guò)id設(shè)置重復(fù)的時(shí)候吧,當(dāng)然,不會(huì)再程序運(yùn)行中出問(wèn)題,那是因?yàn)樵诔绦蜻\(yùn)行之前,就AS已經(jīng)告訴我們了,那樣做是不對(duì)滴??墒窃谑褂胕nclude的時(shí)候,AS還無(wú)法那么職能的判斷出究竟id是否設(shè)置重復(fù),因此只有在運(yùn)行過(guò)程中才會(huì)出現(xiàn)錯(cuò)誤(會(huì)調(diào)用include中的控件,而忽略當(dāng)前布局中同id的控件),所以使用時(shí)一定要慎之又慎。
merge
同樣,看到這英文還是查一下什么意思
在使用git或者svn做版本控制的時(shí)候呢,我們稱其為“和并”,這部分我在良秋的 Android 布局優(yōu)化之include與merge中查到了如下介紹:
merge翻譯成中文是合并的意思,在Android中通過(guò)使用merge能夠減少視圖的節(jié)點(diǎn)數(shù),從而減少視圖在繪制過(guò)程消耗的時(shí)間,達(dá)到提高UI性能的效果。
對(duì)于一個(gè)菜鳥來(lái)說(shuō),看到這么玄乎其玄的解釋,第一反應(yīng)就是蒙,如果是個(gè)勤懇好學(xué)的菜鳥,下一反應(yīng)應(yīng)該是去查一下什么是“節(jié)點(diǎn)”。而懶點(diǎn)的估計(jì)就直接放棄了。實(shí)際上,對(duì)于我來(lái)說(shuō),就是所謂android開發(fā)中的常用布局方式。
下面就開始研究這東西怎么用,說(shuō)實(shí)話,就我這種菜鳥,還真沒什么機(jī)會(huì)用到merge,不是不知道怎么用,其一真的是使用環(huán)境沒有include或者后面要說(shuō)到的ViewStub那么清晰,再者就是使用的條件比較苛刻,最后就是在該使用的時(shí)候,估計(jì)merge早不知道被我們忘哪去了。
下面先列舉一下良秋列舉的merge注意事項(xiàng):
- merge必須放在布局文件的根節(jié)點(diǎn)上;
- merge并不是一個(gè)ViewGroup,也不是一個(gè)View,它相當(dāng)于聲明了一些視圖,等待被添加。
- merge標(biāo)簽被添加到A容器下,那么merge下的所有視圖將被添加到A容器下。
- 因?yàn)閙erge標(biāo)簽并不是View,所以在通過(guò)LayoutInflate.inflate方法渲染的時(shí)候, 第二個(gè)參數(shù)必須指定一個(gè)父容器,且第三個(gè)參數(shù)必須為true,也就是必須為merge下的視圖指定一個(gè)父親節(jié)點(diǎn)。
- 如果Activity的布局文件根節(jié)點(diǎn)是FrameLayout,可以替換為merge標(biāo)簽,這樣,執(zhí)行setContentView之后,會(huì)減少一層FrameLayout節(jié)點(diǎn)。
- 自定義View如果繼承LinearLayout,建議讓自定義View的布局文件根節(jié)點(diǎn)設(shè)置成merge,這樣能少一層結(jié)點(diǎn)。
- 因?yàn)閙erge不是View,所以對(duì)merge標(biāo)簽設(shè)置的所有屬性都是無(wú)效的。
具體的內(nèi)容參見良秋的博客說(shuō),有詳細(xì)的說(shuō)明,這里我就不加以贅述了,只是說(shuō)兩點(diǎn)自己使用中走的彎路,算是對(duì)上述內(nèi)容的一個(gè)佐證吧。
上圖就是對(duì)圖1的佐證,也就是在布局的時(shí)候Android Studio會(huì)給予的提示,但是對(duì)于一些開發(fā)是不那么嚴(yán)謹(jǐn)?shù)?,可能注意不到merge的黃色提示,畢竟ImageView中沒有設(shè)置android:contentDescription(這個(gè)屬性是方便一些生理功能有缺陷的人使用應(yīng)用程序的,比如一些視力有障礙的用戶,如果用戶安裝了輔助瀏覽工具比如TalkBack,TalkBack就會(huì)大聲朗讀出用戶目前正在瀏覽的內(nèi)容。TextView控件TalkBack可以直接讀出里面的內(nèi)容(contentDescription的值),告訴用戶這個(gè)圖片到底是什么)會(huì)出現(xiàn)這類提示,沒有使用到的一些布局控件也會(huì)出現(xiàn)黃色提示(添加一個(gè)為沒有子控件,且沒有id的RelativeLayout)等。畢竟大多數(shù)出現(xiàn)這個(gè)提示是因?yàn)椴唤ㄗh,而不是不能用。
所以我也任性的在測(cè)試機(jī)上跑了一下,說(shuō)不定能用呢,結(jié)果:
于是我不得不死心了。
作為一個(gè)“生命不息,逗逼不止”的人,在使用中自然不可能只吃這一次虧,這次我創(chuàng)建了一個(gè)merge_test.xml布局文件:
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/merge_btn"
android:layout_height="wrap_content"
android:layout_width="match_parent"/>
</merge>
這次是在根節(jié)點(diǎn)上了吧,創(chuàng)建之后自然要使用了:
@SuppressLint("InflateParams")
@Override
protected void findViews() {
view = (Button) LayoutInflater.from(context).inflate(R.layout.merge_test, null).findViewById(R.id.merge_btn);
view.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.merge_btn:
view.setText(count++ + "");
break;
}
}
Shift + F10,merge,是時(shí)候展示你真正的技術(shù)了:
mmp,跪求我心里的陰影面積!難倒這就進(jìn)入了一條死路嗎?。?!好吧,作為一個(gè)行業(yè)菜鳥,這個(gè)時(shí)候不得不去翻看大神們的博客以求幫助。
<merge>標(biāo)簽是作為<include>標(biāo)簽的一種輔助擴(kuò)展來(lái)使用的,它的主要作用是為了防止在引用布局文件時(shí)產(chǎn)生多余的布局嵌套。
既然郭神已經(jīng)在博客中這么說(shuō)了,作為小菜鳥,暫時(shí)先這么用著,至于有沒有其他的玩法,就看日后的使用與積累了。
ViewStub
看到這個(gè)詞呢,這里就不翻譯了,實(shí)在是字面理解真不知道它是做什么的。所以這次就直接說(shuō)如何使用了。
其實(shí)在用法上,ViewStub與include還真有些類似,都是定義好的xml布局,然后在使用的時(shí)候,通過(guò)控件直接引用布局文件:
<ViewStub
android:id="@+id/base_title_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout="@layout/title_layout" />
大家在看看include的布局:
<include
android:id="@+id/base_title_layout"
layout="@layout/title_layout"/>
是不是區(qū)別并不大,在這最基本的引用上:
- include引用布局時(shí)使用的是layout="@layout/title_layout",而ViewStub使用的是android:layout="@layout/title_layout";
- ViewStub必須設(shè)置android:layout_width與android:layout_height,而從上面include中已經(jīng)說(shuō)明,include是不必須設(shè)置這兩個(gè)屬性的。
- 至于使用的環(huán)境,ViewStub是一些不常使用到的地方,這樣在不使用的時(shí)候,ViewStub可以節(jié)省資源,當(dāng)需要使用的時(shí)候,通過(guò)代碼解析一下,便可以得到對(duì)應(yīng)的布局;而include則更偏向于同一個(gè)布局的在多個(gè)頁(yè)面中的復(fù)用。
而關(guān)于ViewStub的是引用方法,在良秋 Android UI布局優(yōu)化之ViewStub中,關(guān)于有如下介紹:
- ViewStub是一個(gè)繼承了View類的視圖。
- ViewStub是不可見的,實(shí)際上是把寬高都設(shè)置為0
- 可以通過(guò)布局文件的android:inflatedId或者調(diào)用ViewStub的setInflatedId方法為懶加載視圖的跟節(jié)點(diǎn)設(shè)置ID
- ViewStub視圖在首次調(diào)用setVisibility或者inflate方法之前,一直存在于視圖樹中
- 只需要調(diào)用ViewStub的setVisibility或者inflate方法即可顯示懶加載的視圖
- 調(diào)用setVisibility或者inflate方法之后,懶加載的視圖會(huì)把ViewStub從父節(jié)點(diǎn)中替換掉
- ViewStub的inflate只能被調(diào)用一次,第二次調(diào)用會(huì)拋出異常,setVisibility可以被調(diào)用多次,但不建議這么做
- 為ViewStub賦值的android:layout_屬性會(huì)替換待加載布局文件的根節(jié)點(diǎn)對(duì)應(yīng)的屬性
- inflate方法會(huì)返回待加載視圖的根節(jié)點(diǎn)
這已經(jīng)基本上說(shuō)明了ViewStub的用法以及相關(guān)的原理,反正至少作為懶漢的我是沒辦法說(shuō)的更明白了。當(dāng)然,想了解更具體的原理的,可以去良秋的這篇博客中仔細(xì)學(xué)習(xí),而單純的使用而言,則不需要那么麻煩。
上面已經(jīng)給出了ViewStub在xml文件中如何引用,不過(guò)往上翻多費(fèi)勁啊,還是在下面再貼一下吧:
<ViewStub
android:id="@+id/base_title_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout="@layout/title_layout" />
總共四行屬性(具體參數(shù)可以修改):
- android:layout="@layout/title_layout":所要引用布局的layout xml文件;
- android:layout_width="match_parent" android:layout_height="wrap_content":所要引用布局的長(zhǎng)寬設(shè)置;
- android:id="@+id/base_title_layout":需要解析時(shí)查找對(duì)應(yīng)ViewStub的id。
誰(shuí)都別問(wèn)我為什么四行屬性,我這里只寫了三點(diǎn)啊,至于強(qiáng)迫癥患者的話,那我只能非常抱歉的說(shuō)一句————你管我?。?!
xml部分完成了,下面就該進(jìn)行java部分了,也就是ViewStub的inflate,其實(shí)也很簡(jiǎn)單:
/**
* Title ViewStub
*/
private ViewStub titleStub;
/**
* 控件初始化
*/
protected void initBaseView() {
if (titleStub == null) {
titleStub = getView(R.id.base_title_layout);
titleStub.inflate();
}
}
想要把ViewStub解析,總共分幾步?四步!
- 創(chuàng)建ViewStub對(duì)象;
- 判斷ViewStub是否已經(jīng)解析過(guò)了,若沒有進(jìn)行解析;
- 根據(jù)id找到需要解析的ViewStub;
- viewStub.inflate()執(zhí)行解析操作;
如此,viewStub就展示到我們的界面上了,再對(duì)其中的各個(gè)控件進(jìn)行操作,實(shí)際上就與include相同了,例如:
/**
* 設(shè)置標(biāo)題
*
* @param title 標(biāo)題的文本
* @param showBack 是否顯示返回鍵
*/
public void setTitle(String title, boolean showBack) {
initBaseView();
((TextView) getView(R.id.base_title)).setText(title);
if (showBack) {
baseBack = getView(R.id.base_back);
baseBack.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
}
}
最后的最后,思考了很久怎么寫結(jié)尾的我,終于還是抵不過(guò)懶漢性格,還是直接摘一段郭神的內(nèi)容放到這里吧:
另外需要提醒大家一點(diǎn),ViewStub所加載的布局是不可以使用<merge>標(biāo)簽的,因此這有可能導(dǎo)致加載出來(lái)的布局存在著多余的嵌套結(jié)構(gòu),具體如何去取舍就要根據(jù)各自的實(shí)際情況來(lái)決定了,對(duì)于那些隱藏的布局文件結(jié)構(gòu)相當(dāng)復(fù)雜的情況,使用ViewStub還是一種相當(dāng)不錯(cuò)的選擇的,即使增加了一層無(wú)用的布局結(jié)構(gòu),仍然還是利大于弊。
注
原本是打算將BaseActivity的封裝放到這里的,不過(guò)寫得篇幅過(guò)長(zhǎng),而且布局優(yōu)化的部分總歸是要寫的,所以這里就將布局優(yōu)化的部分單獨(dú)提出來(lái)寫了一篇,而BaseActivity封裝的內(nèi)容只能放到后一篇再去寫了。