为什么会有这篇文章?
答:因为项目中肯定会用到。
为什么会有这个控件?
- 项目中肯定会遇到"加载中","加载失败","网络错误","数据为空"这四种情况,如果不封装处理起来很不方便。
- 最重要的是上面说的这几种情况,只可能同时出现一种,按照我们平常的写法,每次加载布局的时候都会去加载这些布局,很显然这就降低了一个页面加载的性能。
基于这两点MultiStatusLayout
应运而生,首先说一下我自己的思路吧,既然要处理这么多状态他肯定是一个ViewGroup
了,为了能适用所有的布局文件,它继承自RelativeLayout
。不多哔哔,直接撸起-.-
MultiStatusLayout优点
- 用SparseArray代替HashMap来存储每个View。SparseArray比HashMap更省内存,在某些条件下性能更好,主要是因为它避免了对key的自动装箱(int转为Integer类型),它内部则是通过两个数组来进行数据存储的,一个存储key,另外一个存储value,为了优化性能,它内部对数据还采取了压缩的方式来表示稀疏数组的数据,从而节约内存空间
- 实现按需加载,当你需要做某种操作的时候才会去加载相对应的布局。这样就解决了,多布局堆在一起导致的页面加载效率降低、便于管理多种情况显示的页面的问题。
使用:
在project目录下的build.gradle下添加:
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
在app目录下的build.gradle下添加:
dependencies {
compile 'com.github.Walll-E:MultiStatusLayout:v1.0'
}
在values下面建立attrs资源文件定义所需要的属性
<declare-styleable name="MultiStatusLayout">
<attr name="loadLayout" format="reference" />
<attr name="emptyLayout" format="reference" />
<attr name="netErrorLayout" format="reference" />
<attr name="errorLayout" format="reference" />
<attr name="otherLayout" format="reference"/>
<attr name="targetViewId" format="reference"/>
</declare-styleable>
关于自定义View或者ViewGroup的东西,这里不多介绍,自行百度吧,这里只贴出关键代码,其他代码在我github,文末会有链接
一、获取自定义属性:
- mNetErrorLayout:网络错误时显示的布局id
- mLoadingLayout :加载中显示的布局id
- mErrorLayout : 请求错误时显示的布局id
- mEmptyLayout :请求数据为空时显示的布局id
- mOtherLayout : 此布局id作为扩充以上四种情况不足以满足实际需求时显示的布局id
- mTargetViewId :此id是显示扩充布局时不隐藏View的id(可以是界面title的id)
public MultiStatusLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.MultiStatusLayout, defStyleAttr, 0);
mNetErrorLayout = array.getResourceId(R.styleable.MultiStatusLayout_netErrorLayout, mNetErrorLayout);
mLoadingLayout = array.getResourceId(R.styleable.MultiStatusLayout_loadLayout, mLoadingLayout);
mErrorLayout = array.getResourceId(R.styleable.MultiStatusLayout_errorLayout, mErrorLayout);
mEmptyLayout = array.getResourceId(R.styleable.MultiStatusLayout_emptyLayout, mEmptyLayout);
mOtherLayout = array.getResourceId(R.styleable.MultiStatusLayout_otherLayout, mOtherLayout);
mTargetViewId = array.getResourceId(R.styleable.MultiStatusLayout_targetViewId, -1);
array.recycle();
}
二、加载布局中包裹的每个View,并且将每个View添加至SparseArray
/**
* 重写此方法,获取到其中的子控件个数、子控件将其添加至SparseArray(用于管理不同情况View的显示)中。
*/
@Override
protected void onFinishInflate() {
super.onFinishInflate();
int childCount = mContentViewCount = getChildCount();
mContentViews = new SparseArray<>();
//隐藏布局中的子控件并且添加至mContentViews
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
mContentViews.put(i, childView);
}
}
显示加载中的布局
/**
* 显示加载中布局
*/
public void showLoading() {
//先从中获取加载中的布局
View view = mContentViews.get(mContentViewCount);
//显示加载中的布局
showView(view,mLoadingLayout,mContentViewCount);
}
显示网络错误布局
/**
* 显示网络错误的布局
*/
public void showNetError() {
View view = mContentViews.get(mContentViewCount + 1);
showView(view, mNetErrorLayout, mContentViewCount + 1);
}
显示列表数据为空的布局
/**
* 列表数据为空时显示此布局
*/
public void showEmpty() {
View view = mContentViews.get(mContentViewCount + 2);
showView(view, mEmptyLayout, mContentViewCount + 2);
}
显示数据错误或服务器错误的布局
/**
* 加载数据错误时显示此布局
*/
public void showError() {
View view = mContentViews.get(mContentViewCount + 3);
showView(view, mErrorLayout, mContentViewCount + 3);
}
显示扩充布局
/**
* 显示扩充布局
*/
public void showOther() {
//先从中获取加载中的布局
View view = mContentViews.get(mContentViewCount + 4);
//显示加载中的布局
showView(view, mOtherLayout, mContentViewCount + 4);
}
显示View,如果View为null时去加载并且添加至此layout中,并且添加至SparseArray中
/**
* 显示View,如果View为null时去加载添加至此layout中,并且添加至SparseArray中
* @param view
* @param layoutId
* @param index
*/
private void showView(View view,int layoutId,int index){
//如果子控件处于显示状态先隐藏所有的子控件
hideViews();
if (view==null){
view = inflate(mContext,layoutId,null);
addView(view,layoutParams);
mContentViews.put(index,view);
}else {
view.setVisibility(VISIBLE);
}
//设置网络错误或者数据错误布局的点击事件
if (index == mContentViewCount + 1 || index == mContentViewCount + 3) {
view.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (onReloadDataListener != null) onReloadDataListener.reloadData();
}
});
}
}
隐藏所有的布局
/**
* 除了title之外,隐藏所有显示的View
*/
private void hideViews() {
for (int i = 0; i < mContentViews.size(); i++) {
View view = mContentViews.valueAt(i);
if (mTargetViewId != view.getId() && view.getVisibility() != GONE) {
view.setVisibility(GONE);
}
}
}
获取相应的布局,做你想做的具体操作
/**
* 获取加载布局
* @return
*/
public View getLoadingView(){
View view = mContentViews.get(mContentViewCount);
if (view==null) {
//如果加载中布局为null,证明还未加载此布局
view = inflate(mContext, mLoadingLayout, null);
mContentViews.put(mContentViewCount, view);
}
return view;
}
设置重新加载数据的监听(一般情况下只有网络错误或者服务器错误时需要重新加载数据)
/**
* 设置重新加载数据的监听
*/
public void setOnReloadDataListener(final OnReloadDataListener onReloadDataListener) {
this.onReloadDataListener = onReloadDataListener;
}
/**
* 重新加载的监听器
*/
public interface OnReloadDataListener {
void reloadData();
}
代码已上传至github
网友评论