实现类似万能钥匙信号检测View,主要在于图片旋转的控制,而图片的旋转等操作则离不开Matrix。
本篇充分利用的Matrx的各种变换,以实现动态旋转的view。
先上效果图:
一、 实现动态旋转指示的主要过程如下:
(1)计算缩放倍数、旋转角度,使相关图片调整到设置大小
注意:指针、背景、指示表的中心
其中主要涉及到的Matrix的使用:
//缩放倍数
val scaleX = mViewWidth / mSpeedPointerBitmapSource!!.width.toFloat()
val scaleY = mViewHeight / mSpeedPointerBitmapSource!!.height.toFloat()
//缩放
mSpeedBitmapMatrix!!.postScale(scaleX, scaleY)
//平移
mSpeedBitmapMatrix!!.postTranslate(paddingLeft.toFloat(), paddingTop.toFloat())
//绕中心旋转
mSpeedBitmapMatrix!!.postRotate(mCurrentAngle.toFloat(), mViewCenterX, mViewCenterY)
//外层
val matrix = Matrix()
matrix.postScale(mViewWidth.toFloat() / mSpeedProcessBitmapSource!!.width, mViewHeight.toFloat() / mSpeedProcessBitmapSource!!.height.toFloat())
mSpeedProcessBitmap = Bitmap.createBitmap(mSpeedProcessBitmapSource!!, 0, 0, mSpeedProcessBitmapSource!!.width, mSpeedProcessBitmapSource!!.height, matrix, true)
//刻度盘
mSpeedDialBitmap = Bitmap.createScaledBitmap(mSpeedDialBitmap, mViewWidth, mViewHeight, false)
(2)绘制指针、进度、刻度盘等图片及文字信息
mPaint.isAntiAlias = true
mPaint.color = ContextCompat.getColor(context, android.R.color.white)
//绘制刻度盘
canvas?.drawBitmap(mSpeedDialBitmap, paddingLeft.toFloat(), paddingTop.toFloat(), null)
//绘制圆环
canvas?.drawBitmap(mSpeedProcessBitmap, paddingLeft.toFloat(), paddingTop.toFloat(), null)
//绘制指针
canvas?.drawBitmap(mSpeedPointerBitmapSource, mSpeedBitmapMatrix, null)
//绘制中间指示数值
mPaint.textSize = DensityUtils.sp2px(context!!, 20f).toFloat()
mPaint.textAlign = Paint.Align.CENTER
canvas?.drawText("${(mCurrentAngle / MAX_ANGLE_DEFAULT.toFloat() * 100).toInt()}%", mViewCenterX, mViewCenterY + DensityUtils.sp2px(context!!, 6f).toFloat(), mPaint)
mPaint.color = ContextCompat.getColor(context, android.R.color.darker_gray)
//绘制底部名称
canvas?.drawText(mTargetName, mViewCenterX, height - paddingBottom - DensityUtils.sp2px(context!!, 20f).toFloat(), mPaint)
(3)绘制遮挡层
//设置遮挡圆环的参数
mPaint.style = Paint.Style.STROKE
mPaint.alpha = 240
//环形的宽度
mPaint.strokeWidth = DensityUtils.dp2px(context, 15f).toFloat()
val x = mViewWidth / 2f - DensityUtils.dp2px(context, 41.5f)//41.5dp是图片边缘距离圆环处的距离
val y = mViewHeight / 2f - DensityUtils.dp2px(context, 41.5f)
//绘制圆环遮挡层
val rectF = RectF(mViewCenterX - x, mViewCenterY - y, mViewCenterY + x, mViewCenterY + y)
canvas?.drawArc(rectF, 135f + mCurrentAngle, MAX_ANGLE_DEFAULT - mCurrentAngle.toFloat(), false, mPaint)
(4)计算遮挡层的旋转角度
//初次会转动到最大角度后,返回到指定角度
if (!firstCheck) {
mCurrentAngle += mAngleSpeed
if (mCurrentAngle >= MAX_ANGLE_DEFAULT) {
firstCheck = true
}
} else {
if (mCurrentAngle >= mMaxAngle) {
mCurrentAngle -= mAngleSpeed
}
}
二、 WRAP_CONTENT及PADDING的适配
(1)WRAP_CONTENT适配
当设置WRAP_CONTENT时,则使用默认的大小。
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val widthMeasureSpecMode = MeasureSpec.getMode(widthMeasureSpec)
val widthMeasureSpecSize = MeasureSpec.getSize(widthMeasureSpec)
val heightMeasureSpecMode = MeasureSpec.getMode(heightMeasureSpec)
val heightMeasureSpecSize = MeasureSpec.getSize(heightMeasureSpec)
if (widthMeasureSpecMode == MeasureSpec.AT_MOST && heightMeasureSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(mMaxWidth, mMaxHeight)
} else if (widthMeasureSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(mMaxWidth, heightMeasureSpecSize)
} else if (heightMeasureSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(widthMeasureSpecSize, mMaxHeight)
}
}
(2)PADDING的适配
计算图片的大小时计入padding
mViewWidth = width - paddingLeft - paddingRight
mViewHeight = height - paddingTop - paddingBottom
三、说明
(1)关于遮挡层
由于遮挡层的宽度无法算精确,可能在部分手机上无法完全遮挡住指示,可以根据具体情况调整遮挡层的宽度
(2)代码
代码已上传github,如需源码,可访问Github SignalView