這篇作為XDroid UI系列的最后一篇,我想談?wù)勗赨I布局重構(gòu)時(shí)的幾個(gè)思考和取舍。
四個(gè)月前,接手公司項(xiàng)目,隨即進(jìn)行了一系列的重構(gòu),主要陣對底層庫如UI、Cache、Event、Net等。
對于UI,我們一定會(huì)面對一個(gè)事實(shí):任何一個(gè)設(shè)計(jì)Api通信的界面,都會(huì)包含
Loading、Error、Empty、Content四個(gè)狀態(tài)。
因此,我們有必要封裝一個(gè)ViewGroup,來更方便的實(shí)現(xiàn)需求。
取一個(gè)什么名字?
根據(jù)其實(shí)現(xiàn)的效果,取名為ContentLayout。
藍(lán)圖:我們可以如何使用?
這其實(shí)屬于需求方面的內(nèi)容了,先意淫一下吧。實(shí)現(xiàn)最終我們想這樣:
- 靈活,不受場地(布局層次)的限制,不受控件大小的限制
- 簡單:不需要寫很多重復(fù)代碼,api使用流暢
- 容易定制
思路:怎么實(shí)現(xiàn)上述需求?
我一開始有兩個(gè)思路:
- 在基類中,創(chuàng)建一個(gè)ContentLayout,將整個(gè)Activity & FrameLayout 的布局設(shè)置成其子布局,ContentLayout中提供對應(yīng)的api。
- 將ContentLayout作為自定義ViewGroup,提供自定義attr設(shè)置
我們對兩種思路進(jìn)行分析:
第一種思路其實(shí)也可實(shí)現(xiàn),但是它有很多限制:
- ContentLayout作為了Activity & Fragment 的實(shí)際rootView,其大小一般都:
match_parent,其大小和布局層級會(huì)受到很大的限制。 - 只能通過代碼設(shè)置loading、error、empty對應(yīng)的布局
- 侵入性太強(qiáng),特別針對content對應(yīng)的布局文件
第二種思路,則可以完美實(shí)現(xiàn)前面的需求:
- ContentLayout作為一個(gè)ViewGroup,可以用在任何地方任何層級,適合界面的某部分需要loading的需求
- 其自定義attr可以方便的在布局文件中就指定對應(yīng)狀態(tài)的布局資源文件
暫定四個(gè)自定義attr
- cl_contentLayoutId
- cl_emptyLayoutId
- cl_errorLayoutId
- cl_loadingLayoutId
看命名就知道弄啥的了...
繼承RelativeLayout還是FrameLayout?
RelativeLayout & FrameLayout 都可以實(shí)現(xiàn)需求,但它們有所區(qū)別:RelativeLayout會(huì)measure兩次。
因此我選擇FrameLayout來實(shí)現(xiàn)。
選擇LayoutId還是ViewId?
可能您對我這個(gè)提法有點(diǎn)疑惑,啥是LayoutId,啥是ViewId?
LayoutId即對應(yīng)R.layout.xxx布局資源id
ViewId即對應(yīng)R.id.xxx頁面中view id
其實(shí)這兩個(gè)東東對應(yīng)兩個(gè)思路:
(1)viewid:即在ContentLayout下搞四個(gè)布局,分別設(shè)置一個(gè)id并作為那四個(gè)自定義屬性的值。
(2)layoutid:即搞幾個(gè)layout,作為那四個(gè)自定義屬性的值
對于思路一:我個(gè)人不太喜歡,這樣會(huì)讓ContentLayout子view眾多且層級復(fù)雜,不好調(diào)試,更不美觀
對于思路二:我很推崇,不同的狀態(tài)對應(yīng)單獨(dú)的布局文件
因此,我決定選擇LayoutId的方式實(shí)現(xiàn)。
如何實(shí)現(xiàn)?
選擇LayoutId后,需要做兩件事:
- inflate->view
- 將view作為ContentLayout的子view
后面的事,就是切換哪個(gè)view顯示的問題了。
想到的可以優(yōu)化的點(diǎn):延遲inflate,需要的時(shí)候才inflate。
public QTContentLayout errorView(View errorView) {
bindView(errorView, STATE_ERROR);
return this;
}
content布局怎么搞?
ContentLayout中必定會(huì)有一個(gè)content布局,我們就沒必要搞一個(gè)單獨(dú)的layout??梢灾苯釉赾ontentlayout下搞一個(gè)子viewgroup。
默認(rèn)情況下,將Contentlayout的第一個(gè)子view作為contentView。
如何實(shí)現(xiàn)呢?
重寫onFinishInflate方法中:
int childCount = getChildCount();
if (childCount == 1) {
contentView = getChildAt(0);
}
如何保存view的狀態(tài)?
保存Ui狀態(tài)一般是通過onSaveInstanceState()和onRestoreInstanceState來實(shí)現(xiàn)。
@Override
protected Parcelable onSaveInstanceState() {
Parcelable parcelable = super.onSaveInstanceState();
SavedState savedState = new SavedState(parcelable);
savedState.state = this.displayState;
return savedState;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
SavedState savedState = (SavedState) state;
super.onRestoreInstanceState(savedState.getSuperState());
this.displayState = savedState.state;
setDisplayState(this.displayState);
}
如何使用?
<cn.droidlover.qtcontentlayout.QTContentLayout
android:id="@+id/contentLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="5dp"
app:cl_emptyLayoutId="@layout/view_empty"
app:cl_errorLayoutId="@layout/view_error"
app:cl_loadingLayoutId="@layout/view_loading">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#854678"
android:gravity="center"
android:text="content"
android:textColor="@android:color/white"
android:textSize="28sp" />
</cn.droidlover.qtcontentlayout.QTContentLayout>
蛋疼完畢,具體實(shí)現(xiàn)過程可看源碼。
XDroid:一個(gè)輕量級的Android快速開發(fā)框架。