1.定义VelocityView
package sss
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.view.View
import com.example.sosrcpro.R
class VelocityView :View{
private var mCenterX = 0F //中心点x
private var mCenterY = 0F //中心点y
private var minRadia = 0 //次最外圈的半径
private var maxRadia = 0 //最外圈的半径
private var mRectF: RectF? = null //要绘制的圆区域
private var paint:Paint? = null
private var fillPaint:Paint? = null
private var textPaint:Paint? = null
private var smallScalePaint:Paint? = null
private var bigScalePaint:Paint? = null
private var litterCyclePaint:Paint? = null
private var testPaint:Paint? =null
private var colorArray = IntArray(7)
private var paintWidth = 0
private var mCurrentAngle = 195F
private var valueAnimator: ValueAnimator? = null
constructor(context: Context):this(context,null){
}
constructor(context: Context,attributeSet: AttributeSet?):this(context,attributeSet,0){
//从xml中获得用户设定的值
var valueSet = context.obtainStyledAttributes(attributeSet, R.styleable.VelocityView)
minRadia = valueSet.getInteger(R.styleable.VelocityView_min_circle_radio,180)
maxRadia = valueSet.getInteger(R.styleable.VelocityView_max_cicle_radio,300)
paintWidth = maxRadia-minRadia
paint = Paint(Paint.ANTI_ALIAS_FLAG)
fillPaint = Paint(Paint.ANTI_ALIAS_FLAG)
textPaint = Paint(Paint.ANTI_ALIAS_FLAG)
litterCyclePaint = Paint(Paint.ANTI_ALIAS_FLAG)
smallScalePaint = Paint(Paint.ANTI_ALIAS_FLAG)
bigScalePaint = Paint(Paint.ANTI_ALIAS_FLAG)
testPaint = Paint(Paint.ANTI_ALIAS_FLAG)
colorArray[0] = Color.parseColor("#ff0000")
colorArray[1] = Color.parseColor("#1fff00")
colorArray[2] = Color.parseColor("#0fff00")
colorArray[3] = Color.parseColor("#0f0000")
colorArray[4] = Color.parseColor("#001f00")
colorArray[5] = Color.parseColor("#001c00")
colorArray[6] = Color.parseColor("#001000")
var ff = FloatArray(4)
ff[0]=0.1F
ff[1]=0.3F
ff[2]=0.7F
ff[3]=1F
paint?.apply {
isAntiAlias = true
style = Paint.Style.STROKE
strokeWidth = paintWidth.toFloat()
setShader(SweepGradient(mCenterX,mCenterY,colorArray,null))
}
textPaint?.apply {
color = Color.WHITE
style = Paint.Style.FILL
strokeWidth = 2F
textSize = 40F
setTextAlign(Paint.Align.CENTER) //文字居中,不然瞄点会在左下
}
//小圆圈画笔
litterCyclePaint?.apply {
isAntiAlias = true
strokeWidth = 10F
style = Paint.Style.STROKE
setShader(SweepGradient(mCenterX,mCenterY,colorArray,null))
}
//小刻度画笔
smallScalePaint?.apply {
strokeWidth = 5F
color = Color.WHITE
}
//大刻度画笔
bigScalePaint?.apply {
color = Color.WHITE
strokeWidth = 10F
}
//三角指针画笔
fillPaint?.apply {
strokeWidth = 10F
style = Paint.Style.FILL
setShader(SweepGradient(mCenterX,mCenterY,colorArray,null))
}
testPaint?.apply {
isAntiAlias = true
style = Paint.Style.STROKE
strokeWidth=30F
}
//为什么这么多画笔,不重用,是应为重进入界面时会重绘,画笔不还原到最开始的状态就会出错,你参数都改了
//肯定画不出一样的效果了
}
constructor(context: Context,attributeSet: AttributeSet?,type:Int):super(context,attributeSet){
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
mCenterX = measuredWidth/2F
mCenterY = measuredHeight/2F
//RectF就是你的上下左右离父控件的边距
mRectF = RectF(0F+paintWidth/2,0F+paintWidth/2,measuredWidth*1F-paintWidth/2,measuredHeight*1F-paintWidth/2)
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
//大圈
canvas?.drawArc(RectF(mCenterX-mCenterY+paintWidth/2F,0F+paintWidth/2F,mCenterX+mCenterY-paintWidth/2F,mCenterY*2F-paintWidth/2F),20F,-220F,false,paint!!)
//小圈
canvas?.drawCircle(mCenterX,mCenterY,30F,litterCyclePaint!!)
//刻度盘
drawDial(canvas)
//三角指针
drawAngleByAngle(canvas,mCurrentAngle)
}
fun drawDial(canvas: Canvas?){
//大刻度
var per = 210F/50F;
var flag = 0
var starA = -15F;
for(angle in 0..50){
var finalAngle = starA+angle*per
var value = finalAngle/180F*Math.PI
var starPointX = mCenterX+Math.cos(value)*mCenterY
var starPointY = mCenterY-Math.sin(value)*mCenterY
var minRadiu = mCenterY-paintWidth/3F
var endPointX = mCenterX+Math.cos(value)*minRadiu
var entPointY = mCenterY-Math.sin(value)*minRadiu
if(angle!=0)
canvas?.drawLine(starPointX.toFloat(),starPointY.toFloat(),endPointX.toFloat(),entPointY.toFloat(),smallScalePaint!!)
if(flag%5==0){
var minR = mCenterY-paintWidth/2F
var endPointXX = mCenterX+Math.cos(value)*minR
var entPointYY = mCenterY-Math.sin(value)*minR
canvas?.drawLine(starPointX.toFloat(),starPointY.toFloat(),endPointXX.toFloat(),entPointYY.toFloat(),bigScalePaint!!)
var pointT = getPointByAngleAndRadiu(finalAngle,mCenterY-paintWidth/1F+30F,mCenterX,mCenterY)
var ss:String = (150F-angle*3F).toInt().toString()
canvas?.drawText(ss,pointT.x,pointT.y+10F,textPaint!!)
}
flag++
}
}
//根据角度,半径,中心点得到对应的的坐标
fun getPointByAngleAndRadiu(angle:Float,radiu:Float,centerX:Float,centerY:Float):PointF
{
var radian = angle/180F*Math.PI
var x = centerX+Math.cos(radian)*radiu
var y = centerY-Math.sin(radian)*radiu
return PointF(x.toFloat(),y.toFloat())
}
//画三角指针
fun drawAngleByAngle(canvas: Canvas?,angle: Float){
var angelW = 10F
var path = Path()
var radiu = mCenterY-paintWidth; //指针所在圆的半径
path.reset()
var point1 = getPointByAngleAndRadiu(angle,radiu,mCenterX,mCenterY)
var point2 = getPointByAngleAndRadiu(angle-90F,angelW,mCenterX,mCenterY)
var point3 = getPointByAngleAndRadiu(angle+90F,angelW,mCenterX,mCenterY)
path.moveTo(point1.x,point1.y) //起点
path.lineTo(point2.x,point2.y)
path.lineTo(point3.x,point3.y)
path.lineTo(point1.x,point1.y) //最后连线到起点
canvas?.drawPath(path,fillPaint!!)
canvas?.drawArc(RectF(mCenterX-angelW,mCenterY-angelW,mCenterX+angelW,mCenterY+angelW),90F-angle,180F,false,fillPaint!!)
}
//提供外部接口设置转动角度
fun setNextValue(next:Float){
var per = 2F/210F
var temCur = mCurrentAngle
var time = Math.abs(next-mCurrentAngle)*per
valueAnimator?.also {
it.cancel()
}
valueAnimator = ValueAnimator.ofFloat(0F,1F)
valueAnimator?.duration = if(time.toLong()*1000L<500) 500L else time.toLong()*1000L
valueAnimator?.addUpdateListener(object :ValueAnimator.AnimatorUpdateListener{
override fun onAnimationUpdate(animation: ValueAnimator) {
var ss = animation.animatedValue as Float
mCurrentAngle = temCur+(next-temCur)*(animation.animatedValue as Float)
invalidate() // invalidate() 会重新绘制View,由于参数不断的修改,不断的OnDraW,这就有了动态的效果
}
})
valueAnimator?.start()
}
}
2.为了能在xml布局中设置参数,在values中添加attrs.xml文件
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="VelocityView">
<attr name="min_circle_radio" format="integer"/>
<attr name="max_cicle_radio" format="integer"/>
</declare-styleable>
</resources>
3.xml中引用
<sss.VelocityView
android:layout_width="match_parent"
android:layout_height="300dp"
app:min_circle_radio="150"
app:max_cicle_radio="300"
android:id="@+id/velocity_test"
/>
4.改变指针值,代码里的角度值为-15到195
btn.setOnClickListener {
var value = (-15..195).random()
velocity_test.setNextValue(value.toFloat())
}
5效果图
可以参考思路来源