android loading封装_我们经常用的Loading动画居然还有这种姿势
背景
Loading動畫幾乎每個Android App中都有。
一般在需要用戶等待的場景,顯示一個Loading動畫可以讓用戶知道App正在加載數(shù)據(jù),而不是程序卡死,從而給用戶較好的使用體驗。
同樣的道理,當加載的數(shù)據(jù)為空時顯示一個數(shù)據(jù)為空的視圖、在數(shù)據(jù)加載失敗時顯示加載失敗對應的UI并支持點擊重試會比白屏的用戶體驗更好一些。
加載中、加載失敗、空數(shù)據(jù)的UI風格,一般來說在App內的所有頁面中需要保持一致,也就是需要做到全局統(tǒng)一。
1. 傳統(tǒng)的做法
- void showLoading(); //調用此方法顯示加載中的動畫
- void showLoadFailed(); //調用此方法顯示加載失敗界面
- void showEmpty(); //調用此方法顯示空頁面
- void onClickRetry(); //子類中實現(xiàn),點擊重試的回調方法
這種使用方式耦合度太高,每個頁面的布局文件中都需要添加LoadingView,使用起來不方便而且維護成本較高,一旦UI設計師需要更改布局,修改起來成本較高。
2. 好一點的封裝方法
- void showLoading(); //調用此方法顯示加載中的動畫
- void showLoadFailed(); //調用此方法顯示加載失敗界面
- void showEmpty(); //調用此方法顯示空頁面
- void onClickRetry(); //子類中實現(xiàn),點擊重試的回調方法
- abstract int getContainerId(); //子類中實現(xiàn),LoadingUtil動態(tài)創(chuàng)建LoadingView并添加到該方法返回id對應的控件中
這種封裝的好處是通過封裝動態(tài)地創(chuàng)建LoadingView并添加到指定的父容器中,讓具體頁面無需關注LoadingView的實現(xiàn),只需要指定在哪個容器中顯示即可,很大程度地進行了解耦。如果公司只在一個App中使用,這基本上就夠了。
但是,這種封裝方式還是存在耦合:頁面與它所使用的LoadingView仍然存在綁定關系。如果需要復用到其它App中,因為每個App的UI風格可能不同,對應的LoadingView布局也可能會不一樣,要想復用必須先將頁面與LoadingView解耦。
如何解耦?
1. 梳理一下我們需要實現(xiàn)的效果
- 頁面的LoadingView可切換,且不需要改動頁面代碼
- 頁面中可指定LoadingView的顯示區(qū)域(例如導航欄Title不希望被LoadingView覆蓋)
- 支持在Fragment中使用
- 支持加載失敗頁面中點擊重試
- 兼容不同頁面顯示的UI有細微差別(例如提示文字可能不同)
2. 確定思路
說到View的解耦,很容易聯(lián)想到Android系統(tǒng)中的AdapterView(我們常用的GridView和ListView都是它的子類)及support包里提供的ViewPager、RecyclerView等,它們都是通過Adapter來解耦的,將自身的邏輯與需要動態(tài)變化的子View進行分離。我們也可以按照這個思路來解耦LoadingView:
- 創(chuàng)建一個工具類,用于管理LoadingView各個狀態(tài)的UI展示
- 創(chuàng)建一個Adapter接口,外部提供實現(xiàn)類,通過getView方法創(chuàng)建具體的LoadingView
- 每個App提供一個Adapter的實現(xiàn),并注冊到工具類中
- 工具類從Adapter.getView獲取具體的LoadingView,所以頁面中使用的代碼無需改動
- 由于每個頁面或View的加載狀態(tài)互相之間無關聯(lián)關系,需要創(chuàng)建一個用于管理具體某個LoadingView的狀態(tài)持有類:Holder
- 指定LoadingView所需覆蓋的View時,動態(tài)新建一個FrameLayout布局
- 將原View從ParentView中移除,并用它的LayoutParams將FrameLayout添加到ParentView中替代原View在ParentView中的位置
- 再將原View添加到FrameLayout中
- 在Fragment.onCreateView/RecyclerView.Adapter.onCreateViewHolder等方法中創(chuàng)建的View時,由于View尚未添加到任何容器中,并無getParent()返回null,此時需要用動態(tài)生成的FrameLayout代替原View作為方法的返回值返回
上代碼更容易理解:
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); } (已實現(xiàn))頁面中可指定LoadingView的顯示區(qū)域 (已實現(xiàn))支持在Fragment中使用 另外,還順帶支持在RecyclerView、ListView、GridView、ViewPager等情況下的使用- 為了不侵入UI,將加載失敗點擊重試的點擊功能放在Adapter.getView中實現(xiàn)
- 與Android系統(tǒng)中的Adapter不同的是,我們的Adapter是全局使用的,而失敗重試所需執(zhí)行邏輯每個頁面都不一樣
- 因為Holder可以持有每個具體的LoadingView,可以將retryTask通過Holder傳遞給Adapter
- 只需要在Adapter.getView時將Holder作為參數(shù)傳入,即可在創(chuàng)建LoadingView時獲取該retryTask對象,并在點擊重試按鈕時執(zhí)行retryTask
- 同理,可以通過Holder傳遞一些附加參數(shù)給Adapter,以兼容在不同頁面上布局的細微差異
使用Gloading來輕松實現(xiàn)低耦合的全局LoadingView
Gloading是一個基于Adapter思路實現(xiàn)的深度解耦App中全局LoadingView的輕量級工具(只有一個java文件,不到300行,其中注釋占100+行,aar僅6K)
1、 依賴Gloading
compile 'com.billy.android:gloading:1.0.0'2、 創(chuàng)建Adapter,在getView方法中實現(xiàn)創(chuàng)建各種狀態(tài)視圖(加載中、加載失敗、空數(shù)據(jù)等)的邏輯
Gloading不侵入UI布局,完全由用戶自定義。示例如下:
public class GlobalAdapter implements Gloading.Adapter {@Overridepublic View getView(Gloading.Holder holder, View convertView, int status) {GlobalLoadingStatusView loadingStatusView = null;//convertView為可重用的布局//Holder中緩存了各狀態(tài)下對應的View// 如果status對應的View為null,則convertView為上一個狀態(tài)的View// 如果上一個狀態(tài)的View也為null,則convertView為nullif (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//如果需要支持點擊重試,在適當?shù)臅r機給對應的控件添加點擊事件}public void setStatus(int status) {//設置當前的加載狀態(tài):加載中、加載失敗、空數(shù)據(jù)等//其中,加載失敗可判斷當前是否聯(lián)網(wǎng),可現(xiàn)實無網(wǎng)絡的狀態(tài)// 屬于加載失敗狀態(tài)下的一個分支,可自行決定是否實現(xiàn)}} }3、 初始化Gloading的默認Adapter
Gloading.initDefault(new GlobalAdapter());注:可以用AutoRegister在Gloading類裝載進虛擬機時自動完成初始化注冊,無需在app層執(zhí)行注冊,耦合度更低
4、在需要使用LoadingView的地方獲取Holder
//在Activity中顯示, 父容器為: android.R.id.content Gloading.Holder holder = Gloading.getDefault().wrap(activity);//傳遞點擊重試需要執(zhí)行的task,該task在Adapter中用holder.getRetryTask()獲取 Gloading.Holder holder = Gloading.getDefault().wrap(activity).withRetry(retryTask);//傳遞點擊重試需要執(zhí)行的task和一個任意類型的擴展參數(shù),該參數(shù)在Adapter中用holder.getData()獲取 Gloading.Holder holder = Gloading.getDefault().wrap(activity).withRetry(retryTask).withData(obj);or
//為某個View顯示加載狀態(tài) //Gloading會自動創(chuàng)建一個FrameLayout,將view包裹起來,LoadingView也顯示在其中 Gloading.Holder holder = Gloading.getDefault().wrap(view);//傳遞點擊重試需要執(zhí)行的task,該task在Adapter中用holder.getRetryTask()獲取 Gloading.Holder holder = Gloading.getDefault().wrap(view).withRetry(retryTask);//傳遞點擊重試需要執(zhí)行的task和一個任意類型的擴展參數(shù),該參數(shù)在Adapter中用holder.getData()獲取 Gloading.Holder holder = Gloading.getDefault().wrap(view).withRetry(retryTask).withData(obj);5、 使用Holder來顯示各種加載狀態(tài)
//顯示加載中的狀態(tài),通常是顯示一個加載動畫 holder.showLoading() //顯示加載成功狀態(tài)(一般是隱藏LoadingView) holder.showLoadSuccess()//顯示加載失敗狀態(tài) holder.showFailed()//數(shù)據(jù)加載完成,但數(shù)據(jù)為空 holder.showEmpty()//如果以上默認提供的狀態(tài)不能滿足使用,可使用此方法調用其它狀態(tài) holder.showLoadingStatus(status)更多API詳情請查看 Gloading JavaDocs
更多Demo示例代碼請查看 Gloading Demo, 也可下載Demo apk體驗
6、封裝到BaseActivity/BaseFragment中
- 讓BaseActivity和BaseFragment的子類中使用LoadingView更方便
- 子類中使用LoadingView的業(yè)務邏輯與實現(xiàn)分離
- 如果原來就是封裝到BaseActivity/BaseFragment中的,那么可以無縫切換到Gloading
- 如果以后需要將Gloading移除替換成其它實現(xiàn),也無需修改業(yè)務代碼
示例代碼如下:
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 defaultmHolder = Gloading.getDefault().wrap(this).withRetry(new Runnable() {@Overridepublic 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();}}7、 兼容多App場景下的頁面、View的復用
每個App的LoadingView可能會不同,只需為每個App提供不同的Adapter,不同App調用不同的Gloading.initDefault(new GlobalAdapter());,具體頁面中的使用代碼無需改動。
注:如果使用AutoRegister,則只需在不同App中創(chuàng)建各自的 Adapter實現(xiàn)類即可,無需手動注冊。只需改動2處gradle文件即可:
- 修改根目錄build.gradle,添加對AutoRegister的依賴
- 修改主application module下的build.gradle,添加如下代碼即可實現(xiàn)Adapter的自動注冊
演示
為View添加加載狀態(tài)
總結
本文介紹了全局LoadingView在實際使用過程中可能存在的一些耦合情況,并指出了由此會影響多個App的LoadingView的UI風格不一致導致頁面難以復用的問題,同時給出了解決思路。
另外,本文著重介紹了如何使用Gloading來輕松實現(xiàn)低耦合的全局LoadingView,喜歡的同學請順手甩個star支持一下 :)
總結
以上是生活随笔為你收集整理的android loading封装_我们经常用的Loading动画居然还有这种姿势的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 皮肤变黑有七大原因皮肤越来越黑是什么原因
- 下一篇: centos怎么查看服务是否是开机启动如