LoadingBar - 如何更优雅的使用Loading
Github地址:
https://github.com/xiandanin/LoadingBar
前言
Loading是很普遍的需求,比如请求的时候需要显示Loading,请求完成以后再取消Loading,而一般的实现方式是在布局xml里添加一个ProgressBar,但是这样写就有很多不便,每个页面的layout都要写一个ProgressBar,显示的位置也固定了,还耦合了很多代码。
而LoadingBar就是为了跟方便的操作Loading而生,高度解耦,样式全部通过工厂类决定。
结构介绍
LoadingBar - 适合一些显示数据的操作,比如请求列表
LoadingDialog - 适合一些提交数据的操作,比如注册,登录
Factory - 决定了loading的样式,自定义样式只需实现Factory
快速开始
Android Studio - 在build.gradle中引入
1 2 3 4
| compile 'com.dyhdyh.loadingbar:loadingbar:1.4.7'
implementation "com.android.support:appcompat-v7:xxx"
|
LoadingBar
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| LoadingBar.make(parent).show();
LoadingBar.make(parent,loadingView).show(); LoadingBar.make(parent,loadingFactory).show();
LoadingBar.make(parent,loadingFactory) .setOnClickListener(clickListener) .setOnLoadingBarListener(loadingBarListener) .show();
LoadingBar.cancel(parent);
|
LoadingDialog
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| LoadingDialog.make(context).show();
LoadingDialog.make(context, dialogFactory).show();
LoadingDialog.make(context, dialogFactory) .setMessage(message) .setCancelable(cancelable) .show();
Dialog dialog = LoadingDialog.make(context, dialogFactory) .setMessage(message) .setCancelable(cancelable) .create(); dialog.setOnCancelListener(cancelListener); dialog.set... dialog.show();
LoadingDialog.cancel();
|
自定义Factory
1 2 3 4 5 6 7 8
| public class CustomLoadingFactory implements LoadingFactory {
@Override public View onCreateView(ViewGroup parent) { View loadingView = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_custom, parent,false); return loadingView; } }
|
全局配置
1 2
| LoadingConfig.setFactory(loadingFactory,dialogFactory);
|
资源释放
其实LoadingBar在cancel的时候已经释放掉了,可以不用手动释放,但是这里也提供释放的方法,根据自己需要选择
在Activity onDestroy调用,个人建议在BaseActivity,资源释放只会释放无效的资源
源码解析
定义结构
首先我一开始就想好了,这得有三样东西,LoadingBar与LoadingDialog,用Factory来生产loading所需要的View和Dialog
定义接口
两者的共同点是都会有显示,所以我定义了一个共用的接口
1 2 3
| public interface ILoading { void show(); }
|
LoadingBar除了有show还得有cancel
1 2 3
| public interface ILoadingBar extends ILoading { void cancel(); }
|
LoadingDialog最终都是操作Dialog,所以它得有create,再附加一些设置常用属性的方法
1 2 3 4 5 6 7 8
| public interface ILoadingDialog extends ILoading { Dialog create();
ILoadingDialog setCancelable(boolean flag);
ILoadingDialog setMessage(CharSequence message); }
|
LoadingFactory onCreateView返回的View就决定了Loading长什么样
1 2 3 4
| public interface LoadingFactory {
View onCreateView(ViewGroup parent); }
|
DialogFactory 主要是onCreateDialog,这个方法决定了Dialog长什么样,在这里实现创建Dialog
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public interface DialogFactory { /** * 创建dialog * @param context * @return */ Dialog onCreateDialog(Context context);
/** * 设置提示消息 * @param dialog * @param message */ void setMessage(Dialog dialog,CharSequence message);
/** * 进入退出的动画id * @return */ @StyleRes int getAnimateStyleId(); }
|
LoadingBar的实现
其实就是需要两个View,mView就是factory.onCreateView返回的LoadingView,mParent就是现实
1 2 3 4
| private LoadingBar(ViewGroup parent, LoadingFactory factory) { mParent = parent; mView = factory.onCreateView(mParent); }
|
然后把mView添加到mParent里,这样mView就处于最上层,覆盖着内容,这样就达到了Loading的效果
1 2 3 4 5 6 7 8 9
| public void show() { if (mView != null) { mView.setVisibility(View.VISIBLE); if (mView.getParent() != null) { mParent.removeView(mView); } mParent.addView(mView); } }
|
取消很简单,就直接把mView移除掉就好了
1 2 3 4 5 6 7 8 9 10
| public void cancel() { if (mView != null) { mView.setVisibility(View.GONE); mParent.removeView(mView); mView = null; if (this.mListener != null) { this.mListener.onCancel(mParent); } } }
|
值得一说的还有findSuitableParent,因为Loading是要在可覆盖的布局上才有作用的,而当parent传的是非覆盖的布局(例如LinearLayout),这个方法会一直往外层寻找可覆盖的布局
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| private static ViewGroup findSuitableParent(View parent) { if (parent == null) { return null; } View suitableParent = parent; do { if (suitableParent instanceof FrameLayout || suitableParent instanceof RelativeLayout || "android.support.v4.widget.DrawerLayout".equals(suitableParent.getClass().getName()) || "android.support.design.widget.CoordinatorLayout".equals(suitableParent.getClass().getName()) || "android.support.v7.widget.CardView".equals(suitableParent.getClass().getName())) { return (ViewGroup) suitableParent; } else { final ViewParent viewParent = suitableParent.getParent(); suitableParent = viewParent instanceof View ? (View) viewParent : null; return (ViewGroup) suitableParent; } } while (suitableParent != null); }
|
LoadingDialog的实现
构造方法先用factory创建了dialog,如果有动画设置动画
1 2 3 4 5 6 7 8
| public LoadingDialog(Context context, DialogFactory factory) { this.mDialog = factory.onCreateDialog(context); this.mFactory = factory; int animateStyleId = this.mFactory.getAnimateStyleId(); if (animateStyleId > 0) { this.mDialog.getWindow().setWindowAnimations(animateStyleId); } }
|
因为Dialog是单例,如果在Activity已经finish了再去操作做个Dialog的话,就会抛异常,所以在show与cancel的时候要先检查是否能够操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public void show() { if (isValid() && !mDialog.isShowing()) { mDialog.show(); } }
public void cancelDialog() { if (isValid() && mDialog.isShowing()) { mDialog.cancel(); } }
private boolean isValid() { if (mDialog != null) { Context context = mDialog.getContext(); if (context instanceof ContextWrapper){ context = ((ContextWrapper) context).getBaseContext(); } if (context instanceof Activity) { if (!((Activity) context).isFinishing()) { return true; } } } return false; }
|
总结
使用场景不局限于请求,其实还有很多异步操作都能用上
比如压缩图片,也可以用LoadingDialog
更多玩法等你挖掘,有问题可以去Github的issue提出
Be the first person to leave a comment!