如今絕大多數 App 都須要請求網絡數據,loading 動畫必不可少,而網絡請求會存在失敗的狀況,因此一般有 loading 頁面也會有加載失敗或者別的頁面。java
這些頁面的樣式大多時候是統一的,通常會想到的處理方式是把這些界面 include 到一個佈局裏,而後經過顯示隱藏的方式來切換所需的視圖。這樣會寫不少重複代碼,可能會把這部分切換的邏輯抽取到 Activity 基類中或者抽取到一個自定義 View 裏管理。android
不過這些封裝都不能解決自己存在的問題。不方便修改樣式,樣式有變更就須要修改已經封裝好的代碼。總要在佈局上增長 include 代碼或添加一個處理切換界面的自定義 View,多多少少有些冗餘。git
後面有大佬寫了工具把這些處理好了,好比目前都有上千個 star 的庫 LoadSir 和 Gloading 。這兩個庫都是很不錯的,使用起來都很靈活易用。不過也有點小小的不足,由於佈局上一般會有標題,咱們就只能每次都對標題欄下方的子 View 進行 loading。因爲標題欄樣式大多時候是統一的,一般會在佈局 include 一個標題欄,而後把標題欄的初始化封裝在 Activity 基類中……有沒發現和前面說的很像?這麼寫會存在着與前面相同的問題。github
既然標題欄會影響到 loading 的區域,並且標題欄的一般寫法所存在的問題也和 loading 界面的類似,那麼若是一個 loading 庫把標題欄一塊兒管理了,會怎麼樣呢?服務器
LoadingHelper 是基於 Adapter 思想 和 ActionBar 原理實現的一個深度解耦 loading 界面和標題欄的工具,只用了一個 Kotlin 文件實現,不算上註釋只有 200 多行代碼。markdown
在 build.gradle
添加依賴:網絡
dependencies { implementation 'com.dylanc:loadinghelper:2.1.0' } 複製代碼
第一步,建立一個適配器繼承 LoadingHelper.Adapter<VH extends ViewHolder>
,寫法與 RecyclerView.Adapter
相似。若是須要實現點擊從新請求數據,能夠在點擊事件調用 holder.getOnReloadListener.onReload()
方法。app
public class LoadingAdapter extends LoadingHelper.Adapter<LoadingHelper.ViewHolder> { @NonNull @Override public LoadingHelper.ViewHolder onCreateViewHolder(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) { return new LoadingHelper.ViewHolder(inflater.inflate(R.layout.lce_layout_loading_view, parent, false)); } @Override public void onBindViewHolder(@NonNull LoadingHelper.ViewHolder holder) { } } 複製代碼
第二步,註冊適配器,傳一個視圖類型。有五個默認類型,也能夠傳任意類型數據進行註冊。ide
LoadingHelper loadingHelper = new LoadingHelper(this); loadingHelper.register(ViewType.LOADING, new LoadingAdapter()); // 當須要支持點擊從新請求數據時 loadingHelper.setOnReloadListener(() -> {}) 複製代碼
若是想註冊成全局的適配器,須要配置默認的適配器池。工具
LoadingHelper.setDefaultAdapterPool(adapterPool -> { adapterPool.register(ViewType.LOADING, new LoadingAdapter()); return Unit.INSTANCE; }); 複製代碼
第三步,顯示對應類型的視圖。
loadingHelper.showView(viewType); loadingHelper.showLoadingView(); // 對應視圖類型 ViewType.LOADING loadingHelper.showContentView(); // 對應視圖類型 ViewType.CONTENT loadingHelper.showErrorView(); // 對應視圖類型 ViewType.ERROR loadingHelper.showEmptyView(); // 對應視圖類型 ViewType.EMPTY 複製代碼
動態更新已顯示視圖
在顯示了視圖以後,能夠對視圖進行更改刷新。用法和 RecyclerView.Adapter
同樣,調用 notifyDataSetChanged()
後,會執行適配器的 onBindViewHolder()
方法。
ErrorAdapter errorAdapter = loadingHelper.getAdapter(ViewType.ERROR); errorAdapter.errorText = "服務器繁忙,請稍後重試"; errorAdapter.notifyDataSetChanged(); 複製代碼
若是是普通的標題欄,就是簡單地在內容的上方添加標題欄。
這就要用到添加裝飾頭部的方法,設置以前須要註冊一個繼承 LoadingHelper.Adapter<VH extends ViewHolder>
的適配器,設置後就會在內容的上方添加該適配器建立的 View 了,能夠添加多個。
loadingHelper.register(ViewType.TITLE, new TitleAdapter("標題名")); loadingHelper.register(VIEW_TYPE_SEARCH, new SearchHeaderAdapter(onSearchListener)); loadingHelper.setDecorHeader(ViewType.TITLE, VIEW_TYPE_SEARCH); 複製代碼
若是是特殊的標題欄,好比有聯動效果,就不能直接使用上面的方式了。
先實現一個不含內容的佈局。
<?xml version="1.0" encoding="utf-8"?> <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true"> <com.google.android.material.appbar.AppBarLayout android:id="@+id/app_bar" android:layout_width="match_parent" android:layout_height="@dimen/app_bar_height" android:fitsSystemWindows="true" android:theme="@style/AppTheme.AppBarOverlay"> <com.google.android.material.appbar.CollapsingToolbarLayout android:id="@+id/toolbar_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" app:contentScrim="?attr/colorPrimary" app:layout_scrollFlags="scroll|exitUntilCollapsed" app:toolbarId="@+id/toolbar"> <androidx.appcompat.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" app:layout_collapseMode="pin" app:popupTheme="@style/AppTheme.PopupOverlay" /> </com.google.android.material.appbar.CollapsingToolbarLayout> </com.google.android.material.appbar.AppBarLayout> <FrameLayout android:id="@+id/content_parent" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior" /> </androidx.coordinatorlayout.widget.CoordinatorLayout> 複製代碼
建立一個類繼承另外一個適配器 LoadingHelper.DecorAdapter
,加載實現的佈局,並指定一個 contentParent。
public class ScrollDecorAdapter extends LoadingHelper.DecorAdapter { @NotNull @Override public View onCreateDecorView(@NotNull LayoutInflater inflater) { return inflater.inflate(R.layout.layout_scrolling, null); } @NotNull @Override public ViewGroup getContentParent(@NotNull View decorView) { return decorView.findViewById(R.id.content_parent); } } 複製代碼
最後調用一下設置裝飾適配器的方法。
loadingHelper.setDecorAdapter(new ScrollDecorAdapter()); 複製代碼
上述的兩種使用方式都是能夠進行屢次設置,不過每次設置會把上一次設置的樣式給替換掉。
雖然本庫主要是用來管理加載界面,但最終目的是爲了顯示加載好的內容,內容其實也是個視圖,那麼應該也能夠經過 Adapter 進行註冊管理。不過配置管理加載界面主要是由於樣式一般是統一的,而內容界面基本不同,會有什麼使用場景嗎?固然是有的,咱們多多少少封裝過一些 Activity 基類吧,由於有一些統一的重複操做。
如今能夠將基類對內容的操做解耦出來,方便配置管理。並且咱們封裝了一個 Activity 基類後,一般會再封裝一個相同功能的 Fragment 基類。這兩個基類會有不少相同的操做,咱們解耦後就能很容易複用,後續若是要修改也只是改一份代碼。
用法和前面的差很少,不過是建立一個適配器繼承 LoadingHelper.ContentAdapter
。若是想要使用 Activity 對象,能夠在構造方法傳入或者經過 contentView 對象得到。
public class CommonContentAdapter extends LoadingHelper.ContentAdapter<LoadingHelper.ViewHolder> { @Override public LoadingHelper.ViewHolder onCreateViewHolder(@NonNull View contentView) { return new LoadingHelper.ViewHolder(contentView); } @Override public void onBindViewHolder(@NonNull LoadingHelper.ViewHolder holder) { View contentView = holder.getRootView(); } } 複製代碼
在建立 LoadingHelper
對象時傳入 ContentAdapter
對象,就會當即對內容視圖進行處理。
loadingHelper= new LoadingHelper(this, new CommonContentAdapter()); 複製代碼
LoadingHelper
能夠解耦加載中、加載失敗的界面,解耦標題欄,解耦內容的重複操做,由於最初封裝的目的就是想將視圖層的常見重複代碼進行深度解耦。
下面分享一下我的結合了本庫的特性所封裝的基類用法,展現一下使用 LoadingHelper
對視圖層進行封裝能達到怎樣的效果。
首先配置默認的適配器,否則後續使用可能會由於找不到適配器報錯。
public class App extends Application { @Override public void onCreate() { super.onCreate(); LoadingHelper.setDefaultAdapterPool(adapterPool -> { adapterPool.register(ViewType.LOADING, new LoadingAdapter()); adapterPool.register(ViewType.ERROR, new ErrorAdapter()); adapterPool.register(ViewType.EMPTY, new EmptyAdapter()); return Unit.INSTANCE; }); } } 複製代碼
而後就能繼承 BaseActivity 進行使用。
public class MainActivity extends BaseActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { setContentView(R.layout.activity_main); setToolbar("標題名", TitleConfig.Type.BACK, "完成", v -> {}); loadData() } // 若是適配器調用 holder.getOnReloadListener.onReload(),會執行此方法從新請求 @Override public void onReload() { loadData() } public void loadData() { showLoadingView(); // 展現加載視圖 // (發起請求,回調請求成功或請求失敗的方法) } private void requestDataSuccess(String data){ showContentView(); // 展現內容視圖 Toast.makeText(this, data, Toast.LENGTH_SHORT).show(); } private void requestDataFailure(String msg){ showErrorView(); // 展現錯誤視圖 Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); } } 複製代碼
當須要對該頁面的樣式進行定製時,只需增長少許代碼。
public class MainActivity extends BaseActivity { private static final int VIEW_TYPE_SEARCH = 0; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { // 重載的第二個參數是子 View 的 id,配置後會對該 View 進行 loading,可用於如 DrawLayout 等較複雜佈局 // 重載的第三個參數是用於替換默認管理內容的適配器 setContentView(R.layout.activity_main, R.id.content_view, new CustomContentAdapter()); getLoadingHelper().register(ViewType.TITLE, new CustomTitleAdapter()); // 替換默認標題欄 getLoadingHelper().register(ViewType.LOADING, new CustomLoadingAdapter()); // 替換默認加載視圖 getLoadingHelper().register(ViewType.ERROR, new CustomErrorAdapter()); // 替換默認錯誤視圖 getLoadingHelper().register(VIEW_TYPE_SEARCH, new SearchHeaderAdapter()); // 配置搜索視圖 getLoadingHelper().setDecorHeader(ViewType.TITLE, VIEW_TYPE_SEARCH); // 添加自定義標題欄和搜索頭部 loadData() } @Override public void onReload() { loadData() } @Override public void loadData() { showLoadingView(); // (發起請求,回調請求成功或請求失敗的方法) } private void requestDataSuccess(String data) { showContentView(); Toast.makeText(this, data, Toast.LENGTH_SHORT).show(); } private void requestDataFailure(String msg) { showErrorView(); Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); } } 複製代碼
不是什麼複雜的封裝,主要是結合了些我的以爲比較好的用法。好比沒有用 getLayoutId()
而是保留重載了原有的 setContentView()
方法,經過佈局和控件 id 來肯定 contentView,而後用 showContentView()
方法展現出來。前面 set 了後面 show,代碼閱讀起來也很合理。
本文講述了一些管理 loading 界面的方案,分析了一些存在的不足。重點介紹了能同時管理 loading 界面和標題欄的工具 LoadingHelper,相對於同類型的 loading 庫,該有的功能都有,還能解耦標題欄和解耦對內容的重複操做,可以進一步解耦視圖層。最後展現告終合本庫對基類進行封裝使用能達到怎麼樣的效果。
若是你以爲本庫還不錯的話但願能給個 star 支持一下哦~