效果是这样的,公司需求,画了一天,头皮发麻。
直接把代码贴上吧,懒得讲了,下班。
难点就一个,刻度的渐变效果怎么停下来。我想了半天,最后想到了,到了值,直接把渐变设为null即可。
代码直接Copy是肯定用不了的,只是一种思路,看看就好
View的代码
/**
* 风速计首页表盘View
* @author cym
*/
class AneDialView @JvmOverloads constructor(
mContext: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(mContext, attrs, defStyleAttr) {
// 画笔
private val mPaint: Paint = Paint()
// 下面的渐变蒙版
private lateinit var mBottomMask: Shader
// 刻度渐变
private lateinit var mGraduationShader: Shader
// 圆心坐标
private var mCircleX = 0f
private var mCircleY = 0f
private var mCircleRadius = 0f
// 圆矩形
private lateinit var mRectCircle: RectF
// 刻度弧矩形
private lateinit var mRectGraduation: RectF
// 刻度字矩形
private lateinit var mRectText: RectF
// 里面大圆矩形
private lateinit var mRectBoard: RectF
// 下面的渐变蒙版矩形
private lateinit var mRectBottomMask: RectF
// 弧度,不要改。如果要改,计算渐变的地方也要手动改,懒得判断了
private var mSweepAngle = 252f
// 刻度圆的大小
private var mCircleRadiusSmall = dp2px(1.5f)
private var mCircleRadiusMiddle = dp2px(3f)
private var mCircleRadiusLarge = dp2px(4f)
// 颜色
private val mColorWhite = ContextCompat.getColor(mContext, R.color.colorWhite)
private val mColorGray = ContextCompat.getColor(mContext, R.color.colorFontGray)
private val mColorGraduation = ContextCompat.getColor(mContext, R.color.colorGraduation)
private val mColorGraduationStart = ContextCompat.getColor(mContext, R.color.colorGraduationStart)
private val mColorGraduationMiddle = ContextCompat.getColor(mContext, R.color.colorGraduationMiddle)
private val mColorGraduationEnd = ContextCompat.getColor(mContext, R.color.colorGraduationEnd)
private val mColorBottomMaskStart = ContextCompat.getColor(mContext, R.color.colorBottomMaskStart)
private val mColorBottomMaskEnd = ContextCompat.getColor(mContext, R.color.colorBottomMaskEnd)
// 数字字体
private val mTypefaceNum: Typeface = Typeface.createFromAsset(context.assets, "Oswald-Regular.ttf")
// 文本是否亮。懒得写getSet了,直接在外面设置就好
var mIsShowNum = true
var mIsShowM = true
var mIsShowFT = false
var mIsShowINHG = false
var mIsShowHPA = false
var mIsShowMBAR = false
var mIsShowMS = false
var mIsShowKMH = false
var mIsShowFTMIN = true
var mIsShowKNOTS = false
var mIsShowMPH = false
var mIsShowMAX = true
var mIsShowMIN = false
var mIsShowAVG = false
var mIsShowALT = false
var mIsShowRH = false
var mIsShowDP = false
var mIsShowWCL = false
var mIsShowC = true
var mIsShowF = false
// 当前风的级数,风的级数从0~12,小于0代表没连接设备
private var mCurLv = -1f
// 初始化
init {
mPaint.isAntiAlias = true
// 随机设置风级
mCurLv = (-10..140).random().toFloat() / 10f
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
var width = dp2px(50f).toInt()
var height = dp2px(50f).toInt()
// 不是wrap
if (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.AT_MOST) {
width = MeasureSpec.getSize(widthMeasureSpec)
}
if (MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.AT_MOST) {
height = MeasureSpec.getSize(heightMeasureSpec)
}
setMeasuredDimension(width, height)
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
// 圆的半径是不变的
mCircleRadius = w / 2f - dp2px(1f)
mCircleX = w / 2f
mCircleY = w / 2f
// 圆矩形和圆是一样大的
mRectCircle = RectF(
mCircleX - mCircleRadius,
mCircleY - mCircleRadius,
mCircleX + mCircleRadius,
mCircleY + mCircleRadius
)
// 刻度矩形
mRectGraduation = RectF(
mCircleX - mCircleRadius + dp2px(16f),
mCircleY - mCircleRadius + dp2px(16f),
mCircleX + mCircleRadius - dp2px(16f),
mCircleY + mCircleRadius - dp2px(16f)
)
// 刻度矩形
mRectText = RectF(
mCircleX - mCircleRadius + dp2px(32f),
mCircleY - mCircleRadius + dp2px(32f),
mCircleX + mCircleRadius - dp2px(32f),
mCircleY + mCircleRadius - dp2px(32f)
)
// 里面部分矩形
mRectBoard = RectF(
mCircleX - mCircleRadius + dp2px(40f),
mCircleY - mCircleRadius + dp2px(40f),
mCircleX + mCircleRadius - dp2px(40f),
mCircleY + mCircleRadius - dp2px(40f)
)
// 底部渐变蒙版矩形
mRectBottomMask = RectF(
0f,
h - dp2px(50f),
w * 1.0f,
h * 1.0f
)
// 底部渐变蒙版
mBottomMask = LinearGradient(
0f,
mRectBottomMask.top,
0f,
mRectBottomMask.bottom,
mColorBottomMaskStart,
mColorBottomMaskEnd,
Shader.TileMode.CLAMP
)
// 刻度渐变
val arrayColor = intArrayOf(
mColorGraduationEnd,
mColorGraduationStart,
mColorGraduationStart,
mColorGraduationMiddle,
mColorGraduationEnd
)
val arrayPosition = floatArrayOf(0f, 0.25f, (9 * 18f / 360f), 0.75f, 1.0f)
mGraduationShader = SweepGradient(mCircleX, mCircleY, arrayColor, arrayPosition)
}
override fun onDraw(canvas: Canvas) {
// 左下角的角度
val startAngle = 180 - (mSweepAngle - 180) / 2f
// 画圆形圆弧
mPaint.color = mColorWhite
mPaint.style = Paint.Style.STROKE
mPaint.strokeCap = Paint.Cap.ROUND
mPaint.strokeWidth = dp2px(2f)
canvas.drawArc(mRectCircle, startAngle, mSweepAngle, false, mPaint)
// 画刻度圆弧背景
mPaint.color = mColorGraduation
mPaint.strokeWidth = dp2px(16f)
canvas.drawArc(mRectGraduation, startAngle, mSweepAngle, false, mPaint)
// 设置圆点扫描渐变,到了当前风级清除渐变,就能实现这种效果
mPaint.shader = mGraduationShader
// 判断当前级数的度数
val curAngle = if (mCurLv < 0) {
// 还没有连上设备
-1f
} else {
// 已经连上设备了
startAngle + (mCurLv + 1) / 12f * 216f
}
// 使用角度算XY坐标画圆点
mPaint.color = mColorWhite
mPaint.style = Paint.Style.FILL
mPaint.strokeWidth = 1f
val graduationRadius = (mRectGraduation.width() / 2f).toInt()
for (i in 0 until 71) {
val angle = startAngle + i * (mSweepAngle / 70f)
val x = getXByDegrees(mCircleX.toInt(), graduationRadius, angle).toFloat()
val y = getYByDegrees(mCircleY.toInt(), graduationRadius, angle).toFloat()
// 判断圆的大小
val radius = when {
i % 10 == 0 -> {
// 中圆
mCircleRadiusMiddle
}
i % 5 == 0 -> {
// 大圆
mCircleRadiusLarge
}
else -> {
// 小圆
mCircleRadiusSmall
}
}
if (angle > curAngle) {
// 清除渐变
mPaint.shader = null
}
canvas.drawCircle(x, y, radius, mPaint)
}
mPaint.shader = null
// 画里面的大圆弧背景
mPaint.color = mColorGraduation
mPaint.style = Paint.Style.FILL
canvas.drawCircle(mCircleX, mCircleY, mRectBoard.width() / 2f, mPaint)
// 画底部蒙版
mPaint.color = mColorWhite
mPaint.strokeWidth = 1f
mPaint.shader = mBottomMask
canvas.drawRect(mRectBottomMask, mPaint)
mPaint.shader = null
// 画刻度圆点上的文本。旋转画布会让文字方向也旋转,所以不能使用旋转画布
mPaint.textAlign = Paint.Align.CENTER
mPaint.textSize = dp2px(12f)
val textRadius = (mRectText.width() / 2f).toInt()
for (i in 0..12 step 2) {
val angle = startAngle + (i + 1) * (mSweepAngle / 14)
val x = getXByDegrees(mCircleX.toInt(), textRadius, angle).toFloat()
val y = getYByDegrees(mCircleY.toInt(), textRadius, angle).toFloat()
canvas.drawText(i.toString(), x, getBaseline(y), mPaint)
}
// 画表盘上的文字
var x = 0f
var y = 0f
// Wind Value
mPaint.typeface = Typeface.DEFAULT
mPaint.color = mColorWhite
mPaint.textSize = dp2px(14f)
x = mCircleX
y = mRectBoard.top + dp2px(30f)
canvas.drawText("Wind Value", x, getBaseline(y), mPaint)
// 中心数字
mPaint.typeface = mTypefaceNum
mPaint.color = if (mIsShowNum) mColorWhite else mColorGray
mPaint.textSize = dp2px(52f)
x = mCircleX
y = mCircleY - dp2px(30f)
canvas.drawText("000.0", x, getBaseline(y), mPaint)
// M
mPaint.textAlign = Paint.Align.LEFT
mPaint.typeface = Typeface.DEFAULT
mPaint.color = if (mIsShowM) mColorWhite else mColorGray
mPaint.textSize = dp2px(11f)
x = mCircleX - mRectBoard.width() / 2f + dp2px(18f)
y = mCircleY - dp2px(48f)
canvas.drawText("M", x, getBaseline(y), mPaint)
// FT
mPaint.color = if (mIsShowFT) mColorWhite else mColorGray
x = mCircleX - mRectBoard.width() / 2f + dp2px(30f)
y = mCircleY - dp2px(48f)
canvas.drawText("FT", x, getBaseline(y), mPaint)
// inHg
mPaint.color = if (mIsShowINHG) mColorWhite else mColorGray
x = mCircleX - mRectBoard.width() / 2f + dp2px(18f)
y = mCircleY - dp2px(36f)
canvas.drawText("inHg", x, getBaseline(y), mPaint)
// hpa
mPaint.color = if (mIsShowHPA) mColorWhite else mColorGray
x = mCircleX - mRectBoard.width() / 2f + dp2px(18f)
y = mCircleY - dp2px(24f)
canvas.drawText("hpa", x, getBaseline(y), mPaint)
// mbar
mPaint.color = if (mIsShowMBAR) mColorWhite else mColorGray
x = mCircleX - mRectBoard.width() / 2f + dp2px(18f)
y = mCircleY - dp2px(12f)
canvas.drawText("mbar", x, getBaseline(y), mPaint)
// m/s
mPaint.color = if (mIsShowMS) mColorWhite else mColorGray
x = mCircleX + mRectBoard.width() / 2f - dp2px(42f)
y = mCircleY - dp2px(54f)
canvas.drawText("m/s", x, getBaseline(y), mPaint)
// km/h
mPaint.color = if (mIsShowKMH) mColorWhite else mColorGray
x = mCircleX + mRectBoard.width() / 2f - dp2px(42f)
y = mCircleY - dp2px(42f)
canvas.drawText("km/h", x, getBaseline(y), mPaint)
// ft/min
mPaint.color = if (mIsShowFTMIN) mColorWhite else mColorGray
x = mCircleX + mRectBoard.width() / 2f - dp2px(42f)
y = mCircleY - dp2px(30f)
canvas.drawText("ft/min", x, getBaseline(y), mPaint)
// Knots
mPaint.color = if (mIsShowKNOTS) mColorWhite else mColorGray
x = mCircleX + mRectBoard.width() / 2f - dp2px(42f)
y = mCircleY - dp2px(18f)
canvas.drawText("Knots", x, getBaseline(y), mPaint)
// mph
mPaint.color = if (mIsShowMPH) mColorWhite else mColorGray
x = mCircleX + mRectBoard.width() / 2f - dp2px(42f)
y = mCircleY - dp2px(6f)
canvas.drawText("mph", x, getBaseline(y), mPaint)
// MAX
mPaint.textAlign = Paint.Align.CENTER
mPaint.textSize = dp2px(14f)
mPaint.color = if (mIsShowMAX) mColorWhite else mColorGray
x = mRectBoard.left + mRectBoard.width() / 5f * 1
y = mCircleY + dp2px(18f)
canvas.drawText("MAX", x, getBaseline(y), mPaint)
// MIN
mPaint.color = if (mIsShowMIN) mColorWhite else mColorGray
x = mRectBoard.left + mRectBoard.width() / 5f * 2
y = mCircleY + dp2px(18f)
canvas.drawText("MIN", x, getBaseline(y), mPaint)
// AVG
mPaint.color = if (mIsShowAVG) mColorWhite else mColorGray
x = mRectBoard.left + mRectBoard.width() / 5f * 3
y = mCircleY + dp2px(18f)
canvas.drawText("AVG", x, getBaseline(y), mPaint)
// ALT
mPaint.color = if (mIsShowALT) mColorWhite else mColorGray
x = mRectBoard.left + mRectBoard.width() / 5f * 4
y = mCircleY + dp2px(18f)
canvas.drawText("ALT", x, getBaseline(y), mPaint)
// RH%
mPaint.color = if (mIsShowRH) mColorWhite else mColorGray
x = mRectBoard.left + mRectBoard.width() / 6f * 1
y = mCircleY + dp2px(42f)
canvas.drawText("RH%", x, getBaseline(y), mPaint)
// DP
mPaint.color = if (mIsShowDP) mColorWhite else mColorGray
x = mRectBoard.left + mRectBoard.width() / 6f * 2
y = mCircleY + dp2px(42f)
canvas.drawText("DP", x, getBaseline(y), mPaint)
// WCL
mPaint.color = if (mIsShowWCL) mColorWhite else mColorGray
x = mRectBoard.left + mRectBoard.width() / 6f * 3
y = mCircleY + dp2px(42f)
canvas.drawText("WCL", x, getBaseline(y), mPaint)
// ℃
mPaint.color = if (mIsShowC) mColorWhite else mColorGray
x = mRectBoard.left + mRectBoard.width() / 6f * 4
y = mCircleY + dp2px(42f)
canvas.drawText("℃", x, getBaseline(y), mPaint)
// ℉
mPaint.color = if (mIsShowF) mColorWhite else mColorGray
x = mRectBoard.left + mRectBoard.width() / 6f * 5
y = mCircleY + dp2px(42f)
canvas.drawText("℉", x, getBaseline(y), mPaint)
// 底部温度
mPaint.typeface = Typeface.DEFAULT
mPaint.color = mColorWhite
mPaint.textAlign = Paint.Align.CENTER
mPaint.textSize = dp2px(18f)
x = mCircleX
y = height - dp2px(10f)
canvas.drawText("温度 --", x, getBaseline(y), mPaint)
}
/**
* dp转px
* @param dpValue dp
*/
private fun dp2px(dpValue: Float): Float {
val scale = resources.displayMetrics.density
return dpValue * scale + 0.5f
}
/**
* 根据y计算baseline
* @param y 文本的y
*/
private fun getBaseline(y: Float): Float {
return y + (mPaint.fontMetrics.bottom - mPaint.fontMetrics.top) / 2 - mPaint.fontMetrics.bottom
}
// 根据圆心X,半径,角度计算X坐标
private fun getXByDegrees(centerX: Int, radius: Int, degrees: Float): Int {
return (centerX + radius * cos(degrees * Math.PI / 180)).toInt()
}
// 根据圆心Y,半径,角度计算Y坐标
private fun getYByDegrees(centerY: Int, radius: Int, degrees: Float): Int {
return (centerY + radius * sin(degrees * Math.PI / 180)).toInt()
}
// 根据圆心XY,坐标XY,计算弧度
private fun getDegreesByXY(centerX: Int, centerY: Int, x: Int, y: Int): Float {
var degrees = Math.toDegrees(atan2((y - centerY).toDouble(), (x - centerX).toDouble())).toFloat()
if (degrees < 0) {
degrees += 360f
}
return degrees
}
}
color.xml
<resources>
<color name="colorWhite">#ffffff</color>
<color name="colorMain">#3e82fe</color>
<color name="colorFontGray">#4fffffff</color>
<color name="colorGraduation">#2565D6</color>
<color name="colorGraduationStart">#60ff40</color>
<color name="colorGraduationMiddle">#ffce57</color>
<color name="colorGraduationEnd">#ff4d4d</color>
<color name="colorBottomMaskStart">#003e82fe</color>
<color name="colorBottomMaskEnd">#ff3e82fe</color>
</resources>
布局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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorMain">
<!-- 顶部布局 -->
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cons_top"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent">
<!-- 返回按钮 -->
<ImageView
android:id="@+id/iv_back"
android:layout_width="50dp"
android:layout_height="50dp"
android:scaleType="center"
android:src="@drawable/back_white"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<!-- 蓝牙状态 -->
<TextView
android:id="@+id/tv_connect_state"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/scaning_device_title"
android:textColor="@color/colorWhite"
android:textSize="14dp"
app:layout_constraintBottom_toBottomOf="@id/iv_back"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/iv_back" />
<!-- 表盘View -->
<cn.net.aicare.moudleAnemometer.view.AneDialView
android:id="@+id/ane_dial_view"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintDimensionRatio="w, 615:817"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/iv_back"
app:layout_constraintWidth_percent="0.838" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
这是画出来的??不用贴图嘛
我也想用贴图啊,但是这些文本都要控制亮还是不亮