本文主要是介绍自定义控件-圆形刻度盘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:第一个刻度盘的刻度条的颜色
startLineColor,finishLineColor:第一个刻度盘的刻度条的渐变颜色
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的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!