Android中下载无处不在,下载进度按钮在各大应用中随处可在。
效果图如下:
一、前言
-
下载进度按钮的意义:
Android下载进度按钮的意义在于提升用户体验和增强应用的交互性 在Android应用开发中,下载进度按钮在各大应用中到处可见,通过实时显示下载进度,使用户能够清晰地了解下载状态,从而提升用户的使用体验。这种设计不仅让用户感到被重视,还能减少用户的焦虑感,因为用户可以随时了解下载的进度和剩余时间
-
本文简单介绍如何通过Android Jetpack Compose来实现一个下载进度按钮。
-
本工程库已打包,可自行使用,也可以根据它进行扩展改造。
二、下载按钮数据模型设计:
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)}%"
}
}
三、绘制进度条按钮
- 首先下载时候有三种状态:
正在下载,暂停,下载完成
,通过.pointerInput(Unit) { detectTapGestures
方法实现点击回调 Canvas.drawLine
来根据设置数据模型绘制按钮四条边线Canvas.drawRect
来根据设置数据模型绘制按钮空心矩形条,进度实心矩形条
animateFloatAsState
:通过该动画实现进度条的变化- 当中间文字在实心进度条和空心背景临界处文字字体颜色特殊处理:如下: 当中间文字宽度起始位置即为
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来实现一个下载进度条按钮:
- 该按钮包含3种状态。
下载中,暂停中,下载完成
- 通过组装数据模型,通过
Canvas
来绘制,涉及到相关api
:绘制线条drawLine
,绘制矩形drawRect
,动画变化animateFloatAsState
和horizontalGradient
的相关用法 - 已经封装好了,可以直接使用。