这一篇在第一篇的基础上增加了惯性滑动和双点触控缩放指针的效果,效果如下:
首先是滚动惯性滑动效果,我使用的是属性动画进行实现的,使用了DecelerateInterpolator这个插值器,它的作用是由快到慢,符合我们的要求,代码如下:
/**
* 开启惯性滚动动画
*/
private fun startAni() {
speed = if (scrollType === 1) speed else (-speed!!)
var endPosition = if (nowAgree!!.toInt() > (speed!!).toInt()) nowAgree!!.toInt() + (speed!!).toInt() else (speed!!).toInt()
animtor = ValueAnimator.ofInt(nowAgree!!.toInt(), endPosition)
animtor?.interpolator = DecelerateInterpolator()//插值器,开始快后面慢
// animtor.interpolator = AccelerateDecelerateInterpolator()//开始慢中间快后面慢
animtor?.duration = 1000 + Math.abs(speed!!.toLong())
animtor?.addUpdateListener { animation ->
val updateValue = animation.animatedValue as Int
nowAgree = (updateValue % 360 + 360) % 360.toFloat()
ViewCompat.postInvalidateOnAnimation(this@MyView2)
}
animtor?.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
super.onAnimationEnd(animation)
}
})
animtor?.start()
}
相关属性说明如下:
internal var speed: Double? = null//结束时手指滑动速度
internal var tracter: VelocityTracker? = null//速度获取对象
internal var animtor: ValueAnimator? = null//属性动画
internal var scrollType: Int? = 1//动画滚动方向,1正,0反
主要原理是通过获取滑动结束时滑动的像素速度作为动画旋转角度的结束位置值来进行相关的动画旋转操作,在这个过程中的主要难点在于处理滚动方向,所以我写了个算法用于指定滚动方向和滚动时间,以及滚动距离,代码如下:
/**
* 获取滚动方向的算法
*/
private fun getAgree2(xlength: Float?, ylength: Float?): Float {
if (xlength!! < 0 && ylength!! < 0) {//A
return Math.toDegrees((Math.atan((Math.abs(ylength) / Math.abs(xlength)).toDouble()))).toFloat() - 180
} else if (xlength > 0 && ylength!! < 0) {//B
return -Math.toDegrees((Math.atan((Math.abs(ylength) / Math.abs(xlength)).toDouble()))).toFloat()
} else if (xlength < 0 && ylength!! > 0) {//C
return 180 - Math.toDegrees((Math.atan((Math.abs(ylength) / Math.abs(xlength)).toDouble()))).toFloat()
} else if (xlength > 0 && ylength!! > 0) {//D
return Math.toDegrees((Math.atan((Math.abs(ylength) / Math.abs(xlength)).toDouble()))).toFloat()
} else
return 0F
}
算法原理说明如下:
通过触摸结束点获取到结束点与中心的相对于x轴的夹角,通过速度参数获取速度相对于x轴的夹角,然后将整个平面以中心点相对以x,y轴划分成ABCD四个区域,然后根据区域的不同和两个夹角的大小对比进行正负方向的区分,说明图如下:
其中q是触摸结束点,我们可以拿到它与中心点相对x轴线的夹角a,蓝色线条是以角a为基础的相交的垂直线,s是速度矢量,我们可以获取s相对于x轴的夹角角d,通过角a,我们可以得到角b和角c,角e为角d减去角c获得,根据角c知道,如果角c越小,s就与中心点越垂直,所以通过以蓝色线条的坐标为基础可以根据s的角度去区分滚动方向的正反。
相关角度计算代码如下:
/**
* 获取速度和滚动方向的算法
*/
private fun getSpeedTo(): Int {
var speed_x = tracter!!.xVelocity
var speed_y = tracter!!.yVelocity
var xSpeed = Math.abs(speed_x).toDouble()
var ySpeed = Math.abs(speed_y).toDouble()
var speedDegrees = Math.toDegrees((Math.atan(Math.abs(speed_y / speed_x).toDouble())))
speed = Math.abs(Math.sqrt(Math.pow(xSpeed, 2.0) + Math.pow(ySpeed, 2.0)) * Math.cos(speedDegrees - 90 + Math.abs(endAgree!!)))
var areaDegree = getAgree2(speed_x, speed_y)
if (end_x!! <= rectF!!.centerX() && end_y!! <= rectF!!.centerY()) {//A
if (areaDegree > endAgree!!) return 1 else return 0
} else if (end_x!! > rectF!!.centerX() && end_y!! <= rectF!!.centerY()) {//B
if (areaDegree > endAgree!!) return 1 else return 0
} else if (end_x!! <= rectF!!.centerX() && end_y!! > rectF!!.centerY()) {//C
if (((areaDegree in endAgree!!..90F) or (areaDegree in -180F..-180F + endAgree!!)) or (areaDegree > endAgree!!)) return 1 else return 0
} else if (end_x!! > rectF!!.centerX() && end_y!! > rectF!!.centerY()) {//D
if (areaDegree > endAgree!!) return 1 else return 0
} else return 1
}
滚动动画说完了,我们现在来看下缩放效果的实现:
相关代码如下:需要注意的是判断相关action的时候要用event!!.action and MotionEvent.ACTION_MASK,不然无法触发多点触控的条件。
override fun onTouchEvent(event: MotionEvent?): Boolean {
when (event!!.action and MotionEvent.ACTION_MASK) {//这个地方要加上and MotionEvent.ACTION_MASK这句,不然无法进行多点触控操作
MotionEvent.ACTION_DOWN -> {
animtor?.cancel()//重置动画
start_x = event.x
start_y = event.y
startAgree = getAgree(start_x, start_y)//开始角度
initAgree = nowAgree
//用于速度计算
if (tracter === null) tracter = VelocityTracker.obtain() else tracter!!.clear()
tracter!!.addMovement(event)
}
MotionEvent.ACTION_MOVE -> {
var count: Int = event.pointerCount
if (count >= 2 && nowSpace !== null) {//如果触摸点超过2个就进行缩放触控
actSpace = getSpacing(event)
actLength = (actSpace!! - nowSpace!!) / nowSpace!! * radius!!
nowRadius = initRadius!! + actLength!!
nowRadius = if (nowRadius!! >= 2 * radius!!) 2 * radius!! else nowRadius
nowRadius = if (nowRadius!! <= radius!! / 5) radius!! / 5 else nowRadius
} else {//如果是单点触控,则进行角度和速度计算
end_x = event.x
end_y = event.y
endAgree = getAgree(end_x, end_y)
actAgree = endAgree!! - startAgree!!
nowAgree = (initAgree!! + actAgree!!) % 360
tracter?.addMovement(event)
tracter?.computeCurrentVelocity(500)
}
postInvalidate()
}
MotionEvent.ACTION_UP -> {
initAgree = nowAgree
var count: Int = event.pointerCount
if (count <= 1) {//如果是单点触控,则进行速度计算
tracter?.computeCurrentVelocity(500)
scrollType = getSpeedTo()
startAni()//开始惯性动画播放
if (tracter !== null) {
tracter!!.recycle()
tracter = null
}
}
}
MotionEvent.ACTION_POINTER_DOWN -> {
var count: Int = event.pointerCount
if (count >= 2) {
nowSpace = getSpacing(event)
}
}
MotionEvent.ACTION_POINTER_UP -> {
initRadius = nowRadius
}
}
return true
}
相关缩放位置我只针对了外面那个圆圈,所以代码我也只展示相关位置:
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
...
canvas?.drawCircle(rectF!!.centerX(), rectF!!.centerY(), nowRadius!!, paint)
...
}
相关属性说明如下:
internal var nowSpace: Float? = null//初始两点触控距离
internal var actSpace: Float? = null//滑动之后两点触控距离
internal var actLength: Float? = 0F//两点触控距离变化长度
internal var nowRadius: Float? = 0F//当前半径长度
internal var initRadius: Float? = 0F//初始半径长度
这一篇主要介绍相关原理,如果需要看整体代码,请点击:https://gitee.com/ccdy00/codes/xp0lc63d4jiy8m5nogstr83