最近复习了自定义view相关知识,由于也在练习kotlin的编码方式,所以就用kotlin写了一个轮盘控件,有需要的可以借鉴一下,相关错误可以回复我。
这一篇主要是复习自定义view的相关知识如下:
1.自定义view的相关属性配置,如颜色,大小等。
2.自定义view的绘制方式,如画圆,画刻度,线条等。
3.自定义view的触控滚动事件添加。
下一篇我会增加相关其他知识点:
如动画效果,如手指拨动之后的惯性滚动效果,两点触控的缩放效果,动画的相关插值器效果,事件分发等。
现在进入正片:
相关效果如图:
首先是导入相关自定义view属性:
在kotlin中,自定义view的初始化操作都是在init方法里面进行的,所以第一步是需要在init方法里面获取相关属性值。
init {
var typeArray: TypedArray = context.theme.obtainStyledAttributes(attrs, R.styleable.MyView, 0, 0)
try {
border_color = typeArray.getColor(R.styleable.MyView_border_color, R.color.material_blue_grey_800)
border_withd = typeArray.getDimension(R.styleable.MyView_border_withd, 2.0F)
} finally {
typeArray.recycle()
}
...
}
相关属性文件也是需要在values里面增加一个attrs的xml资源文件,相关内容如下:
目前只给定了颜色和绘制宽度。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MyView">
<attr name="border_color" format="color" />
<attr name="border_withd" format="dimension" />
</declare-styleable>
</resources>
接下来需要根据控件大小给定相关尺寸,这个方法会在初始化之后执行:
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
rectF = RectF(left.toFloat(), top.toFloat(), right.toFloat(), bottom.toFloat())
withd = rectF!!.right - rectF!!.left
height = rectF!!.bottom - rectF!!.top
radius = if (withd!! < height!!) withd!! / 4 else height!! / 4
smallLength = 15F
largerLength = 30F
}
相关变量说明如下:
internal var border_color: Int? = null//指定的颜色
internal var border_withd: Float? = null//指定的宽度
internal var paint: Paint? = null//画笔
internal var rectF: RectF? = null//矩形区域
internal var withd: Float? = null//控件宽度
internal var height: Float? = null//控件高度
internal var radius: Float? = null//圆盘半径
internal var smallLength: Float? = null//短刻度长度
internal var largerLength: Float? = null//长刻度长度
internal var actAgree: Float? = null//变化角度
internal var nowAgree: Float? = null//当前角度
一切就绪之后就是绘制操作了,上代码:
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
//绘制一个圆角矩形边框
canvas?.drawColor(0xFF000000.toInt())
paint?.color = 0x66555555
canvas?.drawRoundRect(RectF(rectF!!.centerX() - 0.9F * withd!! / 2, rectF!!.centerY() - 0.9F * height!! / 2, rectF!!.centerX() + 0.9F * withd!! / 2, rectF!!.centerY() + 0.9F * height!! / 2), 30F, 30F, paint)
//绘制一个半径为radius的圆
paint?.color = border_color!!
canvas?.drawCircle(rectF!!.centerX(), rectF!!.centerY(), radius!!, paint)
//绘制圆上的刻度
var startX: Float? = null
var startY: Float? = null
var endX: Float? = null
var endY: Float? = null
for (i: Int in 1..60) {
var agree = Math.PI / 180 * (i * 6 + actAgree!!)//这行代码用于指定刻度盘的触摸滚动
startX = radius!! * Math.cos(agree).toFloat()
startY = radius!! * Math.sin(agree).toFloat()
if (i % 5 == 0) {
endX = startX + largerLength!! * Math.cos(agree).toFloat()
endY = startY + largerLength!! * Math.sin(agree).toFloat()
} else {
endX = startX + smallLength!! * Math.cos(agree).toFloat()
endY = startY + smallLength!! * Math.sin(agree).toFloat()
}
startX += rectF!!.centerX()
endX += rectF!!.centerX()
startY += rectF!!.centerY()
endY += rectF!!.centerY()
canvas!!.drawLine(startX, startY, endX, endY, paint)
}
//绘制中心的小圆
canvas!!.drawCircle(rectF!!.centerX(), rectF!!.centerY(), 20F, paint)
//指定偏转角度然后绘制指针
// nowAgree = (nowAgree!!+actAgree!!)%360 //用这句可以启动速度旋转
canvas!!.rotate(nowAgree!!, rectF!!.centerX(), rectF!!.centerY())
Log.e("test", "nowAgree" + nowAgree)
canvas!!.drawLine(rectF!!.centerX(), rectF!!.centerY(), rectF!!.centerX(), rectF!!.centerY() - radius!!, paint)
}
现在来处理相关触摸事件:
internal var start_x: Float? = null//开始位置x
internal var start_y: Float? = null//开始位置y
internal var end_x: Float? = null//结束位置x
internal var end_y: Float? = null//结束位置y
internal var startAgree: Float? = null//开始角度
internal var initAgree: Float? = null//初始化角度
override fun onTouchEvent(event: MotionEvent?): Boolean {
when (event!!.action) {
MotionEvent.ACTION_DOWN -> {
start_x = event.x
start_y = event.y
startAgree = getAgree(start_x, start_y)//获取开始位置的角度
initAgree = nowAgree
Log.e("test", "---------------------------")
}
MotionEvent.ACTION_MOVE -> {
end_x = event.x
end_y = event.y
var endAgree = getAgree(end_x, end_y)//获取结束位置的角度
actAgree = endAgree - startAgree!!
//根据偏转角度进行界面刷新
nowAgree = (initAgree!! + actAgree!!) % 360
postInvalidate()
}
MotionEvent.ACTION_UP -> {
initAgree = nowAgree//进行初始化角度的赋值
}
}
return true
}
下面是相关角度计算的算法,这个算法是通过角度相减的方式实现:
private fun getAgree(x: Float?, y: Float?): Float {
if (x!! < rectF!!.centerX() && y!! < rectF!!.centerY()) {//a-180
return Math.toDegrees((Math.atan(((rectF!!.centerY() - y) / (rectF!!.centerX() - x)).toDouble()))).toFloat() - 180
} else if (x!! > rectF!!.centerX() && y!! < rectF!!.centerY()) {//-a
return -Math.toDegrees((Math.atan(((rectF!!.centerY() - y) / (x - rectF!!.centerX())).toDouble()))).toFloat()
} else if (x!! < rectF!!.centerX() && y!! > rectF!!.centerY()) {//180-a
return 180 - Math.toDegrees((Math.atan(((y - rectF!!.centerY()) / (rectF!!.centerX() - x)).toDouble()))).toFloat()
} else if (x!! > rectF!!.centerX() && y!! > rectF!!.centerY()) {//a
return Math.toDegrees((Math.atan(((y - rectF!!.centerY()) / (x - rectF!!.centerX())).toDouble()))).toFloat()
} else
return 0F
}
角度算法的相关原理如下(画的不好见谅):
将相关区域分成ABCD四个,触发起始点为q,结束点为w,相关角度计算都是以x轴线为基础:
AB区计算结果均为负角度,CD区角度均为正角度
A区:计算出来的角度为:(当前坐标以圆心点为直线相对x轴线的角度-180°)
B区:计算出来的角度为:-(当前坐标以圆心点为直线相对x轴线的角度)
C区:计算出来的角度为:180°-(当前坐标以圆心点为直线相对x轴线的角度)
D区:计算出来的角度为:当前坐标以圆心点为直线相对x轴线的角度
偏转角度值为:w点角度减去q点角度
将偏转角度值与当前实际角度值相加,再进行偏转绘制onDraw即可进行触控滚动了
完整代码如下:
package wlw.kotlintest
import android.animation.Animator
import android.content.Context
import android.content.res.TypedArray
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.RectF
import android.os.Handler
import android.os.Message
import android.util.AttributeSet
import android.util.Log
import android.view.MotionEvent
import android.view.SurfaceView
import android.view.VelocityTracker
import java.util.*
import android.animation.AnimatorListenerAdapter
import android.support.v4.view.ViewCompat
import android.animation.ValueAnimator
import android.view.animation.AccelerateDecelerateInterpolator
import android.view.animation.DecelerateInterpolator
/**
* 标题:
* 描述:
* 作者: 陈聪
* 创建时间:2017/10/9.
*/
open class MyView(context: Context, attrs: AttributeSet) : SurfaceView(context) {
internal var border_color: Int? = null//指定的颜色
internal var border_withd: Float? = null//指定的宽度
internal var paint: Paint? = null//画笔
internal var rectF: RectF? = null//矩形区域
internal var withd: Float? = null//控件宽度
internal var height: Float? = null//控件高度
internal var radius: Float? = null//圆盘半径
internal var smallLength: Float? = null//短刻度长度
internal var largerLength: Float? = null//长刻度长度
internal var actAgree: Float? = null//变化角度
internal var nowAgree: Float? = null//当前角度
init {
var typeArray: TypedArray = context.theme.obtainStyledAttributes(attrs, R.styleable.MyView, 0, 0)
try {
border_color = typeArray.getColor(R.styleable.MyView_border_color, R.color.material_blue_grey_800)
border_withd = typeArray.getDimension(R.styleable.MyView_border_withd, 2.0F)
} finally {
typeArray.recycle()
}
paint = Paint(Paint.ANTI_ALIAS_FLAG)
paint?.color = border_color!!
paint?.style = Paint.Style.STROKE
paint?.strokeWidth = border_withd!!
nowAgree = 90F
actAgree = 0F
setWillNotDraw(false)//用于自定义view的onDraw方法的执行
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
//绘制一个圆角矩形边框
canvas?.drawColor(0xFF000000.toInt())
paint?.color = 0x66555555
canvas?.drawRoundRect(RectF(rectF!!.centerX() - 0.9F * withd!! / 2, rectF!!.centerY() - 0.9F * height!! / 2, rectF!!.centerX() + 0.9F * withd!! / 2, rectF!!.centerY() + 0.9F * height!! / 2), 30F, 30F, paint)
//绘制一个半径为radius的圆
paint?.color = border_color!!
canvas?.drawCircle(rectF!!.centerX(), rectF!!.centerY(), radius!!, paint)
//绘制圆上的刻度
var startX: Float? = null
var startY: Float? = null
var endX: Float? = null
var endY: Float? = null
for (i: Int in 1..60) {
var agree = Math.PI / 180 * (i * 6 + actAgree!!)//这行代码用于指定刻度盘的触摸滚动
startX = radius!! * Math.cos(agree).toFloat()
startY = radius!! * Math.sin(agree).toFloat()
if (i % 5 == 0) {
endX = startX + largerLength!! * Math.cos(agree).toFloat()
endY = startY + largerLength!! * Math.sin(agree).toFloat()
} else {
endX = startX + smallLength!! * Math.cos(agree).toFloat()
endY = startY + smallLength!! * Math.sin(agree).toFloat()
}
startX += rectF!!.centerX()
endX += rectF!!.centerX()
startY += rectF!!.centerY()
endY += rectF!!.centerY()
canvas!!.drawLine(startX, startY, endX, endY, paint)
}
//绘制中心的小圆
canvas!!.drawCircle(rectF!!.centerX(), rectF!!.centerY(), 20F, paint)
//指定偏转角度然后绘制指针
// nowAgree = (nowAgree!!+actAgree!!)%360 //用这句可以启动速度旋转
canvas!!.rotate(nowAgree!!, rectF!!.centerX(), rectF!!.centerY())
Log.e("test", "nowAgree" + nowAgree)
canvas!!.drawLine(rectF!!.centerX(), rectF!!.centerY(), rectF!!.centerX(), rectF!!.centerY() - radius!!, paint)
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
rectF = RectF(left.toFloat(), top.toFloat(), right.toFloat(), bottom.toFloat())
withd = rectF!!.right - rectF!!.left
height = rectF!!.bottom - rectF!!.top
radius = if (withd!! < height!!) withd!! / 4 else height!! / 4
smallLength = 15F
largerLength = 30F
}
internal var start_x: Float? = null//开始位置x
internal var start_y: Float? = null//开始位置y
internal var end_x: Float? = null//结束位置x
internal var end_y: Float? = null//结束位置y
internal var startAgree: Float? = null//开始角度
internal var initAgree: Float? = null//初始化角度
override fun onTouchEvent(event: MotionEvent?): Boolean {
when (event!!.action) {
MotionEvent.ACTION_DOWN -> {
start_x = event.x
start_y = event.y
startAgree = getAgree(start_x, start_y)//获取开始位置的角度
initAgree = nowAgree
Log.e("test", "---------------------------")
}
MotionEvent.ACTION_MOVE -> {
end_x = event.x
end_y = event.y
var endAgree = getAgree(end_x, end_y)//获取结束位置的角度
actAgree = endAgree - startAgree!!
//根据偏转角度进行界面刷新
nowAgree = (initAgree!! + actAgree!!) % 360
postInvalidate()
}
MotionEvent.ACTION_UP -> {
initAgree = nowAgree//进行初始化角度的赋值
}
}
return true
}
private fun getAgree(x: Float?, y: Float?): Float {
if (x!! < rectF!!.centerX() && y!! < rectF!!.centerY()) {//a-180
return Math.toDegrees((Math.atan(((rectF!!.centerY() - y) / (rectF!!.centerX() - x)).toDouble()))).toFloat() - 180
} else if (x!! > rectF!!.centerX() && y!! < rectF!!.centerY()) {//-a
return -Math.toDegrees((Math.atan(((rectF!!.centerY() - y) / (x - rectF!!.centerX())).toDouble()))).toFloat()
} else if (x!! < rectF!!.centerX() && y!! > rectF!!.centerY()) {//180-a
return 180 - Math.toDegrees((Math.atan(((y - rectF!!.centerY()) / (rectF!!.centerX() - x)).toDouble()))).toFloat()
} else if (x!! > rectF!!.centerX() && y!! > rectF!!.centerY()) {//a
return Math.toDegrees((Math.atan(((y - rectF!!.centerY()) / (x - rectF!!.centerX())).toDouble()))).toFloat()
} else
return 0F
}
}
码云代码如下:
https://gitee.com/ccdy00/codes/089lrvatd1wki7upb3zqn65