前言
作为一名Android开发,经常会遇到页面无数据、网络异常,这时候我们都会在xml中设置很多ViewGroup来包裹不同的状态,我刚开始做开发的时候也喜欢这么做,这样写的坏处是一是xml被写的很乱,有时候如果view过多,我们自己也会感觉很乱,另外一点就是一个项目中有很多这种页面,有时候网络异常等页面一个项目都是相同的,所以只要做一份即可,第三点就是一个页面放多种ViewGroup,势必要最外层要包裹一层,还有就是如果只是设置View.VISIBLE而不是View.GONE,ViewGroup一样会绘制,这两种情况都会造成过度绘制,作为一名开发,我们有能力的情况下一定要去做优化处理。
自定义属性介绍
<!--这是多类型页面-->
<declare-styleable name="MultiStateView">
<attr name="msv_loadingView" format="reference" />
<attr name="msv_successView" format="reference" />
<attr name="msv_unknownView" format="reference" />
<attr name="msv_netErrorView" format="reference" />
<attr name="msv_emptyView" format="reference" />
<attr name="msv_emptyViewImage" format="reference" />
<attr name="msv_emptyViewText" format="reference|string" />
</declare-styleable>
看到命名应该都知道其中的含义了,有一点需要说明,因为一个项目中关于view的属性有且只能有一个名字相同的,所以我加了前缀,如果出现冲突,修改自定义属性的名称即可。
- 冲突包括:
1、与系统view属性冲突
2、与AAR中的自定义属性冲突
3、与其他第三方自定义属性冲突
对于项目中的不同状态有不同的对待,一般情况下,一个项目中关于网络异常、加载动画、未知错误都是一样的状态,对于空页面就可能会有不同的状态,所以我们对于不同的网络异常做不同的处理。
共同的状态
项目中共同的状态,我个人分为:网络异常、加载动画、未知错误。大致封装过程都是一样的,我这里只放出loading页面,如下:
显示加载状态
/**
* 显示加载中的状态
*/
public void showLoading() {
if (null == mLoadingView) {
mLoadingView = mInflater.inflate(mLoadingViewId, null);
}
if (mLoadingView != null) {
removeAllViews();
addView(mLoadingView, 0, params);
} else {
throw new NullPointerException("you have to set loading view before that");
}
}
- 说明:
removeAllViews(); 调用此方法是为了让当前页面显示之后一种状态
重新设置自定义加载view
/**
* 设置自定义的加载页面
*
* @param layoutResID
*/
public void setLoadingView(@LayoutRes int layoutResID) {
View inflate = mInflater.inflate(layoutResID, null);
setLoadingView(inflate);
}
public void setLoadingView(View view) {
if (view == null) {
throw new NullPointerException("you set loading view is null");
}
mLoadingView = view;
}
获取加载中的view
/**
* 获取加载页面
*/
public View getLoadingView() {
if (null == mLoadingView) {
mLoadingView = mInflater.inflate(mLoadingViewId, null);
}
return mLoadingView;
}
- 说明:
关于网络异常的情况下,一般都会重试,如下是重试回调接口
/**
* 重新加载页面的回调接口
*/
public interface OnReLodListener {
void onReLoad();
}
空数据页面处理
对于页面数据为空状态,因为项目中可能页面数据类型不同,所有有时候会出现不同的形式的空页面,提供了文本和图片两种属性,如果还是不够使用,可以设置layout的形式。
显示空页面
/**
* 显示无数据状态
*/
public void showEmpty() {
if (null == mEmptyView) {
mEmptyView = mInflater.inflate(mEmptyViewId, null);
}
if (mEmptyView != null&¤tState!=STATE_EMPTY) {
removeAllViews();
handleDiffEmpty();
addView(mEmptyView, 0, params);
currentState = STATE_EMPTY;
} else {
throw new NullPointerException("you have to set empty view before that");
}
}
/**
* 对于页面数据为空处理
*/
private void handleDiffEmpty() {
if (mEmptyView instanceof ViewGroup) {
ViewGroup mEmptyViews = (ViewGroup) mEmptyView;
ImageView emptyImage = (ImageView) mEmptyViews.getChildAt(0);
TextView emptyDesc = (TextView) mEmptyViews.getChildAt(1);
if (emptyImage != null && mEmptyDrawable != null) {
emptyImage.setImageDrawable(mEmptyDrawable);
}
if (emptyDesc != null && TextUtils.isEmpty(mEmptyDesc)) {
emptyDesc.setText(mEmptyDesc);
}
}
}
使用
在xml中:
<com.example.multistateview.MultiStateView
android:id="@+id/multiStateView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
app:msv_emptyViewText="我是空页面描述"
app:msv_successView="@layout/successlayout">
</com.example.multistateview.MultiStateView>
*说明:
如果要获取某一个状态下的View,需要先获取当前状态的View,事例如下:
View successView = multiStateView.getSuccessView();
TextView tv_empty_desc = successView.findViewById(R.id.tv_empty_desc);
其中的原理和代码都非常简单,我把全部代码贴到下面,方便使用。
/**
* @author gexinyu
*/
public class MultiStateView extends FrameLayout {
private static final int STATE_LOADING = 2;
private static final int STATE_SUCCESS = 1;
private static final int STATE_EMPTY = 0;
private static final int STATE_NET_ERROR = -1;
private static final int STATE_UNKNOWN = -2;
private static final int STATE_NEW_STATE = -3;
private Context mContext;
//四种状态默认的viewid
private int mLoadingViewId;
private int mSuccessViewId;
private int mEmptyViewId;
private int mUnKnownViewId;
private int mNetErrorViewId;
private Drawable mEmptyDrawable;
private String mEmptyDesc;
//四种展示的view
private View mLoadingView;
private View mSuccessView;
private View mNetErrorView;
private View mEmptyView;
private View mUnKnownView;
private View newStateView;//这是新的状态页面
private LayoutInflater mInflater;
private ViewGroup.LayoutParams params;
private OnReLodListener mOnReLodListener;//重新加载的监听
private int currentState;
public MultiStateView(@NonNull Context context) {
this(context, null);
}
public MultiStateView(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public MultiStateView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.MultiStateView);
mLoadingViewId = array.getResourceId(R.styleable.MultiStateView_msv_loadingView, R.layout.base_multi_state_loading);
mSuccessViewId = array.getResourceId(R.styleable.MultiStateView_msv_successView, 0);
mEmptyViewId = array.getResourceId(R.styleable.MultiStateView_msv_emptyView, R.layout.base_multi_state_empty);
mEmptyDrawable = array.getDrawable(R.styleable.MultiStateView_msv_emptyViewImage);
mEmptyDesc = array.getString(R.styleable.MultiStateView_msv_emptyViewText);
mUnKnownViewId = array.getResourceId(R.styleable.MultiStateView_msv_unknownView, R.layout.base_multi_state_unknow);
mNetErrorViewId = array.getResourceId(R.styleable.MultiStateView_msv_netErrorView, R.layout.base_multi_state_neterror);
array.recycle();
mInflater = LayoutInflater.from(context);
params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
showLoading();
}
//++++++++++++++++++++++++++++++++加载页面++++++++++++++++++++
/**
* 显示加载中的状态
*/
public void showLoading() {
if (null == mLoadingView) {
mLoadingView = mInflater.inflate(mLoadingViewId, null);
}
if (mLoadingView != null&¤tState!=STATE_LOADING) {
removeAllViews();
addView(mLoadingView, 0, params);
currentState = STATE_LOADING;
} else {
throw new NullPointerException("you have to set loading view before that");
}
}
/**
* 设置自定义的加载页面
*
* @param layoutResID
*/
public void setLoadingView(@LayoutRes int layoutResID) {
View inflate = mInflater.inflate(layoutResID, null);
setLoadingView(inflate);
}
public void setLoadingView(View view) {
if (view == null) {
throw new NullPointerException("you set loading view is null");
}
mLoadingView = view;
}
/**
* 获取加载页面
*/
public View getLoadingView() {
if (null == mLoadingView) {
mLoadingView = mInflater.inflate(mLoadingViewId, null);
}
return mLoadingView;
}
//++++++++++++++++++++++++++++++++成功页面++++++++++++++++++++
/**
* 显示成功状态
*/
public void showSuccess() {
if (null == mSuccessView) {
mSuccessView = mInflater.inflate(mSuccessViewId, null);
}
if (mSuccessView != null&¤tState!=STATE_SUCCESS) {
removeAllViews();
addView(mSuccessView, 0, params);
currentState = STATE_SUCCESS;
} else {
throw new NullPointerException("you have to set success view before that");
}
}
/**
* 设置自定义的成功页面
*
* @param layoutResID
*/
public void setSuccessView(@LayoutRes int layoutResID) {
setSuccessView(mInflater.inflate(layoutResID, null));
}
/**
* 设置自定义的成功页面
*
* @param view
*/
public void setSuccessView(View view) {
if (view == null) {
throw new NullPointerException("you set success view is null");
}
mSuccessView = view;
}
/**
* 获取成功页面
*/
public View getSuccessView() {
if (null == mSuccessView) {
mSuccessView = mInflater.inflate(mSuccessViewId, null);
}
return mSuccessView;
}
//++++++++++++++++++++++++++++++++未知错误页面++++++++++++++++++++
/**
* 显示未知错误页面
*/
public void showUnKnown() {
if (null == mUnKnownView) {
mUnKnownView = mInflater.inflate(mUnKnownViewId, null);
}
if (mUnKnownView != null&¤tState!=STATE_UNKNOWN) {
removeAllViews();
addView(mUnKnownView, 0, params);
currentState = STATE_UNKNOWN;
} else {
throw new NullPointerException("you have to set unknown view before that");
}
}
/**
* 设置自定义的未知错误页面
*
* @param layoutResID
*/
public void setUnKnownView(@LayoutRes int layoutResID) {
View inflate = mInflater.inflate(layoutResID, null);
setUnKnownView(inflate);
}
/**
* 设置自定义的未知错误页面
*
* @param view
*/
public void setUnKnownView(View view) {
if (view == null) {
throw new NullPointerException("you set unknown view is null");
}
mUnKnownView = view;
}
/**
* 获取未知错误页面
*/
public View getUnKnownView() {
if (null == mUnKnownView) {
mUnKnownView = mInflater.inflate(mUnKnownViewId, null);
}
return mUnKnownView;
}
//++++++++++++++++++++++++++++++++网络错误页面++++++++++++++++++++
/**
* 显示加载失败(网络错误)状态 带监听器的
*/
public void showNetError() {
if (null == mNetErrorView) {
mNetErrorView = mInflater.inflate(mNetErrorViewId, null);
}
if (mNetErrorView != null&¤tState!=STATE_NET_ERROR) {
removeAllViews();
addView(mNetErrorView, 0, params);
currentState = STATE_NET_ERROR;
mNetErrorView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
showReLoading();
}
});
} else {
throw new NullPointerException("you have to set unknown view before that");
}
}
/**
* 设置自定义的网络异常
*
* @param layoutResID
*/
public void setNetErrorView(@LayoutRes int layoutResID) {
View inflate = mInflater.inflate(layoutResID, null);
setNetErrorView(inflate);
}
/**
* 设置自定义的网络异常
*
* @param view
*/
public void setNetErrorView(View view) {
if (view == null) {
throw new NullPointerException("you set net error view is null");
}
mNetErrorView = view;
}
/**
* 设置获取网络错误页面
*/
public View getNetErrorView() {
if (null == mNetErrorView) {
mNetErrorView = mInflater.inflate(mNetErrorViewId, null);
}
return mNetErrorView;
}
//++++++++++++++++++++++++++++++++空页面页面++++++++++++++++++++
/**
* 显示无数据状态
*/
public void showEmpty() {
if (null == mEmptyView) {
mEmptyView = mInflater.inflate(mEmptyViewId, null);
}
if (mEmptyView != null&¤tState!=STATE_EMPTY) {
removeAllViews();
handleDiffEmpty();
addView(mEmptyView, 0, params);
currentState = STATE_EMPTY;
} else {
throw new NullPointerException("you have to set empty view before that");
}
}
/**
* 对于网络异常处理
*/
private void handleDiffEmpty() {
if (mEmptyView instanceof ViewGroup) {
ViewGroup mEmptyViews = (ViewGroup) mEmptyView;
ImageView emptyImage = (ImageView) mEmptyViews.getChildAt(0);
TextView emptyDesc = (TextView) mEmptyViews.getChildAt(1);
if (emptyImage != null && mEmptyDrawable != null) {
emptyImage.setImageDrawable(mEmptyDrawable);
}
if (emptyDesc != null && TextUtils.isEmpty(mEmptyDesc)) {
emptyDesc.setText(mEmptyDesc);
}
}
}
/**
* 设置自定义的空页面
*
* @param layoutResID
*/
public void setEmptyView(@LayoutRes int layoutResID) {
View inflate = mInflater.inflate(layoutResID, null);
setEmptyView(inflate);
}
/**
* 设置自定义的空页面
*
* @param view
*/
public void setEmptyView(View view) {
if (view == null) {
throw new NullPointerException("you set net empty view is null");
}
mEmptyView = view;
}
/**
* 设置获取空页面
*/
public View getEmptyView() {
if (null == mEmptyView) {
mEmptyView = mInflater.inflate(mEmptyViewId, null);
}
return mEmptyView;
}
/**
* 设置自定义的新增状态页面空页面
*
* @param layoutResID
*/
public void setNewStateView(@LayoutRes int layoutResID) {
View inflate = mInflater.inflate(layoutResID, null);
setNewStateView(inflate);
}
public void setNewStateView(View view) {
if (view == null) {
throw new NullPointerException("you set net new state view is null");
}
newStateView = view;
}
/**
* 显示新状态view
*/
public void showNewStateView() {
if (newStateView != null&¤tState!=STATE_NEW_STATE) {
removeAllViews();
addView(newStateView, 0, params);
currentState = STATE_NEW_STATE;
} else {
throw new NullPointerException("you set new state view is null");
}
}
public View getNewStateView() {
if (newStateView == null) {
throw new IllegalArgumentException("you has not set new state view");
}
return newStateView;
}
/**
* 再次加载数据
*/
private void showReLoading() {
//第一步重新loading
if (mOnReLodListener != null) {
showLoading();
mOnReLodListener.onReLoad();
} else {
//未设置重新加载回调
Log.e("TAG", "请设置重新加载监听");
}
}
/**
* 外部回调
*
* @param onReLodListener
*/
public void setOnReLodListener(OnReLodListener onReLodListener) {
this.mOnReLodListener = onReLodListener;
}
/**
* 重新加载页面的回调接口
*/
public interface OnReLodListener {
void onReLoad();
}
}