【自定义View】洋葱数学同款雷达图深刻解析-RadarView

开箱即用的源码地址

洋葱数学同款雷达图-RadarViewhtml

支持XML自定义属性:java

  • rv_webRadius:雷达网的半径(该属性决定了View的宽高)
  • rv_webMaxProgress:各属性表示的最大进度
  • rv_webLineColor:雷达网的颜色
  • rv_webLineWidth:雷达网的线宽
  • rv_textArrayedColor:各属性文字的颜色
  • rv_textArrayedFontPath:各属性文字和中心处名字的字体路径
  • rv_areaColor:中心链接区域的颜色
  • rv_areaBorderColor:中心链接区域的边框颜色
  • rv_textCenteredName:中心处的名字
  • rv_textCenteredColor:中心文字的颜色
  • rv_textCenteredFontPath:中心数字文字的字体路径
  • rv_animateTime:动画执行时间
  • rv_animateMode:动画模式
    • TIME:时间必定,动画执行时间为rv_animateTime
    • SPEED:速度必定,动画执行速度为rv_webMaxProgress➗rv_animateTime

支持代码设置数据源:git

  • setTextArray(textList: List<String>):设置各属性文字数组,元素个数不能小于3
  • setProgressList(progressList: List<Int>):设置各属性对应的进度数组,该数组元素默认都是0,元素个数必须与文字数组保持一致
  • setOldProgressList(oldProgressList: List<Int>):设置各属性执行动画前,对应的进度数组,该数组元素默认都是0,元素个数必须与文字数组保持一致

支持代码执行动画:github

  • doInvalidate():各个属性的动画一块儿执行
  • doInvalidate(index: Int, block: ((Int) -> Unit)? = null):指定某属性执行动画,可传入参数接收动画结束的回调

起源

近来我司产品侧在重构一项业务,连带UI也有变更,其中就涉及到了雷达图,因此也就有了此次封装的RadarView,其主要特色是:web

  1. 有丰富的自定义属性,可对雷达图外观进行设置
  2. 支持自由设置属性个数
  3. 支持两种动画模式(时间必定、速度必定)
  4. 支持指定某属性执行动画(从而知足UI稿的个性需求,见头图三)

头图三联是演示了该View的主要特色,而后结合局部UI稿,你们能够对比看下(还原度99%,✧(≖ ◡ ≖✿)嘿嘿嘿)。canvas

思考分析

NOTE数组

  1. 咱们把六角形抽象成N角形,后文统一使用N角形表示
  2. 咱们在绘制前会把坐标系原点移动到雷达图中心,后文统一使用原点表示

咱们先来思考下关键技术点:app

  • 绘制N角形雷达网
    • 绘制虚线
      • 虚线能够给Paint设置DashPathEffect实现
      • 以雷达图中心为原点,将坐标系每逆时针旋转360/N度,从原点向上绘制长度为雷达网半径的虚线
    • 绘制实线
      • 在绘制完一条虚线后,紧接着绘制实线
      • 一样以雷达图中心为原点,将坐标系每向上移动雷达网半径/4后,而且顺时针旋转360/N/2度(为何是这个值?你们可自行🤔下),此时的坐标系x轴恰好与对应的实线重合
      • 因此,直接从移动旋转后的新坐标系原点沿x轴绘制实线便可
      • 实线长度经过雷达网半径和相应角度的三角函数等能够算出来
  • 绘制N个角的属性文字
    • 以雷达图中心为原点,以12点方向的角为第一个角,能够知道它的顶点坐标(0,-半径)
    • 经过圆上一点绕圆心(坐标原点)顺时针旋转α弧度得出另外一点坐标的坐标公式,能够算出各角顶点的坐标(后面会推导该公式!)
    • 再结合文字与顶点的间距、Paint的setTextAlign()、以及微调绘制文字时的Y坐标就能够搞定啦
  • 绘制中心链接区域
    • 一样以12点方向属性(角)上的值的为第一个点,能够知道该点坐标为(0,-半径✖进度️)
    • 一样经过坐标公式能够算出各属性的进度值坐标
    • 使用Path链接各点构建出路径,而后绘制与描边就很好说啦
  • 绘制中心数字&名字
    • 就是很基本的绘制文字,但须要注意微调Y坐标,以提升与UI稿的还原度(细节!细节!细节!)
  • 添加动画效果
    • 动画的本质就是不断调整各属性值的坐标,而后重绘View
    • 实际代码咱们使用属性动画ValueAnimator能够搞定,也很简单

整理下思路框架:框架

  1. 定义、初始化属性
    1. 自定义属性部分
    2. 计算属性部分
    3. 绘制依赖的属性部分
    4. 定义设置属性的API
  2. 绘制N角形雷达网
  3. 绘制N个角的属性文字
  4. 绘制中心链接区域
  5. 绘制中心数字&名字
  6. 添加动画效果

技术点、思路理好了,按道理就要着手开始代码了,不过咱们先上道数学题热热身。less

热身数学题

请听题:根据9年义务教育所学,推导出经过圆上一点绕圆心(坐标原点)顺时针旋转α弧度得出另外一点坐标的坐标公式

设圆半径为r,圆上有一点坐标A(x_a,y_a),绕圆心顺时针旋转α弧度后获得坐标B(x_b,y_b),有公式以下:

x_b=x_a\cos\alpha-y_a\sin\alpha

y_b=y_a\cos\alpha+x_a\sin\alpha

推导过程以下:

数学上规定逆时针旋转为正方向

当一条射线从x轴的正方向(向右)开始逆时针方向旋转以后到了一个新位置(按顺序分别到达第1、2、3、四象限)得一个角.为了方便规定这个角是正角,因此逆时针方向也就相应规定为正方向了.

因此为了咱们在代码中以顺时针为正方向,咱们将-\alpha做为\alpha带入上述公式

封装公式

如今咱们就把上面的公式封装成工具代码,这可谓是一『利器』,往后咱们自定义View中也会常常用到!

首先,咱们要把360°的角度制(degree)转化为弧度制(radian),这样咱们在绘制时直接使用角度制会方便不少。

/** * 角度制转弧度制 */
private fun Float.degree2radian(): Float {
    return (this / 180f * PI).toFloat()
}

/** * 计算某角度的sin值 */
fun Float.degreeSin(): Float {
    return sin(this.degree2radian())
}

/** * 计算某角度的cos值 */
fun Float.degreeCos(): Float {
    return cos(this.degree2radian())
}
复制代码

而后,根据公式写代码便可获得咱们的『利器』。这里咱们须要外部传入PointF实例,而不是每次建立,以提高性能

/** * 计算一个点坐标,绕原点旋转必定角度后的坐标 */
fun PointF.degreePointF(outPointF: PointF, degree: Float) {
    outPointF.x = this.x * degree.degreeCos() - this.y * degree.degreeSin()
    outPointF.y = this.y * degree.degreeCos() + this.x * degree.degreeSin()
}
复制代码

绘制过程

1. 定义、初始化属性

1. 自定义属性部分

这一步比较容易,在attrs.xml中定义咱们的属性,在Layout中声明变量,并作初始化便可。这里咱们就只贴出声明变量的代码。

//********************************
//* 自定义属性部分
//********************************

/** * 雷达网图半径 */
private var mWebRadius: Float = 0f

/** * 雷达网图半径对应的最大进度 */
private var mWebMaxProgress: Int = 0

/** * 雷达网线颜色 */
@ColorInt
private var mWebLineColor: Int = 0

/** * 雷达网线宽度 */
private var mWebLineWidth: Float = 0f

/** * 雷达图各定点文字颜色 */
@ColorInt
private var mTextArrayedColor: Int = 0

/** * 雷达图文字数组字体路径 */
private var mTextArrayedFontPath: String? = null

/** * 雷达图中心链接区域颜色 */
@ColorInt
private var mAreaColor: Int = 0

/** * 雷达图中心链接区域边框颜色 */
@ColorInt
private var mAreaBorderColor: Int = 0

/** * 雷达图中心文字名称 */
private var mTextCenteredName: String = default_textCenteredName

/** * 雷达图中心文字颜色 */
@ColorInt
private var mTextCenteredColor: Int = 0

/** * 雷达图中心文字字体路径 */
private var mTextCenteredFontPath: String? = null

/** * 文字数组,且以该数组长度肯定雷达图是几边形 */
private var mTextArray: Array<String> by Delegates.notNull()

/** * 进度数组,与TextArray一一对应 */
private var mProgressArray: Array<Int> by Delegates.notNull()

/** * 执行动画前的进度数组,与TextArray一一对应 */
private var mOldProgressArray: Array<Int> by Delegates.notNull()

/** * 动画时间,为0表明没有动画 * NOTE: 若是是速度必定模式下,表明从雷达中心执行动画到顶点的时间 */
private var mAnimateTime: Long = 0L

/** * 动画模式,默认为时间必定模式 */
private var mAnimateMode: Int = default_animateMode
复制代码

2. 计算属性部分

所谓计算属性就是咱们要经过某自定义属性为基础计算得来的属性。举个🌰:各属性描述文字的字体大小,此处咱们使用雷达图半径✖UI稿的比例得来,其它属性也同理,代码以下:

//********************************
//* 计算属性部分
//********************************

/** * 垂直文本距离雷达主图的宽度 */
private var mVerticalSpaceWidth: Float by Delegates.notNull()

/** * 水平文本距离雷达主图的宽度 */
private var mHorizontalSpaceWidth: Float by Delegates.notNull()

/** * 文字数组中的字体大小 */
private var mTextArrayedSize: Float by Delegates.notNull()

/** * 文字数组设置字体大小后的文字宽度,取字数最多的 */
private var mTextArrayedWidth: Float by Delegates.notNull()

/** * 文字数组设置字体大小后的文字高度 */
private var mTextArrayedHeight: Float by Delegates.notNull()

/** * 该View的宽度 */
private var mWidth: Float by Delegates.notNull()

/** * 该View的高度 */
private var mHeight: Float by Delegates.notNull()
复制代码
/** * 初始化计算属性,基本的宽高、字体大小、间距等数据 * NOTE:以UI稿比例为准,根据[mWebRadius]来计算 */
private fun initCalculateAttributes() {
    //根据比例计算相应属性
    (mWebRadius / 100).let {
        mVerticalSpaceWidth = it * 8
        mHorizontalSpaceWidth = it * 10

        mTextArrayedSize = it * 12
    }

    //设置字体大小后,计算文字所占宽高
    mPaint.textSize = mTextArrayedSize
    mTextArray.maxBy { it.length }?.apply {
        mTextArrayedWidth = mPaint.measureText(this)
        mTextArrayedHeight = mPaint.fontSpacing
    }
    mPaint.utilReset()

    //动态计算出view的实际宽高
    mWidth = (mTextArrayedWidth + mHorizontalSpaceWidth + mWebRadius) * 2.1f
    mHeight = (mTextArrayedHeight + mVerticalSpaceWidth + mWebRadius) * 2.1f
}
复制代码

3. 绘制依赖的属性部分

绘制依赖的属性就是咱们在实际绘制时须要使用的全局属性,咱们会提早初始化他们,这样就能够复用,避免在draw()方法中每次都new对象开辟内存。

假如咱们在draw()方法中new了Paint对象,AS也会提示警告咱们,截图和翻译以下⚠️:

Avoid object allocations during draw/layout operations (preallocate and reuse instead) less... (⌘F1) 
避免在绘制和布局期间建立对象,采用提早建立和能复用的方式代替

Inspection info:You should avoid allocating objects during a drawing or layout operation. 
These are called frequently, so a smooth UI can be interrupted by garbage collection pauses caused by the object allocations. 
你应该避免在绘制和布局期间建立对象。他们会被频繁执行,所以,平滑的UI会被对象分配致使的垃圾收集暂停中断。

The way this is generally handled is to allocate the needed objects up front and to reuse them for each drawing operation.  
通常的处理方式就是提早初始化须要的对象并在每次绘制操做时复用它们。

Some methods allocate memory on your behalf (such as Bitmap.create), and these should be handled in the same way.
有些方法替你分配了内存(好比Bitmap.create),这些方法应该采用相同的处理方式。

Issue id: DrawAllocation
复制代码

因此咱们把画笔、Path、存放坐标的数组等全局声明并初始化,代码以下:

//********************************
//* 绘制使用的属性部分
//********************************

/** * 全局画笔 */
private val mPaint = createPaint()
private val mHelperPaint = createPaint()

/** * 全局路径 */
private val mPath = Path()

/** * 雷达网虚线效果 */
private var mDashPathEffect: DashPathEffect by Delegates.notNull()

/** * 雷达主图各顶点的坐标数组 */
private var mPointArray: Array<PointF> by Delegates.notNull()

/** * 文字数组各文字的坐标数组 */
private var mTextArrayedPointArray: Array<PointF> by Delegates.notNull()

/** * 文字数组各进度的坐标数组 */
private var mProgressPointArray: Array<PointF> by Delegates.notNull()

/** * 做转换使用的临时变量 */
private var mTempPointF: PointF = PointF()

/** * 雷达图文字数组字体 */
private var mTextArrayedTypeface: Typeface? = null

/** * 雷达图中心文字字体 */
private var mTextCenteredTypeface: Typeface? = null

/** * 动画处理器数组 */
private var mAnimatorArray: Array<ValueAnimator?> by Delegates.notNull()

/** * 各雷达属性动画的时间数组 */
private var mAnimatorTimeArray: Array<Long> by Delegates.notNull()
复制代码
/** * 初始化绘制相关的属性 */
private fun initDrawAttributes() {
    context.dpf2pxf(2f).run {
        mDashPathEffect = DashPathEffect(floatArrayOf(this, this), this)
    }

    mPointArray = Array(mTextArray.size) { PointF(0f, 0f) }
    mTextArrayedPointArray = Array(mTextArray.size) { PointF(0f, 0f) }
    mProgressPointArray = Array(mTextArray.size) { PointF(0f, 0f) }
    if (mTextArrayedFontPath != null) {
        mTextArrayedTypeface = Typeface.createFromAsset(context.assets, mTextArrayedFontPath)
    }
    if (mTextCenteredFontPath != null) {
        mTextCenteredTypeface = Typeface.createFromAsset(context.assets, mTextCenteredFontPath)
    }
}
复制代码

4. 定义设置属性的API

这里咱们分别暴露设置文字数组、属性进度数组、执行动画前的进度数组三个API

//********************************
//* 设置数据属性部分
//********************************

fun setTextArray(textList: List<String>) {
    this.mTextArray = textList.toTypedArray()
    this.mProgressArray = Array(mTextArray.size) { 0 }
    this.mOldProgressArray = Array(mTextArray.size) { 0 }
    initView()
}

fun setProgressList(progressList: List<Int>) {
    this.mProgressArray = progressList.toTypedArray()
    initView()
}

/** * 设置执行动画前的进度 */
fun setOldProgressList(oldProgressList: List<Int>) {
    this.mOldProgressArray = oldProgressList.toTypedArray()
    initView()
}
复制代码

2. 绘制N角形雷达网

咱们在绘制前总体将坐标系原点移动到View的中心处,这样很便于以后的绘制。以下:

override fun onDraw(canvas: Canvas?) {
    super.onDraw(canvas)
    if (canvas == null) return
    canvas.helpGreenCurtain(debug)

    canvas.save()
    canvas.translate(mWidth / 2, mHeight / 2)

    //此处作数据校验
    if (checkIllegalData(canvas)) {
        //绘制网状图形
        drawWeb(canvas)

        //绘制文字数组
        drawTextArray(canvas)

        //绘制链接区域
        drawConnectionArea(canvas)

        //绘制中心的文字
        drawCenterText(canvas)
    }

    canvas.restore()
}
复制代码

而后就是绘制N角形网了,代码就是咱们先前思路的具体体现。

/** * 绘制网状图形 */
private fun drawWeb(canvas: Canvas) {
    canvas.save()

    val rDeg = 360f / mTextArray.size

    mTextArray.forEachIndexed { index, _ ->
        //绘制虚线,每次都将坐标系逆时针旋转(rDeg * index)度
        canvas.save()
        canvas.rotate(-rDeg * index)

        mPaint.pathEffect = mDashPathEffect
        mPaint.color = mWebLineColor
        mPaint.strokeWidth = mWebLineWidth
        canvas.drawLine(0f, 0f, 0f, -mWebRadius, mPaint)
        mPaint.utilReset()

        //用三角函数计算出最长的网的边
        val lineW = mWebRadius * (rDeg / 2).degreeSin() * 2
        for (i in 1..4) {
            //绘制网的边,每次将坐标系向上移动(mWebRadius / 4f)*i,
            //且顺时针旋转(rDeg / 2)度,而后绘制长度为(lineW / 4f * i)的实线
            canvas.save()
            canvas.translate(0f, -mWebRadius / 4f * i)
            canvas.rotate(rDeg / 2)

            mPaint.color = mWebLineColor
            mPaint.strokeWidth = mWebLineWidth
            canvas.drawLine(0f, 0f, lineW / 4f * i, 0f, mPaint)
            mPaint.utilReset()

            canvas.restore()
        }

        canvas.restore()
    }

    canvas.restore()
}
复制代码

3. 绘制N个角的属性文字

这一步除了用代码实现咱们先前的思路外,也有关于文字位置的总体处理与微调处理,这样一顿猛如虎的操做以后咱们的还原度才能更上一层楼。

/** * 绘制文字数组 */
private fun drawTextArray(canvas: Canvas) {
    canvas.save()

    val rDeg = 360f / mTextArray.size

    //先计算出雷达图各个顶点的坐标
    mPointArray.forEachIndexed { index, pointF ->
        if (index == 0) {
            pointF.x = 0f
            pointF.y = -mWebRadius
        } else {
            mPointArray[index - 1].degreePointF(pointF, rDeg)
        }

        //绘制辅助圆点
        if (debug) {
            mHelperPaint.color = Color.RED
            canvas.drawCircle(pointF.x, pointF.y, 5f, mHelperPaint)
            mHelperPaint.utilReset()
        }
    }

    //基于各顶点坐标,计算出文字坐标并绘制文字
    mTextArrayedPointArray.mapIndexed { index, pointF ->
        pointF.x = mPointArray[index].x
        pointF.y = mPointArray[index].y
        return@mapIndexed pointF
    }.forEachIndexed { index, pointF ->
        mPaint.color = mTextArrayedColor
        mPaint.textSize = mTextArrayedSize
        if (mTextArrayedTypeface != null) {
            mPaint.typeface = mTextArrayedTypeface
        }

        when {
            index == 0 -> {
                //微调修正文字y坐标
                pointF.y += mPaint.getBottomedY()

                pointF.y = -(pointF.y.absoluteValue + mVerticalSpaceWidth)
                mPaint.textAlign = Paint.Align.CENTER
            }
            mTextArray.size / 2f == index.toFloat() -> {
                //微调修正文字y坐标
                pointF.y += mPaint.getToppedY()

                pointF.y = (pointF.y.absoluteValue + mVerticalSpaceWidth)
                mPaint.textAlign = Paint.Align.CENTER
            }
            index < mTextArray.size / 2f -> {
                //微调修正文字y坐标
                if (pointF.y < 0) {
                    pointF.y += mPaint.getBottomedY()
                } else {
                    pointF.y += mPaint.getToppedY()
                }

                pointF.x = (pointF.x.absoluteValue + mHorizontalSpaceWidth)
                mPaint.textAlign = Paint.Align.LEFT
            }
            index > mTextArray.size / 2f -> {
                //微调修正文字y坐标
                if (pointF.y < 0) {
                    pointF.y += mPaint.getBottomedY()
                } else {
                    pointF.y += mPaint.getToppedY()
                }

                pointF.x = -(pointF.x.absoluteValue + mHorizontalSpaceWidth)
                mPaint.textAlign = Paint.Align.RIGHT
            }
        }

        canvas.drawText(mTextArray[index], pointF.x, pointF.y, mPaint)
        mPaint.utilReset()
    }

    canvas.restore()
}
复制代码

4. 绘制中心链接区域

这一步也算是得心应手的操做了,根据各属性进度以及坐标公式,得出各点坐标,而后构建Path,绘制便可。

/** * 绘制雷达链接区域 */
private fun drawConnectionArea(canvas: Canvas) {
    canvas.save()

    val rDeg = 360f / mTextArray.size

    //根据雷达图第一个坐标最为基坐标进行相应计算,算出各个进度坐标
    val bPoint = mPointArray.first()
    mProgressPointArray.forEachIndexed { index, pointF ->
        val progress = mProgressArray[index] / mWebMaxProgress.toFloat()
        pointF.x = bPoint.x * progress
        pointF.y = bPoint.y * progress
        pointF.degreePointF(mTempPointF, rDeg * index)

        pointF.x = mTempPointF.x
        pointF.y = mTempPointF.y

        //绘制辅助圆点
        if (debug) {
            mHelperPaint.color = Color.BLACK
            canvas.drawCircle(pointF.x, pointF.y, 5f, mHelperPaint)
            mHelperPaint.utilReset()
        }

        //使用路径链接各个点
        if (index == 0) {
            mPath.moveTo(pointF.x, pointF.y)
        } else {
            mPath.lineTo(pointF.x, pointF.y)
        }
        if (index == mProgressPointArray.lastIndex) {
            mPath.close()
        }
    }
    //绘制区域路径
    mPaint.color = mAreaColor
    canvas.drawPath(mPath, mPaint)
    mPaint.utilReset()

    //绘制区域路径的边框
    mPaint.color = mAreaBorderColor
    mPaint.style = Paint.Style.STROKE
    mPaint.strokeWidth = mWebLineWidth
    mPaint.strokeJoin = Paint.Join.ROUND
    canvas.drawPath(mPath, mPaint)
    mPath.reset()
    mPaint.utilReset()

    canvas.restore()
}
复制代码

5. 绘制中心数字&名字

这一步也是常规操做,惟一需注意的也是对文字位置的微调,提升还原度

/** * 绘制中心文字 */
private fun drawCenterText(canvas: Canvas) {
    canvas.save()

    //绘制数字
    mPaint.color = mTextCenteredColor
    mPaint.textSize = mTextArrayedSize / 12 * 20
    mPaint.textAlign = Paint.Align.CENTER
    if (mTextCenteredTypeface != null) {
        mPaint.typeface = mTextCenteredTypeface
    }
    //将坐标系向下微调移动
    canvas.translate(0f, mPaint.fontMetrics.bottom)
    var sum = mProgressArray.sum().toString()
    //添加辅助文本
    if (debug) {
        sum += "ajk你好"
    }
    canvas.drawText(sum, 0f, mPaint.getBottomedY(), mPaint)
    mPaint.utilReset()

    //绘制名字
    mPaint.color = mTextCenteredColor
    mPaint.textSize = mTextArrayedSize / 12 * 10
    mPaint.textAlign = Paint.Align.CENTER
    if (mTextArrayedTypeface != null) {
        mPaint.typeface = mTextArrayedTypeface
    }
    canvas.drawText(mTextCenteredName, 0f, mPaint.getToppedY(), mPaint)
    mPaint.utilReset()

    //绘制辅助线
    if (debug) {
        mHelperPaint.color = Color.RED
        mHelperPaint.strokeWidth = context.dpf2pxf(1f)
        canvas.drawLine(-mWidth, 0f, mWidth, 0f, mHelperPaint)
        mHelperPaint.utilReset()
    }

    canvas.restore()
}
复制代码

6. 添加动画效果

动画的本质就是不断调整各属性值的坐标,而后重绘View

在咱们的代码中各属性的坐标又是根据属性进度得来的,因此不断调整各属性进度就能产生动画。

在初始化操做时要提早初始化动画处理器

/** * 初始化动画处理器 */
private fun initAnimator() {
    mAnimatorArray = Array(mTextArray.size) { null }
    mAnimatorTimeArray = Array(mTextArray.size) { 0L }
    mAnimatorArray.forEachIndexed { index, _ ->
        val sv = mOldProgressArray[index].toFloat()
        val ev = mProgressArray[index].toFloat()
        mAnimatorArray[index] = if (sv == ev) null else ValueAnimator.ofFloat(sv, ev)

        if (mAnimateMode == ANIMATE_MODE_TIME) {
            mAnimatorTimeArray[index] = mAnimateTime
        } else {
            //根据最大进度和动画时间算出恒定速度
            val v = mWebMaxProgress.toFloat() / mAnimateTime
            mAnimatorTimeArray[index] = if (sv == ev) 0L else ((ev - sv) / v).toLong()
        }
    }
}
复制代码
/** * 各属性动画一块儿执行 */
fun doInvalidate() {
    mAnimatorArray.forEachIndexed { index, _ ->
        doInvalidate(index)
    }
}

/** * 指定某属性开始动画 */
fun doInvalidate(index: Int, block: ((Int) -> Unit)? = null) {
    if (index >= 0 && index < mAnimatorArray.size) {
        val valueAnimator = mAnimatorArray[index]
        val at = mAnimatorTimeArray[index]
        if (valueAnimator != null && at > 0) {
            valueAnimator.duration = at
            valueAnimator.removeAllUpdateListeners()
            valueAnimator.addUpdateListener {
                val av = (it.animatedValue as Float)
                mProgressArray[index] = av.toInt()

                invalidate()
            }
            //设置动画结束监听
            if (block != null) {
                valueAnimator.removeAllListeners()
                valueAnimator.addListener(object : Animator.AnimatorListener {
                    override fun onAnimationRepeat(animation: Animator?) {}

                    override fun onAnimationEnd(animation: Animator?) {
                        block.invoke(index)
                    }

                    override fun onAnimationCancel(animation: Animator?) {}

                    override fun onAnimationStart(animation: Animator?) {}
                })
            }
            valueAnimator.start()
        } else {
            block?.invoke(index)
        }
    }
}
复制代码

文末

我的能力有限,若有不正之处欢迎你们批评指出,我会虚心接受并第一时间修改,以不误导你们

个人其它文章

相关文章
相关标签/搜索