自定义控件-圆形刻度盘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

相关文章

使用Sentinel自定义返回和实现区分来源方式

《使用Sentinel自定义返回和实现区分来源方式》:本文主要介绍使用Sentinel自定义返回和实现区分来源方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Sentinel自定义返回和实现区分来源1. 自定义错误返回2. 实现区分来源总结Sentinel自定

如何自定义Nginx JSON日志格式配置

《如何自定义NginxJSON日志格式配置》Nginx作为最流行的Web服务器之一,其灵活的日志配置能力允许我们根据需求定制日志格式,本文将详细介绍如何配置Nginx以JSON格式记录访问日志,这种... 目录前言为什么选择jsON格式日志?配置步骤详解1. 安装Nginx服务2. 自定义JSON日志格式各

Android自定义Scrollbar的两种实现方式

《Android自定义Scrollbar的两种实现方式》本文介绍两种实现自定义滚动条的方法,分别通过ItemDecoration方案和独立View方案实现滚动条定制化,文章通过代码示例讲解的非常详细,... 目录方案一:ItemDecoration实现(推荐用于RecyclerView)实现原理完整代码实现

基于Spring实现自定义错误信息返回详解

《基于Spring实现自定义错误信息返回详解》这篇文章主要为大家详细介绍了如何基于Spring实现自定义错误信息返回效果,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录背景目标实现产出背景Spring 提供了 @RestConChina编程trollerAdvice 用来实现 HTT

SpringSecurity 认证、注销、权限控制功能(注销、记住密码、自定义登入页)

《SpringSecurity认证、注销、权限控制功能(注销、记住密码、自定义登入页)》SpringSecurity是一个强大的Java框架,用于保护应用程序的安全性,它提供了一套全面的安全解决方案... 目录简介认识Spring Security“认证”(Authentication)“授权” (Auth

SpringBoot自定义注解如何解决公共字段填充问题

《SpringBoot自定义注解如何解决公共字段填充问题》本文介绍了在系统开发中,如何使用AOP切面编程实现公共字段自动填充的功能,从而简化代码,通过自定义注解和切面类,可以统一处理创建时间和修改时间... 目录1.1 问题分析1.2 实现思路1.3 代码开发1.3.1 步骤一1.3.2 步骤二1.3.3

dubbo3 filter(过滤器)如何自定义过滤器

《dubbo3filter(过滤器)如何自定义过滤器》dubbo3filter(过滤器)类似于javaweb中的filter和springmvc中的intercaptor,用于在请求发送前或到达前进... 目录dubbo3 filter(过滤器)简介dubbo 过滤器运行时机自定义 filter第一种 @A

CSS自定义浏览器滚动条样式完整代码

《CSS自定义浏览器滚动条样式完整代码》:本文主要介绍了如何使用CSS自定义浏览器滚动条的样式,包括隐藏滚动条的角落、设置滚动条的基本样式、轨道样式和滑块样式,并提供了完整的CSS代码示例,通过这些技巧,你可以为你的网站添加个性化的滚动条样式,从而提升用户体验,详细内容请阅读本文,希望能对你有所帮助...

Vue项目中Element UI组件未注册的问题原因及解决方法

《Vue项目中ElementUI组件未注册的问题原因及解决方法》在Vue项目中使用ElementUI组件库时,开发者可能会遇到一些常见问题,例如组件未正确注册导致的警告或错误,本文将详细探讨这些问题... 目录引言一、问题背景1.1 错误信息分析1.2 问题原因二、解决方法2.1 全局引入 Element

C#实现WinForm控件焦点的获取与失去

《C#实现WinForm控件焦点的获取与失去》在一个数据输入表单中,当用户从一个文本框切换到另一个文本框时,需要准确地判断焦点的转移,以便进行数据验证、提示信息显示等操作,本文将探讨Winform控件... 目录前言获取焦点改变TabIndex属性值调用Focus方法失去焦点总结最后前言在一个数据输入表单