您当前的位置: 首页 >  华为

蓝不蓝编程

暂无认证

  • 0浏览

    0关注

    706博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

安卓实现动态环状百分比动画(仿华为天气预报)

蓝不蓝编程 发布时间:2019-09-04 15:43:10 ,浏览量:0

实现效果

在这里插入图片描述

实现方案

自定义view

主要代码
  1. CircleProgress.kt
class CircleProgress(context: Context, attrs: AttributeSet) : View(context, attrs) {

    private val ANTI_ALIAS = true
    private val DEFAULT_SIZE = 150
    private val DEFAULT_START_ANGLE = 270
    private val DEFAULT_SWEEP_ANGLE = 360
    private val DEFAULT_ANIM_TIME = 1500
    private val DEFAULT_MAX_VALUE = 100
    private val DEFAULT_VALUE = 50
    private val DEFAULT_HINT_SIZE = 25
    private val DEFAULT_UNIT_SIZE = 30
    private val DEFAULT_VALUE_SIZE = 40
    private val DEFAULT_TEXT_SIZE = 30
    private val DEFAULT_ARC_WIDTH = 15

    //默认大小
    private var mDefaultSize: Int = 0
    //是否开启抗锯齿
    var isAntiAlias: Boolean = false
    //绘制提示
    private lateinit var mHintPaint: TextPaint
    var hint: CharSequence? = null
    private var mHintColor: Int = 0
    private var mHintSize: Float = 0.toFloat()
    private var mHintOffset: Float = 0.toFloat()

    //绘制单位
    private lateinit var mUnitPaint: TextPaint
    var unit: CharSequence? = null
    private var mUnitColor: Int = 0
    private var mUnitSize: Float = 0.toFloat()
    private var mUnitOffset: Float = 0.toFloat()

    //绘制数值
    private lateinit var mValuePaint: TextPaint
    private var mValue: Float = 0.toFloat()
    private var mValueOffset: Float = 0.toFloat()
    private var mPrecision: Int = 0
    private lateinit var mPrecisionFormat: String
    private var mValueColor: Int = 0
    private var mValueSize: Float = 0.toFloat()

    //绘制最大值和最小值
    private lateinit var mMaxAndMinPaint: TextPaint
    var maxValue: Int = 0
    private var mMaxAndMinXOffset: Float = 0.toFloat()
    private var mMaxAndMinYOffset: Float = 0.toFloat()
    private var mMaxXLocate: Float = 0.toFloat()
    private var mMaxYLocate: Float = 0.toFloat()
    private var mMinXLocate: Float = 0.toFloat()
    private var mMinYLocate: Float = 0.toFloat()
    private var mMaxAndMinColor: Int = 0
    private var mMaxAndMinSize: Float = 0.toFloat()

    //绘制圆弧
    private lateinit var mArcPaint: Paint
    private var mArcWidth: Float = 0.toFloat()
    private var mStartAngle: Float = 0.toFloat()
    private var mSweepAngle: Float = 0.toFloat()
    private lateinit var mRectF: RectF
    //渐变的角度是360度,如果只显示270度,会缺失一段颜色
    private var mSweepGradient: SweepGradient? = null
    private var mGradientColors = intArrayOf(Color.GREEN, Color.YELLOW, Color.WHITE)
    //当前进度
    private var mPercent: Float = 0.toFloat()
    //动画时间
    var animTime: Long = 0
    //属性动画
    private lateinit var mAnimator: ValueAnimator

    //绘制背景圆弧
    private lateinit var mBgArcPaint: Paint
    private var mBgArcColor: Int = 0
    private var mBgArcWidth: Float = 0.toFloat()

    //圆心坐标,半径
    private lateinit var mCenterPoint: Point
    private var mRadius: Float = 0.toFloat()
    private var mTextOffsetPercentInRadius: Float = 0.toFloat()

    /**
     * 设置值
     *
     * @param value
     */
    var value: Float
        get() = mValue
        set(value) {
            var value = value
            if (value > maxValue) {
                value = maxValue.toFloat()
            }
            mValue = value

        }

    var precision: Int
        get() = mPrecision
        set(mPrecision) {
            this.mPrecision = mPrecision
            mPrecisionFormat = getPrecisionFormat(mPrecision)
        }

    init {
        init(attrs)
    }

    private fun init(attrs: AttributeSet) {
        mDefaultSize = dipToPx(DEFAULT_SIZE.toFloat())
        mAnimator = ValueAnimator()
        mRectF = RectF()
        mCenterPoint = Point()
        initAttrs(attrs)
        initPaint()
        value = mValue
    }

    private fun initAttrs(attrs: AttributeSet) {
        val typedArray = context.obtainStyledAttributes(attrs, R.styleable.CircleProgressBar)
        isAntiAlias =
            typedArray.getBoolean(R.styleable.CircleProgressBar_antiAlias, ANTI_ALIAS)
        hint = typedArray.getString(R.styleable.CircleProgressBar_hint)
        mHintColor = typedArray.getColor(R.styleable.CircleProgressBar_hintColor, Color.BLACK)
        mHintSize = typedArray.getDimension(
            R.styleable.CircleProgressBar_hintSize, DEFAULT_HINT_SIZE.toFloat()
        )

        mValue = typedArray.getFloat(
            R.styleable.CircleProgressBar_value, DEFAULT_VALUE.toFloat()
        )
        mPrecision = typedArray.getInt(R.styleable.CircleProgressBar_precision, 0)
        mPrecisionFormat = getPrecisionFormat(mPrecision)
        mValueColor = typedArray.getColor(R.styleable.CircleProgressBar_valueColor, Color.BLACK)
        mValueSize = typedArray.getDimension(
            R.styleable.CircleProgressBar_valueSize, DEFAULT_VALUE_SIZE.toFloat()
        )

        maxValue =
            typedArray.getInt(R.styleable.CircleProgressBar_maxValue, DEFAULT_MAX_VALUE)
        mMaxAndMinColor =
            typedArray.getColor(R.styleable.CircleProgressBar_maxAndMinValueColor, Color.GRAY)
        mMaxAndMinSize = typedArray.getDimension(
            R.styleable.CircleProgressBar_maxAndMinValueSize, DEFAULT_TEXT_SIZE.toFloat()
        )
        mMaxAndMinXOffset =
            typedArray.getFloat(R.styleable.CircleProgressBar_maxAndMinXOffsetPercent, 0.70f)
        mMaxAndMinYOffset =
            typedArray.getFloat(R.styleable.CircleProgressBar_maxAndMinYOffsetPercent, 0.90f)

        unit = typedArray.getString(R.styleable.CircleProgressBar_unit)
        mUnitColor = typedArray.getColor(R.styleable.CircleProgressBar_unitColor, Color.BLACK)
        mUnitSize = typedArray.getDimension(
            R.styleable.CircleProgressBar_unitSize, DEFAULT_UNIT_SIZE.toFloat()
        )

        mArcWidth = typedArray.getDimension(
            R.styleable.CircleProgressBar_arcWidth, DEFAULT_ARC_WIDTH.toFloat()
        )
        mStartAngle = typedArray.getFloat(
            R.styleable.CircleProgressBar_startAngle, DEFAULT_START_ANGLE.toFloat()
        )
        mSweepAngle = typedArray.getFloat(
            R.styleable.CircleProgressBar_sweepAngle, DEFAULT_SWEEP_ANGLE.toFloat()
        ) + 2

        mBgArcColor = typedArray.getColor(R.styleable.CircleProgressBar_bgArcColor, Color.WHITE)
        mBgArcWidth = typedArray.getDimension(
            R.styleable.CircleProgressBar_bgArcWidth, DEFAULT_ARC_WIDTH.toFloat()
        )
        mTextOffsetPercentInRadius =
            typedArray.getFloat(R.styleable.CircleProgressBar_textOffsetPercentInRadius, 0.43f)

        animTime =
            typedArray.getInt(R.styleable.CircleProgressBar_animTime, DEFAULT_ANIM_TIME)
                .toLong()

        val gradientArcColors = typedArray.getResourceId(R.styleable.CircleProgressBar_arcColors, 0)
        if (gradientArcColors != 0) {
            val gradientColors = resources.getIntArray(gradientArcColors)
            /**
             * 如果渐变色数组为0,则以单色读取;
             * 如果只有一种颜色,默认设置为两种相同颜色.
             */
            when {
                gradientColors.isEmpty() -> {
                    val color = ContextCompat.getColor(context, gradientArcColors)
                    mGradientColors = IntArray(2)
                    mGradientColors[0] = color
                    mGradientColors[1] = color
                }
                gradientColors.size == 1 -> {
                    mGradientColors = IntArray(2)
                    mGradientColors[0] = gradientColors[0]
                    mGradientColors[1] = gradientColors[0]
                }
                else -> mGradientColors = gradientColors
            }
        }

        typedArray.recycle()
    }


    private fun initPaint() {
        mHintPaint = TextPaint()
        //设置抗锯齿
        mHintPaint.isAntiAlias = isAntiAlias
        //绘制文字大小
        mHintPaint.textSize = mHintSize
        //设置画笔颜色
        mHintPaint.color = mHintColor
        //从中间向两边绘制,不用再次计算文字
        mHintPaint.textAlign = Paint.Align.CENTER

        mValuePaint = TextPaint()
        mValuePaint.isAntiAlias = isAntiAlias
        mValuePaint.textSize = mValueSize
        mValuePaint.color = mValueColor
        // 设置Typeface对象,即字体风格,包括粗体,斜体以及衬线体,非衬线体等
        mValuePaint.typeface = Typeface.DEFAULT_BOLD
        mValuePaint.textAlign = Paint.Align.CENTER

        mUnitPaint = TextPaint()
        mUnitPaint.isAntiAlias = isAntiAlias
        mUnitPaint.textSize = mUnitSize
        mUnitPaint.color = mUnitColor
        mUnitPaint.textAlign = Paint.Align.CENTER

        mArcPaint = Paint()
        mArcPaint.isAntiAlias = isAntiAlias
        //设置画笔样式,FILL,FILL_OR_STROKE,STROKE
        mArcPaint.style = Paint.Style.STROKE
        //设置画笔粗细
        mArcPaint.strokeWidth = mArcWidth
        //当画笔样式为STROKE 或 FILL_OR_STROKE ,设置笔刷图形样式
        mArcPaint.strokeCap = Paint.Cap.ROUND

        mMaxAndMinPaint = TextPaint()
        mMaxAndMinPaint.color = mMaxAndMinColor
        mMaxAndMinPaint.isAntiAlias = isAntiAlias
        mMaxAndMinPaint.textSize = mMaxAndMinSize
        mMaxAndMinPaint.textAlign = Paint.Align.CENTER

        mBgArcPaint = Paint()
        mBgArcPaint.isAntiAlias = isAntiAlias
        mBgArcPaint.color = mBgArcColor
        mBgArcPaint.style = Paint.Style.STROKE
        mBgArcPaint.strokeWidth = mBgArcWidth
        mBgArcPaint.strokeCap = Paint.Cap.ROUND
    }


    fun startAnimation() {
        mAnimator = ValueAnimator.ofFloat(0f, mValue / maxValue.toFloat())
        mAnimator.duration = (2000 + 20 * mValue).toLong()
        mAnimator.addUpdateListener { animation ->
            mPercent = animation.animatedValue as Float
            invalidate()
        }
        mAnimator.start()
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        setMeasuredDimension(
            internalMeasure(widthMeasureSpec, mDefaultSize),
            internalMeasure(heightMeasureSpec, mDefaultSize)
        )

    }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        //求圆弧和圆弧背景的最大宽度
        val maxArcWidth = mArcWidth.coerceAtLeast(mBgArcWidth)
        //求最小值作为实际值
        val minSize =
            (w - paddingLeft - paddingRight - 2 * maxArcWidth.toInt()).coerceAtMost(w - paddingTop - paddingBottom - 2 * maxArcWidth.toInt())
        mRadius = (minSize / 2).toFloat()
        //获取圆的参数
        mCenterPoint.x = w / 2
        mCenterPoint.y = h / 2
        //绘制圆弧边界,画圆弧的大小
        mRectF.left =
            mCenterPoint.x.toFloat() - mRadius - maxArcWidth / 2//?不除以2的话会有一半圆弧跑出去,recf指定的是外边界圆弧的中点吧
        mRectF.top = mCenterPoint.y.toFloat() - mRadius - maxArcWidth / 2
        mRectF.right = mCenterPoint.x.toFloat() + mRadius + maxArcWidth / 2
        mRectF.bottom = mCenterPoint.y.toFloat() + mRadius + maxArcWidth / 2
        //计算文字的baseline
        //由于文字的baseline,descent,ascent等属性只与textSize和typeface 有关,所以此时可以直接计算
        //若value,hint,unit 由同一个画笔绘制或者需要动态设置文字的大小,则需要在每次更新后再次绘制
        mValueOffset = mCenterPoint.y + getBaselineOffsetFromY(mValuePaint) / 2
        mHintOffset = mCenterPoint.y - mRadius * mTextOffsetPercentInRadius
        mUnitOffset =
            mCenterPoint.y.toFloat() + mRadius * mTextOffsetPercentInRadius + getBaselineOffsetFromY(
                mUnitPaint
            )
        mMaxXLocate = mCenterPoint.x + mRadius * mMaxAndMinXOffset
        mMaxYLocate =
            mCenterPoint.y.toFloat() + mRadius * mMaxAndMinYOffset + getBaselineOffsetFromY(
                mMaxAndMinPaint
            )
        mMinXLocate = mCenterPoint.x - mRadius * mMaxAndMinXOffset
        mMinYLocate =
            mCenterPoint.y.toFloat() + mRadius * mMaxAndMinYOffset + getBaselineOffsetFromY(
                mMaxAndMinPaint
            )

        updateArcPaint()
    }


    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        drawText(canvas)
        drawArc(canvas)
    }

    private fun drawText(canvas: Canvas) {
        //计算文字宽度,由于Paint已设置为设置为居中绘制,故此处不需要重新计算
        canvas.drawText(
            String.format(mPrecisionFormat, mValue),
            mCenterPoint.x.toFloat(),
            mValueOffset,
            mValuePaint
        )
        canvas.drawText(maxValue.toString() + "", mMaxXLocate, mMaxYLocate, mMaxAndMinPaint)
        canvas.drawText("0", mMinXLocate, mMinYLocate, mMaxAndMinPaint)
        if (hint != null) {
            canvas.drawText(
                hint.toString(),
                mCenterPoint.x.toFloat(),
                mHintOffset,
                mHintPaint
            )
        }
        if (unit != null) {
            canvas.drawText(
                unit.toString(),
                mCenterPoint.x.toFloat(),
                mUnitOffset,
                mUnitPaint
            )
        }

    }

    private fun drawArc(canvas: Canvas) {
        //绘制圆弧背景
        //从进度圆弧结束的地方重新开始绘制,优化性能
        canvas.save()
        val currentAngle = mSweepAngle * mPercent
        canvas.rotate(
            mStartAngle,
            mCenterPoint.x.toFloat(),
            mCenterPoint.y.toFloat()
        )//旋转,以stratAngle 为起点
        canvas.drawArc(
            mRectF,
            currentAngle,
            mSweepAngle - currentAngle,
            false,
            mBgArcPaint
        )//这里最好画个图理解一下
        //第二个参数:startAngel 起始角度,第三个参数:圆弧度数,
        //3点钟方向为0度,顺时针递增,startAngle超过取360 或小于0 与360 取余
        //useCenter 如果为true 时 ,绘制圆弧将圆心包含,通常用来绘制扇形
        canvas.drawArc(mRectF, 0f, currentAngle, false, mArcPaint)
        canvas.restore()
    }

    /**
     * 更新圆弧画笔
     */
    private fun updateArcPaint() {
        //设置渐变
        //        int[] mGradientColors = {Color.GREEN,Color.YELLOW,Color.RED};
        mSweepGradient = SweepGradient(
            mCenterPoint.x.toFloat(),
            mCenterPoint.y.toFloat(),
            mGradientColors,
            null
        )
        mArcPaint.shader = mSweepGradient
    }

    private fun getBaselineOffsetFromY(paint: Paint): Float {
        return measureTextHeight(paint) / 2
    }

    fun setmGradientColors(mGradientColors: IntArray) {
        this.mGradientColors = mGradientColors
        updateArcPaint()
    }

    fun getmGradientColors(): IntArray {
        return mGradientColors
    }


    /**
     * 测量 view
     * @param measureSpec
     * @param defaultSize View的默认大小
     * @return
     */
    private fun internalMeasure(measureSpec: Int, defaultSize: Int): Int {
        var result = defaultSize
        val specMode = MeasureSpec.getMode(measureSpec)
        val specSize = MeasureSpec.getSize(measureSpec)
        if (specMode == MeasureSpec.EXACTLY) {
            result = specSize
        } else if (specMode == MeasureSpec.AT_MOST) {
            result = result.coerceAtMost(specSize)
        }
        return result
    }

    /**
     * dp 转 px
     * @param context
     * @param dip
     * @return
     */
    private fun dipToPx(dip: Float): Int {
        val density = context.resources.displayMetrics.density
        return (dip * density + 0.5f * if (dip >= 0) 1 else -1).toInt()
    }


    /**
     * 获取数值精度格式化字符串
     * @param precision
     * @return
     */
    private fun getPrecisionFormat(precision: Int): String {
        return "%." + precision + "f"
    }


    /**
     * 测量文字高度
     */
    private fun measureTextHeight(paint: Paint): Float {
        val fontMetrics = paint.fontMetrics
        return abs(fontMetrics.ascent) + fontMetrics.descent
    }

}
  1. 调用代码
  1. 布局文件中添加

  1. 启动动画
circleProgress.startAnimation()
lib库集成方法
  1. 在工程根目录下build.gradle文件中增加jitpack仓库地址:
allprojects {
	repositories {
		...
		maven { url 'https://jitpack.io' }
	}
}
  1. 在app工程下build.gradle文件中增加依赖:
dependencies {
    implementation 'com.github.cxyzy1:circleprogress:1.0.1'
}
源代码

https://github.com/cxyzy1/circleProgress.git

关注
打赏
1639405877
查看更多评论
立即登录/注册

微信扫码登录

0.0829s