蛋疼:UI布局重構的幾個思考

這篇作為XDroid UI系列的最后一篇,我想談談在UI布局重構時的幾個思考和取舍。

四個月前,接手公司項目,隨即進行了一系列的重構,主要陣對底層庫如UI、Cache、Event、Net等。

對于UI,我們一定會面對一個事實:任何一個設計Api通信的界面,都會包含LoadingErrorEmptyContent四個狀態。
因此,我們有必要封裝一個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快速開發框架。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容