写在前面
android项目中经常需要从网络服务器端获取数据并显示到页面上,由于网络速度不稳定,客户端发起请求而服务端还未返回数据时,页面需要有加载中
状态;如果请求失败,页面又需要显示为网络连接失败
状态;如果这次请求的数据为空,页面还需要显示为暂无数据
;只有服务端返回有效的数据时,页面才会正常显示。
这个需求在平时的开发过程中非常常见,因此我写了一个简单的多状态布局,包含这四种状态,方便在以后的项目中使用。这个loadingLayout的代码我全都上传到github上了,本来想发布到jCenter上,好给大家轻松通过gradle构建,后来又想了下,这个功能很简单,添加gradle依赖太重了,大家可以通过这篇文章自己实现,并配合自己的项目进行修改和扩展。
源码及demo地址:https://github.com/mavsforlife/LoadingLayoutDemo
大家可以去看看给我提意见啊,更欢迎star哈哈哈~~~
好的,啰嗦了一大堆,下面我们来正式开整,快速打造一个简单的loadingLayout。
如何实现
大家应该很容易想到FrameLayout,将loading
error
empty
content
这四种状态下的view放入一个FrameLayout
中,提供方法根据状态来显示某一层view,隐藏其他层。
首先我们新建一个LoadingLayout类继承自FrameLayout,并定义mEmptyView
mErrorView
mLoadingView
三个View对象,定义两个onclickListener用于处理重新加载的逻辑(稍后会说到)。
public class LoadingLayout extends FrameLayout {
private View mEmptyView, mErrorView, mLoadingView;
private OnClickListener onErrorClickListener;
private OnClickListener onEmptyClickListener;
private LayoutInflater mLayoutInflater;
}
初始化
我们在它的构造方法中完成一些初始化的工作
public LoadingLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.LoadingLayout, 0, 0);
try {
int emptyView = a.getResourceId(R.styleable.LoadingLayout_emptyView, R.layout.empty_view);
int errorView = a.getResourceId(R.styleable.LoadingLayout_errorView, R.layout.error_view);
int loadingView = a.getResourceId(R.styleable.LoadingLayout_loadingView, R.layout.loading_view);
mLayoutInflater = LayoutInflater.from(getContext());
mEmptyView = mLayoutInflater.inflate(emptyView, this, true);
mErrorView = mLayoutInflater.inflate(errorView, this, true);
mLoadingView = mLayoutInflater.inflate(loadingView, this, true);
}finally {
a.recycle();
}
}
上面这段代码非常的简单,初始化了这个loadingView以后,在这个viewGroup中依次添加了emptyView
errorView
loadingView
这三个子view。由于LoadingLayout是继承自FrameLayout的,因此这三个子view是叠成3层显示的。
自定义属性
大家看到了我定义了emptyView
,errorView
,loadingView
三个属性,并且设置了默认值,所以我们要先在android app的styles文件中先定义好这三个属性,并且创建empty_view
, error_view
, loading_view
三个默认的xml布局文件。
- 默认的empty_view文件
- 默认的error_view文件
- 默认的loading_view文件
重写onFinishInflate
方法
当View及其子View从xml文件中加载完成以后,会调用onFinishInflate
方法,我们先将所有子view都隐藏。
@Override
protected void onFinishInflate() {
super.onFinishInflate();
for (int i = 0; i < getChildCount() - 1; i++) {
getChildAt(i).setVisibility(GONE);
}
}
如何显示不同状态的view
接下来就是重点了,我们根据不同的业务场景显示不同的view,其实非常简单,我们将loadingLayout的某一层布局显示出来,隐藏其他子布局就好了。由于我们是按照emptyView
errorView
loadingView
contentView
这样的顺序添加的,因此可以通过view.getChildAt()
方法,显示或隐藏指定布局。
- 显示emptyView(emptyView为getChildAt(0))
public void showEmpty() {
for (int i = 0; i < this.getChildCount(); i++) {
View child = this.getChildAt(i);
if (i == 0) {
child.setVisibility(VISIBLE);
} else {
child.setVisibility(GONE);
}
}
}
- 显示errorView(errorView为getChildAt(1))
public void showError() {
for (int i = 0; i < this.getChildCount(); i++) {
View child = this.getChildAt(i);
if (i == 1) {
child.setVisibility(VISIBLE);
} else {
child.setVisibility(GONE);
}
}
}
- 显示loadingView(loadingView为getChildAt(2))
public void showLoading() {
for (int i = 0; i < this.getChildCount(); i++) {
View child = this.getChildAt(i);
if (i == 2) {
child.setVisibility(VISIBLE);
} else {
child.setVisibility(GONE);
}
}
}
- 显示contentView(contentView为getChildAt(3))
public void showContent() {
for (int i = 0; i < this.getChildCount(); i++) {
View child = this.getChildAt(i);
if (i == 3) {
child.setVisibility(VISIBLE);
} else {
child.setVisibility(GONE);
}
}
}
设置重试点击事件
在实际项目中,如果页面为空,可能业务上需要我们提供一个按钮点击跳转到首页?
购买页面?
其他指定页面?
;如果因为网络原因加载失败,页面上一般会有一个重新加载按钮
。这就是我在文章的开头说到的两个onclickListener的作用.
我们首先要提供两个set方法来设置onclickListener
public LoadingLayout setOnEmptyClickListener(OnClickListener onEmptyClickListener) {
this.onEmptyClickListener = onEmptyClickListener;
return this;
}
public LoadingLayout setOnErrorClickListener(OnClickListener onErrorClickListener) {
this.onErrorClickListener = onErrorClickListener;
return this;
}
然后再在onFinishInflate
方法中,给按钮的点击事件实现这两个接口。
findViewById(R.id.btn_empty).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (null != onEmptyClickListener) {
onEmptyClickListener.onClick(v);
}
}
});
findViewById(R.id.btn_error).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (null != onErrorClickListener) {
onErrorClickListener.onClick(v);
}
}
});
一些额外提供的方法
前面的工作做完,基本已经实现了需求,只是有时候我们不方便在xml中定义emptyView,又不想使用自定义的emptyView,所以我又写了一些扩展方法。
在java类中直接设置emptyView/errorView/loadingView。
public LoadingLayout setEmptyView(@LayoutRes int layout) {
removeView(getChildAt(0));
mEmptyView = mLayoutInflater.inflate(layout, null, true);
addView(mEmptyView, 0);
onFinishInflate();
return this;
}
public LoadingLayout setErrorView(@LayoutRes int layout) {
removeView(getChildAt(1));
mErrorView = mLayoutInflater.inflate(layout, null, true);
addView(mErrorView, 1);
onFinishInflate();
return this;
}
public LoadingLayout setLoadingView(@LayoutRes int layout) {
removeView(getChildAt(2));
mLoadingView = mLayoutInflater.inflate(layout, null, true);
addView(mLoadingView, 2);
return this;
}
修改自定义emptyView/errorView的文字
public LoadingLayout setEmptyText(String text) {
((TextView) findViewById(R.id.btn_empty)).setText(text);
return this;
}
public LoadingLayout setErrorText(String text) {
((TextView) findViewById(R.id.tv_error)).setText(text);
return this;
}
自定义emptyView及errorView的注意事项。
我在ids.xml
文件中定义了三个id。
在自定义errorView中,一定要创建一个button并将id设置为btn_error
,创建一个textView并将id设置为tv_error
;同时在自定义emptyView时,要创建一个textView并将id设置为btn_epmty
,否则会引发nullPointerException,切记切记!
最终效果及使用方法
下图就是在activity中最终的显示效果啦,忽略丑丑的布局,仓促写的。。。
使用方法:首先在activity或fragment的布局文件中插入loadingLayout,loadingLayout中包裹的就是contentView。(只允许包裹一个子 view,因此如果有多个view,需要用ScrollView等ViewGroup再包一层)
然后在java代码中,通过findViewById方法初始化view,并实现点击重试接口。使用showContent
方法显示contView。
loadingLayout = (LoadingLayout) findViewById(R.id.loading_layout);
loadingLayout
.setOnEmptyClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
loadingLayout.showLoading();
}
})
.setOnErrorClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
loadingLayout.showLoading();
}
})
.showContent();
最后
感谢大家,撒花~~
以及,再次求star啊啊啊啊啊