【问题标题】:Drawing a progress animation in custom view(Button) using canvas in Android在Android中使用画布在自定义视图(按钮)中绘制进度动画
【发布时间】:2020-04-16 11:56:34
【问题描述】:

我想在按钮中从左到右绘制进度动画,有点像这样:

这里的按钮只是一个自定义视图。所以画布应该画这个,我只需要扩展视图类。我不确定我应该在这里使用什么我尝试使用值动画器使用 canvas.drawPath 但没有成功。 我不确定这里应该使用哪种画布方法以及如何从左到右对其进行动画处理。 任何人都可以在这里帮助我吗?

【问题讨论】:

    标签: android android-canvas android-custom-view


    【解决方案1】:

    这对我有用,希望它也能帮助你!

    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" />
    

    谢谢你:)

    【讨论】:

      【解决方案2】:

      要从自定义视图开始,请记住以下几点

      1. 正如它所指示的,它是一个进度指示器视图,上面有文本。所以 选择 TextView 来扩展功能。

      2. 这里的背景动画,这意味着我们的进度绘制部分有 在原始 textview 绘制周期之前完成。

      3. 将进度更新保持在自定义视图之外是明智之举。

      考虑到这一点,自定义视图将如下所示

      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) 进行计算优化,而绘制一个矩形就足够了

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-11-19
        • 1970-01-01
        • 2023-03-14
        • 1970-01-01
        • 1970-01-01
        • 2012-11-01
        相关资源
        最近更新 更多