IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android实现加载状态控件LoadingLayout -> 正文阅读

[移动开发]Android实现加载状态控件LoadingLayout

通常我们浏览新闻APP的时候,会有一个表示加载状态的控件,表示当前正在加载数据或是网络断开导致加载出错了,数据为空等,就比如下面的今日头条:

在这篇文章中,我将实现一个加载状态控件LoadingLayout。

首先,要明确一下这个加载控件会有几种状态,第一肯定是先要有一个正在加载的状态,加载中的状态可以用一个gif动图或是动画来实现;当加载完成请求到数据后又可以分为两种状态,有数据和无数据,有数据自然是要隐藏整个加载控件,无数据时要有一个表示无数据的状态,这就有三种状态了;但是还要考虑到数据加载出错的情况,比如网络断开了或是接口挂了,所以基本上是要有四种状态:加载中、隐藏、无数据、加载出错。

private const val STATE_LOADING = 0  //加载中
private const val STATE_EMPTY = 1    //无数据
private const val STATE_FAIL = 2     //加载失败
private const val STATE_HIDE = 3     //隐藏

本文实现的LoadingLayout会有下面的自定义属性:

<!--加载状态布局-->
<declare-styleable name="LoadingLayout">
    <attr name="loadingSrc" format="reference" />
    <attr name="loadingDescText" format="string" />
    <attr name="emptySrc" format="reference" />
    <attr name="emptyDescText" format="string" />
    <attr name="emptyActionText" format="string" />
    <attr name="failSrc" format="reference" />
    <attr name="failDescText" format="string" />
    <attr name="failActionText" format="string" />
    <attr name="intercept" format="boolean" />
</declare-styleable>

说明一下这些属性都是做什么用的,首先看一下加载中的状态:

加载中会有两个自定义属性,loadingSrc表示加载中的图片,loadingDescText是图片下面加载中的文本描述,loadingDescText为null或者空字符串的时候就不会显示这部分内容了。

看一下无数据的状态:

无数据有三个属性,图片emptySrc,图片下暂无数据的文本描述emptyDescText,按钮的文本描述emptyActionText,emptyDescText和emptyActionText为null或者空字符串的时候不会显示相应的内容。

然后是加载出错的状态:

同样加载出错也有三个属性,failSrc表示加载失败的图片,failDescText是图片下加载失败的文本描述,failActionText表示按钮重试的文本描述,failDescText和failActionText为null或者空字符串的时候不会显示相应的内容。

还有一个intercept属性表示是否拦截点击事件,设为true时底下的View就无法收到点击事件了。

完整的代码如下:

class LoadingLayout @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr) {

    companion object {
        private const val STATE_LOADING = 0  //加载中
        private const val STATE_EMPTY = 1    //无数据
        private const val STATE_FAIL = 2     //加载失败
        private const val STATE_HIDE = 3     //隐藏
    }

    var loadingSrc: Drawable? = null  //加载中的图片
        set(value) {
            field = value
            mLoadingIv.setImageDrawable(value)
        }
    var loadingDescText: String = ""  //加载中的文本描述
        set(value) {
            field = value
            setDescText(mDescTv, value)
        }
    var emptySrc: Drawable? = null  //无数据的图片
        set(value) {
            field = value
            mEmptyIv.setImageDrawable(value)
        }
    var emptyDescText: String = ""  //无数据的文本描述
        set(value) {
            field = value
            setDescText(mDescTv, value)
        }
    var emptyActionText: String = ""  //无数据时操作按钮的文本
        set(value) {
            field = value
            setActionText(mActionBtn, value)
        }
    var failSrc: Drawable? = null  //加载失败的图片
        set(value) {
            field = value
            mFailIv.setImageDrawable(value)
        }
    var failDescText: String = ""  //加载失败的文本描述
        set(value) {
            field = value
            setDescText(mDescTv, value)
        }
    var failActionText: String = ""  //重试的文本描述
        set(value) {
            field = value
            setActionText(mActionBtn, value)
        }
    private var intercept: Boolean = true  //是否拦截点击事件,为true时底下的View无法收到点击事件

    //获取根布局,以便设置布局的LayoutParams
    fun getRootLayout(): LinearLayout = mRootLayout

    private val mRootLayout: LinearLayout
    private val mLoadingIv: ImageView
    private val mEmptyIv: ImageView
    private val mFailIv: ImageView
    private val mDescTv: TextView
    private val mActionBtn: Button

    init {
        val layout = LayoutInflater.from(context).inflate(R.layout.layout_loading, this)
        with(layout) {
            mRootLayout = findViewById(R.id.root_ll)
            mLoadingIv = findViewById(R.id.loading_iv)
            mEmptyIv = findViewById(R.id.empty_iv)
            mFailIv = findViewById(R.id.fail_iv)
            mDescTv = findViewById(R.id.desc_tv)
            mActionBtn = findViewById(R.id.action_btn)
        }
        attrs?.let {
            val ta = context.obtainStyledAttributes(it, R.styleable.LoadingLayout)
            loadingSrc = ta.getDrawable(R.styleable.LoadingLayout_loadingSrc) ?: resources.getDrawable(R.drawable.ic_load_loading)
            loadingDescText = ta.getString(R.styleable.LoadingLayout_loadingDescText) ?: ""
            emptySrc = ta.getDrawable(R.styleable.LoadingLayout_emptySrc) ?: resources.getDrawable(R.drawable.ic_load_empty)
            emptyDescText = ta.getString(R.styleable.LoadingLayout_emptyDescText) ?: ""
            emptyActionText = ta.getString(R.styleable.LoadingLayout_emptyActionText) ?: ""
            failSrc = ta.getDrawable(R.styleable.LoadingLayout_failSrc) ?: resources.getDrawable(R.drawable.ic_load_fail)
            failDescText = ta.getString(R.styleable.LoadingLayout_failDescText) ?: ""
            failActionText = ta.getString(R.styleable.LoadingLayout_failActionText) ?: ""
            intercept = ta.getBoolean(R.styleable.LoadingLayout_intercept, true)
            ta.recycle()
        }
        setLoadState(STATE_HIDE)
    }

    //开始加载
    fun loadStart() {
        setLoadState(STATE_LOADING)
    }

    //无数据
    fun loadEmpty() {
        setLoadState(STATE_EMPTY)
    }

    //加载失败
    fun loadFail() {
        setLoadState(STATE_FAIL)
    }

    //加载完成,则隐藏
    fun loadComplete() {
        setLoadState(STATE_HIDE)
    }

    private fun setLoadState(loadState: Int) {
        if (loadState == STATE_HIDE) {
            visibility = View.GONE
        } else {
            visibility = View.VISIBLE
            mLoadingIv.clearAnimation()  //取消动画
            mLoadingIv.visibility = View.GONE
            mEmptyIv.visibility = View.GONE
            mFailIv.visibility = View.GONE
            mActionBtn.visibility = View.GONE
            when (loadState) {
                STATE_LOADING -> {
                    mLoadingIv.visibility = View.VISIBLE
                    mLoadingIv.setImageDrawable(loadingSrc)
                    val animation = RotateAnimation(
                        0f, 359f,
                        Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f
                    )
                    mLoadingIv.startAnimation(animation.apply {
                        duration = 800
                        repeatCount = Animation.INFINITE
                        repeatMode = Animation.RESTART
                        interpolator = AccelerateDecelerateInterpolator()
                    })
                    setDescText(mDescTv, loadingDescText)
                }
                STATE_EMPTY -> {
                    mEmptyIv.visibility = View.VISIBLE
                    mEmptyIv.setImageDrawable(emptySrc)
                    setDescText(mDescTv, emptyDescText)
                    setActionText(mActionBtn, emptyActionText)
                    mActionBtn.setOnClickListener { mOnEmptyListener?.invoke() }
                }
                STATE_FAIL -> {
                    mFailIv.visibility = View.VISIBLE
                    mFailIv.setImageDrawable(failSrc)
                    setDescText(mDescTv, failDescText)
                    setActionText(mActionBtn, failActionText)
                    mActionBtn.setOnClickListener { mOnFailListener?.invoke() }
                }
            }
        }
    }

    private fun setDescText(tv: TextView, desc: String) {
        if (TextUtils.isEmpty(desc)) {
            tv.visibility = View.GONE
        } else {
            tv.visibility = View.VISIBLE
            tv.text = desc
        }
    }

    private fun setActionText(btn: Button, text: String) {
        if (TextUtils.isEmpty(text)) {
            btn.visibility = View.GONE
        } else {
            btn.visibility = View.VISIBLE
            btn.text = text
        }
    }

    private var mOnEmptyListener: (() -> Unit)? = null

    //无数据时相应的操作
    fun setOnEmptyListener(listener: () -> Unit) {
        mOnEmptyListener = listener
    }

    private var mOnFailListener: (() -> Unit)? = null

    //加载失败时相应的操作,如点击重试
    fun setOnFailListener(listener: () -> Unit) {
        mOnFailListener = listener
    }

    override fun onTouchEvent(event: MotionEvent?): Boolean {
        return intercept  //拦截点击事件
    }

}

?layout_loading布局如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tool="http://schemas.android.com/tools"
    android:id="@+id/root_ll"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical">

    <ImageView
        android:id="@+id/loading_iv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        tool:src="@drawable/ic_load_loading" />

    <ImageView
        android:id="@+id/empty_iv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        tool:src="@drawable/ic_load_empty" />

    <ImageView
        android:id="@+id/fail_iv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        tool:src="@drawable/ic_load_fail" />

    <TextView
        android:id="@+id/desc_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="30dp"
        android:layout_marginTop="10dp"
        android:layout_marginRight="30dp"
        android:includeFontPadding="false"
        android:textColor="#a3a3a3"
        android:textSize="12sp"
        tool:text="网络不给力,请刷新重试" />

    <Button
        android:id="@+id/action_btn"
        style="@style/Widget.AppCompat.Button.Borderless"
        android:layout_width="wrap_content"
        android:layout_height="30dp"
        android:layout_marginTop="20dp"
        android:background="@drawable/shape_loading_btn_bg"
        android:includeFontPadding="false"
        android:paddingLeft="38dp"
        android:paddingRight="38dp"
        android:textColor="@color/black"
        android:textSize="16sp"
        tool:text="刷新" />

</LinearLayout>

使用如下:

在xml布局中:

<com.android.widget.LoadingLayout
    android:id="@+id/loading_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:emptyActionText="去看看"
    app:emptyDescText="暂无数据"
    app:failActionText="点击重试"
    app:failDescText="网络不给力,请刷新重试"
    app:loadingDescText="加载中..." />

?在代码中使用:

loadingLayout.loadStart()     //开始加载
loadingLayout.loadComplete()  //加载完成,隐藏控件
loadingLayout.loadFail()      //加载失败
loadingLayout.loadEmpty()     //无数据
loadingLayout.setOnEmptyListener { }  //无数据时点击按钮回调
loadingLayout.setOnFailListener { }   //加载失败时点击按钮回调

?源码地址

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-08-30 12:09:58  更:2021-08-30 12:12:23 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年1日历 -2025/1/31 5:59:51-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码