仿照ContentLoadingProgressBar 的特点在Android项目中自定义Loading对话框

ContentLoadingProgressBar 是 Android 中的一个控件,继承自 ProgressBar。它在 ProgressBar 的基础上添加了一些特殊功能,主要用于在加载内容时显示进度。它的一些主要特点如下:

  1. 自动隐藏和显示:ContentLoadingProgressBar 会在内容加载完成后自动隐藏,并在内容开始加载时自动显示。这减少了手动控制进度条显示和隐藏的代码量。
  2. 延迟显示:为了避免在短时间内频繁显示和隐藏进度条,ContentLoadingProgressBar 提供了一个延迟显示的功能。如果内容加载时间非常短,进度条可能不会显示出来。
  3. 延迟隐藏:类似地,ContentLoadingProgressBar 也提供了延迟隐藏的功能,以确保进度条在内容加载完成后不会立即消失,从而提供更好的用户体验。

这些功能使 ContentLoadingProgressBar 成为一个更智能、更易用的进度条控件,特别适合在需要频繁加载内容的应用中使用。

1、ContentLoadingProgressBar 的特性

从注释中可以看出,ContentLoadingProgressBar 在 ProgressBar 的基础上添加了以下特性:

  1. 在显示之前会等待一段时间来被隐藏:这意味着在显示之前,ContentLoadingProgressBar 会等待一段时间,如果在这段时间内被隐藏,那么就不会显示出来。
  2. 一旦显示,ContentLoadingProgressBar 会在一段时间内保持可见:这确保了进度条不会在短时间内频繁显示和隐藏,避免了 UI 视图的“闪烁”现象。

这种“闪烁”现象在项目开发中很常见,例如在进行网络请求之前显示 Loading 对话框,请求完成之后再隐藏。如果网络请求耗时很短,就会导致对话框在短时间内显示和隐藏,造成“闪烁”现象。ContentLoadingProgressBar 的这两个特性很好地解决了这个问题。

2、ContentLoadingProgressBar 的实现

ContentLoadingProgressBar 中定义了两个 int 类型的常量 MIN_SHOW_TIMEMIN_DELAY,分别表示显示的最短时间和延迟显示的时间,值都是 500ms。mDelayedShowmDelayedHide 是两个 Runnable 任务,分别对应延时显示和延时隐藏。在控制 ContentLoadingProgressBar 的显示和隐藏时不能使用 setVisibility() 方法,而是需要使用 show()hide() 方法。

show() 方法

public void show() {
    mStartTime = -1;
    mPostedHide = false;
    mPostedShow = true;
    removeCallbacks(mDelayedHide);
    if (!mPostedShow) {
        postDelayed(mDelayedShow, MIN_DELAY);
    }
}

show() 方法首先会做一些状态的恢复处理,将 mStartTime 恢复为 -1,mStartTime 记录了 ContentLoadingProgressBar 开始显示的时间,接着将延时隐藏任务 mDelayedHide 从任务队列中移除。方法最后会判断 mPostedShow 的值,如果为 false 就调用 postDelayed() 方法延迟 MIN_DELAY(500ms)后执行 mDelayedShow 任务。mPostedShow 用于标记 mDelayedShow 是否已添加到任务队列中,防止任务的重复执行。mDelayedShow 任务的逻辑很简单,主要就是记录开始显示的时间并执行 setVisibility(View.VISIBLE) 将 ContentLoadingProgressBar 显示出来。

hide() 方法

public void hide() {
    mPostedHide = true;
    removeCallbacks(mDelayedShow);
    long diff = System.currentTimeMillis() - mStartTime;
    if (diff >= MIN_SHOW_TIME || mStartTime == -1) {
        setVisibility(View.GONE);
    } else {
        postDelayed(mDelayedHide, MIN_SHOW_TIME - diff);
    }
}

hide() 方法和 show() 方法类似,首先将延时显示任务 mDelayedShow 从任务队列中移除,因此如果调用 show()hide() 方法之间的间隔时间小于 MIN_DELAY(500ms),mDelayedShow 就不会执行了,ContentLoadingProgressBar 也就不会显示了。接下来会计算 System.currentTimeMillis() - mStartTime 的值,即此时 ContentLoadingProgressBar 的显示时间,如果此时 mStartTime 的值为 -1(ContentLoadingProgressBar 还没有显示)或者显示时间超过了 MIN_SHOW_TIME(500ms),直接执行 setVisibility(View.GONE) 隐藏 ContentLoadingProgressBar;反之则说明 ContentLoadingProgressBar 的显示时间没有达到最短时间 500ms,计算剩余的时间,延时执行隐藏任务,保证 ContentLoadingProgressBar 最短可以显示 500ms。这里的 mPostedHide 作用同样是防止延时隐藏任务的重复执行。mDelayedHide 任务的逻辑也比较简单,将 mStartTime 恢复为 -1,执行 setVisibility(View.GONE) 隐藏 ContentLoadingProgressBar。

3、自定义Loading 对话框

ContentLoadingProgressBar 给了我们很好的思路,解决 Loading 对话框“闪烁”问题需要做到以下两点:

  1. 显示 Loading 对话框之前先等待一段时间
  2. 隐藏 Loading 对话框时判断显示时间是否达到了最短显示时间,如果没有达到就延时执行隐藏任务

清楚思路后就可以优化 Loading 对话框了,直接附上完整代码:

package com.jpc.customwidgetstudy.widget

import android.app.Activity
import android.app.AlertDialog
import android.content.Context
import android.os.Handler
import android.os.Looper
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.TextView
import com.jpc.customwidgetstudy.R

/**
 * 自定义Loading Dialog, 用于显示加载中的状态
 */
class LoadingDialog(context: Context): AlertDialog(context, R.style.Theme_AppCompat_Dialog){
    companion object{
        // 最短显示时间
        private const val MIN_SHOW_TIME = 500L
        // 最短延迟时间
        private const val MIN_DELAY_TIME = 500L
    }
    private var tvMessage: TextView
    init {
        val parent = (context as? Activity)?.findViewById<ViewGroup>(android.R.id.content)
        val loadView = LayoutInflater.from(context).inflate(R.layout.dialog_loading, parent, false)
        setView(loadView)
        tvMessage = loadView.findViewById(R.id.tv_message)
    }
    // 记录开始时间
    private var mStartTime: Long = -1
    // 防止延时隐藏任务的重复执行
    private var mPostedHide: Boolean = false
    // 防止延时显示任务的重复执行
    private var mPostedShow: Boolean = false
    // 是否已经消失
    private var mDismissed: Boolean = false
    // 主线程Handler
    private val mHandler = Handler(Looper.getMainLooper())
    // 显示
    private val mDelayedShow: Runnable = Runnable {
        mPostedShow = false
        if (!mDismissed){
            mStartTime = System.currentTimeMillis()
            show()
        }
    }
    // 隐藏
    private val mDelayedHide: Runnable = Runnable {
        mPostedHide = false
        mStartTime = -1
        dismiss()
    }
    // 显示Dialog
    fun showDialog(message: String){
        tvMessage.text = message
        mStartTime = -1
        mDismissed = false
        mHandler.removeCallbacks(mDelayedHide)
        mPostedHide = false
        if (!mPostedShow){
            mHandler.postDelayed(mDelayedShow, MIN_DELAY_TIME)
            mPostedShow = true
        }
    }
    // 隐藏Dialog
    fun hideDialog(){
        mDismissed = true
        mHandler.removeCallbacks(mDelayedShow)
        mPostedShow = false
        val diff = System.currentTimeMillis() - mStartTime
        if (diff >= MIN_SHOW_TIME || mStartTime == -1L){
            dismiss()
        }else{
            if (!mPostedHide){
                mHandler.postDelayed(mDelayedHide, MIN_SHOW_TIME - diff)
                mPostedHide = true
            }
        }
    }
    // 从Window移除时移除所有的Callback
    override fun onDetachedFromWindow() {
        super.onDetachedFromWindow()
        mHandler.removeCallbacks(mDelayedHide)
        mHandler.removeCallbacks(mDelayedShow)
    }
}

可以定义Dialog的大小

    <style name="Theme.AppCompat.Dialog" parent="Theme.AppCompat.Light.Dialog">
        <!-- Customize your dialog theme here -->
        <item name="android:windowBackground">@color/loading_color</item>
        <item name="android:windowMinWidthMajor">30%</item>
        <item name="android:windowMinWidthMinor">30%</item>
        <item name="android:padding">6dp</item>
    </style>
    <!-- Custom ProgressBar style -->
    <style name="CustomProgressBar" parent="Widget.AppCompat.ProgressBar">
        <item name="android:indeterminateTint">@color/colorPrimary</item>
    </style>
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <ProgressBar
        android:id="@+id/progressBar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toTopOf="@id/tv_message"
        app:layout_constraintStart_toStartOf="@id/tv_message"
        app:layout_constraintEnd_toEndOf="@id/tv_message"
        style="@style/CustomProgressBar"/>
    <TextView
        android:id="@+id/tv_message"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="加载中..."
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

布局文件就是一个 ProgressBar 和一个 TextView,用于展示提示信息。控制 Loading 对话框的显示和隐藏直接使用 showDialog()hideDialog() 方法就可以了。为了简单示例,这里自定义的 Dialog 直接继承自 AlertDialog,注意要在适当的时机移除延时任务,防止内存泄漏。

效果如下:
在这里插入图片描述

总结

本文通过分析 ContentLoadingProgressBar 的原理引出了项目开发中 Loading 对话框的一种优化方式,避免对话框显示和隐藏间隔时间太短导致的“闪烁”现象。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/871481.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

初级python代码编程学习----简单的图形化闹钟小程序

我们来创建一个简单的图形化闹钟程序通常需要使用图形用户界面&#xff08;GUI&#xff09;库。以下是使用Python的Tkinter库创建一个基本闹钟程序的步骤&#xff1a; 环境准备 确保已安装Python。安装Tkinter库&#xff08;Python 3.8及以上版本自带Tkinter&#xff0c;无需…

软件测试学习笔记丨Allure2报告添加附件报告定制

本文转自测试人社区&#xff0c;原文链接&#xff1a;https://ceshiren.com/t/topic/31810 一、Allure2报告中添加附件-图片 1.1 附件类型 TEXT ("text/plain", "txt") CSV ("text/csv", "csv") TSV ("text/tab-separated-v…

leetcode:2119. 反转两次的数字(python3解法)

难度&#xff1a;简单 反转 一个整数意味着倒置它的所有位。 例如&#xff0c;反转 2021 得到 1202 。反转 12300 得到 321 &#xff0c;不保留前导零 。 给你一个整数 num &#xff0c;反转 num 得到 reversed1 &#xff0c;接着反转 reversed1 得到 reversed2 。如果 reverse…

GEC6818开发板的学习

1、开发板的简介 首先连接 开发板与电脑,需电脑安装串口驱动:例CH340 2、开发板的特性: 像素:800*480Pix分辨率:高,宽两个维度的像素点数目开发板色深为32位一个像素点占4个字节:分别为灰度保留位、RGB三原色各占一位3、为什么要内存映射 虽然LCD设备本质上也可以看作…

OW-VISCap——开放世界视频实例分割方法研究

概述 论文地址&#xff1a;https://arxiv.org/pdf/2404.03657 本文提出了一种名为 OW-VISCap&#xff08;开放世界视频实例分割和字幕&#xff09;的方法。其三大贡献是 开放世界对象查询&#xff1a;除了已知对象查询外&#xff0c;还引入了开放世界对象查询&#xff0c;以发…

【安全靶场】-DC-7

❤️博客主页&#xff1a; iknow181 &#x1f525;系列专栏&#xff1a; 网络安全、 Python、JavaSE、JavaWeb、CCNP &#x1f389;欢迎大家点赞&#x1f44d;收藏⭐评论✍ 一、收集信息 1.查看主机是否存活 nmap -T4 -sP 192.168.216.149 2.主动扫描 看开放了哪些端口和功能 n…

WPF调用CEF插件运行时启动CefSharp.BrowserSubprocess.exe三个进程

cefsharp.browsersubprocess.exe 是CefSharp&#xff08;一个基于Chromium的开源浏览器控件&#xff09;的一部分。这个可执行文件通常在以下情况下启动&#xff1a; 渲染进程&#xff1a;CefSharp使用多进程架构&#xff0c;类似于Chrome浏览器。cefsharp.browsersubprocess.e…

ArcGIS 数据服务在三维 Cesium/SuperMap 项目中使用遇到的一些问题及其解决方法

ArcGIS 数据服务在三维 Cesium/SuperMap 项目中使用遇到的一些问题及其解决方法 一、三维系统支持的 ArcGIS 服务及其投影 1、动态服务 ArcGIS 动态服务的数据&#xff0c;支持任意投影在三维系统中加载。 2、切片服务 ArcGIS 切片服务仅支持 3857(web 墨卡托投影)&#x…

19529 照明灯安装

### 详细分析 这个问题可以通过二分查找和贪心算法来解决。我们需要找到一个最大值&#xff0c;使得在这个最大值下&#xff0c;能够在给定的坐标上安装 k 个照明灯&#xff0c;并且相邻的照明灯之间的距离至少为这个最大值。 ### 思路 1. **排序**&#xff1a;首先对给定的…

arthas源码刨析:启动 (1)

文章目录 arthas-bootBootstrap Created with Raphal 2.3.0 开始 检查监听端口 jps 列表java应用 下载 lib 依赖 功能移交给 arthas-core 结束 arthas-boot 该module 的代码只有3个类&#xff1a; Bootstrap 启动类 Bootstrap &#xff0c;开头的注解就是 alibaba 的 cli 中…

凡图公益行:凡图家庭教育以行动筑梦,点亮孩子心中的光芒

在教育的路上&#xff0c;每一步都承载着未来的希望&#xff0c;凡图(山东)教育科技集团有限公司一直致力于为每一个孩子及家庭提供最优质的心理咨询服务。 在这样的背景下&#xff0c;凡图家庭教育以独特的使命感和责任感&#xff0c;成为了众多家庭信赖的伙伴。 也因此成为…

阿里Qwen2开源大模型本地部署及调试全攻略

阿里Qwen2开源大模型本地部署及调试全攻略 #Qwen2系列大模型性能卓越&#xff0c;超越业界知名模型。开源后受到AI开发者关注&#xff0c;支持多种语言&#xff0c;提升多语言理解。在预训练和微调上优化&#xff0c;实现智能水平提升。Qwen2系列模型在各项能力上均领先&#…

glibc 2.24 下 IO_FILE 的利用

文章目录 glibc 2.24 下 IO_FILE 的利用介绍&#xff1a;新的利用技术fileno 与缓冲区的相关利用实例&#xff1a;1. _IO_str_jumps -> overflow实例&#xff1a; 2. _IO_str_jumps -> finish实例: 最后拓展一下上一篇博客house of orange题目的做法: glibc 2.24 下 IO_F…

怎么对前端的一些按钮做一个权限校验

在一般情况下,我们需要对一些按钮做一个权限校验,来保证只有有权限的用户才能看到 1.创建一个js文件,来写我们的全局方法 我的方法是这样的 import Vue from vue;Vue.mixin({methods:{hasAuth(perm) {var authority this.$store.state.menu.permList;if (authority.indexOf(…

又搞到两款海外AI神器,确实强!

哈喽&#xff0c;各位小伙伴们好&#xff0c;我是给大家带来各类黑科技与前沿资讯的小武。 今天给大家安利两款海外AI神器&#xff0c;均已解除专业版限制&#xff0c;免翻&#xff0c;即可使用AI图片/视频修复(清晰度增强)、AI图片/视频一键抠图、AI证件照、美颜相机等强大功…

STM32的GPIO

GPIO基本控制 GPIO(General-Purpose input/output,通用输入/输出接口) 用于感知外部信号&#xff08;输入模式&#xff09;和控制外部设备&#xff08;输出模式&#xff09; 简单模块&#xff1a;LED,按键&#xff0c;蜂鸣器&#xff0c;温度传感器&#xff0c;使用一个GPIO…

安装MySQL入门基础指令

一.安装MySQL(以5.7版本为例) 1.一路默认安装&#xff0c;截图供大家参考 修改自己window安装名字即可 2.配置环境变量 C:\Program Files\MySQL\MySQL Server 5.7\bin 写入系统环境变量即可在window窗口使用其服务了 3.登录MySQL服务 进入控制台输入命令 mysql -u root …

MySQL InnoDB引擎四大特性ACID实现方案分析

文章目录 概要InnoDb引擎ACID模型的实现方案小结 概要 对于Mysql&#xff0c;事物的支撑并不依赖于Server层&#xff0c;不同的存储引擎对于事物的支持也不一样&#xff0c;对于我们常用的InnoDB引擎&#xff0c;其提供了一套基于【ACID模型】的事物完整的解决方案。为什么MyIS…

初识C++:开启C++之旅

目录 1.C的第一个程序 2.namesapce命名空间域 2.1namespace的意义 2.2.2namespace的定义 2.3命名空间的使用 3.C输入/输出 4.缺省参数 5.函数重载 6.引用 6.1引用的特性 6.2引用的使用 1.C的第一个程序 c版本&#xff1a; #include<iostream>using std::cout…

线性代数:每日一题1/特征值与相似对角化

设A, B 为二阶矩阵&#xff0c;且 AB BA , 则“A有两个不相等的特征值”是“B可对角化"的&#xff08;&#xff09; A. 充分必要条件 B. 充分不必要条件 C.必要不充分条件 D.既不充分也不必要条件 知识点&#xff1a; 特征向量与特征值的关系 相似矩阵的定义和性质 n阶…