家资是何物,积帙列梁梠。这篇文章主要讲述Android技术分享|自定义View实现Material Design的Loading效果相关的知识,希望能为你提供帮助。
预期效果
文章图片
文章图片
实现思路
分析一下这个动画,效果应该是通过两个动画来实现的。
- 一个不停变速伸缩的扇形动画
- 一个固定速度的旋转动画
canvas#drawArc
来实现旋转动画可以用
setMatrix
实现圆角背景可以通过
canvas#drawRoundRect
实现还需要一个计时器来实现动画效果
这个View最好能够更方便的修改样式,所以需要定义一个declare-styleable,方便通过布局来修改属性。
这些元素应该包括:
- 最底层的卡片颜色
- 卡片内变局
- 内部长条的颜色
- 长条的粗细
- 长条的距离中心的半径
- 字体大小
- 字体颜色
代码实现
- 定义一下styleable
< declare-styleable name="MaterialLoadingProgress"> < attr name="loadingProgress_circleRadius" format="dimension" /> < attr name="loadingProgress_cardColor" format="color" /> < attr name="loadingProgress_cardPadding" format="dimension" /> < attr name="loadingProgress_strokeWidth" format="dimension" /> < attr name="loadingProgress_strokeColor" format="color" /> < attr name="loadingProgress_text" format="string" /> < attr name="loadingProgress_textSize" format="dimension" /> < attr name="loadingProgress_textColor" format="color" /> < /declare-styleable>
- 在代码中解析styleable
init val defCircleRadius = context.resources.getDimension(R.dimen.dp24) val defCardColor = Color.WHITE val defCardPadding = context.resources.getDimension(R.dimen.dp12) val defStrokeWidth = context.resources.getDimension(R.dimen.dp5) val defStrokeColor = ContextCompat.getColor(context, R.color.teal_200) val defTextSize = context.resources.getDimension(R.dimen.sp14) val defTextColor = Color.parseColor("#333333") if (attrs != null) val attrSet = context.resources.obtainAttributes(attrs, R.styleable.MaterialLoadingProgress) circleRadius = attrSet.getDimension(R.styleable.MaterialLoadingProgress_loadingProgress_circleRadius, defCircleRadius) cardColor = attrSet.getColor(R.styleable.MaterialLoadingProgress_loadingProgress_cardColor, defCardColor) cardPadding = attrSet.getDimension(R.styleable.MaterialLoadingProgress_loadingProgress_cardPadding, defCardPadding) strokeWidth = attrSet.getDimension(R.styleable.MaterialLoadingProgress_loadingProgress_strokeWidth, defStrokeWidth) strokeColor = attrSet.getColor(R.styleable.MaterialLoadingProgress_loadingProgress_strokeColor, defStrokeColor) text = attrSet.getString(R.styleable.MaterialLoadingProgress_loadingProgress_text) ?: "" textSize = attrSet.getDimension(R.styleable.MaterialLoadingProgress_loadingProgress_textSize, defTextSize) textColor = attrSet.getColor(R.styleable.MaterialLoadingProgress_loadingProgress_textColor, defTextColor) attrSet.recycle() else circleRadius = defCircleRadius cardColor = defCardColor cardPadding = defCardPadding strokeWidth = defStrokeWidth strokeColor = defStrokeColor textSize = defTextSize textColor = defTextColorpaint.textSize = textSize if (text.isNotBlank()) textWidth = paint.measureText(text)
- 实现一个计时器,再定义一个数据类型来存储动画相关数据,还有一个动画插值器
val taskIterator = taskList.iterator()
while (taskIterator.hasNext())
val task = taskIterator.next()
task.progress += 17 if (task.progress > task.duration) task.progress = task.durationif (task.progress == task.duration) if (!task.convert) task.startAngle -= 40 if (task.startAngle < 0) task.startAngle += 360task.progress = 0 task.convert = !task.converttask.progressFloat = task.progress / task.duration.toFloat() task.interpolatorProgress = interpolator(task.progress / task.duration.toFloat()) task.currentAngle = (320 * task.interpolatorProgress).toInt() posttask.onProgress(task)
, 0, 16)
timer = t
> 定义一个数据模型 ```Kotlin private data class AnimTask( var startAngle: Int = 0,// 扇形绘制起点 val duration: Int = 700,// 动画时间 var progress: Int = 0,// 动画已执行时间 var interpolatorProgress: Float = 0f,// 插值器计算后的值,取值0.0f ~ 1.0f var progressFloat: Float = 0f,// 取值0.0f ~ 1.0f var convert: Boolean = false,// 判断扇形的绘制进程,为true时反向绘制 var currentAngle: Int = 0,// 绘制扇形使用 val onProgress: (AnimTask) -> Unit// 计算完当前帧数据后的回调 )
- 定义初始化缓冲帧
- 实现扇形的绘制
private fun drawFrame(task: AnimTask) bufferBitmap.eraseColor(Color.TRANSPARENT)val centerX = measuredWidth.shr(1) val centerY = measuredHeight.shr(1) rectF.set( centerX - circleRadius, centerY - circleRadius, centerX + circleRadius, centerY + circleRadius ) paint.strokeWidth = strokeWidth paint.color = strokeColor paint.strokeCap = Paint.Cap.ROUND paint.style = Paint.Style.STROKE// 这里的判断,对应扇形逐渐延长、及逐渐缩短 if (task.convert) bufferCanvas.drawArc( rectF, task.startAngle.toFloat(), -(320.0f - task.currentAngle.toFloat()), false, paint ) else bufferCanvas.drawArc( rectF, task.startAngle.toFloat(), task.currentAngle.toFloat(), false, paint )invalidate()
- 实现扇形整体缓慢转圈
private fun drawRotation(task: AnimTask) val centerX = measuredWidth.shr(1) val centerY = measuredHeight.shr(1) bufferMatrix.reset() bufferMatrix.postRotate(task.progressFloat * 360f, centerX.toFloat(), centerY.toFloat()) bufferCanvas.setMatrix(bufferMatrix)
文章图片
到这里,核心功能基本就完成了。
- 定义一个
showProgress
方法以及dismissProgress
方法,方便外部使用
【Android技术分享|自定义View实现Material Design的Loading效果】if (!this::bufferBitmap.isInitialized)
initCanvas()
taskList.add(AnimTask
drawFrame(it)
)
taskList.add(AnimTask(duration = 5000)
drawRotation(it)
)
startTimerTask()
showing = true
visibility = VISIBLE
> 关闭 ```Kotlin fun dismissProgress() if (!showing) returnpurgeTimer() showing = false visibility = GONE
View#onDraw
的实现:override fun onDraw(canvas: Canvas)
val centerX = measuredWidth.shr(1)
val centerY = measuredHeight.shr(1)val rectHalfDimension = if (circleRadius >
textWidth / 2f) circleRadius + cardPadding else textWidth / 2f + cardPadding
rectF.set(
centerX - rectHalfDimension,
centerY - rectHalfDimension,
centerX + rectHalfDimension,
if (text.isNotBlank()) centerY + paint.textSize + rectHalfDimension else centerY + rectHalfDimension
)paint.color = cardColor
paint.style = Paint.Style.FILL
canvas.drawRoundRect(rectF, 12f, 12f, paint)if (text.isNotBlank())
val dx = measuredWidth.shr(1) - textWidth / 2
paint.color = textColor
canvas.drawText(text, dx, rectF.bottom - paint.textSize, paint)if (this::bufferBitmap.isInitialized)
canvas.drawBitmap(bufferBitmap, bufferMatrix, paint)
源代码请移步:ARCallPlus。
文章图片
推荐阅读
- GIT 版本管理 - 3 (分支管理)
- HCL模拟器防火墙WEB方式登录配置
- 设计模式之策略模式
- AOP详解之三-创建AOP代理后记,创建AOP代理
- 平衡树(为什么Redis内部实现用跳跃表)
- linux下lvm逻辑卷配置
- 存储性能测试漫谈
- Kubeadm集群证书过期后的处理
- C语言简单实现“猜数字小游戏”