美文网首页新收藏
Android 用Kotlin画一个圆弧计步器

Android 用Kotlin画一个圆弧计步器

作者: as_pixar | 来源:发表于2021-10-29 09:14 被阅读0次

    生活在失败的恐惧之中,永远也无法激发潜能        —— 海军四星上将

    William H. McRaven

    圆弧跑步器.gif

    看完效果后,我们先定义控件的样式

    <!-- 自定义View的名字 StepView -->
            <!-- name 属性名称  format 格式
            string 文字    color 颜色    dimension 字体大小
            integer 数字   reference 资源或者颜色
            -->
    <declare-styleable name="StepView">
            <attr name="borderWidth" format="dimension" />
            <attr name="outColor" format="color" />
            <attr name="innerColor" format="color" />
            <attr name="unit" format="string" />
            <attr name="currentStep" format="integer" />
            <attr name="maxStep" format="integer" />
        </declare-styleable>
    

    接下来我们自定义一个StepView(记步的View)

    package cn.wwj.customview.widget
    
    import android.animation.ValueAnimator
    import android.content.Context
    import android.graphics.Canvas
    import android.graphics.Paint
    import android.graphics.Rect
    import android.graphics.RectF
    import android.util.AttributeSet
    import android.util.Log
    import android.util.TypedValue
    import android.view.animation.AccelerateInterpolator
    import androidx.appcompat.widget.AppCompatTextView
    import cn.wwj.customview.R
    
    class StepView : AppCompatTextView {
    
        /**
         * 当前走了多少步
         */
        private var mCurrentStep: Int = 0
    
        /**
         * 最大走多少步,比如两万步 20000步
         * 默认100步,有个成语50步笑100步
         */
        private var mMaxStep: Int = 100
    
        /**
         * 单位:步 %等
         */
        private var mUnit: String?
    
        /**
         * 设置圆弧的边框线宽度
         */
        private var mBorderWidth: Float = dp2px(6F)
    
        /**
         * 设置外部圆弧的颜色
         */
        private var mOuterColor: Int = resources.getColor(android.R.color.holo_blue_light)
    
        /**
         * 设置内部圆弧的颜色
         */
        private var mInnerColor: Int = resources.getColor(android.R.color.holo_red_light)
    
        /**
         * 圆弧画笔
         */
        private var mArcPaint: Paint = Paint()
    
        /**
         * 文本画笔,用于绘画走了多少步
         */
        private var mTextPaint: Paint = Paint()
    
    
        /**
         * 日志过滤标签
         */
        private val TAG = javaClass.simpleName
    
        /**
         * 圆弧起始角度
         */
        private var mStartAngle = 135F
    
        /**
         * 圆弧从起始角度开始,扫描过的角度
         */
        private var mSweepAngle = mStartAngle * 2
    
        /**
         *  比如用于获取一个圆弧的矩形,onDraw()方法会调用多次,不必每次都创建Rect()对象
         */
        private val mArcRect = RectF()
    
        /**
         *  比如用于获取文字的大小,onDraw()方法会调用多次,不必每次都创建Rect()对象
         *  用同一个对象即可
         */
        private val mTextRect = Rect()
    
        /**
         * 值动画师
         */
        private var valueAnimator: ValueAnimator? = null
    
        /**
         * 最大进度
         */
        private val MAX_PROGRESS = 100F
    
        constructor(context: Context)
                : this(context, null)
    
        constructor(context: Context, attrs: AttributeSet?)
                : this(context, attrs, 0)
    
        constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int)
                : super(context, attrs, defStyleAttr) {
            val appearance = context.obtainStyledAttributes(attrs, R.styleable.StepView)
    
            mBorderWidth = appearance.getDimension(R.styleable.StepView_borderWidth, mBorderWidth)
            mOuterColor = appearance.getColor(R.styleable.StepView_outColor, mOuterColor)
            mInnerColor = appearance.getColor(R.styleable.StepView_innerColor, mInnerColor)
            mUnit = appearance.getString(R.styleable.StepView_unit)
            mCurrentStep = appearance.getInt(R.styleable.StepView_currentStep, 0)
            mMaxStep = appearance.getInt(R.styleable.StepView_maxStep, mMaxStep)
    
            appearance.recycle()
    
            setPaint()
        }
    
    
        /**
         * 设置 圆弧画笔用于绘制圆弧 和 文本画笔,用于绘画走了多少步
         */
        private fun setPaint() {
            // 画笔的颜色
            mArcPaint.color = mOuterColor
    
            // 抗抖动
            mArcPaint.isDither = true
    
            // 抗锯齿
            mArcPaint.isAntiAlias = true
    
            // 画笔的样式描边,笔划突出为半圆
            mArcPaint.style = Paint.Style.STROKE
    
            // 设置描边的线帽样式
            mArcPaint.strokeCap = Paint.Cap.ROUND
    
            // 设置描边的宽度
            mArcPaint.strokeWidth = mBorderWidth
    
    
            // 画笔的颜色
            mTextPaint.color = currentTextColor
    
            // 抗抖动
            mTextPaint.isDither = true
    
            // 抗锯齿
            mTextPaint.isAntiAlias = true
    
            // 画笔的样式描边,笔划突出为半圆
            mTextPaint.style = Paint.Style.FILL
    
            // 设置描边的线帽样式
            mTextPaint.strokeCap = Paint.Cap.ROUND
    
            // 设置文本大小
            mTextPaint.textSize = textSize
        }
    
        override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec)
    
            /**
             * 获取控件的宽高
             */
            val widthSize = MeasureSpec.getSize(widthMeasureSpec)
            val heightSize = MeasureSpec.getSize(heightMeasureSpec)
    
            /**
             * 如果宽度大于高度,取高度
             * 否则取宽度
             */
            val result = if (widthSize > heightSize) {
                heightSize
            } else {
                widthSize
            }
    
            /**
             * 设置控件的宽高
             */
            setMeasuredDimension(result, result)
        }
    
        override fun onDraw(canvas: Canvas?) {
            // 将矩形设置为 (0,0,0,0)
            mArcRect.setEmpty()
            mTextRect.setEmpty()
    
            // 圆弧矩形左边距,顶边距,右边距,底边距
            val left = mBorderWidth / 2
            val top = mBorderWidth / 2
            val right = width - mBorderWidth / 2
            val bottom = height - mBorderWidth / 2
            mArcRect.set(left, top, right, bottom)
    
            // 绘制外部圆弧
            mArcPaint.color = mOuterColor
            canvas?.drawArc(mArcRect, mStartAngle, mSweepAngle, false, mArcPaint)
    
            // 绘制内部部圆弧
            mArcPaint.color = mInnerColor
            val sweepAngle = mCurrentStep * 1F / mMaxStep * mSweepAngle
            canvas?.drawArc(mArcRect, mStartAngle, sweepAngle, false, mArcPaint)
    
            val stepText = if (mUnit != null) {
                "$mCurrentStep $mUnit"
            } else {
                mCurrentStep.toString()
            }
    
            // 获取文本的宽高
            mTextPaint.getTextBounds(stepText, 0, stepText.length, mTextRect)
            val textX = width / 2F - mTextRect.width() / 2
            val textY = height / 2F + getBaseline(mTextPaint)
            // 绘制文本,第二个参数文本的起始索引,第三个参数要绘制的文字长度
            // 开始绘制文字的x 坐标 y 坐标
            canvas?.drawText(stepText, 0, stepText.length, textX, textY, mTextPaint)
        }
    
        /**
         * @param progress 进入0-100 之间
         * @param duration 动画时长,默认 350毫秒
         */
        fun setProgress(progress: Int, duration: Long = 350) {
            valueAnimator?.cancel()
            valueAnimator = null
            val step = (progress / MAX_PROGRESS * mMaxStep).toInt()
            valueAnimator = ValueAnimator.ofInt(mCurrentStep, step.coerceAtMost(mMaxStep))
            valueAnimator?.duration = duration
            valueAnimator?.interpolator = AccelerateInterpolator()
            valueAnimator?.addUpdateListener {
                mCurrentStep = it.animatedValue as Int
                Log.d(TAG, "------$mCurrentStep")
                invalidate()
            }
            valueAnimator?.startDelay = 10
            valueAnimator?.start()
        }
    
        /**
         * @param maxStep  最多走多少步,比如2000步
         * @param duration 默认动画时长200
         */
        fun setMaxStep(maxStep: Int, duration: Long = 0) {
            mMaxStep = maxStep
            val progress = (mCurrentStep * 1F / mMaxStep * 100).toInt()
            setProgress(progress, duration)
        }
    
        /**
         * @param currentStep 当前走了多少步
         * @param duration 默认动画时长200
         */
        fun setCurrentStep(currentStep: Int, duration: Long = 200) {
            mCurrentStep = currentStep
            val progress = (mCurrentStep * 1F / mMaxStep * 100).toInt()
            setProgress(progress, duration)
        }
    
        /**
         * 视图从窗口分离时
         */
        override fun onDetachedFromWindow() {
            super.onDetachedFromWindow()
            valueAnimator?.cancel()
            valueAnimator = null
        }
    
        private fun dp2px(value: Float): Float {
            return TypedValue.applyDimension(
                TypedValue.COMPLEX_UNIT_DIP, value, resources.displayMetrics
            )
        }
    
        /**
         * 计算绘制文字时的基线到中轴线的距离
         * @param paint 画笔
         * @return 返回基线的距离
         */
        private fun getBaseline(paint: Paint): Float {
            val fontMetrics: Paint.FontMetrics = paint.fontMetrics
            return (fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom
        }
    
    
    }
    

    绘制圆弧是是从3点中开始,它位于0度。比如我们可以试试绘制0到90度的圆弧,多试试几次,你很快就能明白了额

    绘制文本横坐标是控件宽度的一半减去字体宽度的一半,绘制文本的纵坐标是控件高度的一半加上文字的基线。

    文字基线我们看个图清楚了

    Android获取中线到基线距离的代码,实际获取到的Ascent是负数
    /**
         * 计算绘制文字时的基线到中轴线的距离
         * @param paint 画笔
         * @return 返回基线的距离
         */
        private fun getBaseline(paint: Paint): Float {
            val fontMetrics: Paint.FontMetrics = paint.fontMetrics
            return (fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom
        }
    
    

    github 项目地址 https://github.com/githubwwj/MyAndroid.git

    相关文章

      网友评论

        本文标题:Android 用Kotlin画一个圆弧计步器

        本文链接:https://www.haomeiwen.com/subject/raevaltx.html