activity中fragment 返回键不退出_优雅地处理加载中(loading),重试(retry)和无数据(empty)等...
LoadSir是一個高效易用,低碳環保,擴展性良好的加載反饋頁管理框架,在加載網絡或其他數據時候,根據需求切換狀態頁面,可添加自定義狀態頁面,如加載中,加載失敗,無數據,網絡超時,占位圖,登錄失效等常用頁面。可配合網絡加載框架,結合返回狀態碼,錯誤碼,數據進行狀態頁自動切換,封裝使用效果更佳。
效果預覽
使用場景
下面為大家常見的加載反饋頁面:
面對這么多狀態頁面,你是不是還在用include的方式,setVisibility(View.VISIBLE/GONE),這種方式即不方便控制,也造成了視圖層級冗余(你要把所有狀態布局include進一個視圖)。如果有一種工具,能把這些事都做了就好了。恰好, ?LoadSir 把這些事做了,接下來我們就來了解一下它。
LoadSir的功能及特點
支持Activity,Fragment,Fragment(v4),View狀態回調
適配多個Fragment切換,及Fragment+ViewPager切換,不會狀態疊加或者狀態錯亂
利用泛型轉換輸入信號和輸出狀態,可根據網絡返回體的狀態碼或者數據返回自動適配狀態頁,實現全局自動狀態切換
只加載唯一一個狀態視圖,不會預加載全部視圖
可保留標題欄(Toolbar,titile view等)
可設置重新加載點擊事件(OnReloadListener)
可自定義狀態頁(繼承Callback類)
可在子線程直接切換狀態
可設置初始狀態頁(常用進度頁作為初始狀態)
不需要設置枚舉或者常量狀態值,直接用狀態頁類類型(xxx.class)作為狀態碼
可擴展狀態頁面,在配置中添加自定義狀態頁
可對單個狀態頁單獨設置點擊事件,根據返回boolean值覆蓋或者結合OnReloadListener使用,如網絡錯誤可跳轉設置頁
可全局單例配置,也可以單獨配置
無預設頁面,低耦合,開發者隨心配置
開始使用LoadSir
LoadSir的使用只需要簡單的三步,三步上籃的三步。
添加依賴
compile?'com.kingja.loadsir:loadsir:1.3.6'第一步: 配置
全局配置方式
全局配置方式,使用的是單例模式,即獲取的配置都是一樣的。可在Application中配置,添加狀態頁,設置初始化狀態頁,建議使用這種配置方式。
public?class?App?extends?Application?{????@Override
????public?void?onCreate()?{
????????super.onCreate();
????????LoadSir.beginBuilder()
????.addCallback(new?ErrorCallback())//'添加各種狀態頁
????.addCallback(new?EmptyCallback())
????.addCallback(new?LoadingCallback())
????.addCallback(new?TimeoutCallback())
????.addCallback(new?CustomCallback())
????.setDefaultCallback(LoadingCallback.class)//設置默認狀態頁
????.commit();
????}
}
單獨配置方式
如果你即想保留全局配置,又想在某個特殊頁面加點不同的配置,可采用該方式。
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();
}
第三步: 回調
直接回調
protected?void?loadNet()?{????????//?進行網絡訪問...
????????//?進行回調
????????loadService.showSuccess();//成功回調
????????loadService.showCallback(EmptyCallback.class);//其他回調
????}
轉換器回調 (推薦使用)
如果你不想再每次回調都要手動進行的話,可以選擇注冊的時候加入轉換器,可根據返回的數據,適配對應的回調。
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://成功回調if?(httpResult.getData().size()?==?0)?{
????????resultCode?=?EmptyCallback.class;
????}else{
????????resultCode?=?SuccessCallback.class;
????}break;case?ERROR_CODE:
????resultCode?=?ErrorCallback.class;break;
????????}return?resultCode;
????}
});
回調的時候直接傳入轉換器指定的數據類型。
loadService.showWithConvertor(httpResult);自定義回調頁
LoadSir為了完全解耦,沒有預設任何狀態頁,開發者根據需求自定義自己的回調頁面,比如加載中,沒數據,錯誤,超時等常用頁面,
設置布局及自定義點擊邏輯
????@Override
????protected?int?onCreateView()?{
????????return?R.layout.layout_custom;
????}
????@Override
????protected?boolean?onRetry(final?Context?context,?View?view)?{
????????//布局點擊事件
????????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時傳入的重試點擊事件,返回false則兩個都執行
????}
????//是否在顯示Callback視圖的時候顯示原始圖(SuccessView),返回true顯示,false隱藏
????@Override
????public?boolean?getSuccessVisible()?{
????????return?super.getSuccessVisible();
????}
????//將Callback添加到當前視圖時的回調,View為當前Callback的布局View
????@Override
????public?void?onAttach(Context?context,?View?view)?{
????????super.onAttach(context,?view);
????}
????//將Callback從當前視圖刪除時的回調,View為當前Callback的布局View
????@Override
????public?void?onDetach()?{
????????super.onDetach(context,?view);
????}
}
動態修改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效果狀態頁類似ShimmerRecyclerView的效果. LoadSir只用了一個自定義狀態頁PlaceHolderCallback就完成類似的效果,是不是很棒 :p
看到這,想必各位使用LoadSir應該沒問題了,如果還想再進一步了解它的內部結構,可以繼續往下看。
原理解析
流程圖
關鍵類
LoadSir:提供單例模式獲取全局唯一實例,內部保存配置信息,根據配置創建LoadService。
LoadService:具體操作服務類,提供showSuccess,showCallback,showWithCoverator等方法來進行狀態頁回調。
LoadLayout:最終顯示在用戶面前的視圖View,替換了原布局,是LoadService直接操作對象,要顯示的狀態頁的視圖會被添加到LoadLayout上。
Callback:狀態頁抽象類,抽象自定義布局和自定義點擊事件兩個方法留給子類實現。
Coverator:轉換接口,可將網絡返回實體轉換成對應的狀態頁,達到自動適配狀態頁的目的。
我們直接觀察在Activity中普通加載和使用LoadSir加載視圖的區別
>>>沒使用LoadSir
>>>使用LoadSir
大家可以看到,LoadSir用LoadLayout把原來的布局給替代掉了,原來的布局加在了LoadLayout上,其它自定義的狀態頁也同樣會被加到這個LoadLayout上(顯示的時候),而且LoadLayout的子View只有一個,就是當前要顯示的狀態頁布局,并沒有把當前不顯示的比如加載中布局,錯誤布局,無數據布局加載進來,這也是LoadSir的優點之一,按需加載,并且只加載一個狀態布局。
>>>替換邏輯
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上。這樣也達到了一樣的目的。
下面是ViewPager+Fragment場景中使用LoadSir的視圖,兩個Fragment用各自的LoadLayout進行視圖分離,避免了狀態頁疊加或錯位。
看到這的童鞋應該也大概知道LoadSir是怎么回事了,如果想明白LoadSir的代碼實現,請繼續往下看。
源碼解析
我們按上面三步上籃的步驟來稍微分析下源碼
>>>第一步:配置
單例模式獲取LoadSir,在LoadSir構造的時候創建默認配置
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主要提供添加狀態頁,和設置默認狀態頁的方法
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()來設置全局配置。
public?class?LoadSir??{???...
????public?static?Builder?beginBuilder()?{
????????return?new?Builder();
????}
????public?static?class?Builder?{
????????public?void?commit()?{
getDefault().setBuilder(this);
????????}
??????...
????}
}
>>>第二步:注冊
LoadSir注冊后返回的是LoadService,一看名字大家就明白這是服務類,就是我們所說的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的構造方法中根據target等信息創建Success視圖,并且生成LoadLayout,相當于LoadSir每次注冊都會創建一個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);
????}
>>>第三步:回調
LoadService的三個回調方法最終調用的都是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是否配置判斷,然后進行線程安全操作。重點還是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);
????????}
????}
這個方法可以說是最后的執行者,就做兩件事,刪除LoadLayout所有子View(重置),添加指定的布局頁View(回調)。
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一個完整的配置,注冊,回調的過程完成了。不知道你們明白了沒,反正我是有點口渴了。
總結
建議在Application中全局配置,在BaseActivity,BaseFragment或者MVP中封裝使用,能極大的減少代碼量,讓你的代碼更加優雅,生活更加愉快。時間和個人能力有限,如果大家發現需要改進的地方,歡迎提交issue。
如果這個庫對你有用的話,也請點個star。
Github傳送門:https://github.com/KingJA/LoadSir
大家都在看
Android仿抖音的音樂旋轉效果
安卓技術架構演進及未來
Android應用安全提升攻略
Android性能優化指南 文末附花絮視頻
歡迎前往安卓巴士博客區投稿,技術成長于分享
期待巴友留言,共同探討學習
總結
以上是生活随笔為你收集整理的activity中fragment 返回键不退出_优雅地处理加载中(loading),重试(retry)和无数据(empty)等...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 挖矿哪家公司最好 这几个品牌的矿机行
- 下一篇: mysql 更改 uf_mysql 常用