activity中fragment 返回键不退出_优雅地处理加载中(loading),重试(retry)和无数据(empty)等...
LoadSir是一個高效易用,低碳環(huán)保,擴(kuò)展性良好的加載反饋頁管理框架,在加載網(wǎng)絡(luò)或其他數(shù)據(jù)時候,根據(jù)需求切換狀態(tài)頁面,可添加自定義狀態(tài)頁面,如加載中,加載失敗,無數(shù)據(jù),網(wǎng)絡(luò)超時,占位圖,登錄失效等常用頁面。可配合網(wǎng)絡(luò)加載框架,結(jié)合返回狀態(tài)碼,錯誤碼,數(shù)據(jù)進(jìn)行狀態(tài)頁自動切換,封裝使用效果更佳。
效果預(yù)覽
使用場景
下面為大家常見的加載反饋頁面:
面對這么多狀態(tài)頁面,你是不是還在用include的方式,setVisibility(View.VISIBLE/GONE),這種方式即不方便控制,也造成了視圖層級冗余(你要把所有狀態(tài)布局include進(jìn)一個視圖)。如果有一種工具,能把這些事都做了就好了。恰好, ?LoadSir 把這些事做了,接下來我們就來了解一下它。
LoadSir的功能及特點(diǎn)
支持Activity,Fragment,Fragment(v4),View狀態(tài)回調(diào)
適配多個Fragment切換,及Fragment+ViewPager切換,不會狀態(tài)疊加或者狀態(tài)錯亂
利用泛型轉(zhuǎn)換輸入信號和輸出狀態(tài),可根據(jù)網(wǎng)絡(luò)返回體的狀態(tài)碼或者數(shù)據(jù)返回自動適配狀態(tài)頁,實(shí)現(xiàn)全局自動狀態(tài)切換
只加載唯一一個狀態(tài)視圖,不會預(yù)加載全部視圖
可保留標(biāo)題欄(Toolbar,titile view等)
可設(shè)置重新加載點(diǎn)擊事件(OnReloadListener)
可自定義狀態(tài)頁(繼承Callback類)
可在子線程直接切換狀態(tài)
可設(shè)置初始狀態(tài)頁(常用進(jìn)度頁作為初始狀態(tài))
不需要設(shè)置枚舉或者常量狀態(tài)值,直接用狀態(tài)頁類類型(xxx.class)作為狀態(tài)碼
可擴(kuò)展?fàn)顟B(tài)頁面,在配置中添加自定義狀態(tài)頁
可對單個狀態(tài)頁單獨(dú)設(shè)置點(diǎn)擊事件,根據(jù)返回boolean值覆蓋或者結(jié)合OnReloadListener使用,如網(wǎng)絡(luò)錯誤可跳轉(zhuǎn)設(shè)置頁
可全局單例配置,也可以單獨(dú)配置
無預(yù)設(shè)頁面,低耦合,開發(fā)者隨心配置
開始使用LoadSir
LoadSir的使用只需要簡單的三步,三步上籃的三步。
添加依賴
compile?'com.kingja.loadsir:loadsir:1.3.6'第一步: 配置
全局配置方式
全局配置方式,使用的是單例模式,即獲取的配置都是一樣的。可在Application中配置,添加狀態(tài)頁,設(shè)置初始化狀態(tài)頁,建議使用這種配置方式。
public?class?App?extends?Application?{????@Override
????public?void?onCreate()?{
????????super.onCreate();
????????LoadSir.beginBuilder()
????.addCallback(new?ErrorCallback())//'添加各種狀態(tài)頁
????.addCallback(new?EmptyCallback())
????.addCallback(new?LoadingCallback())
????.addCallback(new?TimeoutCallback())
????.addCallback(new?CustomCallback())
????.setDefaultCallback(LoadingCallback.class)//設(shè)置默認(rèn)狀態(tài)頁
????.commit();
????}
}
單獨(dú)配置方式
如果你即想保留全局配置,又想在某個特殊頁面加點(diǎn)不同的配置,可采用該方式。
LoadSir?loadSir?=?new?LoadSir.Builder()????.addCallback(new?LoadingCallback())
????.addCallback(new?EmptyCallback())
????.addCallback(new?ErrorCallback())
????.build();
????????loadService?=?loadSir.register(this,?new?Callback.OnReloadListener()?{
@Override
public?void?onReload(View?v)?{
????//?重新加載邏輯
}
????????});
第二步: 注冊
在Activity中使用
@Overrideprotected?void?onCreate(@Nullable?Bundle?savedInstanceState)?{
????super.onCreate(savedInstanceState);
????setContentView(R.layout.activity_content);
????//?Your?can?change?the?callback?on?sub?thread?directly.
????LoadService?loadService?=?LoadSir.getDefault().register(this,?new?Callback.OnReloadListener()?{
????????@Override
????????public?void?onReload(View?v)?{
//?重新加載邏輯
????????}
????});
}}
在View 中使用
ImageView?imageView?=?(ImageView)?findViewById(R.id.iv_img);LoadSir?loadSir?=?new?LoadSir.Builder()
????????.addCallback(new?TimeoutCallback())
????????.setDefaultCallback(LoadingCallback.class)
????????.build();
loadService?=?loadSir.register(imageView,?new?Callback.OnReloadListener()?{
????@Override
????public?void?onReload(View?v)?{
????????loadService.showCallback(LoadingCallback.class);
????????//?重新加載邏輯
????}
});
在Fragment 中使用
由于Fragment添加到Activitiy方式多樣,比較特別,所以在Fragment中注冊方式不同于上面兩種,大家先看模板代碼:
@Nullable@Override
public?View?onCreateView(LayoutInflater?inflater,?@Nullable?ViewGroup?container,?@Nullable?Bundle
????????savedInstanceState)?{
????//第一步:獲取布局View
????rootView?=?View.inflate(getActivity(),?R.layout.fragment_a_content,?null);
????//第二步:注冊布局View
????LoadService?loadService?=?LoadSir.getDefault().register(rootView,?new?Callback.OnReloadListener()?{
????????@Override
????????public?void?onReload(View?v)?{
//?重新加載邏輯
????????}
????});
????//第三步:返回LoadSir生成的LoadLayout
????return?loadService.getLoadLayout();
}
第三步: 回調(diào)
直接回調(diào)
protected?void?loadNet()?{????????//?進(jìn)行網(wǎng)絡(luò)訪問...
????????//?進(jìn)行回調(diào)
????????loadService.showSuccess();//成功回調(diào)
????????loadService.showCallback(EmptyCallback.class);//其他回調(diào)
????}
轉(zhuǎn)換器回調(diào) (推薦使用)
如果你不想再每次回調(diào)都要手動進(jìn)行的話,可以選擇注冊的時候加入轉(zhuǎn)換器,可根據(jù)返回的數(shù)據(jù),適配對應(yīng)的回調(diào)。
LoadService?loadService?=?LoadSir.getDefault().register(this,?new?Callback.OnReloadListener()?{????@Override
????public?void?onReload(View?v)?{
//?重新加載邏輯
????}},?new?Convertor()?{@Overridepublic?Class?extends?Callback>?map(HttpResult?httpResult)?{
????????Class?extends?Callback>?resultCode?=?SuccessCallback.class;switch?(httpResult.getResultCode())?{case?SUCCESS_CODE://成功回調(diào)if?(httpResult.getData().size()?==?0)?{
????????resultCode?=?EmptyCallback.class;
????}else{
????????resultCode?=?SuccessCallback.class;
????}break;case?ERROR_CODE:
????resultCode?=?ErrorCallback.class;break;
????????}return?resultCode;
????}
});
回調(diào)的時候直接傳入轉(zhuǎn)換器指定的數(shù)據(jù)類型。
loadService.showWithConvertor(httpResult);自定義回調(diào)頁
LoadSir為了完全解耦,沒有預(yù)設(shè)任何狀態(tài)頁,開發(fā)者根據(jù)需求自定義自己的回調(diào)頁面,比如加載中,沒數(shù)據(jù),錯誤,超時等常用頁面,
設(shè)置布局及自定義點(diǎn)擊邏輯
????@Override
????protected?int?onCreateView()?{
????????return?R.layout.layout_custom;
????}
????@Override
????protected?boolean?onRetry(final?Context?context,?View?view)?{
????????//布局點(diǎn)擊事件
????????Toast.makeText(context.getApplicationContext(),?"Hello?mother?fuck!?:p",?Toast.LENGTH_SHORT).show();
????????//子控件事件
????????(view.findViewById(R.id.iv_gift)).setOnClickListener(new?View.OnClickListener()?{
@Override
public?void?onClick(View?v)?{
????Toast.makeText(context.getApplicationContext(),?"It's?your?gift!?:p",?Toast.LENGTH_SHORT).show();
}
????????});
????????return?true;//返回true則覆蓋了register時傳入的重試點(diǎn)擊事件,返回false則兩個都執(zhí)行
????}
????//是否在顯示Callback視圖的時候顯示原始圖(SuccessView),返回true顯示,false隱藏
????@Override
????public?boolean?getSuccessVisible()?{
????????return?super.getSuccessVisible();
????}
????//將Callback添加到當(dāng)前視圖時的回調(diào),View為當(dāng)前Callback的布局View
????@Override
????public?void?onAttach(Context?context,?View?view)?{
????????super.onAttach(context,?view);
????}
????//將Callback從當(dāng)前視圖刪除時的回調(diào),View為當(dāng)前Callback的布局View
????@Override
????public?void?onDetach()?{
????????super.onDetach(context,?view);
????}
}
動態(tài)修改Callback
loadService?=?LoadSir.getDefault().register(...);loadService.setCallBack(EmptyCallback.class,?new?Transport()?{
???@Override
???public?void?order(Context?context,?View?view)?{
???????TextView?mTvEmpty?=?(TextView)?view.findViewById(R.id.tv_empty);
???????mTvEmpty.setText("fine,?no?data.?You?must?fill?it!");
???}
});
代碼混淆
-dontwarn?com.kingja.loadsir.**-keep?class?com.kingja.loadsir.**?{*;}
占位圖布局效果
placeholder效果狀態(tài)頁類似ShimmerRecyclerView的效果. LoadSir只用了一個自定義狀態(tài)頁P(yáng)laceHolderCallback就完成類似的效果,是不是很棒 :p
看到這,想必各位使用LoadSir應(yīng)該沒問題了,如果還想再進(jìn)一步了解它的內(nèi)部結(jié)構(gòu),可以繼續(xù)往下看。
原理解析
流程圖
關(guān)鍵類
LoadSir:提供單例模式獲取全局唯一實(shí)例,內(nèi)部保存配置信息,根據(jù)配置創(chuàng)建LoadService。
LoadService:具體操作服務(wù)類,提供showSuccess,showCallback,showWithCoverator等方法來進(jìn)行狀態(tài)頁回調(diào)。
LoadLayout:最終顯示在用戶面前的視圖View,替換了原布局,是LoadService直接操作對象,要顯示的狀態(tài)頁的視圖會被添加到LoadLayout上。
Callback:狀態(tài)頁抽象類,抽象自定義布局和自定義點(diǎn)擊事件兩個方法留給子類實(shí)現(xiàn)。
Coverator:轉(zhuǎn)換接口,可將網(wǎng)絡(luò)返回實(shí)體轉(zhuǎn)換成對應(yīng)的狀態(tài)頁,達(dá)到自動適配狀態(tài)頁的目的。
我們直接觀察在Activity中普通加載和使用LoadSir加載視圖的區(qū)別
>>>沒使用LoadSir
>>>使用LoadSir
大家可以看到,LoadSir用LoadLayout把原來的布局給替代掉了,原來的布局加在了LoadLayout上,其它自定義的狀態(tài)頁也同樣會被加到這個LoadLayout上(顯示的時候),而且LoadLayout的子View只有一個,就是當(dāng)前要顯示的狀態(tài)頁布局,并沒有把當(dāng)前不顯示的比如加載中布局,錯誤布局,無數(shù)據(jù)布局加載進(jìn)來,這也是LoadSir的優(yōu)點(diǎn)之一,按需加載,并且只加載一個狀態(tài)布局。
>>>替換邏輯
public?static?TargetContext?getTargetContext(Object?target)?{????????ViewGroup?contentParent;
????????Context?context;
????????if?(target?instanceof?Activity)?{
Activity?activity?=?(Activity)?target;
context?=?activity;
contentParent?=?(ViewGroup)?activity.findViewById(android.R.id.content);
????????}?else?if?(target?instanceof?View)?{
View?view?=?(View)?target;
contentParent?=?(ViewGroup)?(view.getParent());
context?=?view.getContext();
????????}?else?{
throw?new?IllegalArgumentException("The?target?must?be?within?Activity,?Fragment,?View.");
????????}
???????...
????????if?(contentParent?!=?null)?{
contentParent.removeView(oldContent);
????????}
????????return?new?TargetContext(context,?contentParent,?oldContent,?childIndex);
????}
大家可以看到,在Activity和View中的情況都比較簡單,直接獲取target的父控件,然后在父控件中替換掉該布局即可。在Fragment中,由于可能多個Fragment的布局View并存在一個父控件里,所以不能簡單地使用父控件刪除子View方式替換,也有可能父控件是ViewPager,不能通過addView()的方式添加LoadLayout。因此Fragment的注冊方式是直接返回了LoadLayout到Activity上。這樣也達(dá)到了一樣的目的。
下面是ViewPager+Fragment場景中使用LoadSir的視圖,兩個Fragment用各自的LoadLayout進(jìn)行視圖分離,避免了狀態(tài)頁疊加或錯位。
看到這的童鞋應(yīng)該也大概知道LoadSir是怎么回事了,如果想明白LoadSir的代碼實(shí)現(xiàn),請繼續(xù)往下看。
源碼解析
我們按上面三步上籃的步驟來稍微分析下源碼
>>>第一步:配置
單例模式獲取LoadSir,在LoadSir構(gòu)造的時候創(chuàng)建默認(rèn)配置
public?static?LoadSir?getDefault()?{????????if?(loadSir?==?null)?{
synchronized?(LoadSir.class)?{
????if?(loadSir?==?null)?{
????????loadSir?=?new?LoadSir();
????}
}
????????}
????????return?loadSir;
????}
????private?LoadSir()?{
????????this.builder?=?new?Builder();
????}
Builder主要提供添加狀態(tài)頁,和設(shè)置默認(rèn)狀態(tài)頁的方法
public?static?class?Builder?{????????private?List?callbacks?=?new?ArrayList<>();private?Class?extends?Callback>?defaultCallback;public?Builder?addCallback(Callback?callback)?{
callbacks.add(callback);return?this;
????????}public?Builder?setDefaultCallback(Class?extends?Callback>?defaultCallback)?{this.defaultCallback?=?defaultCallback;return?this;
????????}
??????...public?LoadSir?build()?{return?new?LoadSir(this);
????????}
????}
LoadSir提供beginBuilder()…commit()來設(shè)置全局配置。
public?class?LoadSir??{???...
????public?static?Builder?beginBuilder()?{
????????return?new?Builder();
????}
????public?static?class?Builder?{
????????public?void?commit()?{
getDefault().setBuilder(this);
????????}
??????...
????}
}
>>>第二步:注冊
LoadSir注冊后返回的是LoadService,一看名字大家就明白這是服務(wù)類,就是我們所說的Service層。
public?LoadService?register(Object?target,?Callback.OnReloadListener?onReloadListener)?{????????return?register(target,?onReloadListener,?null);
????}
????public??LoadService?register(Object?target,?Callback.OnReloadListener?onReloadListener,?Convertor
convertor)?{
????????TargetContext?targetContext?=?LoadSirUtil.getTargetContext(target);return?new?LoadService<>(convertor,?targetContext,?onReloadListener,?builder);
????}
在LoadService的構(gòu)造方法中根據(jù)target等信息創(chuàng)建Success視圖,并且生成LoadLayout,相當(dāng)于LoadSir每次注冊都會創(chuàng)建一個LoadLayout。
LoadService(Convertor?convertor,?TargetContext?targetContext,?Callback.OnReloadListener?onReloadListener,?LoadSir.Builder?builder)?{this.convertor?=?convertor;
????????Context?context?=?targetContext.getContext();
????????View?oldContent?=?targetContext.getOldContent();
????????loadLayout?=?new?LoadLayout(context,?onReloadListener);
????????loadLayout.addCallback(new?SuccessCallback(oldContent,?context,
????onReloadListener));if?(targetContext.getParentView()?!=?null)?{
targetContext.getParentView().addView(loadLayout,?targetContext.getChildIndex(),?oldContent
????????.getLayoutParams());
????????}
????????initCallback(builder);
????}
>>>第三步:回調(diào)
LoadService的三個回調(diào)方法最終調(diào)用的都是loadLayout.showCallback(callback);
public?void?showSuccess()?{????????loadLayout.showCallback(SuccessCallback.class);
????}
????public?void?showCallback(Class?extends?Callback>?callback)?{
????????loadLayout.showCallback(callback);
????}
????public?void?showWithConvertor(T?t)?{
????????if?(convertor?==?null)?{
throw?new?IllegalArgumentException("You?haven't?set?the?Convertor.");
????????}
????????loadLayout.showCallback(convertor.map(t));
????}
我們直接看LoadLayout的showCallback方法,先做Callback是否配置判斷,然后進(jìn)行線程安全操作。重點(diǎn)還是showCallbackView(callback);
public?void?showCallback(final?Class?extends?Callback>?callback)?{????????if?(!callbacks.containsKey(callback))?{
throw?new?IllegalArgumentException(String.format("The?Callback?(%s)?is?nonexistent.",?callback
????????.getSimpleName()));
????????}
????????if?(LoadSirUtil.isMainThread())?{
showCallbackView(callback);
????????}?else?{
postToMainThread(callback);
????????}
????}
這個方法可以說是最后的執(zhí)行者,就做兩件事,刪除LoadLayout所有子View(重置),添加指定的布局頁View(回調(diào))。
private?void?showCallbackView(Class?extends?Callback>?status)?{????????if?(getChildCount()?>?0)?{
removeAllViews();
????????}
????????for?(Class?key?:?callbacks.keySet())?{
if?(key?==?status)?{
????addView(callbacks.get(key).getRootView());
}
????????}
????}
自此,LoadSir一個完整的配置,注冊,回調(diào)的過程完成了。不知道你們明白了沒,反正我是有點(diǎn)口渴了。
總結(jié)
建議在Application中全局配置,在BaseActivity,BaseFragment或者M(jìn)VP中封裝使用,能極大的減少代碼量,讓你的代碼更加優(yōu)雅,生活更加愉快。時間和個人能力有限,如果大家發(fā)現(xiàn)需要改進(jìn)的地方,歡迎提交issue。
如果這個庫對你有用的話,也請點(diǎn)個star。
Github傳送門:https://github.com/KingJA/LoadSir
大家都在看
Android仿抖音的音樂旋轉(zhuǎn)效果
安卓技術(shù)架構(gòu)演進(jìn)及未來
Android應(yīng)用安全提升攻略
Android性能優(yōu)化指南 文末附花絮視頻
歡迎前往安卓巴士博客區(qū)投稿,技術(shù)成長于分享
期待巴友留言,共同探討學(xué)習(xí)
總結(jié)
以上是生活随笔為你收集整理的activity中fragment 返回键不退出_优雅地处理加载中(loading),重试(retry)和无数据(empty)等...的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 挖矿哪家公司最好 这几个品牌的矿机行
- 下一篇: mysql 更改 uf_mysql 常用