圆环进度动画

2020-10-15  本文已影响0人  爱写代码的小王子

本篇文章主要记录本周学习的进度动画,一个特别有趣的例子,当我们点击的时候,会开始进度值变化,还会有圆环进度变化
演示:


image.png

下面让我们一起实现一下

activity_mian界面布局

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <me.jrl.demo4.DownloadView
        android:id="@+id/mDownLoadView"
        android:layout_width="200dp"
        android:layout_height="200dp"
        app:bgColor="@color/colorAccent"
        app:pgColor="@color/colorPrimary"
        app:txColor="@android:color/black"
        app:lineWidth="20dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

布局可以根据自己需要调整,这里为了好看,我就设置了屏幕居中了
DownloadView自定义View

import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.RectF
import android.text.TextPaint
import android.util.AttributeSet
import android.util.Log
import android.view.MotionEvent
import android.view.View
import kotlin.math.log
import kotlin.math.min

class DownloadView : View {

    /**
     * 圆环画笔,绘制背景
     * */
    private val mBgPaint by lazy {
        Paint().apply {
            strokeWidth = mLineWidth
            color = mBgColor
            style = Paint.Style.STROKE
            isAntiAlias = true
            isDither = true
        }
    }

    /**
     * 背景颜色
     * */
    private var mBgColor = 0

    /**
     * 圆环画笔,绘制进度
     * */
    private val mPgPaint by lazy {
        Paint().apply {
            strokeWidth = mLineWidth
            color = mPgColor
            style = Paint.Style.STROKE
            isAntiAlias = true
            isDither = true
        }
    }


    /**
     * 进度颜色
     * */
    private var mPgColor = 0

    /**
     * 线宽
     * */
    private var mLineWidth = dp2px(0)

    /**
     * 画笔,绘制进度值
     * */
    private val mTxPaint by lazy {
        TextPaint().apply {
            strokeWidth = dp2px(2)
            textSize = dp2px(20)
            color = mTxColor
            style = Paint.Style.STROKE
            isAntiAlias = true
            isDither = true
        }
    }

    /**
     * 文本颜色
     * */
    private var mTxColor = 0

    /**
     * 控件中心点
     * */
    private var cx = 0f
    private var cy = 0f

    /**
     * 圆环半径
     * */
    private var radius = 0f

    /**
     * 下载动画因子,也就是进度值
     * */
    private var progress = 0f

    /**
     * 下载动画
     * */
    private var mAnimator: ValueAnimator? = null

    /**
     * 矩阵
     * */
    private val mRectF by lazy {
        RectF(cx - radius,cy - radius,cx + radius,cy + radius)
    }

    /**
     * 进度值
     * */
    private var mText = "0%"

    /**
     * 文字矩阵
     * */
    private val mFontMetricsInt by lazy {
        mTxPaint.fontMetricsInt
    }

    constructor(context: Context) : super(context) {}

    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
        init(context, attrs)
    }


    /**
     * 提取自定义属性
     * */
    private fun init(context: Context, attrs: AttributeSet?) {

        val array = context.obtainStyledAttributes(attrs, R.styleable.DownloadView)
        mBgColor = array.getColor(R.styleable.DownloadView_bgColor, Color.GREEN)
        mPgColor = array.getColor(R.styleable.DownloadView_pgColor, Color.BLUE)
        mTxColor = array.getColor(R.styleable.DownloadView_txColor, Color.BLACK)
        mLineWidth = array.getDimension(R.styleable.DownloadView_lineWidth, dp2px(20))
        array.recycle()

    }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        cx = measuredWidth / 2f
        cy = measuredHeight / 2f
        //半径取宽高的最小值的一半,再减去线的宽度
        radius = min(measuredWidth, measuredHeight) / 2f - mLineWidth
    }

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        //绘制背景圆环
        canvas?.drawCircle(cx,cy,radius,mBgPaint)
        //绘制进度圆环
        canvas?.drawArc(mRectF,0f,progress * 360,false,mPgPaint)
        //文字宽度的一半
        val mHalfWidth = mTxPaint.measureText(mText) / 2
        Log.v("pxd","mHalfWidth:$mHalfWidth")
        //绘制显示的进度值
        canvas?.drawText(mText,cx - mHalfWidth,cy - mFontMetricsInt.ascent / 2,mTxPaint)
    }

    /**
     * 下载,实现下载动画
     * */
    private fun initAnimator() {

        mAnimator = ValueAnimator.ofFloat(0f, 1f).apply {
            duration = 2000
            addUpdateListener {
                progress = it.animatedValue as Float
                mText = "${(progress * 100).toInt()}%"
                //重绘
                invalidate()
            }
            start()
        }
    }

    /**
     * 开始动画或者暂停动画
     * */
    fun start() {
        if (mAnimator != null) {
            if (mAnimator!!.isPaused) {
                mAnimator!!.resume()
            } else {
                mAnimator!!.start()
            }
        } else {
            initAnimator()
        }
    }

    override fun onTouchEvent(event: MotionEvent?): Boolean {
        if (event?.action == MotionEvent.ACTION_DOWN){
            start()
        }
        return true
    }

    /**
     * dp 转 px
     * */
    private fun dp2px(dpValue: Int): Float {
        val scale = context.resources.displayMetrics.density
        return (dpValue * scale + 0.5f)
    }
}

这里有两个需要提到的点

提取自定义属性

第一步在values所对应的包名下新建attrsxml文件
第二步声明自己想要的属性
第三步在构造方法中提取属性,也可以单独写一个方法来提取属性,我这里就是
最后在xml文件里引用自定义的属性

atrrsxml文件

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="ProgressView">
        <attr name="bg_color" format="color|integer"/>
        <attr name="pg_color" format="color|integer"/>
        <attr name="tv_color" format="color|integer"/>

        <attr name="lineWidth" format="float|dimension"/>
        <attr name="textSize" format="float|dimension"/>
    </declare-styleable>
</resources>

init方法,用来提取属性

/**
     * 提取自定义属性
     * */
    private fun init(context: Context, attrs: AttributeSet?) {

        val array = context.obtainStyledAttributes(attrs, R.styleable.DownloadView)
        mBgColor = array.getColor(R.styleable.DownloadView_bgColor, Color.GREEN)
        mPgColor = array.getColor(R.styleable.DownloadView_pgColor, Color.BLUE)
        mTxColor = array.getColor(R.styleable.DownloadView_txColor, Color.BLACK)
        mLineWidth = array.getDimension(R.styleable.DownloadView_lineWidth, dp2px(20))
        array.recycle()

    }

这里需要注意的是

        /**
        * 1.使用obtainStyledAttributes
        * resId: R.styleable.ProgressView 指定从哪个样式中解析
        * attrs: 从哪里解析 解析的数据在哪里
        * 返回值 TypedArray
        * 必须使用 recycle() 回收
        * */

最后在xml文件里使用属性,自定义属性一般以app:开头

        app:bgColor="@color/colorAccent"
        app:pgColor="@color/colorPrimary"
        app:txColor="@android:color/black"
        app:lineWidth="20dp"

那么我们如何绘制文本呢,具体来说就是调用canvas?.drawText方法
里面有4个参数,第一个参数是绘制的内容,中间两个参数表示绘制的起始坐标,第4个参数画笔,这里我们的x坐标取绘制文字的起始点的横坐标,也就是圆中心坐标cx减去一半文字宽度的距离mHalfWidth,y坐标取值文字高度的一半,这里求文字高度的方法不是唯一的,可以通过文字矩阵的高度的一半 - bottom的距离,这里通过将y坐标向下平移一半的ascent的距离得到,ascent的值为负,所有减去一半就是向下平移一半的距离cy - mFontMetricsInt.ascent / 2

//绘制显示的进度值
canvas?.drawText(mText,cx - mHalfWidth,cy - mFontMetricsInt.ascent / 2,mTxPaint)
上一篇下一篇

猜你喜欢

热点阅读