這篇作為XDroid UI系列的最后一篇,我想談談在UI布局重構時的幾個思考和取舍。
四個月前,接手公司項目,隨即進行了一系列的重構,主要陣對底層庫如UI、Cache、Event、Net等。
對于UI,我們一定會面對一個事實:任何一個設計Api通信的界面,都會包含
Loading
、Error
、Empty
、Content
四個狀態。
因此,我們有必要封裝一個ViewGroup
,來更方便的實現需求。
取一個什么名字?
根據其實現的效果,取名為ContentLayout。
藍圖:我們可以如何使用?
這其實屬于需求方面的內容了,先意淫一下吧。實現最終我們想這樣:
- 靈活,不受場地(布局層次)的限制,不受控件大小的限制
- 簡單:不需要寫很多重復代碼,api使用流暢
- 容易定制
思路:怎么實現上述需求?
我一開始有兩個思路:
- 在基類中,創建一個ContentLayout,將整個Activity & FrameLayout 的布局設置成其子布局,ContentLayout中提供對應的api。
- 將ContentLayout作為自定義ViewGroup,提供自定義attr設置
我們對兩種思路進行分析:
第一種思路其實也可實現,但是它有很多限制:
- ContentLayout作為了Activity & Fragment 的實際rootView,其大小一般都:
match_parent
,其大小和布局層級會受到很大的限制。 - 只能通過代碼設置loading、error、empty對應的布局
- 侵入性太強,特別針對content對應的布局文件
第二種思路,則可以完美實現前面的需求:
- ContentLayout作為一個ViewGroup,可以用在任何地方任何層級,適合界面的某部分需要loading的需求
- 其自定義attr可以方便的在布局文件中就指定對應狀態的布局資源文件
暫定四個自定義attr
- cl_contentLayoutId
- cl_emptyLayoutId
- cl_errorLayoutId
- cl_loadingLayoutId
看命名就知道弄啥的了...
繼承RelativeLayout還是FrameLayout?
RelativeLayout & FrameLayout 都可以實現需求,但它們有所區別:RelativeLayout會measure兩次。
因此我選擇FrameLayout來實現。
選擇LayoutId還是ViewId?
可能您對我這個提法有點疑惑,啥是LayoutId,啥是ViewId?
LayoutId即對應R.layout.xxx
布局資源id
ViewId即對應R.id.xxx
頁面中view id
其實這兩個東東對應兩個思路:
(1)viewid:即在ContentLayout下搞四個布局,分別設置一個id并作為那四個自定義屬性的值。
(2)layoutid:即搞幾個layout,作為那四個自定義屬性的值
對于思路一:我個人不太喜歡,這樣會讓ContentLayout子view眾多且層級復雜,不好調試,更不美觀
對于思路二:我很推崇,不同的狀態對應單獨的布局文件
因此,我決定選擇LayoutId的方式實現。
如何實現?
選擇LayoutId后,需要做兩件事:
- inflate->view
- 將view作為ContentLayout的子view
后面的事,就是切換哪個view顯示的問題了。
想到的可以優化的點:延遲inflate,需要的時候才inflate。
public QTContentLayout errorView(View errorView) {
bindView(errorView, STATE_ERROR);
return this;
}
content布局怎么搞?
ContentLayout中必定會有一個content布局,我們就沒必要搞一個單獨的layout。可以直接在contentlayout下搞一個子viewgroup。
默認情況下,將Contentlayout的第一個子view作為contentView。
如何實現呢?
重寫onFinishInflate
方法中:
int childCount = getChildCount();
if (childCount == 1) {
contentView = getChildAt(0);
}
如何保存view的狀態?
保存Ui狀態一般是通過onSaveInstanceState()
和onRestoreInstanceState
來實現。
@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>
蛋疼完畢,具體實現過程可看源碼。
XDroid:一個輕量級的Android快速開發框架。