Android下载进度百分比按钮,Compose轻松秒杀实现

Android中下载无处不在,下载进度按钮在各大应用中随处可在。

效果图如下:

一、前言

  1. 下载进度按钮的意义:

    ‌Android下载进度按钮的意义在于提升用户体验和增强应用的交互性 在Android应用开发中,下载进度按钮在各大应用中到处可见,通过实时显示下载进度,使用户能够清晰地了解下载状态,从而提升用户的使用体验。这种设计不仅让用户感到被重视,还能减少用户的焦虑感,因为用户可以随时了解下载的进度和剩余时间‌

  2. 本文简单介绍如何通过Android Jetpack Compose来实现一个下载进度按钮。

  3. 本工程库已打包,可自行使用,也可以根据它进行扩展改造。

二、下载按钮数据模型设计:

ProgressModel :下载按钮模型: title:按钮名称
statusText:下载时各种状态文案
strokeColor:按钮边线颜色
strokeSize:边线宽度
mBackgroundColor:没有进度条时候的背景颜色,空心背景颜色
mBackgroundSecondColor:下载过的进度范围颜色,实心背景颜色 maxProgress:进度最大值
statusFinishText:下载完成时候显示文案
coverTextColor:下载进度实心背景过了,文字显示颜色
formatString:百分百格式化到小数点后几位,可以自定义

kotlin 复制代码
data class ProgressModel(
    val title: String,  //按钮名称
    var statusText: String = "", //下载时各种状态文案
    val strokeColor: Color = Color.Blue, // 按钮边线颜色
    val strokeSize: Float = 2f,  //边线宽度
    val mBackgroundColor: Color = Color.White, //背景颜色
    val mBackgroundSecondColor: Color = Color.Blue, //下载过的进度范围颜色
) {
    var maxProgress: Float = 100f
    var statusFinishText: String = "点击安装" //下载完成时候显示文案
    var coverTextColor: Color = Color.White  //下载进度背景过了,文字显示颜色


    var formatString: String = "%.2f"//百分百格式化到小数点后几位 自定义
    fun getTextValueFormat(value: Float): String {
        return "${formatString.format(value)}%"
    }
}

三、绘制进度条按钮

  1. 首先下载时候有三种状态:正在下载,暂停,下载完成 ,通过.pointerInput(Unit) { detectTapGestures方法实现点击回调
  2. Canvas.drawLine 来根据设置数据模型绘制按钮四条边线
  3. Canvas.drawRect 来根据设置数据模型绘制按钮 空心矩形条,进度实心矩形条
  4. animateFloatAsState:通过该动画实现进度条的变化
  5. 当中间文字在实心进度条和空心背景临界处文字字体颜色特殊处理:如下: 当中间文字宽度起始位置即为startX:进度条超过startX,但是没有超过中间文字最右边位置时候,这时进度条的值便是:endX,listColor里面可以设置2种以上字体颜色,即是临界线左右的两种颜色,注意它是慢慢渐变的。
ini 复制代码
AnnotatedString(
text = text.trimIndent(),
spanStyle = SpanStyle( fontSize = style.fontSize, 
brush = Brush.horizontalGradient( listColor, 
startX = 0f, 
endX = currWidth - leftX 
) 
) )

6.Canvas.drawText 通过该方法绘制当前显示的文案。

完整实现代码如下:

ini 复制代码
const val WX_PROGRESS_BUTTON_DOWNLOADING = 1  //正在下载
const val WX_PROGRESS_BUTTON_DOWNLOAD_PAUSE = 2 //暂停
const val WX_PROGRESS_BUTTON_DOWNLOAD_COMPLETE = 3 //完成

@Composable
fun ProgressButton(modifier: Modifier, textMeasurer: TextMeasurer, style: TextStyle, it: ProgressModel, progress: Float, onClick: (mode: Int) -> Unit) {
    val context = LocalContext.current
    var mSize by remember { mutableStateOf(Size(0f, 0f)) }
    var mode by remember { mutableIntStateOf(0) }
    if (progress >= it.maxProgress) {
        mode = WX_PROGRESS_BUTTON_DOWNLOAD_COMPLETE
        it.statusText = it.statusFinishText
    }
    val text = remember(it.statusText, mode, progress) { if (mode == WX_PROGRESS_BUTTON_DOWNLOADING) it.getTextValueFormat(100 * progress / it.maxProgress) else if (mode == 0) it.title else it.statusText }

    val width = mSize.width
    val height = mSize.height
    val listColor = remember { listOf(it.coverTextColor, it.coverTextColor, style.color) }
    val fontStyle = remember { TextStyle(fontSize = style.fontSize) }
    val coverTextStyle = remember { TextStyle(color = it.coverTextColor, fontSize = style.fontSize) }
    val fontSizeDip = DisplayUtil.sp2dp(context, style.fontSize.value)
    val dl = getStrPhysicsLength(if (mode == 0) it.title else text) * fontSizeDip
    val fontHightVcenterOffset = height / 2 - (fontSizeDip + 0.5f) / 2
    val animatedBar by animateFloatAsState(targetValue = progress, animationSpec = FloatTweenSpec(200))
    val currWidth = animatedBar / it.maxProgress * (width - it.strokeSize)
    Canvas(
        modifier = modifier
            .graphicsLayer()
            .pointerInput(Unit) {
                detectTapGestures(onTap = {
                    mode = when (mode) {
                        0 -> WX_PROGRESS_BUTTON_DOWNLOADING //正在下载
                        WX_PROGRESS_BUTTON_DOWNLOADING -> WX_PROGRESS_BUTTON_DOWNLOAD_PAUSE //暂停
                        WX_PROGRESS_BUTTON_DOWNLOAD_PAUSE -> WX_PROGRESS_BUTTON_DOWNLOADING //恢复下载
                        WX_PROGRESS_BUTTON_DOWNLOAD_COMPLETE -> WX_PROGRESS_BUTTON_DOWNLOAD_COMPLETE //下载完成
                        else -> 0 //恢复到最初
                    }
                    onClick.invoke(mode)
                })
            }) {
        mSize = size
        drawLine(start = Offset(0f, 0f), end = Offset(width, 0f), color = it.strokeColor, strokeWidth = it.strokeSize)
        drawLine(start = Offset(0f, 0f), end = Offset(0f, height), color = it.strokeColor, strokeWidth = it.strokeSize)
        drawLine(start = Offset(width, 0f), end = Offset(width, height), color = it.strokeColor, strokeWidth = it.strokeSize)
        drawLine(start = Offset(0f, height), end = Offset(width, height), color = it.strokeColor, strokeWidth = it.strokeSize)
        drawRect(it.mBackgroundColor, topLeft = Offset(it.strokeSize / 2 + currWidth, it.strokeSize / 2), size = Size(width - it.strokeSize - currWidth, height - it.strokeSize))
        drawRect(it.mBackgroundSecondColor, topLeft = Offset(it.strokeSize / 2, it.strokeSize / 2), size = Size(currWidth, height))
        if (mode == WX_PROGRESS_BUTTON_DOWNLOADING || WX_PROGRESS_BUTTON_DOWNLOAD_PAUSE == mode) {
            val leftX = width / 2 - dl / 2
            val rightX = width / 2 + dl / 2
            if (currWidth < leftX) {
                drawText(textMeasurer, text = text, style = style, topLeft = Offset(leftX, fontHightVcenterOffset))
            } else if (currWidth > rightX) {
                drawText(textMeasurer, text = text, style = coverTextStyle, topLeft = Offset(width / 2 - dl / 2, fontHightVcenterOffset))
            } else {
                val measuredText = textMeasurer.measure(
                    AnnotatedString(
                        text = text.trimIndent(), spanStyle = SpanStyle(
                            fontSize = style.fontSize, brush = Brush.horizontalGradient(
                                listColor, startX = 0f, endX = currWidth - leftX
                            )
                        )
                    ), style = fontStyle
                )
                drawText(textLayoutResult = measuredText, topLeft = Offset(leftX, fontHightVcenterOffset))
            }
        } else if (mode == WX_PROGRESS_BUTTON_DOWNLOAD_COMPLETE) {
            drawText(textMeasurer, text = text, style = coverTextStyle, topLeft = Offset(width / 2 - dl / 2, fontHightVcenterOffset))
        } else {
            drawText(textMeasurer, text = text, style = style, topLeft = Offset(width / 2 - dl / 2, fontHightVcenterOffset))
        }
    }
}

四、封装成库,使用方法

1、repositories中添加如下maven

rust 复制代码
   repositories {
        maven { url 'https://repo1.maven.org/maven2/' }
        maven { url 'https://s01.oss.sonatype.org/content/repositories/releases/' }
    }
}

2、 dependencies中添加依赖

scss 复制代码
implementation("io.github.wgllss:Wgllss-ProgressButton:1.0.02")

3. ViewModel中提供数据模型

提供点击方法,根据不同的状态进行不同的操作,当下载的进度值变化时候,可以值更新数据progress的值就可以了, Compose中UI自动变化。同时,在按钮数据模型最初,可以根据自己的需求,更换按钮颜色,背景,文字颜色设置等,全部都可以自定义。

kotlin 复制代码
private val _datas = MutableLiveData<ProgressModel>()
val datas: LiveData<ProgressModel> = _datas

private val _progress = MutableLiveData<Float>()
val progress: LiveData<Float> = _progress

fun init() {
        val model = ProgressModel("下载按钮", strokeColor = Color.Red, mBackgroundSecondColor = Color.Red).apply {
            maxProgress = 100f
            statusFinishText = "点击安装"
        }
        _datas.value = model
}

fun onClick(mode: Int) = when (mode) {
    WX_PROGRESS_BUTTON_DOWNLOADING -> {
        //正在下载
        download()
    }

    WX_PROGRESS_BUTTON_DOWNLOAD_PAUSE -> {
        //暂停
        pause()
    }

    WX_PROGRESS_BUTTON_DOWNLOAD_COMPLETE -> {
        //下载完成
        finish()
    }

    else -> {
    }
}

4.Compose真正调用处 使用的时候,可以配置按钮大小,默认按钮状态下的文字大小,文字颜色。

scss 复制代码
@Composable
fun ProgressButtonSample(viewModel: ProgressViewModel) {
    val datas by viewModel.datas.observeAsState()

    val progress by viewModel.progress.observeAsState(0f)
    val textMeasurer = rememberTextMeasurer()

    Column(
        modifier = Modifier
            .fillMaxWidth()
            .fillMaxHeight()
    ) {
        datas?.let {
            ProgressButton(
                Modifier
                    .padding(50.dp, 50.dp, 50.dp, 0.dp)
                    .fillMaxWidth()
                    .height(50.dp), textMeasurer, TextStyle(color = Color.Black, fontSize = 16.sp), it, progress
            ) {
                viewModel.onClick(it)
            }
        }
    }
}

五、总结

本文简单介绍了如何通过Android 中Jetpack中Compose来实现一个下载进度条按钮:

  1. 该按钮包含3种状态。下载中,暂停中,下载完成
  2. 通过组装数据模型,通过Canvas来绘制,涉及到相关api:绘制线条drawLine,绘制矩形drawRect,动画变化animateFloatAsStatehorizontalGradient的相关用法
  3. 已经封装好了,可以直接使用。

感谢阅读:

欢迎用你发财的小手 关注,点赞、收藏

这里你会学到不一样的东西

相关推荐
_一条咸鱼_14 分钟前
深度解析 Android MVI 架构原理
android·面试·kotlin
雨白19 分钟前
流程定制型动画Animatable
android jetpack
AronTing32 分钟前
09-RocketMQ 深度解析:从原理到实战,构建可靠消息驱动微服务
后端·面试·架构
AronTing36 分钟前
11-Spring Cloud OpenFeign 深度解析:从基础概念到对比实战
后端·spring cloud·架构
D龙源1 小时前
VSCode-IoC和DI
后端·架构
火柴就是我1 小时前
git rebase -i 修改某次提交的message
android
zhangphil1 小时前
Android ExifInterface rotationDegrees图旋转角度,Kotlin
android·kotlin
火柴就是我1 小时前
需求开发提交了几个 commit,提交 master 领导 review 后,说你第一笔 commit 代码有问题,让你改一下,怎么办?
android
AronTing1 小时前
10-Spring Cloud Alibaba 之 Dubbo 深度剖析与实战
后端·面试·架构
KdanMin2 小时前
Android系统通知机制深度解析:Framework至SystemUI全链路剖析
android