Android开发Kotlin实现圆弧计步器示例详解

目录
  • 效果图
  • 定义控件的样式
  • 自定义StepView
  • 绘制文本坐标
  • Android获取中线到基线距离

效果图

定义控件的样式

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

<!-- 自定义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

接下来我们自定义一个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获取中线到基线距离

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
    }

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

以上就是Android开发Kotlin绘制圆弧计步器示例详解的详细内容,更多关于Android Kotlin绘制圆弧计步器的资料请关注我们其它相关文章!

(0)

相关推荐

  • Android使用kotlin实现多行文本上下滚动播放

    最近在项目中用到了上下滚动展示条目内容,就使用kotlin简单编写实现了一下该功能. 使用kotlin实现viewflipper展示textview的上下滚动播放 其中包含了kotlin的一些简单的使用 - 首先是在布局文件中如下代码: <ViewFlipper         android:id="@+id/viewFlipper"         android:layout_width="match_parent"         android:la

  • Android使用Kotlin API实践WorkManager

    WorkManager 提供了一系列 API 可以更加便捷地规划异步任务,即使在应用被关闭之后或者设备重启之后,仍然需要保证立即执行的或者推迟执行的任务被正常处理.对于 Kotlin 开发者,WorkManager 为协程提供了最佳的支持.在本文中,我将通过实践 WorkManager codelab 为大家展示 WorkManager 中与协程相关的基本操作.那么让我们开始吧! WorkManager 基础 当您需要某个任务保持运行状态,即使用户切换到别的界面或者用户将应用切换到后台,甚至设备

  • Kotlin-Android之Activity使用详解

    目录 Activity中Toast的使用 Activity中不使用findViewById()获取控件ID Activity中使用菜单Menu Activity中intent的使用 intent显式 intent隐式 intent数据的传递 传递数据 返回数据 扩展 参考 Activity中Toast的使用 Toast.makeText(this,"ADD",Toast.LENGTH_SHORT).show() // Toast.makeText(Activity,提醒字符,lengt

  • 一文读懂Android Kotlin的数据流

    目录 一.Android分层架构 二.ViewModel + LiveData 2.1 LiveData 特性 观察者的回调永远发生在主线程 仅持有单个且最新数据 自动取消订阅 提供「可读可写」和「仅可读」两种方式 配合 DataBinding 实现「双向绑定」 2.2 LiveData的缺陷 value 可以是 nullable 的 传入正确的 lifecycleOwner 粘性事件 默认不防抖 transformation 工作在主线程 2.3 LiveData 小结 三.Flow 3.1

  • Kotlin实现Android系统悬浮窗详解

    目录 Android 弹窗浅谈 系统悬浮窗具体实现 权限申请 代码设计 具体实现 FloatWindowService 类 FloatWindowManager 类 FloatWindowManager 类代码 FloatLayout 类及其 Layout HomeKeyObserverReceiver 类 FloatWindowUtils 类 总结 Android 弹窗浅谈 我们知道 Android 弹窗中,有一类弹窗会在应用之外也显示,这是因为他被申明成了系统弹窗,除此之外还有2类弹窗分别是

  • Android自定义View实现九宫格图形解锁(Kotlin版)

    本文实例为大家分享了Android自定义View实现九宫格图形解锁的具体代码,供大家参考,具体内容如下 效果: 代码: package com.example.kotlin_test import android.content.Context import android.graphics.Canvas import android.graphics.Color import android.graphics.Paint import android.util.AttributeSet imp

  • Android Kotlin使用SQLite案例详解

    Kotlin使用SQLite 首先确定我们的目标,SQLite只是一种工具,我们需要掌握就是增删改查就可以,我们真正需要动脑的还是项目中的业务逻辑.我这篇文章写得比较适合新手,没用过SQLite的同学. 前期准备工作 新建一个类MyDataBaseHelper继承自SQLiteOpenHelper,代码如下: class MyDatabaseHelper(var context: Context, name: String, version: Int) : SQLiteOpenHelper(co

  • Android Kotlin全面详细类使用语法学习指南

    目录 前言 1. 类的声明 & 实例化 2. 构造函数 2.1 主构造函数 2.2 次构造函数 3. 类的属性 4. 可见性修饰符 5. 继承 & 重写 6. 特殊类 6.1 嵌套类(内部类) 6.2 接口 6.3 数据类 6.4 枚举类 总结 前言 Kotlin被Google官方认为是Android开发的一级编程语言 今天,我将主要讲解kotlin中的类的所有知识,主要内容包括如下: 1. 类的声明 & 实例化 // 格式 class 类名(参数名1:参数类型,参数名2:参数类型

  • Android使用ViewBinding的详细步骤(Kotlin简易版)

    ViewBinding 是什么 2020年的3月份 巨佬 JakeWharton 开源的 butterknife 被官宣 停止维护,在github 上 说明 Attention: This tool is now deprecated. Please switch to view binding. Existing versions will continue to work, obviously, but only critical bug fixes for integration with

  • Android开发Kotlin实现圆弧计步器示例详解

    目录 效果图 定义控件的样式 自定义StepView 绘制文本坐标 Android获取中线到基线距离 效果图 定义控件的样式 看完效果后,我们先定义控件的样式 <!-- 自定义View的名字 StepView --> <!-- name 属性名称 format 格式 string 文字 color 颜色 dimension 字体大小 integer 数字 reference 资源或者颜色 --> <declare-styleable name="StepView&q

  • Android开发Jetpack组件DataBinding用例详解

    目录 简介 使用方式 1. build.gradle 中添加 kapt,并启用dataBinding 2.修改布局文件,添加 layout 和 data 标签 3.使用 DataBindingUtil 绑定布局 4.布局的 data 标签中添加数据变量,并使用其参数 5.BindingAdapter的使用 简介 DataBinding 是 Jetpack 组件之一,适用于 MVVM 模式开发,也是Google官方推荐使用的组件之一.使用DataBinding可以很容易的达到视图与逻辑分离,直接在

  • Android开发使用HttpURLConnection进行网络编程详解【附源码下载】

    本文实例讲述了Android开发使用HttpURLConnection进行网络编程.分享给大家供大家参考,具体如下: --HttpURLConnection URLConnection已经可以非常方便地与指定站点交换信息,URLConnection下还有一个子类:HttpURLConnection,HttpURLConnection在URLConnection的基础上进行改进,增加了一些用于操作HTTP资源的便捷方法. setRequestMethod(String):设置发送请求的方法 get

  • Android开发使用URLConnection进行网络编程详解

    本文实例讲述了Android开发使用URLConnection进行网络编程.分享给大家供大家参考,具体如下: URL的openConnection()方法将返回一个URLConnection,该对象表示应用程序和URL之间的通信连接,程序可以通过URLConnection实例向该URL发送请求,读取URL引用的资源.通常创建一个和URL的连接,并发送请求,读取此URL引用的资源. 需要如下步骤: a)通过调用URL对象openConnection()方法来创建URLConnection对象 b)

  • Android开发之自定义加载动画详解

    目录 一.demo简介 二.分析贪吃动画的尺寸比例 三.画圆 四.实现张嘴闭嘴动画 五.小球移动动画 一.demo简介 1.效果展示如下图,我截了三个瞬间,但其实这是一个连续的动画,就是这个大圆不停地吞下小圆. 2.这个动画可以拆分为两部分,首先是大圆张嘴闭嘴的动画,相当于画一个圆弧,规定一下它的角度就好.小圆就是一个从右向左移动的动画.然后不停地刷新界面,让动画的持续时间为永恒,这样就会有一个持续的动态效果. 二.分析贪吃动画的尺寸比例 1.在制作动画之前,我们要先建一个模型,来确定一下大圆和

  • Android 动态加载 so实现示例详解

    目录 背景 so动态加载介绍 从一个例子出发 so库检索与删除 动态加载so 结束了吗? ELF文件 扩展 总结 背景 对于一个普通的android应用来说,so库的占比通常都是巨高不下的,因为我们无可避免的在开发中遇到各种各样需要用到native的需求,所以so库的动态化可以减少极大的包体积,自从2020腾讯的bugly团队发部关于动态化so的相关文章后,已经过去两年了,相关文章,经过两年的考验,实际上so动态加载也是非常成熟的一项技术了. 但是很遗憾,许多公司都还没有这方面的涉略又或者说不知

  • Android 开发中使用Linux Shell实例详解

    Android 开发中使用Linux Shell实例详解 引言 Android系统是基于Linux内核运行的,而做为一名Linux粉,不在Android上面运行一下Linux Shell怎么行呢? 最近发现了一个很好的Android Shell工具代码,在这里分享一下. Shell核心代码 import java.io.BufferedReader; import java.io.DataOutputStream; import java.io.IOException; import java.

  • Android开发Jetpack组件WorkManager用例详解

    目录 一.简介 二.导入 三.基本使用 3.1 定义后台任务 3.2 配置任务运行条件 3.2.1 只需执行一次的任务 3.2.2 周期性执行的任务 3.3 将任务传给 WorkManager 四.高级配置 4.1 设置任务延迟执行 4.2 给任务添加标签 4.3 取消任务 4.3.1 根据标签取消任务 4.3.2 根据 request 的 id 取消任务 4.3.3 取消所有任务 4.4 任务重试 4.5 监听任务结果 4.6 传递数据 4.7 链式任务 一.简介 WorkManager 用于

  • Android Flutter实现3D动画效果示例详解

    目录 前言 AnimatedWidget 简介 3D 旋转动画的实现 总结 前言 上一篇我们介绍了 Animation 和 AnimationController 的使用,这是最基本的动画构建类.但是,如果我们想构建一个可复用的动画组件,通过外部参数来控制其动画效果的时候,上一篇的方法就不太合适了.在 Flutter 中提供了 AnimatedWidget 组件用于构建可复用的动画组件.本篇我们用 AnimatedWidget 来实现组件的3D 旋转效果,如下图所示. AnimatedWidge

  • iOS开发探索多线程GCD任务示例详解

    目录 引言 同步任务 死锁 异步任务 总结 引言 在上一篇文章中,我们探寻了队列是怎么创建的,串行队列和并发队列之间的区别,接下来我们在探寻一下GCD的另一个核心 - 任务 同步任务 void dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block); 我们先通过lldb查看其堆栈信息,分别查看其正常运行和死锁状态的信息 我们再通过源码查询其实现 #define _dispatch_Block_

随机推荐