android 封装状态页面,Android——页面Loading控件封装

311ebd76cd5f8cb30acc0338dbaa10b1.png

概述

在Android开发过程中通常在有网络请求的页面,需要设计加载中、加载失败等UI效果,来提升用户体验。本文就此需求实现了一个简单的LoadingLayout控件,可以比较方便的实现加载中、加载失败、网络错误等UI效果,并提供失败点击重试等操作。

实现思路

常用一般有以下几种请求状态:

LOADING_STATE 加载中状态

LOAD_SUCCESS_STATE 加载成功状态

LOAD_FAILURE_STATE 加载失败状态(包含超时、404、接口数据错误等)

NULL_DATA_STATE 没有数据状态(接口访问成功,接口没有数据)

NET_ERROR_STATE 网络错误状态(未连接网络)

除加载成功状态每种状态都对应着一种UI效果对应各自View,不同状态的View的展示和消失肯定是通过在ViewGroup添加和删除View实现的,所以我们的LoadingLayout需要继承一个ViewGroup,此ViewGroup我们选择为FrameLayout,不同状态的View可以通过自定义属性自由指定,然后通过开放接口实现切换UI的效果就可以了。

代码实现

定义自定义属性

定义了一个默认ID和四种UI状态的属性。

java代码实现

public class LoadingLayout extends FrameLayout {

private static final String TAG = LoadingLayout.class.getSimpleName();

/** 加载中 */

public static final int LOADING_STATE = 0x001;

/** 加载成功 */

public static final int LOAD_SUCCESS_STATE = 0x002;

/** 网络错误 */

public static final int NET_ERROR_STATE = 0x003;

/** 加载失败,超时、接口错误、404等 */

public static final int LOAD_FAILURE_STATE = 0x004;

/** 没有数据 */

public static final int NULL_DATA_STATE = 0x005;

private View loadingView; //loading 加载中。。

private View netErrorView; //网络错误View

private View loadFailureView; //加载失败View

private View nullDataView; //没有数据View

//定义注释限定参数值,可以去掉

@IntDef({LOADING_STATE, LOAD_SUCCESS_STATE, NET_ERROR_STATE, LOAD_FAILURE_STATE,NULL_DATA_STATE})

public @interface LoadingState{}

private final LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);

public LoadingLayout(@NonNull Context context) {

this(context, null);

}

public LoadingLayout(@NonNull Context context, @NonNull AttributeSet attrs) {

this(context, attrs, 0);

}

public LoadingLayout(@NonNull Context context, @NonNull AttributeSet attrs, int defStyleAttr) {

super(context, attrs, defStyleAttr);

//指定默认ID

if(getId() == -1){

setId(R.id.loading_layout_id);

}

try{

TypedArray a = context.obtainStyledAttributes(attrs,

R.styleable.LoadingLayout, 0, 0);

//为每种状态指定默认效果

int progressViewRes = a.getResourceId(R.styleable.LoadingLayout_loadingViewDrawable,

R.layout.default_loading_layout);

int netErrorViewRes = a.getResourceId(R.styleable.LoadingLayout_netErrorViewDrawable,

R.layout.default_net_error_layout);

int loadFailureViewRes = a.getResourceId(R.styleable.LoadingLayout_loadFailureViewDrawable,

R.layout.default_load_failure_layout);

int nullDataViewRes = a.getResourceId(R.styleable.LoadingLayout_nullDataViewDrawable,

R.layout.default_null_data_layout);

loadingView = LayoutInflater.from(context).inflate(progressViewRes, null, true);

netErrorView = LayoutInflater.from(context).inflate(netErrorViewRes, null, true);

loadFailureView = LayoutInflater.from(context).inflate(loadFailureViewRes, null, true);

nullDataView = LayoutInflater.from(context).inflate(nullDataViewRes, null, true);

} catch (Exception e){

e.printStackTrace();

} finally {

a.recycle();

}

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

this.layoutParams.width = this.getMeasuredWidth();

this.layoutParams.height = this.getMeasuredHeight();

}

/**

* 通过状态刷新界面显示

* @param state 当前状态

*/

public void refreshView(@LoadingState int state){

refreshView(state, null);

}

/**

* 通过状态刷新界面显示

* @param state 当前状态

* @param onClickListener 点击监听

*/

public void refreshView(@LoadingState int state, OnClickListener onClickListener){

switch (state){

case LOAD_SUCCESS_STATE:

//加载成功,移除所有状态View

removeStateView();

break;

case LOADING_STATE:

showLoading();

break;

case NET_ERROR_STATE:

showNetErrorMessage(onClickListener);

break;

case LOAD_FAILURE_STATE:

showLoadFailureMessage(onClickListener);

break;

case NULL_DATA_STATE:

showNullDataMessage();

break;

default:

Log.d(TAG, "state error");

break;

}

}

/**

* 显示loading

*/

public void showLoading(){

removeStateView();

if(loadingView != null){

this.addView(loadingView);

} else{

Log.d(TAG, "Please init loadingView first");

}

}

/**

* 显示网络错误提示

*/

public void showNetErrorMessage(){

showNetErrorMessage(null);

}

/**

* 显示网络错误提示

* @param onClickListener 指定点击效果监听

*/

public void showNetErrorMessage(OnClickListener onClickListener){

removeStateView();

if(netErrorView != null){

if(onClickListener != null){

netErrorView.setOnClickListener(onClickListener);

}

this.addView(netErrorView);

} else{

Log.d(TAG, "Please init netErrorView first");

}

}

/**

* 显示无数据提示

*/

public void showNullDataMessage(){

removeStateView();

if(nullDataView != null){

this.addView(nullDataView);

} else{

Log.d(TAG, "Please init nullDataView first");

}

}

/**

* 显示加载失败提示

*/

public void showLoadFailureMessage(){

showLoadFailureMessage(null);

}

/**

* 显示加载失败提示

* @param onClickListener 指定点击效果监听

*/

public void showLoadFailureMessage(OnClickListener onClickListener){

removeStateView();

if(loadFailureView != null){

if(onClickListener != null){

loadFailureView.setOnClickListener(onClickListener);

}

this.addView(loadFailureView);

} else{

Log.d(TAG, "Please init loadFailureView first");

}

}

/**

* 移除所有状态View

*/

private void removeStateView(){

if(loadingView != null){

this.removeView(loadingView);

}

if(netErrorView != null){

this.removeView(netErrorView);

}

if(loadFailureView != null){

this.removeView(loadFailureView);

}

if(nullDataView != null){

this.removeView(nullDataView);

}

}

}

代码注释比较详细了,不过多解释。

默认UI样式

android:layout_width="match_parent"

android:layout_height="match_parent"

android:background="@android:color/white">

android:layout_width="50dp"

android:layout_height="50dp"

style="@android:style/Widget.ProgressBar.Large"

android:layout_centerInParent="true"

android:indeterminateDrawable="@drawable/loading_anim"/>

loading_anim:

android:drawable="@drawable/loading"

android:duration="100"

android:fromDegrees="0"

android:interpolator="@android:anim/linear_interpolator"

android:pivotX="50%"

android:pivotY="50%"

android:toDegrees="720" />

其他的样式比较简单,就不一一列举了。

使用

布局:

android:layout_width="match_parent"

android:layout_height="0dp"

android:layout_marginStart="8dp"

android:layout_marginTop="8dp"

android:layout_marginEnd="8dp"

app:layout_constraintBottom_toTopOf="@+id/net_error_btn"

app:layout_constraintEnd_toEndOf="parent"

app:layout_constraintStart_toStartOf="parent"

app:layout_constraintTop_toTopOf="parent" >

android:id="@+id/image_view"

android:layout_width="200dp"

android:layout_height="200dp"

android:layout_gravity="center" />

需要在父布局中引用自定义属性LoadingLayout。

代码实现:

在代码中需要通过loadingLayout.showLoading();,loadingLayout.refreshView(state);等方法来控制状态展示效果。

扩展

在写完这个控件之后发现网上有好多类似的实现,好吧。。。至少说明我的实现思路应该是没问题的。但是这种实现其实并不完美:

每次使用时需要在父布局中使用自定义布局LoadingLayout略显繁琐;

在某些特殊布局中需要为此效果单独添加一层父布局LoadingLayout,会在一定程度上造成资源上的浪费;

因为使用了自定义属性和布局指定默认UI样式,也不利于维护和使用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值