自定义控件-圆形刻度盘ui

2023-11-05 12:10

本文主要是介绍自定义控件-圆形刻度盘ui,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

圆形刻度盘

UI效果

新项目又来了,做的手表功能,有个圆形刻度盘的ui,图如下:
在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

思路

1.刻度条其实是直线,先将坐标系移到圆心,画一条刻度(直线),再将坐标系旋转一定角度,再画一条刻度(直线),直到旋转一周。
2.总共画两层刻度盘,第一层是白色刻度条的刻度盘,再画第二次有颜色刻度条的刻度盘,第二个刻度盘的刻度条从0到阀值,通过动画实现。
3.取消的话,其实也可以增加一个动画,就是第二个刻度盘的刻度条从阀值到0

关于坐标系

画了一个图,简单易懂:
在这里插入图片描述

代码

直接放代码了:
1.attrs.xml 自定义了一些属性

<?xml version="1.0" encoding="utf-8"?>
<resources><declare-styleable name="CircularMeter"><attr name="lineWidth" format="dimension" /><attr name="lineNumber" format="integer" /><attr name="lineLength" format="dimension" /><attr name="backColor" format="color" /><attr name="backLineColor" format="color" /><attr name="startLineColor" format="color" /><attr name="finishLineColor" format="color" /><attr name="fraction" format="fraction" /><attr name="startDuration" format="integer" /><attr name="stopDuration" format="integer" /></declare-styleable>
</resources>

lineWidth:刻度条的线宽
lineNumber:刻度条的总共数量,要被360整除,这样每条线旋转的角度就是360/lineNumber
lineLength:刻度条的长度,肯定要小于半径
backColor:刻度盘的背景色
backLineColor:第一个刻度盘的刻度条的颜色
startLineColorfinishLineColor:第一个刻度盘的刻度条的渐变颜色
fraction:取值0到1,1就是一圈,0.5就是半圈,第四个UI图的fraction就是0.2,红色刻度条的数量就是lineNumber*fraction
startDuration:刻度条增加的动画时间
stopDuration:刻度条减少的动画时间

2.CircularMeterView.kt

class CircularMeterView(context: Context, @Nullable attrs: AttributeSet?, defStyleAttr: Int) :View(context, attrs, defStyleAttr) {constructor(context: Context) : this(context, null)constructor(context: Context, @Nullable attrs: AttributeSet?) : this(context, attrs, 0)private val TAG = "CircularMeterView"private var mBackPaint: Paintprivate var mBackLinePaint: Paintprivate var mProgLinePaint: Paint//半径private var mRadius: Float = 0.0f//圆心坐标private var mCenterX: Float = 0.0fprivate var mCenterY: Float = 0.0f//刻度条数量private var mLineNumber: Int//刻度条长度private var mLineLength: Float//启动动画时间private var mStartDuration: Int = 0//关闭动画时间private var mStopDuration: Int = 0//渐变色private var mStartLineColor: Intprivate var mFinishLineColor: Int//背景色private var mBackColor: Int//有色刻度盘的比例(0到1)private var mFraction: Float = 0F//动画实时改变的值,小于mFractionprivate var currentFraction = 0Fprivate var animator: ValueAnimator? = nullprivate var mListenerList: ArrayList<ListenerBuilder> = arrayListOf()init {@SuppressLint("Recycle")val typedArray: TypedArray =context.obtainStyledAttributes(attrs, R.styleable.CircularMeter)mBackPaint = Paint()mBackPaint.run {style = Paint.Style.FILL_AND_STROKEisAntiAlias = truecolor = typedArray.getColor(R.styleable.CircularMeter_backColor, Color.TRANSPARENT)}mBackLinePaint = Paint()mBackLinePaint.run {style = Paint.Style.STROKEstrokeCap = Paint.Cap.ROUND // 设置圆角isAntiAlias = true // 设置抗锯齿isDither = true // 设置抖动strokeWidth = typedArray.getDimension(R.styleable.CircularMeter_lineWidth, 2f)color = typedArray.getColor(R.styleable.CircularMeter_backLineColor, resources.getColor(R.color.colorMeterBack))}mProgLinePaint = Paint()mProgLinePaint.run {style = Paint.Style.STROKEstrokeCap = Paint.Cap.ROUNDisAntiAlias = trueisDither = truestrokeWidth = typedArray.getDimension(R.styleable.CircularMeter_lineWidth, 2f)color = Color.BLUE}typedArray.run {mLineNumber = getInt(R.styleable.CircularMeter_lineNumber, 60)mLineLength = getDimension(R.styleable.CircularMeter_lineLength, 30f)mStartLineColor = getColor(R.styleable.CircularMeter_startLineColor, -1)mFinishLineColor = getColor(R.styleable.CircularMeter_finishLineColor, -1)mBackColor = getColor(R.styleable.CircularMeter_backColor, -1)mStartDuration = getInt(R.styleable.CircularMeter_startDuration, 2000)mStopDuration = getInt(R.styleable.CircularMeter_stopDuration, 200)mFraction = getFraction(R.styleable.CircularMeter_fraction, 1, 1, 0.0f)recycle()}}override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {super.onMeasure(widthMeasureSpec, heightMeasureSpec)val viewWith: Float = (measuredWidth - paddingLeft - paddingRight).toFloat()val viewHigh: Float = (measuredHeight - paddingTop - paddingBottom).toFloat()mCenterX = measuredWidth.toFloat() / 2mCenterY = measuredHeight.toFloat() / 2mRadius = min(viewWith, viewHigh) / 2}override fun onDraw(canvas: Canvas) {super.onDraw(canvas)drawBackGroud(canvas)drawBackLine(canvas)drawFrogLine(canvas)}private fun drawBackGroud(canvas: Canvas) {canvas.drawCircle(mCenterX,mCenterY,mRadius,mBackPaint)}private fun drawFrogLine(canvas: Canvas) {//第二个刻度盘实时的刻度条数量val number = (mLineNumber * currentFraction).toInt()if (number > 0) {canvas.save()canvas.translate(mCenterX, mCenterX)//旋转180度,使坐标系的Y轴方向和数学坐标系的Y轴方向一致canvas.rotate(180.0f)for (i in 1..number) {if (mStartLineColor != -1 && mFinishLineColor != -1) {mProgLinePaint.shader = LinearGradient(0.0f, mRadius, 0.0f, mRadius - mLineLength, mStartLineColor, mFinishLineColor, Shader.TileMode.CLAMP)}//-0.5f为了避免毛刺效果canvas.drawLine(0.0f, mRadius -0.5f , 0.0f, mRadius - mLineLength, mProgLinePaint)canvas.rotate((360 / mLineNumber).toFloat())}canvas.restore()}}private fun drawBackLine(canvas: Canvas) {canvas.save()//坐标系移到圆心canvas.translate(mCenterX, mCenterX)for (i in 1..mLineNumber) {//画一条刻度线,坐标(0, mRadius, 0, mRadius - mLineLength)canvas.drawLine(0.0f, mRadius, 0.0f, mRadius - mLineLength, mBackLinePaint)//坐标系旋转canvas.rotate((360 / mLineNumber).toFloat())}canvas.restore()}/*** fraction 0到1,有动画过程* duration 动画时间(毫秒)* */fun startCircular(fraction: Float = mFraction, duration: Int = mStartDuration) {if (fraction < 0 || fraction > 1) {"startCircular error , fraction = $fraction".d(TAG)return}if (duration < 0) {"startCircular duration < 0".d(TAG)return}mFraction = fractionmStartDuration = duration"mFraction = $mFraction".d(TAG)animator?.run { cancel() }animator = ValueAnimator.ofFloat(0.0f, mFraction)animator?.run {addUpdateListener { animation ->currentFraction = animation.animatedValue as Floatinvalidate()if (currentFraction == mFraction) {mListenerList.takeUnless { it.isEmpty() }?.run { forEach { it.mComplete?.invoke() } }}}interpolator = LinearInterpolator()this.duration = mStartDuration.toLong()start()}}/*** fraction 0到1,无动画过程* */fun setCircular(fraction: Float = mFraction) {if (fraction < 0 || fraction > 1) {"setCircular error , fraction = $fraction".d(TAG)return}animator?.run { cancel() }mFraction = fractioncurrentFraction = fractioninvalidate()mListenerList.takeUnless { it.isEmpty() }?.run { forEach { it.mComplete?.invoke() } }}/*** duration 动画时间(毫秒)* */fun stopCircular(duration: Int = mStopDuration) {if (duration < 0) {"stopCircular duration < 0".d(TAG)mStopDuration = 0}mStopDuration = durationmStopDuration = (mStopDuration * currentFraction).toInt()animator?.run { cancel() }animator = ValueAnimator.ofFloat(currentFraction, 0.0f)animator?.run {addUpdateListener { animation ->currentFraction = animation.animatedValue as Floatinvalidate()/*if (currentFraction <= 0.0f) {mListenerList.takeUnless { it.isEmpty() }?.run { forEach { it.mComplete?.invoke() } }}*/}interpolator = LinearInterpolator()this.duration = mStopDuration.toLong()start()}}fun setFrogLineColor(startColor: Int, finishColor: Int) {mStartLineColor = startColormFinishLineColor = finishColorinvalidate()}fun registerListener(listenerBuilder: ListenerBuilder.() -> Unit) {mListenerList.add(ListenerBuilder().also(listenerBuilder))}fun removeAllListener() {mListenerList.clear()}class ListenerBuilder {//动画结束的回调internal var mComplete: (() -> Unit)? = nullfun onComplete(action: () -> Unit) {mComplete = action}}}

3.fragment_lock.xml 布局

<?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"android:layout_gravity="center"><com.qinggan.app.havalapp.view.CircularMeterViewandroid:layout_width="214px"android:layout_height="214px"android:id="@+id/f_lock_circularMeterView"app:backColor="#EEEEEE"app:lineWidth="2px"app:lineNumber="60"app:lineLength="29px"app:backLineColor="@color/colorMeterBack"app:startLineColor="@color/colorOilMeterStartGreen"app:finishLineColor="@color/colorOilMeterFinishGreen"app:fraction = "100%"app:startDuration = "3000"app:stopDuration = "400"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

3.简单调用

class LockFragment : Fragment() {private val TAG = "LockFragment"private lateinit var meterView: CircularMeterViewoverride fun onResume() {super.onResume()"onResume ".d(TAG)meterView.startCircular()}override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {"onCreateView ".d(TAG)val view = inflater.inflate(R.layout.fragment_lock, container, false)meterView = view.findViewById<CircularMeterView>(R.id.f_lock_circularMeterView)// meterView.setColor(resources.getColor(R.color.colorOilMeterStartRed),resources.getColor(R.color.colorOilMeterFinishRed))meterView.registerListener {onComplete {"onComplete animation".d(TAG)}}meterView.setOnClickListener{//meterView.setCircular(0.0f)meterView.stopCircular()}return view}
}

这篇关于自定义控件-圆形刻度盘ui的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/349764

相关文章

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

自定义类型:结构体(续)

目录 一. 结构体的内存对齐 1.1 为什么存在内存对齐? 1.2 修改默认对齐数 二. 结构体传参 三. 结构体实现位段 一. 结构体的内存对齐 在前面的文章里我们已经讲过一部分的内存对齐的知识,并举出了两个例子,我们再举出两个例子继续说明: struct S3{double a;int b;char c;};int mian(){printf("%zd\n",s

Spring 源码解读:自定义实现Bean定义的注册与解析

引言 在Spring框架中,Bean的注册与解析是整个依赖注入流程的核心步骤。通过Bean定义,Spring容器知道如何创建、配置和管理每个Bean实例。本篇文章将通过实现一个简化版的Bean定义注册与解析机制,帮助你理解Spring框架背后的设计逻辑。我们还将对比Spring中的BeanDefinition和BeanDefinitionRegistry,以全面掌握Bean注册和解析的核心原理。

Oracle type (自定义类型的使用)

oracle - type   type定义: oracle中自定义数据类型 oracle中有基本的数据类型,如number,varchar2,date,numeric,float....但有时候我们需要特殊的格式, 如将name定义为(firstname,lastname)的形式,我们想把这个作为一个表的一列看待,这时候就要我们自己定义一个数据类型 格式 :create or repla

lvgl8.3.6 控件垂直布局 label控件在image控件的下方显示

在使用 LVGL 8.3.6 创建一个垂直布局,其中 label 控件位于 image 控件下方,你可以使用 lv_obj_set_flex_flow 来设置布局为垂直,并确保 label 控件在 image 控件后添加。这里是如何步骤性地实现它的一个基本示例: 创建父容器:首先创建一个容器对象,该对象将作为布局的基础。设置容器为垂直布局:使用 lv_obj_set_flex_flow 设置容器

HTML5自定义属性对象Dataset

原文转自HTML5自定义属性对象Dataset简介 一、html5 自定义属性介绍 之前翻译的“你必须知道的28个HTML5特征、窍门和技术”一文中对于HTML5中自定义合法属性data-已经做过些介绍,就是在HTML5中我们可以使用data-前缀设置我们需要的自定义属性,来进行一些数据的存放,例如我们要在一个文字按钮上存放相对应的id: <a href="javascript:" d

一步一步将PlantUML类图导出为自定义格式的XMI文件

一步一步将PlantUML类图导出为自定义格式的XMI文件 说明: 首次发表日期:2024-09-08PlantUML官网: https://plantuml.com/zh/PlantUML命令行文档: https://plantuml.com/zh/command-line#6a26f548831e6a8cPlantUML XMI文档: https://plantuml.com/zh/xmi

Golang GUI入门——andlabs ui

官方不提供gui标准库,只好寻求第三方库。 https://github.com/google/gxui 这个gui库是谷歌内部人员提供的,并不是谷歌官方出品,现在停止维护,只好作罢。 第三方gui库 找了好多,也比较了好多,最终决定使用的是还是 https://github.com/andlabs/ui 相信golang gui还会发展的更好,期待更优秀的gui库 由于andlabs

argodb自定义函数读取hdfs文件的注意点,避免FileSystem已关闭异常

一、问题描述 一位同学反馈,他写的argo存过中调用了一个自定义函数,函数会加载hdfs上的一个文件,但有些节点会报FileSystem closed异常,同时有时任务会成功,有时会失败。 二、问题分析 argodb的计算引擎是基于spark的定制化引擎,对于自定义函数的调用跟hive on spark的是一致的。udf要通过反射生成实例,然后迭代调用evaluate。通过代码分析,udf在