适用范围
主要适用于Acitivty/Fragment需要根据不同的状态来展示对应的状态页面。
设计思路
- 设计一个布局管理器,用于管理各个状态的布局显示/隐藏
- 在布局管理器的根layout文件中,通过ViewStubCompat导入对应状态的layout
- 通过getViewGroup方法获取对应调用该管理类调用者的根Layout——即数据正常展示的UI
- 根据State进行对应的Page显示/隐藏
实现后的StatusViewController.java类
package com.dohenes.common.view;
import android.annotation.SuppressLint;
import android.content.Context;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.ViewStubCompat;
import com.dohenes.common.R;
/**
* ClassName StatusViewController
* Describe TODO<状态试图控制器>
* Author jgduan
* Date 2019/12/16 11:09
* Version v1.0
*/
public abstract class StatusViewController extends LinearLayout {
private static final String TAG = StatusViewController.class.getSimpleName();
/**
* 状态枚举类,对应不同的状态
* Empty对应空数据内容提示
* Load对应加载中内容提示
* Error对应发生异常提示
* NoNetwork对应无网络提示
* Success对应的是调用者自身的初始布局页面
*/
public enum State {
Empty, Load, Error, NoNetwork, Success
}
private View mEmptyView;// 空数据提示View
private View mErrorView;// 异常提示View
private View mLoadingView;// 加载中View
private View mNoNetworkView;// 无网络提示View
private ViewGroup mSuccessView;// 这个实际上是对标调用StatusViewController的Activity/Fragment根View
private View mTitle;// Activity设置的Title
public StatusViewController(@NonNull Context context) {
super(context);
init(context);
}
public StatusViewController(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context);
}
/**
* 一些初始化的操作
*
* @param context context
*/
private void init(@NonNull Context context) {
setOrientation(VERTICAL);
inflate(context, R.layout.status_view, this);// 引入多状态布局layout
mSuccessView = getViewGroup();// 获取调用者自身的初始View
mTitle = mSuccessView.findViewWithTag("title");
this.addView(mSuccessView, ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);// 添加默认的初始View进来
}
/**
* 开始载入数据
*/
private void loadData() {
showPageWithState(State.Load);
onLoadData();// 通知调用的Activity这里触发了载入数据操作,需要它配合做出处理
}
/**
* 自定义的点击事件,只针对重新加载按钮
*/
private OnClickListener mRetryClickListener = new OnClickListener() {
@Override
public void onClick(View v) {
loadData();
}
};
/**
* 获取调用该控制器Activity/Fragment的根ViewGroup
*
* @return ViewGroup
*/
public abstract ViewGroup getViewGroup();
/**
* 载入数据回调
*/
public abstract void onLoadData();
/**
* 根据状态类型展示对应的页面
*
* @param status State
*/
@SuppressLint("RestrictedApi")
public void showPageWithState(State status) {
if (mTitle != null) {
String tag = (String) getChildAt(0).getTag();
if (!TextUtils.equals(tag, "title")) {// 没有标题栏
mSuccessView.removeView(mTitle);
addView(mTitle, 0);
}
}
switch (status) {
case Success:
if (mLoadingView != null) {
mLoadingView.setVisibility(View.GONE);
}
if (mErrorView != null) {
mErrorView.setVisibility(View.GONE);
}
if (mNoNetworkView != null) {
mNoNetworkView.setVisibility(View.GONE);
}
if (mEmptyView != null) {
mEmptyView.setVisibility(View.GONE);
}
mSuccessView.setVisibility(View.VISIBLE);
break;
case Empty:
mSuccessView.setVisibility(View.GONE);
if (mLoadingView != null) {
mLoadingView.setVisibility(View.GONE);
}
if (mErrorView != null) {
mErrorView.setVisibility(View.GONE);
}
if (mNoNetworkView != null) {
mNoNetworkView.setVisibility(View.GONE);
}
if (mEmptyView == null) {
ViewStubCompat viewStub = findViewById(R.id.svc_stub_empty);
mEmptyView = viewStub.inflate();
} else {
mEmptyView.setVisibility(View.VISIBLE);
}
mEmptyView.findViewById(R.id.vcs_btn_empty)
.setOnClickListener(mRetryClickListener);
break;
case Load:
mSuccessView.setVisibility(View.GONE);
if (mEmptyView != null) {
mEmptyView.setVisibility(View.GONE);
}
if (mErrorView != null) {
mErrorView.setVisibility(View.GONE);
}
if (mNoNetworkView != null) {
mNoNetworkView.setVisibility(View.GONE);
}
if (mLoadingView == null) {
ViewStubCompat viewStub = findViewById(R.id.svc_stub_loading);
mLoadingView = viewStub.inflate();
} else {
mLoadingView.setVisibility(View.VISIBLE);
}
break;
case Error:
mSuccessView.setVisibility(View.GONE);
if (mEmptyView != null) {
mEmptyView.setVisibility(View.GONE);
}
if (mLoadingView != null) {
mLoadingView.setVisibility(View.GONE);
}
if (mNoNetworkView != null) {
mNoNetworkView.setVisibility(View.GONE);
}
if (mErrorView == null) {
ViewStubCompat viewStub = findViewById(R.id.svc_stub_error);
mErrorView = viewStub.inflate();
} else {
mErrorView.setVisibility(View.VISIBLE);
}
mErrorView.findViewById(R.id.vcs_btn_error)
.setOnClickListener(mRetryClickListener);
break;
case NoNetwork:
mSuccessView.setVisibility(View.GONE);
if (mEmptyView != null) {
mEmptyView.setVisibility(View.GONE);
}
if (mErrorView != null) {
mErrorView.setVisibility(View.GONE);
}
if (mLoadingView != null) {
mLoadingView.setVisibility(View.GONE);
}
if (mNoNetworkView == null) {
ViewStubCompat viewStub = findViewById(R.id.svc_stub_no_network);
mNoNetworkView = viewStub.inflate();
} else {
mNoNetworkView.setVisibility(View.VISIBLE);
}
mNoNetworkView.findViewById(R.id.vcs_btn_no_net)
.setOnClickListener(mRetryClickListener);
break;
default:
break;
}
}
}
// 用法示例:修改baseActivity中的setContentView(getView());
// private StatusViewController statusViewController;
//
// private View getView() {
// statusViewController = new StatusViewController(this) {
// @Override
// public ViewGroup getViewGroup() {
// return getLayoutInflaterViewGroup();
// }
//
// @Override
// public void onLoadData() {
// // 这里需要在开始载入数据,暂时写个延迟模拟操作成功的效果
// Observable.timer(3, TimeUnit.SECONDS, AndroidSchedulers.mainThread())
// .subscribe(new Observer<Long>() {
// @Override
// public void onCompleted() {
// statusViewController.showPageWithState(State.Success);
// }
//
// @Override
// public void onError(Throwable e) {
//
// }
//
// @Override
// public void onNext(Long aLong) {
//
// }
// });
// }
// };
// // 默认应该是Load状态,示例放上Empty状态,便于更直观的体验流程
// statusViewController.showPageWithState(StatusViewController.State.Empty);
// return statusViewController;
// }
//
// private ViewGroup getLayoutInflaterViewGroup() {
// return (ViewGroup) View.inflate(this, getContentViewID(), null);
// }
对应的布局代码
- StatusViewController对应的根布局
status_view.mxl
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<androidx.appcompat.widget.ViewStubCompat
android:id="@+id/svc_stub_loading"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout="@layout/status_loading" />
<androidx.appcompat.widget.ViewStubCompat
android:id="@+id/svc_stub_empty"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout="@layout/status_empty" />
<androidx.appcompat.widget.ViewStubCompat
android:id="@+id/svc_stub_error"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout="@layout/status_error" />
<androidx.appcompat.widget.ViewStubCompat
android:id="@+id/svc_stub_no_network"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout="@layout/status_no_network" />
</merge>
status_empty.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<Button
android:id="@+id/vcs_btn_empty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/vsc_empty_tips" />
</LinearLayout>
status_error.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<Button
android:id="@+id/vcs_btn_error"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/vcs_error_tips" />
</LinearLayout>
status_loading.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
android:gravity="center"
android:orientation="vertical">
<ProgressBar
android:id="@+id/vcs_pb_loading"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
status_no_network.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
android:gravity="center"
android:orientation="vertical">
<Button
android:id="@+id/vcs_btn_no_net"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/vcs_no_net_tips" />
</LinearLayout>
- 对应的values值
<string name="vsc_empty_tips">没有加载到数据,重新加载</string>
<string name="vcs_error_tips">发生错误,点击重新加载</string>
<string name="vcs_no_net_tips">没有网络,点击重试</string>