android 贝塞尔曲线实现进度球,自定义View之BezierProgressView贝塞尔曲线实现百分比注水球...

package com.stone.templateapp.module.view

import android.animation.ValueAnimator

import android.content.Context

import android.graphics.*

import android.util.AttributeSet

import android.view.View

import android.view.animation.LinearInterpolator

import com.stone.commonutils.ctx

import com.stone.log.Logs

import com.stone.templateapp.R

import java.text.NumberFormat

/**

* Created By: sqq

* Created Time: 2019/1/7 18:39.

*

* Bezier实现百分比进度的注水球

*

*/

class BezierProgressView : View {

constructor(ctx: Context) : this(ctx, null)

constructor(ctx: Context, attributeSet: AttributeSet?) : this(ctx, attributeSet, 0)

constructor(ctx: Context, attributeSet: AttributeSet?, defStyle: Int) : super(ctx, attributeSet, defStyle) {

initAttr(attributeSet, defStyle)

}

private fun initAttr(attributeSet: AttributeSet?, defStyle: Int) {

val ta = ctx.obtainStyledAttributes(attributeSet, R.styleable.BezierProgressView, defStyle, 0)

mPercent = ta.getFloat(R.styleable.BezierProgressView_bezier_percent, 0.5f).toDouble()

ta.recycle()

}

private val mPaint = Paint()

private val mCirclePaint = Paint()

private val mTextPaint = Paint()

private val mPath = Path()

/**

* 当前进度的百分比

*/

private var mPercent = 0.3

var percent = 50

set(value) {

field = when {

value > 100 -> 100

value < 0 -> 0

else -> value

}

mPercent = field.toDouble() / 100

postInvalidate()

}

init {

mPaint.isAntiAlias = true

mPaint.color = Color.parseColor("cyan")

mPaint.strokeWidth = 5f

mPaint.style = Paint.Style.FILL

mPaint.isDither = true

mCirclePaint.isAntiAlias = true

mCirclePaint.color = Color.parseColor("black")

mCirclePaint.strokeWidth = 15f

mCirclePaint.style = Paint.Style.STROKE

mCirclePaint.isDither = true

mTextPaint.isAntiAlias = true

mTextPaint.color = Color.parseColor("purple")

mTextPaint.strokeWidth = 5f

mTextPaint.style = Paint.Style.STROKE

mTextPaint.isDither = true

mTextPaint.textSize = 140f

}

//View的大小

private var mViewWidth: Int = 0

private var mViewHeight: Int = 0

//可绘制区域大小(去除Padding之后)

private var mWidth: Int = 0

private var mHeight: Int = 0

private var r: Float = 0f

private lateinit var rectF: RectF

private val mPointF = PointF(0f, 0f)

private var mAnimateDx = 0f

private var mAnimateMaxDx = 0f

override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {

super.onSizeChanged(w, h, oldw, oldh)

mViewWidth = w

mViewHeight = h

mWidth = mViewWidth - paddingLeft - paddingRight

mHeight = mViewHeight - paddingTop - paddingBottom

r = Math.min(mWidth, mHeight) * 0.4f

mAnimateMaxDx = r

rectF = RectF(-r, -r, r, r)

}

override fun onDraw(canvas: Canvas) {

super.onDraw(canvas)

//保存之前Canvas的状态,save之后可以调用Canvas的平移、旋转等操作

canvas.save()

canvas.translate(measuredWidth / 2f, measuredHeight / 2f)

// canvas.drawPoint(0f, 0f, mCirclePaint)//绘制原点

mPath.rewind()

mPath.addCircle(0f, 0f, r + mCirclePaint.strokeWidth / 2, Path.Direction.CW)

//裁剪画布为圆形,不绘制超出圆边界的path

canvas.clipPath(mPath)

//递增or递减控制点的Y坐标

mPath.rewind()

//根据当前百分比,计算出当前圆的Y坐标

val yCoordinate = r * (1 - 2 * mPercent)

//根据y坐标计算出,原点与交点形成的直线 与 Y的正半轴形成的夹角度数

val angle = Math.acos(yCoordinate / r)

//根据夹角度数计算出当前交点的x坐标

val xCoordinate = r * Math.sin(angle)

//正弦周期的x轴长度

val t = xCoordinate.toFloat()

mPath.moveTo(-2 * t + mAnimateDx, yCoordinate.toFloat())

// mPath.moveTo(-xCoordinate.toFloat(), yCoordinate.toFloat())

//利用二阶Bezier实现正弦波水纹效果

// mPath.rQuadTo(xCoordinate.toFloat() / 2, -r / 8, xCoordinate.toFloat(), 0f)

// mPath.rQuadTo(xCoordinate.toFloat() / 2, r / 8, xCoordinate.toFloat(), 0f)

for (i in 0..2) {

mPath.rQuadTo(t / 4, t / 8, t / 2, 0f)

mPath.rQuadTo(t / 4, -t / 8, t / 2, 0f)

}

//利用三阶Bezier实现水纹效果

// mPath.rCubicTo(xCoordinate.toFloat() / 2, -r / 6, xCoordinate.toFloat() * 3 / 2, r / 6, 2 * xCoordinate.toFloat(), 0f)

val dDegree = Math.toDegrees(angle).toFloat()

mPath.addArc(rectF, 90 - dDegree, dDegree * 2)

// mPath.close()

canvas.drawPath(mPath, mPaint)

mPath.rewind()

val numberFormat = NumberFormat.getPercentInstance()

//百分比保留几位小数:0:10%;1:10.0%

numberFormat.minimumFractionDigits = 1

numberFormat.maximumFractionDigits = 3

val format = numberFormat.format(mPercent)

// Logs.i("onDraw: $format")

textCenter(arrayOf(format), mTextPaint, canvas, mPointF, Paint.Align.CENTER)

// textCenter(arrayOf("进度", format), mTextPaint, canvas, mPointF, Paint.Align.CENTER)

canvas.drawCircle(0f, 0f, r, mCirclePaint)

//与 save() 成对出现,恢复之前保存的canvas状态,防止上述save之后的canvas操作对后续的绘制产生影响

canvas.restore()//

if (mAnimateMaxDx.toInt() != t.toInt()) {

startAnimation(t)

}

// postInvalidateDelayed(200)

Logs.d("onDraw: r: $r,mWith: $mWidth,mAnimateDx:$mAnimateDx")

}

private var animator: ValueAnimator? = null

fun startAnimation(max: Float) {

mAnimateMaxDx = max

animator?.cancel()

animator = ValueAnimator.ofFloat(0f, max)

// animator.repeatMode = ValueAnimator.REVERSE

animator?.repeatCount = ValueAnimator.INFINITE

animator?.duration = 1000

animator?.interpolator = LinearInterpolator()

animator?.addUpdateListener {

mAnimateDx = it.animatedValue as Float

invalidate()

}

animator?.start()

}

override fun onDetachedFromWindow() {

super.onDetachedFromWindow()

animator?.cancel()

animator = null

}

/**

* 多行文本居中、居右、居左

* @param strings 文本字符串列表

* @param paint 画笔

* @param canvas 画布

* @param point 点的坐标

* @param align 居中、居右、居左

*/

protected fun textCenter(strings: Array, paint: Paint, canvas: Canvas, point: PointF, align: Paint.Align) {

paint.textAlign = align

val fontMetrics = paint.fontMetrics

val top = fontMetrics.top

val bottom = fontMetrics.bottom

val length = strings.size

val total = (length - 1) * (-top + bottom) + (-fontMetrics.ascent + fontMetrics.descent)

val offset = total / 2 - bottom

for (i in 0 until length) {

val yAxis = -(length - i - 1) * (-top + bottom) + offset

canvas.drawText(strings[i], point.x, point.y + yAxis, paint)

}

}

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android 中,可以通过继承 View 或 SurfaceView 实现贝塞尔曲线。 一般来说,需要使用 Path 对象来绘制贝塞尔曲线。Path 对象可以通过 moveTo、lineTo、quadTo、cubicTo 等方法来绘制线条、二次贝塞尔曲线和三次贝塞尔曲线。其中,quadTo 和 cubicTo 方法用于绘制贝塞尔曲线。 下面是一个简单的例子,演示了如何在自定义视图中绘制一条二次贝塞尔曲线: ```java public class BezierView extends View { private Paint mPaint; private Path mPath; public BezierView(Context context) { this(context, null); } public BezierView(Context context, AttributeSet attrs) { super(context, attrs); init(); } private void init() { mPaint = new Paint(); mPaint.setColor(Color.RED); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth(5); mPath = new Path(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); int startX = 100; int startY = getHeight() / 2; int endX = getWidth() - 100; int endY = getHeight() / 2; int controlX = getWidth() / 2; int controlY = getHeight() / 2 - 200; mPath.reset(); mPath.moveTo(startX, startY); mPath.quadTo(controlX, controlY, endX, endY); canvas.drawPath(mPath, mPaint); } } ``` 在这个例子中,我们绘制了一条二次贝塞尔曲线,起点为 `(startX, startY)`,终点为 `(endX, endY)`,控制点为 `(controlX, controlY)`。我们使用 `mPath.quadTo(controlX, controlY, endX, endY)` 方法来绘制曲线,并且在 `onDraw` 方法中调用 `canvas.drawPath(mPath, mPaint)` 来绘制出来。 当然,这只是一个非常简单的例子,实际使用中可能需要更多的控制点和更复杂的路径。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值