手撸一个空调温度调节自定义控件,效果如下
这个控件的难点主要是手势控制,其他的都很简单
说下思路吧
* 首先绘制圆盘,刻度,阴影(需要关闭硬件加速),文字
* 然后根据划过的角度绘制进度条
* 最后根绝touch事件重新绘制,并设置数据回调
代码如下
class TempView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : View(context, attrs, defStyleAttr) {
//画外部的圆的画笔
private val mOutCirclePaint = Paint(Paint.ANTI_ALIAS_FLAG)
//画内部圆的画笔
private val mInCirclePaint = Paint(Paint.ANTI_ALIAS_FLAG)
//画外边缘的线的画笔
private val mGrayLinePaint = Paint(Paint.ANTI_ALIAS_FLAG)
//画中间文字的画笔
private val mTextPaint = Paint(Paint.ANTI_ALIAS_FLAG)
//画进度的画笔1
private val mLineProgressPaint = Paint(Paint.ANTI_ALIAS_FLAG)
private val mProgressCirclePaint = Paint(Paint.ANTI_ALIAS_FLAG)
private val mProgressPaint = Paint(Paint.ANTI_ALIAS_FLAG)
// 控件宽
private var mWidth: Int = 0
// 控件高
private var mHeight: Int = 0
// 刻度盘半径
private var dialRadius: Int = 0
// 中间圆半径
private var arcRadius: Int = 0
private var mBgRectf: RectF? = null
private var currentAngle: Float = 0f
// 当前按钮旋转的角度
private var rotateAngle: Float = 0.toFloat()
//初始化温度值
private var temText: Int = 15
private var isDown: Boolean = false
private var isMove: Boolean = false
private var downX: Float = 0f
private var downY: Float = 0f
init {
mOutCirclePaint.color = Color.WHITE
mOutCirclePaint.style = Paint.Style.STROKE
mOutCirclePaint.setShadowLayer(15f, 0f, 0f, Color.parseColor("#836FFF"))
mInCirclePaint.color = Color.WHITE
mInCirclePaint.strokeWidth = 1f
mInCirclePaint.style = Paint.Style.FILL
mInCirclePaint.setShadowLayer(15f, 0f, 0f, Color.parseColor("#836FFF"))
mGrayLinePaint.color = Color.parseColor("#88836FFF")
mGrayLinePaint.strokeWidth = dp2px(1f)
mGrayLinePaint.style = Paint.Style.STROKE
mTextPaint.textSize = sp2px(32f)
mTextPaint.style = Paint.Style.FILL
mTextPaint.color = Color.parseColor("#436EEE")
mTextPaint.textAlign = Paint.Align.CENTER
mTextPaint.setFakeBoldText(true)
mLineProgressPaint.style = Paint.Style.STROKE
mLineProgressPaint.strokeWidth = dp2px(10f)
mLineProgressPaint.color = Color.parseColor("#436EEE")
// mLineProgressPaint.setShadowLayer(15f, 0f, 0f, Color.GRAY)
// mLineProgressPaint.strokeCap = Paint.Cap.ROUND
mProgressCirclePaint.style = Paint.Style.FILL
mProgressPaint.style = Paint.Style.STROKE
mProgressPaint.strokeWidth = dp2px(8f)
mProgressPaint.color = Color.parseColor("#436EEE")
mProgressPaint.strokeCap = Paint.Cap.ROUND
mProgressPaint.setShadowLayer(15f, 0f, 0f, Color.GRAY)
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val width = View.MeasureSpec.getSize(widthMeasureSpec)
val height = View.MeasureSpec.getSize(heightMeasureSpec)
val imageSize = if (width < height) width else height
setMeasuredDimension(imageSize, imageSize)
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
mHeight = Math.min(h, w)
mWidth = height
dialRadius = (width / 2 - dp2px(10f)).toInt()
//设置外圆的宽度
mOutCirclePaint.strokeWidth = dialRadius / 3f
arcRadius = dialRadius / 2
mBgRectf = RectF(-dialRadius * 1f / 2 - dp2px(5f), -dialRadius * 1f / 2 - dp2px(5f), dialRadius * 1f / 2 + dp2px(5f), dialRadius * 1f / 2 + dp2px(5f))
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.translate(mWidth * 1f / 2, mHeight * 1f / 2);
drawBgCircle(canvas)
drawInCircle(canvas)
drawLine(canvas)
drawText(canvas)
drawProgress(canvas)
drawPointLine(canvas)
}
private val drawPointLine = { canvas: Canvas ->
canvas.save()
canvas.rotate(rotateAngle + 2)
val point = dialRadius * 1f / 2 + dp2px(5f)
canvas.drawLine(point - dialRadius / 6, 0f, point + dialRadius / 6, 0f, mProgressPaint)
canvas.restore()
}
private val drawProgress = { canvas: Canvas ->
if (rotateAngle>0){
mProgressCirclePaint.color =Color.parseColor("#98F5FF")
canvas.drawCircle(dialRadius*1f/2+dp2px(5f),0f,dp2px(5f),mProgressCirclePaint)
}
val colors = intArrayOf(Color.parseColor("#98F5FF"), Color.parseColor("#8470FF"))
val mShader = SweepGradient(0f, 0f, colors, null)
mLineProgressPaint.shader = mShader
canvas.drawArc(mBgRectf, 0f, rotateAngle, false, mLineProgressPaint)
}
private val drawText = { canvas: Canvas ->
val baseLineY = Math.abs(mTextPaint.ascent() + mTextPaint.descent()) / 2
canvas.drawText("${temText}°", 0f, baseLineY, mTextPaint)
}
private val drawLine = { canvas: Canvas ->
canvas.save()
var beginAngle: Float = 0f
for (i in 0..144) {
canvas.save()
canvas.rotate(beginAngle)
canvas.drawLine(dialRadius * 7f / 9, 0f, dialRadius * 8f / 9, 0f, mGrayLinePaint)
beginAngle += 2.5f
canvas.restore()
}
canvas.restore()
}
private val drawInCircle = { canvas: Canvas ->
canvas.drawCircle(0f, 0f, arcRadius.toFloat(), mInCirclePaint)
}
private val drawBgCircle = { canvas: Canvas ->
canvas.drawCircle(0f, 0f, dialRadius.toFloat() * 5 / 6, mOutCirclePaint)
}
override fun onTouchEvent(event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
isDown = true
downX = event.x
downY = event.y
currentAngle = calcAngle(downX, downY)
}
MotionEvent.ACTION_MOVE -> {
isMove = true
val targetX: Float
val targetY: Float
targetX = event.x
targetY = event.y
val angle = calcAngle(targetX, targetY)
// 滑过的角度增量
var angleIncreased = angle - currentAngle
if (angleIncreased < -180) {
angleIncreased = angleIncreased + 360
} else if (angleIncreased > 180) {
angleIncreased = angleIncreased - 360
}
IncreaseAngle(angleIncreased)
currentAngle = angle
invalidate()
// 滑过的角度增量
}
MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> {
if (isDown) {
if (isMove) {
temparetureChangeListner?.invoke(temText)
isMove = false
}
isDown = false
}
}
}
return true
}
/**
* 增加旋转角度
*
* @param angle 增加的角度
*/
private val IncreaseAngle = { angle: Float ->
rotateAngle += angle
if (rotateAngle < 0) {
rotateAngle = 0f
} else if (rotateAngle > 360f) {
rotateAngle = 360f
}
temText = (rotateAngle / 360 * 15).toInt() + 15
}
/**
* 以按钮圆心为坐标圆点,建立坐标系,求出(targetX, targetY)坐标与x轴的夹角
*
* @param targetX x坐标
* @param targetY y坐标
* @return (targetX, targetY)坐标与x轴的夹角
*/
private val calcAngle = { targetX: Float, targetY: Float ->
val x = targetX - width / 2
val y = targetY - height / 2
val radian: Double
if (x != 0f) {
val tan = Math.abs(y / x)
if (x > 0) {
if (y >= 0) {
radian = Math.atan(tan.toDouble())
} else {
radian = 2 * Math.PI - Math.atan(tan.toDouble())
}
} else {
if (y >= 0) {
radian = Math.PI - Math.atan(tan.toDouble())
} else {
radian = Math.PI + Math.atan(tan.toDouble())
}
}
} else {
if (y > 0) {
radian = Math.PI / 2
} else {
radian = -Math.PI / 2
}
}
(radian * 180 / Math.PI).toFloat()
}
var temparetureChangeListner: ((Int) -> Unit)? = null
}