一款优雅的,无需改动布局的界面状态切换方案

前言

项目中的页面,只要不是纯静态数据的页面,都有进行状态展示和切换的需求。比如一个列表页面正在请求数据,需要展示Loading的效果;如果请求失败还需要展示失败的界面;如果数据未空,还需要展示空数据的页面。

这种状态页面的需求会涵盖项目中大部分的页面,如果实现的不优雅,写起来会非常的棘手。

硬编码方案

我最早见过的一种是硬编码方案,就是将所有的状态布局代码都写在每个页面中,默认都gone掉,就算你独立抽取一个布局,通过include引入也是属于这种方案。然后在代码中提供几个showLoading(), showEmpty(),showError()之类的方法来控制布局的隐藏和可见。

布局的代码大概类似于这样:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <!--界面原有布局 -->
    <FrameLayout/>

    <!--请求Loading布局 -->
    <FrameLayout  android:visibility="gone"/>

    <!--请求失败布局 -->
    <FrameLayout  android:visibility="gone"/>
    
    <!--请求数据为空的布局 -->
    <FrameLayout  android:visibility="gone"/>
</LinearLayout>
复制代码

这种方案直接侵入了每一个界面的布局,降低了布局的可读性,不可取。

上面这种方案可以通过重构减少代码,比如在基类中抽取出showLoading(), showEmpty(),showError()之类的方法,子类调用即可。

细节处的状态切换

上面的硬编码方案只适用于整体界面需要状态切换的场景,但有时候我们需要对按钮进行状态切换,比如一个登录按钮,收藏按钮,关注按钮。这是基于用户体验的考虑,用户需要知道软件的运行状态,软件需要在逻辑上给用户交互反馈,某种意义上和Material Design的触摸反馈理念是一致的。

当用户点击关注时,按钮应该有关注中的状态效果,关注中的状态下不可进行交互。效果如下:

如果是这种需求的话,那上面的硬编码方案就很难写了。当然如果你愿意的话,你可以给所有需要状态切换的按钮外面包裹一些布局,也不是不能做。

我所期望的效果是,可以动态给我们的界面或者某个具体的按钮加上状态切换的功能,而写布局的时候我们不需要编写一行状态相关的东西。

实现

基于我的期望,我实现了一个StateLayout,它是一个自定义的布局,代码并不复杂。它能做到的效果是这样:

我不打算贴代码了,因为没有什么难的东西。它用起来非常简单,对现有的布局零侵入,通过动态的给目标View包裹一些状态布局来实现。

对Activity/Fragment使用:

val stateLayout = StateLayout(this)
            .wrap(this)
            .showLoading()
复制代码

对指定View使用:

val layout2 = StateLayout(this)
            .wrap(view)
            .showLoading()
复制代码

默认情况下是显示内容布局,改变状态的方法:

stateLayout.showLoading() //default state
stateLayout.showContent()
stateLayout.showError()
stateLayout.showEmpty()
复制代码

你还可以自定义每种状态对应的布局,毕竟默认的状态布局是有点丑的:

StateLayout(this)
    .config(loadingLayoutId = R.layout.custom_loading, //自定义加载中布局
            errorLayoutId = R.layout.custom_error, //自定义加载失败布局
            emptyLayoutId = R.layout.custom_empty, //自定义数据位为空的布局
            useContentBgWhenLoading = true, //加载过程中是否使用内容的背景
            enableLoadingShadow = true, //加载过程中是否启用半透明阴影盖在内容上面
            retryAction = { //点击errorView的回调
                Toast.makeText(this, "点击了重试", Toast.LENGTH_SHORT).show()
            })
    .wrap(view)
    .showLoading()
复制代码

类库的地址在这里:github.com/li-xiaojun/…

最后

我致力于构建现代化的Android开发技术栈,用兼顾优雅和效率的代码,来开发高质量的Android应用。

猜你喜欢

转载自blog.csdn.net/weixin_34232617/article/details/91361751