优雅地处理加载中(loading),重试(retry)和无数据(empty)等---LoadSir
2017-09-26 09:59
495 查看
LoadSir是一个高效易用,低碳环保,扩展性良好的加载反馈页管理框架,在加载网络或其他数据时候,根据需求切换状态页面,可添加自定义状态页面,如加载中,加载失败,无数据,网络超时,占位图,登录失效等常用页面。可配合网络加载框架,结合返回状态码,错误码,数据进行状态页自动切换,封装使用效果更佳。Github传送门
本文前面是使用流程,后面是原理解析,如果大家有兴趣,可耐心看完。
效果预览
in Activity | in View | in Fragment |
---|---|---|
Placeholder | Muitl-Fragment | ViewPage+Fragment |
---|---|---|
使用场景
下面为大家常见的加载反馈页面:loading | error | timeout |
---|---|---|
empty | custom | placeholder |
---|---|---|
LoadSir把这些事做了,接下来我们就来了解一下它。
LoadSir的功能及特点
支持Activity,Fragment,Fragment(v4),View状态回调适配多个Fragment切换,及Fragment+ViewPager切换,不会状态叠加或者状态错乱
利用泛型转换输入信号和输出状态,可根据网络返回体的状态码或者数据返回自动适配状态页,实现全局自动状态切换
只加载唯一一个状态视图,不会预加载全部视图
可设置重新加载点击事件(OnReloadListener)
可自定义状态页(继承Callback类)
可在子线程直接切换状态
可设置初始状态页(常用进度页作为初始状态)
不需要设置枚举或者常量状态值,直接用状态页类类型(xxx.class)作为状态码
可扩展状态页面,在配置中添加自定义状态页
可对单个状态页单独设置点击事件,根据返回boolean值覆盖或者结合OnReloadListener使用,如网络错误可跳转设置页
可全局单例配置,也可以单独配置
无预设页面,低耦合,开发者随心配置
开始使用LoadSir
LoadSir的使用只需要简单的三步,三步上篮的三步。添加依赖
compile 'com.kingja.loadsir:loadsir:1.2.0'
第一步: 配置
全局配置方式全局配置方式,使用的是单例模式,即获取的配置都是一样的。可在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 T f0d2 imeoutCallback()) .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中使用@Override protected 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<HttpResult>() { @Override public 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为了完全解耦,没有预设任何状态页,开发者根据需求自定义自己的回调页面,比如加载中,没数据,错误,超时等常用页面,设置布局及自定义点击逻辑
public class CustomCallback extends Callback { @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则两个都执行 } }
代码混淆
-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<Callback> 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 <T> LoadService register(Object target, Callback.OnReloadListener onReloadListener, Convertor<T> convertor) { TargetContext targetContext = LoadSirUtil.getTargetContext(target); return new LoadService<>(convertor, targetContext, onReloadListener, builder); }
在LoadService的构造方法中根据target等信息创建Success视图,并且生成LoadLayout,相当于LoadSir每次注册都会创建一个LoadLayout。
LoadService(Convertor<T> 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:p Github传送门
作者:KingJA
链接:http://www.jianshu.com/p/2d3537101281
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
相关文章推荐
- 优雅地处理加载中(loading),重试(retry)和无数据(empty)等
- 无缝为Activity、Fragment、任何View设置加载(loading)、重试(retry)和无数据(empty)页面
- 无缝为Activity、Fragment、任何View设置加载(loading)、重试(retry)和无数据(empty)页面。
- 二、瀑布流数据加载后的布局处理(瀑布流的封装)
- kendo listview 从服务器读取数据时加载菊花图(loading...)解决方案.
- loading数据加载的6种形式
- 如何将需要处理的数据加载到项目中Scala项目中
- EntityFramework 1.0 在查询中需要引用子表信息时的子表数据加载处理
- Android官方开发文档Training系列课程中文版:后台加载数据之处理CursorLoader的查询结果
- js 处理加载数据等待
- 处理顶点——从XML文件加载数据
- SpringMVC 转换器实现控制器返回字节流数据优雅处理
- JQuery、AJAX加载数据时候的loading加载动画实现步骤
- 带图片的ListView(GridView)的图片异步加载、OOM处理以及图片和数据缓存策略的研究
- 使用Android JobScheduler优雅的处理后台数据
- Android Fragment切换 和 数据懒加载的分离处理
- 返回JSON数据,懒加载异常的处理
- Error loading opeating system 加载系统错误处理方法
- Silverlight数据加载时,等待图标显示与隐藏(Loading)
- winform耗时代码处理,仿win10加载动画Loading