【发布时间】:2020-04-16 11:56:34
【问题描述】:
我想在按钮中从左到右绘制进度动画,有点像这样:
这里的按钮只是一个自定义视图。所以画布应该画这个,我只需要扩展视图类。我不确定我应该在这里使用什么我尝试使用值动画器使用 canvas.drawPath 但没有成功。 我不确定这里应该使用哪种画布方法以及如何从左到右对其进行动画处理。 任何人都可以在这里帮助我吗?
【问题讨论】:
标签: android android-canvas android-custom-view
我想在按钮中从左到右绘制进度动画,有点像这样:
这里的按钮只是一个自定义视图。所以画布应该画这个,我只需要扩展视图类。我不确定我应该在这里使用什么我尝试使用值动画器使用 canvas.drawPath 但没有成功。 我不确定这里应该使用哪种画布方法以及如何从左到右对其进行动画处理。 任何人都可以在这里帮助我吗?
【问题讨论】:
标签: android android-canvas android-custom-view
这对我有用,希望它也能帮助你!
LoadingButton.kt
import android.animation.AnimatorInflater
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.view.View
import androidx.core.content.ContextCompat
import kotlin.properties.Delegates
class LoadingButton @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private var bgColor: Int = Color.BLACK
private var textColor: Int = Color.BLACK // default color
private var widthSize = 0
private var heightSize = 0
@Volatile
private var progress: Double = 0.0
private var valueAnimator: ValueAnimator
private var buttonState: ButtonState by Delegates.observable(ButtonState.Completed) {
p, old, new ->
}
private val updateListener = ValueAnimator.AnimatorUpdateListener {
progress = (it.animatedValue as Float).toDouble()
invalidate()
requestLayout()
}
fun hasCompletedDownload() {
// cancel the animation when file is downloaded
valueAnimator.cancel()
buttonState = ButtonState.Completed
invalidate()
requestLayout()
}
init {
isClickable = true
valueAnimator = AnimatorInflater.loadAnimator(
context, R.animator.loading_animation
) as ValueAnimator
valueAnimator.addUpdateListener(updateListener)
val attr = context.theme.obtainStyledAttributes(
attrs,
R.styleable.LoadingButton,
0,
0
)
try {
bgColor = attr.getColor(
R.styleable.LoadingButton_bgColor,
ContextCompat.getColor(context, R.color.colorPrimary)
)
textColor = attr.getColor(
R.styleable.LoadingButton_textColor,
ContextCompat.getColor(context, R.color.colorPrimary)
)
} finally {
attr.recycle()
}
}
private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
style = Paint.Style.FILL
textAlign = Paint.Align.CENTER
textSize = 55.0f
typeface = Typeface.create("", Typeface.BOLD)
}
override fun performClick(): Boolean {
super.performClick()
if (buttonState == ButtonState.Completed) buttonState = ButtonState.Loading
animation()
return true
}
private fun animation() {
valueAnimator.start()
}
private val rect = RectF(
740f,
50f,
810f,
110f
)
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
paint.strokeWidth = 0f
paint.color = bgColor
canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), paint)
if (buttonState == ButtonState.Loading) {
paint.color = Color.parseColor("#004349")
canvas.drawRect(
0f, 0f,
(width * (progress / 100)).toFloat(), height.toFloat(), paint
)
paint.color = Color.parseColor("#F9A825")
canvas.drawArc(rect, 0f, (360 * (progress / 100)).toFloat(), true, paint)
}
val buttonText =
if (buttonState == ButtonState.Loading)
resources.getString(R.string.loading)
else resources.getString(R.string.download)
paint.color = textColor
canvas.drawText(buttonText, (width / 2).toFloat(), ((height + 30) / 2).toFloat(),
paint)
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val minw: Int = paddingLeft + paddingRight + suggestedMinimumWidth
val w: Int = resolveSizeAndState(minw, widthMeasureSpec, 1)
val h: Int = resolveSizeAndState(
MeasureSpec.getSize(w),
heightMeasureSpec,
0
)
widthSize = w
heightSize = h
setMeasuredDimension(w, h)
}
}
下载文件时调用 hasCompletedDownload() 函数以停止动画。
在“res”文件夹下的“values”文件夹中添加一个新文件(比如 attrs.xml),为自定义按钮添加属性。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="LoadingButton">
<attr name="bgColor" format="integer" />
<attr name="textColor" format="integer" />
</declare-styleable>
</resources>
在你的布局中添加这个
<com.android.example.LoadingButton
android:id="@+id/custom_button"
android:layout_width="0dp"
android:layout_height="60dp"
android:layout_margin="20dp"
app:textColor="@color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
在“res”文件夹下添加另一个文件夹(比如动画),然后添加一个文件“loading_animation.xml”,以便为自定义按钮添加动画属性。
<?xml version="1.0" encoding="utf-8"?>
<animator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="500"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:valueFrom="0f"
android:valueTo="100f"
android:valueType="floatType" />
谢谢你:)
【讨论】:
要从自定义视图开始,请记住以下几点
正如它所指示的,它是一个进度指示器视图,上面有文本。所以 选择 TextView 来扩展功能。
这里的背景动画,这意味着我们的进度绘制部分有 在原始 textview 绘制周期之前完成。
将进度更新保持在自定义视图之外是明智之举。
考虑到这一点,自定义视图将如下所示
class DownloadButton : androidx.appcompat.widget.AppCompatTextView {
/// constructor
private val bgPaint: Paint = Paint().apply {
color = 0xff216353.toInt()
}
private val progressPaint: Paint = Paint().apply {
color = 0xff75daad.toInt()
}
var progress: Float = 0f
override fun onDraw(canvas: Canvas?) {
// Draw before original content drawn
// Compute the dx based on the progress
// and draw 2 rects
canvas?.let {
val dx = it.width * progress
it.drawRect(RectF(0f, 0f, dx, it.height * 1f), bgPaint)
it.drawRect(RectF(dx, 0f, it.width * 1f, it.height * 1f), progressPaint)
}
super.onDraw(canvas)
}
fun updateProgress(progress: Float) {
this.progress = progress
val percent = (progress * 100).toInt()
text = "Progress $percent%"
invalidate()
}
}
在 xml 中像使用任何 TextView 一样使用它
<com.example.custom.DownloadButton
android:id="@+id/downloadButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:padding="16dp"
android:text="Download"
android:textColor="#fff"
android:textSize="20sp"
android:textStyle="bold"
android:layout_margin="16dp"
android:typeface="monospace" />
在代码的任何地方,调用 downloadButton.updateProgress() 来重绘进度。
请注意,这是一个最低限度的实现,我们还没有对边缘情况 (0 - 100) 进行计算优化,而绘制一个矩形就足够了
【讨论】: