前言
在Android开发中我们经常遇到这样的场景:去加载网络数据时需要显示进度条,提示用户正在加载,加载失败需要给用户提示加载失败,还需要能够重新加载,数据为空也要给用户相应的提示。对于一个初入编程之世的程序员来说这肯定是令人头(dan)疼(teng)的问题,在开发中他们会这么做:在一个XML布局文件中添加加载成功,失败,数据为空的布局,在请求过程中通过设置View的“显示”和“隐藏”来达到这种效果,每当看到这样写,我的内心是崩溃的(当然,之前我也是这样的O.o),后来随着公司的项目功能扩展也来越庞大,我觉得不能再用这样扯淡的方法来解决这种操蛋的问题,就决定自定义一个控件解决该问题,经过几天的思考ProgressStateLayout诞生了。
思路
提起自定义控件,我们首先就会想到自定义控件的基本流程:onFinishInflate(),onMeasure(),onLayout(),onDraw(),onAttachedToWindow()当然,有这样的想法很正确,但 不一定每个方法都要重写,这需要结合实际的业务需求。在ProgressStateLayout中需要封装四中状态(四个View),这时简单的View已经不能满足我们的需求,我们需要通过ViewGroup来实现该功能。
1:ProgressStateLayout需要继承RelativeLayout。
2:通过LayoutInflater实例化失,数据为空,加载进度的View。
3:重写addView()方法,把View添加到ViewGroup中。
4:通过switchState()方法来实现不同状态的相互切换。
具体实现
ProgressStateLayout需要继承RelativeLayout,然后修改里面的构造方法。让一个参数的调用两个参数的,两个参数的调用三个参数的,在三个参数的构造方法中添加一个Init()方法用于一些初始化操作。
private LayoutParamslayoutParams;
private LayoutInflaterinflater;
private Listviews=null;
private static final StringTAG_LOADING="loading";
private static final StringTAG_EMPTY="empty";
private static final StringTAG_ERROR="error";
private View viewLoading,viewEmpty,viewError;
一个方参数的构造方法
public ProgressStateLayout(Context context) {
this(context,null);
}
两个参数的构造方法
public ProgressStateLayout(Contextcontext, AttributeSet attrs){
this(context, attrs,0);
}
三个参数的构造方法
public ProgressStateLayout(Context context, AttributeSet attrs,intdefStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
写个接口,用于“重新加载”按钮的点击回调
private ReloadListenerlistener;
//重新加载按钮的接口,用于监听重新加载按钮的监听回调
public interface ReloadListener {
void onClick();
}
枚举类型用于标示四种不同的状态(当然你也可以用int,String类型来表示,这取决于个人的习惯,枚举类型能够很好的规范参数的形式,个人比较喜欢用枚举,至少代码风格会显得高大上一点哈哈)
//四种不同状态
private enum Type {
LOADING,EMPTY,CONTENT,ERROR;
}
init()初始化方法,你会注意到这里实例化了一个List集合,你会想这个集合什么鬼?,当然有用了,别想多了,这个List不是用来保存四个状态的View,是用来保存ViewGroup里面加载成功的View除了正在加载,数据为空,加载失败
/**
*初始化操作
*/
public voidinit() {
views=newArrayList();
inflater= (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
layoutParams=newLayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
layoutParams.addRule(CENTER_IN_PARENT);
}
设置“正在加载”的View
private void setLoadingView() {
if(viewLoading==null) {
viewLoading=inflater.inflate(R.layout.layout_loading,null);
viewLoading.setTag(TAG_LOADING);
viewLoading.requestLayout();
addView(viewLoading,layoutParams);
}else{
viewLoading.setVisibility(View.VISIBLE);
}}
设置“数据为空”的View
private void setEmptyView(intresid, String msg) {
if(viewEmpty==null) {
viewEmpty=inflater.inflate(R.layout.layout_empty,null);
if(resid !=0) {
ImageView imageView = (ImageView)viewEmpty.findViewById(R.id.img_nodata);
imageView.setImageResource(resid);}
if(!TextUtils.isEmpty(msg)) {
TextView tv_msg = (TextView) findViewById(R.id.text_nodata_tips);
tv_msg.setText(msg);}
viewEmpty.setTag(TAG_EMPTY);
viewEmpty.requestLayout();
addView(viewEmpty,layoutParams);}else{
viewEmpty.setVisibility(View.VISIBLE);
}}
设置“加载失败”的View
private void setErrorView(int resid, String title, String msg, String btntext) {
if (viewError == null) {
viewError = inflater.inflate(R.layout.layout_error, null);
if (resid != 0) {
ImageView img = (ImageView) findViewById(R.id.img_nodata);
img.setImageResource(resid);}
if (!TextUtils.isEmpty(title)) {
TextView tv_title = (TextView) findViewById(R.id.tv_title);
tv_title.setText(title);}
if (!TextUtils.isEmpty(msg)) {
TextView tv_msg = (TextView) findViewById(R.id.tv_msg);
tv_msg.setText(title);}
Button btn_reload = (Button) viewError.findViewById(R.id.btn_reload);
if (!TextUtils.isEmpty(btntext)) {
btn_reload.setText(btntext);}
btn_reload.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (listener != null) {
listener.onClick();}}});
viewError.requestLayout();
viewError.setTag(TAG_ERROR);
addView(viewError, layoutParams);} else {
viewError.setVisibility(View.VISIBLE);}
}
设置“加载成功”后ViewGroup里面内容
private void setContentView(booleanflag) {
for(View v :views) {
v.setVisibility(flag ? View.VISIBLE: View.GONE);
}}
设置类型状态的隐藏
private void hideLoadingView() {
if(viewLoading!=null) {
viewLoading.setVisibility(View.GONE);}}
private void hideEmptyView() {
if(viewEmpty!=null) {
viewEmpty.setVisibility(View.GONE);}}
private void hideErrorView() {
if(viewError!=null) {
viewError.setVisibility(View.GONE);}}
对外提供方法用于类型的切换,可以传递信息参数,如果不传,使用默认
public void showLoading() {
switchState(Type.LOADING,
0,null,null,null);}
public void showError(intresid, String title, String msg,
String btntext, ReloadListener listener) {
this.listener= listener;
switchState(Type.ERROR, resid, title, msg, btntext);}
public void showEmpty(intresid, String msg) {
switchState(Type.EMPTY, resid, msg,null,null);}
public void showContent() {
switchState(Type.CONTENT,0,null,null,null);}
public void showError(ReloadListener listener) {
this.listener= listener;
switchState(Type.ERROR,0,null,null,null);}
public void showEmpty() {
switchState(
Type.EMPTY,0,null,null,null);
}
切换状态方法switchState()
/**
*改变状态方法
*@paramtype
*/
private void switchState(Type type,intresid, String title, String msg, String btntext) {
switch(type) {
caseLOADING:
hideEmptyView();
hideErrorView();
setContentView(false);
setLoadingView();
break;
caseEMPTY:
hideErrorView();
hideLoadingView();
setContentView(false);
setEmptyView(resid, title);
break;
caseERROR:
hideEmptyView();
hideLoadingView();
setContentView(false);
setErrorView(resid, title, msg, btntext);
break;
caseCONTENT:
hideEmptyView();
hideLoadingView();
hideErrorView();
setContentView(true);
break;}}
重写addView()方法,这里需要为不同类型的View设置Tag标示,用于区分不同状态的view和父布局内的控件。
@Override
public void addView(View child, ViewGroup.LayoutParams params) {
super.addView(child, params);
//把ProgressStateView内的子控件内容添加到list集合中,保证不同状态间相互切换内容的隐藏与显示
if(child.getTag() ==null|| (!child.getTag().equals(TAG_LOADING) &&
!child.getTag().equals(TAG_EMPTY) && !child.getTag().equals(TAG_ERROR))) {
views.add(child);}
}
how to use
截图:
正在加载:
数据为空:
请求失败:
请求成功:
示例代码请点击:ProgressLayout
结束语
本人是一个技术渣渣,自学Android起步,对Android的极深奥义尚未了解,通过对在开发中的问题进行整理总结,希望对开发中遇到同样问题的小伙伴有所帮助,第一次在这里写文章,文中有很多瑕疵,还请多多关照。