Loading動畫幾乎每一個Android App中都有。java
通常在須要用戶等待的場景,顯示一個Loading動畫可讓用戶知道App正在加載數據,而不是程序卡死,從而給用戶較好的使用體驗。android
一樣的道理,當加載的數據爲空時顯示一個數據爲空的視圖、在數據加載失敗時顯示加載失敗對應的UI並支持點擊重試會比白屏的用戶體驗更好一些。git
加載中、加載失敗、空數據的UI風格,通常來講在App內的全部頁面中須要保持一致,也就是須要作到全局統一。github
LoadingView
)BaseActivity/BaseFragment
中封裝LoadingView
的初始化邏輯,並封裝加載狀態切換時的UI顯示邏輯,暴露給子類如下方法:
void showLoading();
//調用此方法顯示加載中的動畫void showLoadFailed();
//調用此方法顯示加載失敗界面void showEmpty();
//調用此方法顯示空頁面void onClickRetry();
//子類中實現,點擊重試的回調方法BaseActivity/BaseFragment
的子類中可經過上一步的封裝比較方便地使用加載狀態顯示功能這種使用方式耦合度過高,每一個頁面的佈局文件中都須要添加LoadingView
,使用起來不方便並且維護成本較高,一旦UI設計師須要更改佈局,修改起來成本較高。緩存
LoadingView
)LoadingUtil
)來管理LoadingView
,不一樣狀態顯示不一樣的UI(或者在多個View之間切換顯示)BaseActivity/BaseFragment
中對LoadingUtil
的使用進行封裝,暴露給子類如下方法:
void showLoading();
//調用此方法顯示加載中的動畫void showLoadFailed();
//調用此方法顯示加載失敗界面void showEmpty();
//調用此方法顯示空頁面void onClickRetry();
//子類中實現,點擊重試的回調方法BaseActivity/BaseFragment
的子類中可經過上一步的封裝比較方便地使用加載狀態顯示功能這種封裝的好處是經過封裝動態地建立LoadingView
並添加到指定的父容器中,讓具體頁面無需關注LoadingView
的實現,只須要指定在哪一個容器中顯示便可,很大程度地進行了解耦。若是公司只在一個App中使用,這基本上就夠了。bash
可是,這種封裝方式仍是存在耦合:頁面與它所使用的LoadingView
仍然存在綁定關係。若是須要複用到其它App中,由於每一個App的UI風格可能不一樣,對應的LoadingView
佈局也可能會不同,要想複用必須先將頁面與LoadingView
解耦。網絡
LoadingView
可切換,且不須要改動頁面代碼LoadingView
的顯示區域(例如導航欄Title不但願被LoadingView
覆蓋)說到View的解耦,很容易聯想到Android系統中的AdapterView(咱們經常使用的GridView和ListView都是它的子類)及support包裏提供的ViewPager、RecyclerView等,它們都是經過Adapter來解耦的,將自身的邏輯與須要動態變化的子View進行分離。咱們也能夠按照這個思路來解耦LoadingView
:app
(已實現)頁面的LoadingView可切換,且不須要改動頁面代碼
複製代碼
上代碼更容易理解:ide
public Holder wrap(View view) {
FrameLayout wrapper = new FrameLayout(view.getContext());
ViewGroup.LayoutParams lp = view.getLayoutParams();
if (lp != null) {
wrapper.setLayoutParams(lp);
}
if (view.getParent() != null) {
ViewGroup parent = (ViewGroup) view.getParent();
int index = parent.indexOfChild(view);
parent.removeView(view);
parent.addView(wrapper, index);
}
LayoutParams newLp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
wrapper.addView(view, newLp);
return new Holder(mAdapter, view.getContext(), wrapper);
}
複製代碼
(已實現)頁面中可指定LoadingView的顯示區域
(已實現)支持在Fragment中使用
另外,還順帶支持在RecyclerView、ListView、GridView、ViewPager等狀況下的使用
複製代碼
Adapter.getView
中實現Adapter
是全局使用的,而失敗重試所需執行邏輯每一個頁面都不同Holder
能夠持有每一個具體的LoadingView
,能夠將retryTask
經過Holder
傳遞給Adapter
Adapter.getView
時將Holder
做爲參數傳入,便可在建立LoadingView
時獲取該retryTask
對象,並在點擊重試按鈕時執行retryTask
Holder
傳遞一些附加參數給Adapter
,以兼容在不一樣頁面上佈局的細微差別(已實現)支持加載失敗頁面中點擊重試
(已實現)兼容不一樣頁面顯示的UI有細微差異(例如提示文字可能不一樣)
複製代碼
Gloading是一個基於Adapter思路實現的深度解耦App中全局LoadingView的輕量級工具(只有一個java文件,不到300行,其中註釋佔100+行,aar僅6K)工具
一、 依賴Gloading
compile 'com.billy.android:gloading:1.0.0'
複製代碼
二、 建立Adapter
,在getView
方法中實現建立各類狀態視圖(加載中、加載失敗、空數據等)的邏輯
Gloading不侵入UI佈局,徹底由用戶自定義。示例以下:
public class GlobalAdapter implements Gloading.Adapter {
@Override
public View getView(Gloading.Holder holder, View convertView, int status) {
GlobalLoadingStatusView loadingStatusView = null;
//convertView爲可重用的佈局
//Holder中緩存了各狀態下對應的View
// 若是status對應的View爲null,則convertView爲上一個狀態的View
// 若是上一個狀態的View也爲null,則convertView爲null
if (convertView != null && convertView instanceof GlobalLoadingStatusView) {
loadingStatusView = (GlobalLoadingStatusView) convertView;
}
if (loadingStatusView == null) {
loadingStatusView = new GlobalLoadingStatusView(holder.getContext(), holder.getRetryTask());
}
loadingStatusView.setStatus(status);
return loadingStatusView;
}
class GlobalLoadingStatusView extends RelativeLayout {
public GlobalLoadingStatusView(Context context, Runnable retryTask) {
super(context);
//初始化LoadingView
//若是須要支持點擊重試,在適當的時機給對應的控件添加點擊事件
}
public void setStatus(int status) {
//設置當前的加載狀態:加載中、加載失敗、空數據等
//其中,加載失敗可判斷當前是否聯網,可現實無網絡的狀態
// 屬於加載失敗狀態下的一個分支,可自行決定是否實現
}
}
}
複製代碼
三、 初始化Gloading
的默認Adapter
Gloading.initDefault(new GlobalAdapter());
複製代碼
注:能夠用AutoRegister在Gloading類裝載進虛擬機時自動完成初始化註冊,無需在app層執行註冊,耦合度更低
四、在須要使用LoadingView
的地方獲取Holder
//在Activity中顯示, 父容器爲: android.R.id.content
Gloading.Holder holder = Gloading.getDefault().wrap(activity);
//傳遞點擊重試須要執行的task,該task在Adapter中用holder.getRetryTask()獲取
Gloading.Holder holder = Gloading.getDefault().wrap(activity).withRetry(retryTask);
//傳遞點擊重試須要執行的task和一個任意類型的擴展參數,該參數在Adapter中用holder.getData()獲取
Gloading.Holder holder = Gloading.getDefault().wrap(activity).withRetry(retryTask).withData(obj);
複製代碼
or
//爲某個View顯示加載狀態
//Gloading會自動建立一個FrameLayout,將view包裹起來,LoadingView也顯示在其中
Gloading.Holder holder = Gloading.getDefault().wrap(view);
//傳遞點擊重試須要執行的task,該task在Adapter中用holder.getRetryTask()獲取
Gloading.Holder holder = Gloading.getDefault().wrap(view).withRetry(retryTask);
//傳遞點擊重試須要執行的task和一個任意類型的擴展參數,該參數在Adapter中用holder.getData()獲取
Gloading.Holder holder = Gloading.getDefault().wrap(view).withRetry(retryTask).withData(obj);
複製代碼
五、 使用Holder
來顯示各類加載狀態
//顯示加載中的狀態,一般是顯示一個加載動畫
holder.showLoading()
//顯示加載成功狀態(通常是隱藏LoadingView)
holder.showLoadSuccess()
//顯示加載失敗狀態
holder.showFailed()
//數據加載完成,但數據爲空
holder.showEmpty()
//若是以上默認提供的狀態不能知足使用,可以使用此方法調用其它狀態
holder.showLoadingStatus(status)
複製代碼
更多API詳情請查看 Gloading JavaDocs
更多Demo示例代碼請查看 Gloading Demo, 也可下載Demo apk體驗
六、封裝到BaseActivity/BaseFragment中
示例代碼以下:
public abstract class BaseActivity extends Activity {
protected Gloading.Holder mHolder;
/** * make a Gloading.Holder wrap with current activity by default * override this method in subclass to do special initialization * @see SpecialActivity */
protected void initLoadingStatusViewIfNeed() {
if (mHolder == null) {
//bind status view to activity root view by default
mHolder = Gloading.getDefault().wrap(this).withRetry(new Runnable() {
@Override
public void run() {
onLoadRetry();
}
});
}
}
protected void onLoadRetry() {
// override this method in subclass to do retry task
}
public void showLoading() {
initLoadingStatusViewIfNeed();
mHolder.showLoading();
}
public void showLoadSuccess() {
initLoadingStatusViewIfNeed();
mHolder.showLoadSuccess();
}
public void showLoadFailed() {
initLoadingStatusViewIfNeed();
mHolder.showLoadFailed();
}
public void showEmpty() {
initLoadingStatusViewIfNeed();
mHolder.showEmpty();
}
}
複製代碼
七、 兼容多App場景下的頁面、View的複用
每一個App的LoadingView
可能會不一樣,只需爲每一個App提供不一樣的Adapter
,不一樣App調用不一樣的Gloading.initDefault(new GlobalAdapter());
,具體頁面中的使用代碼無需改動。
注:若是使用AutoRegister,則只需在不一樣App中建立各自的 Adapter
實現類便可,無需手動註冊。只需改動2處gradle文件便可:
buildscript {
//...
dependencies {
//...
classpath 'com.billy.android:autoregister:使用最新版'
}
}
複製代碼
apply plugin: 'auto-register'
autoregister {
registerInfo = [
[
'scanInterface' : 'com.billy.android.loading.Gloading$Adapter'
, 'codeInsertToClassName' : 'com.billy.android.loading.Gloading'
, 'registerMethodName' : 'initDefault'
]
]
}
複製代碼
爲View添加加載狀態
本文介紹了全局LoadingView在實際使用過程當中可能存在的一些耦合狀況,並指出了由此會影響多個App的LoadingView的UI風格不一致致使頁面難以複用的問題,同時給出瞭解決思路。
另外,本文着重介紹瞭如何使用Gloading來輕鬆實現低耦合的全局LoadingView。並且,還能夠按照本文的思路來解決項目中其它解耦工做。
最後,你的star會成爲做者開源的動力哦 :)