您好,登錄后才能下訂單哦!
一、現狀
頁面多狀態布局是開發中常見的需求,即頁面在不同狀態需要顯示不同的布局,實現的方式也比較多,最簡單粗暴的方式就是在 XML 中先將不同狀態對應的布局隱藏起來,根據需要改變其可見狀態,如果多個界面公用相同的狀態布局,缺點也很明顯,繁瑣、重復、不優雅等,類似的實現也可以使用 ViewStub,這樣性能會更好些。所以我們要做的就是盡可能避免這些方式所導致的問題,更加高效、優雅的管理不同的狀態布局。
二、目標
我們要實現的 StatusView 要實現的主要功能如下:
效果預覽如下:
preview
三、實現
這里只對實現過程中一些比較重要的點進行分析。
3.1、初始化
首先有一個最重要的知識點需要明確,XML 布局中的每個View都有其對應的父 View,必然在其父View中都有固定的位置,如果是 Activity 對應的 XML,那XML根布局View的父View是誰呢?其實就是一個 id 為 android.R.id.content
的 View,如果是 Fragment 對應的 XML,那 XML 根布局 View 的父 View 可以通過 fragment.getView()
方法得到。所以現在我們可以得到XML 中每一個View和對應的 LayoutParams 位置信息。
既然有了 View 和其對應的 LayoutParams 位置信息,就可以通過其父 View 將指定的子 View 移除掉,然后將 StatusView 添加到被移除的 View 的位置,進而就可以控制 StatusView 來切換不同的狀態布局。
簡單總結下,就是用 StatusView 替換掉要進行多狀態布局切換的 View,這個 View 可以時 XML 中的任意 View。這也是直接在 Activity、Fragment 中使用 StatusView 要做的核心初始化工作。
那么 StatusView 又是個什么呢?其實就是一個繼承了 FrameLayout
的 ViewGroup,之所以要繼承 FrameLayout,因為 StatusView 此時僅僅是作為父容器存在的,并不關心內部各種狀態 View 的具體情況,所以使用 FrameLayout 就夠了,更有通用性。這樣 StatusView 也就可以在 XML 中使用了
先將上邊這部分內容轉化成代碼:
public class StatusView extends FrameLayout { ...... /** * 在 Activity 中的初始化方法,默認頁面的根布局使用多狀態布局 */ public static StatusView init(Activity activity) { View contentView = ((ViewGroup) activity.findViewById(android.R.id.content)).getChildAt(0); return init(contentView); } /** * 在 Activity 中的初始化方法 * @param viewId 使用多狀態布局的 ViewId */ public static StatusView init(Activity activity, @IdRes int viewId) { View rootView = ((ViewGroup) activity.findViewById(android.R.id.content)).getChildAt(0); View contentView = rootView.findViewById(viewId); return init(contentView); } /** * 在Fragment中的初始化方法 * @param viewId 使用多狀態布局的 ViewId */ public static StatusView init(Fragment fragment, @IdRes int viewId) { View rootView = fragment.getView(); View contentView = null; if (rootView != null) { contentView = rootView.findViewById(viewId); } return init(contentView); } /** * 用 StatusView 替換要使用多狀態布局的 View */ private static StatusView init(View contentView) { if (contentView == null) { throw new RuntimeException("ContentView can not be null!"); } ViewGroup parent = (ViewGroup) contentView.getParent(); if (parent == null) { throw new RuntimeException("ContentView must have a parent view!"); } ViewGroup.LayoutParams lp = contentView.getLayoutParams(); int index = parent.indexOfChild(contentView); parent.removeView(contentView); StatusView statusView = new StatusView(contentView.getContext()); statusView.addView(contentView); statusView.setContentView(contentView); parent.addView(statusView, index, lp); return statusView; } ...... }
如果在 XML 中使用 StatusView 如何進行初始化呢,自然是通過 onFinishInflate()
方法:
@Override protected void onFinishInflate() { super.onFinishInflate(); if (getChildCount() == 1) { View view = getChildAt(0); setContentView(view); } }
3.2、狀態布局的切換
StatusView 默認支持 Loading、Empty、Error 三種狀態布局,加上原始的頁面內容布局,一共四種。切換狀態布局時,我們做法是直接從 StatusView 中移除掉正在顯示的狀態布局,然后添加要顯示的狀態布局:
private void switchStatusView(View statusView) { if (statusView == currentView) { return; } removeView(currentView); currentView = statusView; addView(currentView); }
3.3、狀態布局的懶加載
在APP使用環境良好的情況下,有些狀態布局可能根本沒有顯示的機會,如果在初始化時一股腦的加載出來自然不可取,影響性能,所以我們要做的就是按需加載,即僅在狀態布局初次顯示時加載并初始化,之后復用即可:
private View generateStatusView(@LayoutRes int layoutId) { View statusView = viewArray.get(layoutId); if (statusView == null) { statusView = inflate(layoutId); viewArray.put(layoutId, statusView); configStatusView(layoutId, statusView); } return statusView; }
3.4、更自由的用法
一般的多狀態布局管理都會提供默認的 Loading、Empty、Error 三種狀態布局,并可以自定義對應的狀態布局, 并提供對應的開放 api。但這樣會有些局限性,如果有其它業務場景的狀態布局,雖然布局文件可以自定義,但原有的api方法調用起來難免會有違和感,并不友好!所以有必要在常用業務場景的基礎上再提供更加通用的api方法,并不局限于特定的場景。
目前的做法是用狀態布局和對應的索引之間的關系來實現:
// 添加指定索引對應的狀態布局 statusView.setStatusView(int index, @LayoutRes int layoutId) // 為指定索引的狀態布局設置初次顯示的監聽事件,用來進行狀態布局的相關初始化 statusView.setOnStatusViewConvertListener(int index, StatusViewConvertListener listener) // 顯示指定索引的狀態布局 statusView.showStatusView(int index)
3.5、注意事項
主要的點就這么多了,剩下的就是些屬性配置的內容,其實挺簡單的,更多細節和用法可參考GitHub: StatusView
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。