自定义ProgressStateLayout实现网络请求状态间的相互切换

前言

在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


把它当作相对布局使用

截图:

正在加载:


正在加载,progressbar没有显示完整

数据为空:


没有找到数据

请求失败:


加载失败,点击重试

请求成功:


数据请求成功


示例代码请点击:ProgressLayout


结束语

本人是一个技术渣渣,自学Android起步,对Android的极深奥义尚未了解,通过对在开发中的问题进行整理总结,希望对开发中遇到同样问题的小伙伴有所帮助,第一次在这里写文章,文中有很多瑕疵,还请多多关照。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容