O、前言
在应用开发中,由于访问网络是一个相当耗时的操作,当页面加载数据或进行数据提交时,一般我们会在页面上展示一个loading加载框,提示用户进行片刻等待。实现loading加载框的方式多种多样,比如有些刷新框架在用户手动下拉刷新时自带的loading图,或者使用Dialog自定义加载弹框,当需要显示时showDialog当需要隐藏时dismiss即可。
但在实际使用过程中刷新框架自带的下拉loading往往无法在提交数据时使用,而自定义的Dialog在使用过程中可能出现窗体泄露的异常,对此本篇文章要介绍一种不同于以上方法的loading实现方式:借助Activity的addContentView()方法在Activity的基类封装一个通用的加载loading,子类可以直接使用,并对此进行优化实现防止页面按钮多次重复点击的作用。
一、在Activity的基类封装通用的加载loading
Android项目一般都会有一个Activity基类BaseActivity,直接在BaseActivity中添加如下代码:
/**
* mliuxb:通用的Loading加载图
* 使用方法:在需要显示Loading的地方调用showCenterLoading(),在需要隐藏loading的地方调用hideCenterLoading()即可
* 若需修改Loading底部的文字提示则调用重载方法showCenterLoading(String text)
* 注意:以下三个方法在本类中没有调用,而是在子类中调用,所以不影响本基类的任何逻辑
*/
private LinearLayout llLoading;
private TextView tvLoading;
/**
* 显示Loading
*/
public void showCenterLoading() {
showCenterLoading("数据加载中...");
}
/**
* 显示Loading
*/
public void showCenterLoading(String text) {
addLoadingView();
if (llLoading != null && tvLoading != null) {
tvLoading.setText(text);
llLoading.setVisibility(View.VISIBLE);
}
}
/**
* 隐藏Loading
*/
public void hideCenterLoading() {
if (llLoading != null) {
llLoading.setVisibility(View.GONE);
}
}
/**
* 添加Loading布局
*/
private void addLoadingView() {
if (llLoading == null || tvLoading == null) {
final View loadingView = View.inflate(this, R.layout.layout_global_center_loading, null);
final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT);
params.gravity = Gravity.CENTER;
addContentView(loadingView, params);//核心方法
llLoading = findViewById(R.id.ll_global_center_loading);
tvLoading = findViewById(R.id.tv_global_center_loading);
}
}
其中布局文件 layout_global_center_loading 如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/ll_global_center_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@drawable/shape_global_center_loading_bg"
android:gravity="center"
android:orientation="vertical"
android:padding="20dp"
android:visibility="gone">
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminateDrawable="@drawable/anim_global_rotate_loading" />
<TextView
android:id="@+id/tv_global_center_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:textColor="@android:color/black"
android:textSize="14sp"
tools:text="数据加载中..." />
</LinearLayout>
布局文件中 shape_global_center_loading_bg 如下:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<!--颜色-->
<solid android:color="#CCCCCCCC"/>
<!--给矩形加圆角-->
<corners android:radius="10dp"/>
<!--边框-->
<!--<stroke android:width="5dp" android:color="@color/blue_base"/>-->
</shape>
布局文件中 anim_global_rotate_loading 如下:
<?xml version="1.0" encoding="utf-8"?>
<rotate
xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/icon_global_center_loading"
android:fromDegrees="0"
android:toDegrees="1800">
</rotate>
以上即在Activity的基类封装一个通用的加载loading,正如注释中所述在子类中进行调用,在需要显示Loading的地方调用showCenterLoading(),在需要隐藏loading的地方调用hideCenterLoading()即可。
两个方法随处可用并且避免了自定义Dialog出现的窗体泄露的风险,一般情况下在网络访问发送请求前调用showCenterLoading()方法,在收到响应后调用hideCenterLoading()。
二、优化loading实现防止页面按钮多次重复点击的作用
在应用的实际使用过程中,由于网络卡顿或功能过于耗时,用户难免着急进行多次重复操作,如果是提交数据的情况(即数据库进行写数据操作),就可能造成多次提交,从而数据库可能多次写入数据,形成脏数据。此时服务端进行校验是必须的,当然在前端添加第一道防线也是必要的。
当前端与服务端交互时,此时我们的loading加载框是显示的,这个时间段恰好是应该避免用户重复操作的时间,所以修改loading图为全屏,并设置根布局为 android:clickable="true" ,此时底层正常页面的布局就被盖住且不能响应点击等操作,很好的避免了用户重复点击。
修改BaseActivity基类中的代码如下:
/**
* mliuxb更新:通用的Loading加载图
* 使用方法:在需要显示Loading的地方调用showCenterLoading(),在需要隐藏loading的地方调用hideCenterLoading()即可
* 若需修改Loading底部的文字提示则调用重载方法showCenterLoading(String text)
* 注意:以下三个方法在本类中没有调用,而是在子类中调用,所以不影响本类的任何逻辑
*/
private FrameLayout flLoading;
private TextView tvLoading;
/**
* 显示Loading
*/
public void showCenterLoading() {
showCenterLoading("数据加载中...");
}
/**
* 显示Loading
*/
public void showCenterLoading(String text) {
addLoadingView();
if (flLoading != null && tvLoading != null) {
tvLoading.setText(text);
flLoading.setVisibility(View.VISIBLE);
}
}
/**
* 隐藏Loading
*/
public void hideCenterLoading() {
if (flLoading != null) {
flLoading.setVisibility(View.GONE);
}
}
/**
* 添加Loading布局
*/
private void addLoadingView() {
if (flLoading == null || tvLoading == null) {
final View loadingView = View.inflate(this, R.layout.layout_global_center_loading, null);
final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT);
params.gravity = Gravity.CENTER;
addContentView(loadingView, params);//核心方法
flLoading = findViewById(R.id.fl_global_center_loading);
tvLoading = findViewById(R.id.tv_global_center_loading);
}
}
主要是布局参数从 FrameLayout.LayoutParams.WRAP_CONTENT 改为 FrameLayout.LayoutParams.MATCH_PARENT,保证loading布局全屏。
对应的布局文件 layout_global_center_loading 修改如下:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/fl_global_center_loading"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/transparent"
android:clickable="true"
android:focusable="true"
android:visibility="gone">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@drawable/shape_global_center_loading_bg"
android:gravity="center"
android:orientation="vertical"
android:padding="20dp">
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminateDrawable="@drawable/anim_global_rotate_loading" />
<TextView
android:id="@+id/tv_global_center_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:textColor="@android:color/black"
android:textSize="14sp"
tools:text="数据加载中..." />
</LinearLayout>
</FrameLayout>
如上,首先增加了一层FrameLayout嵌套,让根布局FrameLayout为match_parent,保证原loading的大小不变,然后设置根布局 android:clickable="true" ,此时底层的布局就被盖住且不能响应点击事件等操作,这就避免了用户重复点击。
其中 shape_global_center_loading_bg 和 anim_global_rotate_loading 保持不变,与之前相同。
我将相关布局文件进行了上传,资源地址:https://download.csdn.net/download/beita08/16016301
三、总结
针对多次重复操作的情况,loading加载框展示时不再只覆盖中间的小片区域,而是全屏覆盖住下面的整个页面(此时loading全屏但是背景设置为透明,所以展示上看起来还是只在中间区域)。此外对全屏的loading设置点击属性,这样下面被覆盖住的页面就不能进行点击等任何操作,从而防止了网络卡顿时用户重复点击操作,保证了移动端的幂等性。
开发时能够借助基类统一的方便实现防止重复点击等操作,不必在每个业务逻辑中添加对页面个个按钮的禁用放开等控制逻辑,大大的减少了业务层无关业务的页面控制逻辑代码,使业务层只关心自己的业务逻辑即可。
以上封装在项目的使用过程中我进行了好几次修改优化,最终形成了现在的样子,个人觉得还是比较好用的。