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