平时我们在app的开发中肯定或多或少都会使用到加载等待框,来提升用户体验。相信我们绝大多数开发者在开发过程中都是使用在需要加载等待框的布局文件中加一个带动画效果的view,然后通过代码逻辑控制view的显示和隐藏,但是这样造成的后果就是view和我们的代码耦合度非常高,并且也会造成很多冗余的代码。可能在使用加载框的过程中会有一部分人通过构建utils工具类来进行view的统一管理,然后让需要使用的页面去调用,但是还是不能达到解耦的目的。 为了解决这个困惑,发现了一个加载等待框的解耦神器,做到了加载框和app项目的低耦合,只需要封装一次,就可以在多个APP中同时使用;这个神器就是Gloading-master:
大多数人可能对这个开源框架不太了解,所以在这里根据个人理解简单介绍下Gloading-master的原理和使用方式:
、Gloading-master框架分为三个主要部分, 、Gloading, Gloading.Adapter,Gloading.Holder;
它主要是用了适配器adapter的思想来进行view的解耦,像ListView,RecycleView都是利用Adapter来进行解耦,
首先介绍Gloading.Adapter这个接口
/**
* Status view to show current page loading status
*/
public interface Adapter {
/**
* get view for current status
* @param holder Holder
* @param convertView The old view to reuse, if possible.
* @param status current status
* @return status view to show. Maybe convertView for reuse.
* @see Holder
*/
View getView(Holder holder, View convertView, int status);
}
用户要先通过实现adapter接口的getView方法把需要显示加载框的view(convertview)传递进去,实现adapter和view的绑定;示例代码如下
public class SpecialAdapter implements Gloading.Adapter {
@Override
public View getView(Gloading.Holder holder, View convertView, int status) {
if (status == STATUS_LOADING) {
//only loading UI special
if (convertView == null || !(convertView instanceof
SpecialLoadingStatusView)) {
convertView = new SpecialLoadingStatusView(holder.getContext());
}
} else {
//other status use global UI
GlobalLoadingStatusView view;
if (convertView == null || !(convertView instanceof
GlobalLoadingStatusView)) {
view = new GlobalLoadingStatusView(holder.getContext(),
holder.getRetryTask());
convertView = view;
} else {
view = (GlobalLoadingStatusView) convertView;
}
view.setStatus(status);
}
return convertView;
}
/**
* special loading status view for only one activity usage
* @author billy.qi
* @since 19/3/19 23:12
*/
class SpecialLoadingStatusView extends RelativeLayout implements
View.OnClickListener{
private final Runnable mRetryTask;
public SpecialLoadingStatusView(Context context,Runnable mRetryTask) {
super(context);
setGravity(Gravity.CENTER);
setBackgroundColor(0xCCCCCCCC);
LayoutInflater.from(context).inflate(R.layout.view_special_loading, this,
true);
this.mRetryTask = mRetryTask;
LVFinePoiStar lvFinePoiStar = findViewById(R.id.loading_anim);
lvFinePoiStar.setViewColor(Color.WHITE);
lvFinePoiStar.setCircleColor(Color.YELLOW);
lvFinePoiStar.setDrawPath(true);
lvFinePoiStar.startAnim(2000);
}
@Override
public void onClick(View v) {
if (mRetryTask != null) {
mRetryTask.run();
}
}
}
}
通过上面代码可以看到,实现了Adapter接口以后,紧接着在getView方法中将我们传递进去convertview和实现动画效果的view的实现关联;
接着我们看第二个关键类Gloading.Holder
Gloading.Holder比喻为一个管理者,它通过掌管各种状态(加载中,加载失败,加载完成)来控制加载动画的显示与隐藏,我 们先看下源码
*/
public static class Holder {
private Adapter mAdapter;
private Context mContext;
private Runnable mRetryTask;
private View mCurStatusView;
private ViewGroup mWrapper;
private int curState;
private SparseArray mStatusViews = new SparseArray<>(4);
private Object mData;
private Holder(Adapter adapter, Context context, ViewGroup wrapper) {
this.mAdapter = adapter;
this.mContext = context;
this.mWrapper = wrapper;
}
/**
* set retry task when user click the retry button in load failed page
* @param task when user click in load failed UI, run this task
* @return this
*/
public Holder withRetry(Runnable task) {
mRetryTask = task;
return this;
}
/**
* set extension data. maybe we need this data within {@link Adapter#getView(Holder, View, int)}
* @param data extension data
* @return this
*/
public Holder withData(Object data) {
this.mData = data;
return this;
}
/** show UI for status: {@link #STATUS_LOADING} */
public void showLoading() {
showLoadingStatus(STATUS_LOADING);
}
/** show UI for status: {@link #STATUS_LOAD_SUCCESS} */
public void showLoadSuccess() {
showLoadingStatus(STATUS_LOAD_SUCCESS);
}
/** show UI for status: {@link #STATUS_LOAD_FAILED} */
public void showLoadFailed() {
showLoadingStatus(STATUS_LOAD_FAILED);
}
/** show UI for status: {@link #STATUS_EMPTY_DATA} */
public void showEmpty() {
showLoadingStatus(STATUS_EMPTY_DATA);
}
/**
* Show specific status UI
* @param status status
* @see #showLoading()
* @see #showLoadFailed()
* @see #showLoadSuccess()
* @see #showEmpty()
*/
public void showLoadingStatus(int status) {
if (curState == status || !validate()) {
return;
}
curState = status;
//first try to reuse status view
View convertView = mStatusViews.get(status);
if (convertView == null) {
//secondly try to reuse current status view
convertView = mCurStatusView;
}
try {
//call customer adapter to get UI for specific status. convertView can be reused
View view = mAdapter.getView(this, convertView, status);
if (view == null) {
printLog(mAdapter.getClass().getName() + ".getView returns null");
return;
}
if (view != mCurStatusView || mWrapper.indexOfChild(view) < 0) {
if (mCurStatusView != null) {
mWrapper.removeView(mCurStatusView);
}
mWrapper.addView(view);
ViewGroup.LayoutParams lp = view.getLayoutParams();
if (lp != null) {
lp.width = ViewGroup.LayoutParams.MATCH_PARENT;
lp.height = ViewGroup.LayoutParams.MATCH_PARENT;
}
} else if (mWrapper.indexOfChild(view) != mWrapper.getChildCount() - 1) {
// make sure loading status view at the front
view.bringToFront();
}
mCurStatusView = view;
mStatusViews.put(status, view);
} catch(Exception e) {
if (DEBUG) {
e.printStackTrace();
}
}
}
private boolean validate() {
if (mAdapter == null) {
printLog("Gloading.Adapter is not specified.");
}
if (mContext == null) {
printLog("Context is null.");
}
if (mWrapper == null) {
printLog("The mWrapper of loading status view is null.");
}
return mAdapter != null && mContext != null && mWrapper != null;
}
public Context getContext() {
return mContext;
}
/**
* get wrapper
* @return container of gloading
*/
public ViewGroup getWrapper() {
return mWrapper;
}
/**
* get retry task
* @return retry task
*/
public Runnable getRetryTask() {
return mRetryTask;
}
/**
*
* get extension data
* @param return type
* @return data
*/
public T getData() {
try {
return (T) mData;
} catch(Exception e) {
if (DEBUG) {
e.printStackTrace();
}
}
return null;
}
}
通过源码可以看到holder持有adapter和mCurStatusView以及Runnable mRetryTask;
的引用,他会调用showLoadingStatus()方法,在此方法中通过
View view = mAdapter.getView(this, convertView, status);来得到一个view对象;
然后将这view对象赋值给mCurStatusView = view; 而我们的showLoading(),showLoadSuccess()等方法都是最终调用了showLoadingStatus(),然后外部再通过调用holder的showLoading(),showLoadSuccess()来实现对动画view状态的管理;
mRetryTask成员变量主要是给用户点击重试之后回调用的一个任务,后面我们会介绍这个变量
相信写到这里大家应该能对Gloading-master原理有个大致的了解了。
我们看了Gloading.Adapter,Gloading.Holder类的源码和使用方式,应该会有一个疑问,外部如何去得到Gloading.Holder,我们自己实现的GlaodingAdapter又如何传递赋值给Gloading.Holder类中的Adapter呢
这样就需要我们的Gloading类登场了
public static void initDefault(Adapter adapter) {
getDefault().mAdapter = adapter;
}
/**
* Create a new Gloading different from the default one
* @param adapter another adapter different from the default one
* @return Gloading
*/
public static Gloading from(Adapter adapter) {
Gloading gloading = new Gloading();
gloading.mAdapter = adapter;
return gloading;
}
/**
* get default Gloading object for global usage in whole app
* @return default Gloading object
*/
public static Gloading getDefault() {
if (mDefault == null) {
synchronized (Gloading.class) {
if (mDefault == null) {
mDefault = new Gloading();
}
}
}
return mDefault;
}
我们看第一个静态方法initDefault(Adapter adapter),没错我们就是通过这个方法把我们自己实现的adapter传递到Gloading
示例代码
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
Gloading.initDefault(new SpecialAdapter());
}
}
Adapter传递到Gloading之后,我们再调用Gloading类的wrap方法
public Holder wrap(Activity activity) {
ViewGroup wrapper = activity.findViewById(android.R.id.content);
return new Holder(mAdapter, activity, wrapper);
}
此方法返回的是一个holder对象,并且把Gloading的Adapter变量通过构造holder方法赋值给了holder类的成员变量Adapter;
mHolder = Gloading.getDefault().wrap(this).withRetry(new Runnable() {
@Override public void run() {
onLoadRetry();
} });
有同学可能对
mHolder = Gloading.getDefault().wrap(this).withRetry(new Runnable() {
@Override
public void run() {
onLoadRetry();
}
});
}
中的withRetry(Runnable)不太理解,这个runnable并不是开启了线程,他只是一个任务,作用就是我们在点击了重试之后,会触发的一个方法,大家可以上面Gloading.Adapter的SpecialLoadingStatusView里面持有Runnable的引用。就是用来给用户点击重试的回调任务;
实现代码示例,我们自定义的Adapter的是持有mRetryTask的引用,通过holder的
public Holder withRetry(Runnable task) {
mRetryTask = task;
return this;
}
方法给runnable赋值,在我们自定义的Adapter的getView方法里面实现了点击事件的回调,当用户点击了重试,就会执行
mRetryTask.run();
这里我们就可以在需要用加载动画的类中,通过mHolder类来控制动画view的显示与隐藏了;
实例代码
public class BaseActivity extends AppCompatActivity {
Gloading.Holder mHolder;
protected void initLoadingStatus() {
if (null == mHolder) {
mHolder = Gloading.getDefault().wrap(this).withRetry(new Runnable() {
@Override
public void run() {
onLoadRetry();
}
});
}
}
protected void onLoadRetry() {
}
protected void showLoading() {
initLoadingStatus();
if (null != mHolder) {
mHolder.showLoadingStatus(Gloading.STATUS_LOADING);
}
}
protected void showLoadSuccess() {
initLoadingStatus();
if (null != mHolder) {
mHolder.showLoadingStatus(Gloading.STATUS_LOAD_SUCCESS);
}
}
protected void showLoadFailed() {
initLoadingStatus();
if (null != mHolder) {
mHolder.showLoadingStatus(Gloading.STATUS_LOAD_FAILED);
}
}
protected void showEmpty() {
initLoadingStatus();
if (null != mHolder) {
mHolder.showLoadingStatus(Gloading.STATUS_EMPTY_DATA);
}
}
}
子类只需要集成baseActivity、然后调用各种状态下的show方法就可以了;
还有更多关于Gloading-master的使用,我就不一一举例了大家可以上github找一下关于这个神器的用法