* 可以产生形变,回弹的小球
*
* 整个view的核心:
* Scroller函数
* 1.调用scroller.public void startScroll(int startX, int startY, int dx, int dy, int duration);
* 在指定时间范围内,startX,startY,会移动dx,dy距离
*
* 2.然后调用:invalidate(); 系统内部会调用onDraw();
*
* 3.在onDraw方法内部又会调用  computeScrollOffset()函数。
* 所以,实现scroll.computeScrollOffset(); //如果还没有完全移动完成,就执行XXXXX
*
* 4.如果返回true 继续调用 invalidate();
*
* 这样就会在位移的过程中,执行你:  if(scroll.computeScrollOffset()){
* //你自己的方法
* }
*

class BallViewTwo @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : androidx.appcompat.widget.AppCompatImageView(context, attrs) {
private var mContext: Context = context
//屏幕的宽高
private var displayHeight: Int = 0
private var displayWidth: Int = 0
private var changelength = 30 //第一次形变的大小
private var mCurrentDirection = -1 //碰撞后,此时的方向
private var mDuration = 450 //变形需要持续的时间
/**
* flag=-1 正常移动
* flag=0 压缩变形
* flag=1 恢复压缩形变
* flag=2 往相反的方向弹
* flag=3 弹回原先的位置
*/
private var flag = -1
private var mShotOver: ShotOver? = null //回调函数
private var moveToLeft = 100 //正常状态下,小球移动到x轴的位置
private var moveToTop = 100 //正常状态下,小球移动到y轴的位置
private val centerX = 180 //小球圆心x
private val centerY = 180 //小球圆心y
private val radius = 180 //半径
private val bubbleWidth = radius * 2 //小球的宽
private val bubbleHeight = radius * 2 //小球的高
private var paint: Paint // 画笔
private var scroller: Scroller // 整个view的核心
private var rectF: RectF? = null //绘制椭圆
private var ovalLeft = 0
private var ovalTop = 0
private var ovalRight = 0
private var ovalBottom = 0 //椭圆的左上右下
private var currY = 0
private var currX = 0
private var offset = 0 //发生的移动量
private val shotBackDuration = 100 //回弹执行的时间
private val shotBackChange = 15 //回弹需要移动的距离
private var newOvalLeft = 0
private var newOvalTop = 0
private var newOvalRight = 0
private var newOvalBottom = 0
private var isShotBack = true // 是否开启回弹效果
init {
paint = Paint()
paint.color = Color.RED
scroller = Scroller(context)
rectF = RectF()
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
displayHeight = mContext.resources.displayMetrics.heightPixels
displayWidth = mContext.resources.displayMetrics.widthPixels
Log.i("qq", "ballview-------displayHeight=-$displayHeight displayWidth=$displayWidth")
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
}
override fun onDraw(canvas: Canvas) {
when (flag) {
-1 -> {
canvas.translate(moveToLeft.toFloat(), moveToTop.toFloat())
canvas.drawCircle(centerX.toFloat(), centerY.toFloat(), radius.toFloat(), paint)
Log.i("qq", "正常移动小球 小球left=$moveToLeft top=$moveToTop")
}
0 -> circleToOval(canvas)
1 -> ovalToCircle(canvas)
2 -> shotBackLeaveBounds(canvas)
3 -> shotBackGotoBounds(canvas)
}
super.onDraw(canvas)
}
/**
* 小球变形完再回弹,靠近边界
*/
private fun shotBackGotoBounds(canvas: Canvas) {
if (scroller.computeScrollOffset()) {
ovalLeft = scroller.currX
ovalTop = scroller.currY
canvas.translate(ovalLeft.toFloat(), ovalTop.toFloat())
canvas.drawCircle(centerX.toFloat(), centerY.toFloat(), radius.toFloat(), paint)
invalidate()
Log.i("shotBack", "远离边界。。moveToLeft=$ovalLeft moveToTop=$ovalTop")
} else {
Log.i("shotBack", "所有效果都结束")
canvas.translate(ovalLeft.toFloat(), ovalTop.toFloat())
canvas.drawCircle(centerX.toFloat(), centerY.toFloat(), radius.toFloat(), paint)
isShotBack = false
startChange(shotBackChange)
}
}
/**
* 小球变形完再回弹,也就是远离边界
*/
private fun shotBackLeaveBounds(canvas: Canvas) {
if (scroller.computeScrollOffset()) {
ovalLeft = scroller.currX
ovalTop = scroller.currY
canvas.translate(ovalLeft.toFloat(), ovalTop.toFloat())
canvas.drawCircle(centerX.toFloat(), centerY.toFloat(), radius.toFloat(), paint!!)
invalidate()
Log.i("shotBack", "远离边界。。moveToLeft=$ovalLeft moveToTop=$ovalTop")
} else {
canvas.translate(ovalLeft.toFloat(), ovalTop.toFloat())
canvas.drawCircle(centerX.toFloat(), centerY.toFloat(), radius.toFloat(), paint!!)
flag = 3
finishShotBack()
}
}
/**
* 将椭圆恢复成圆形
*/
private fun ovalToCircle(canvas: Canvas) {
if (scroller!!.computeScrollOffset()) {
when (mCurrentDirection) {
0 -> {
currY = scroller!!.currY
offset = newOvalTop - currY
ovalLeft = newOvalLeft + offset
ovalTop = currY + offset
ovalRight = newOvalRight - offset
ovalBottom = newOvalBottom + offset + offset
rectF!![ovalLeft.toFloat(), ovalTop.toFloat(), ovalRight.toFloat()] =
ovalBottom.toFloat()
canvas.drawOval(rectF!!, paint!!)
Log.i(
"qq",
"将椭圆----恢复成圆形,方向向北 currY=$currY offset=$offset"
)
Log.i(
"qq",
"将椭圆----恢复成圆形,方向向北 ovalLeft=$ovalLeft ovalTop=$ovalTop ovalRight=$ovalRight ovalBottom=$ovalBottom"
)
}
1 -> {
currX = scroller!!.currX
offset = newOvalLeft - currX
ovalLeft = currX - offset
ovalTop = newOvalTop + offset
ovalRight = newOvalRight + offset - offset
ovalBottom = newOvalBottom - offset
rectF!![ovalLeft.toFloat(), ovalTop.toFloat(), ovalRight.toFloat()] =
ovalBottom.toFloat()
canvas.drawOval(rectF!!, paint!!)
}
2 -> {
currY = scroller!!.currY
offset = newOvalTop - currY
ovalLeft = newOvalLeft + offset
ovalTop = currY - offset
ovalRight = newOvalRight - offset
ovalBottom = newOvalBottom
rectF!![ovalLeft.toFloat(), ovalTop.toFloat(), ovalRight.toFloat()] =
ovalBottom.toFloat()
canvas.drawOval(rectF!!, paint!!)
Log.i(
"qq",
"将椭圆----恢复成圆形,方向向南 currY=$currY offset=$offset"
)
Log.i(
"qq",
"将椭圆----恢复成圆形,方向向南 ovalLeft=$ovalLeft ovalTop=$ovalTop ovalRight=$ovalRight ovalBottom=$ovalBottom"
)
}
3 -> {
currX = scroller!!.currX
offset = newOvalLeft - currX
ovalLeft = currX + offset
ovalTop = newOvalTop + offset
ovalRight = newOvalRight + offset + offset
ovalBottom = newOvalBottom - offset
rectF!![ovalLeft.toFloat(), ovalTop.toFloat(), ovalRight.toFloat()] =
ovalBottom.toFloat()
canvas.drawOval(rectF!!, paint!!)
}
}
invalidate()
} else {
canvas.drawOval(rectF!!, paint!!)
//如果需要回弹的话
if (isShotBack) {
flag = 2
startShotBack()
} else {
flag = -1
if (mShotOver != null) {
mShotOver!!.bubbleShotEnd()
}
}
}
}
/**
* 圆挤压成椭圆
*/
private fun circleToOval(canvas: Canvas) {
if (scroller!!.computeScrollOffset()) {
when (mCurrentDirection) {
0 -> {
currY = scroller!!.currY
offset = currY - ovalTop
newOvalLeft = ovalLeft - offset
newOvalTop = currY - offset
newOvalRight = ovalRight + offset
newOvalBottom = ovalBottom - offset - offset
rectF!![newOvalLeft.toFloat(), newOvalTop.toFloat(), newOvalRight.toFloat()] =
newOvalBottom.toFloat()
canvas.drawOval(rectF!!, paint!!)
}
1 -> {
currX = scroller!!.currX
offset = currX - ovalLeft
newOvalLeft = currX + offset
newOvalTop = ovalTop - offset
newOvalRight = ovalRight - offset + offset
newOvalBottom = ovalBottom + offset
rectF!![newOvalLeft.toFloat(), newOvalTop.toFloat(), newOvalRight.toFloat()] =
newOvalBottom.toFloat()
canvas.drawOval(rectF!!, paint!!)
}
2 -> {
currY = scroller!!.currY
offset = currY - ovalTop
newOvalLeft = ovalLeft - offset
newOvalTop = currY + offset
newOvalRight = ovalRight + offset
newOvalBottom = ovalBottom
rectF!![newOvalLeft.toFloat(), newOvalTop.toFloat(), newOvalRight.toFloat()] =
newOvalBottom.toFloat()
canvas.drawOval(rectF!!, paint!!)
Log.i("qq", "圆挤压成椭圆,方向向南 currY=$currY offset=$offset")
Log.i(
"qq",
"圆挤压成椭圆,方向向南 newOvalLeft=$newOvalLeft newOvalTop=$newOvalTop newOvalRight=$newOvalRight newOvalBottom=$newOvalBottom"
)
}
3 -> {
currX = scroller!!.currX
offset = currX - ovalLeft
newOvalLeft = currX - offset
newOvalTop = ovalTop - offset
newOvalRight = ovalRight - offset - offset
newOvalBottom = ovalBottom + offset
rectF!![newOvalLeft.toFloat(), newOvalTop.toFloat(), newOvalRight.toFloat()] =
newOvalBottom.toFloat()
canvas.drawOval(rectF!!, paint!!)
}
}
invalidate()
} else {
canvas.drawOval(rectF!!, paint!!)
reverse()
}
}
/**
* 碰撞变形结束后,开启弹一弹效果
*/
fun startShotBack() {
when (mCurrentDirection) {
0 -> scroller!!.startScroll(ovalLeft, ovalTop, 0, shotBackChange, shotBackDuration)
1 -> scroller!!.startScroll(ovalLeft, ovalTop, -shotBackChange, 0, shotBackDuration)
2 -> scroller!!.startScroll(ovalLeft, ovalTop, 0, -shotBackChange, shotBackDuration)
3 -> scroller!!.startScroll(ovalLeft, ovalTop, shotBackChange, 0, shotBackDuration)
}
invalidate()
}
/**
* 结束 “弹的一段距离”
*/
fun finishShotBack() {
when (mCurrentDirection) {
0 -> scroller!!.startScroll(ovalLeft, ovalTop, 0, -shotBackChange, shotBackDuration)
1 -> scroller!!.startScroll(ovalLeft, ovalTop, shotBackChange, 0, shotBackDuration)
2 -> scroller!!.startScroll(ovalLeft, ovalTop, 0, shotBackChange, shotBackDuration)
3 -> scroller!!.startScroll(ovalLeft, ovalTop, -shotBackChange, 0, shotBackDuration)
}
invalidate()
}
/**
* 移动小球
*/
fun moveTo(l: Int, t: Int, direction: Int, duration: Int, shotBack: Boolean) {
isShotBack = shotBack
mDuration = duration
mCurrentDirection = direction
moveToLeft = l
moveToTop = t
if (t == 0) {
mCurrentDirection = 0
startChange(30)
} else if (l == displayWidth - bubbleWidth) {
mCurrentDirection = 1
startChange(30)
} else if (t == displayHeight - bubbleHeight) {
mCurrentDirection = 2
startChange(30)
} else if (l == 0) {
mCurrentDirection = 3
startChange(30)
} else {
invalidate()
}
}
/**
* 开始变形
*/
private fun startChange(change: Int) {
changelength = change
if (mShotOver != null) {
mShotOver!!.bubbleShotStart(mCurrentDirection)
}
flag = 0
//发生变形时,先初始化椭圆刚发生变形时的位置
ovalLeft = moveToLeft
ovalTop = moveToTop
ovalRight = moveToLeft + bubbleWidth
ovalBottom = ovalTop + bubbleHeight
when (mCurrentDirection) {
0 -> scroller!!.startScroll(moveToLeft, moveToTop, 0, changelength, mDuration)
1 -> scroller!!.startScroll(moveToLeft, moveToTop, changelength, 0, mDuration)
2 -> scroller!!.startScroll(moveToLeft, moveToTop, 0, changelength, mDuration)
3 -> scroller!!.startScroll(moveToLeft, moveToTop, changelength, 0, mDuration)
}
Log.i(
"qq",
"小球开始变形,方向=$mCurrentDirection 当前小球的坐标ovalLeft=$ovalLeft ovalTop=$ovalTop ovalRight=$ovalRight ovalBottom=$ovalBottom"
)
invalidate()
}
/**
* 回复变形前的状态
*/
private fun reverse() {
flag = 1
when (mCurrentDirection) {
0 -> scroller!!.startScroll(newOvalLeft, newOvalTop, 0, -changelength, mDuration)
1 -> scroller!!.startScroll(newOvalLeft, newOvalTop, -changelength, 0, mDuration)
2 -> scroller!!.startScroll(newOvalLeft, newOvalTop, 0, -changelength, mDuration)
3 -> scroller!!.startScroll(newOvalLeft, newOvalTop, -changelength, 0, mDuration)
}
invalidate()
}
fun setShotOver(shotOver: ShotOver?) {
mShotOver = shotOver
}
/**
* 碰撞变形效果完成
*/
interface ShotOver {
fun bubbleShotStart(direction: Int)
fun bubbleShotEnd()
}
/**
* (辅助函数)//没有实现效果
* 圆挤压成椭圆时,变形加速减少
*/
/*
public void circleToOvalAndAcceleratedReduce(Canvas canvas){
if(gradient == 0){
newOvalLeft = ovalLeft;
newOvalTop = ovalTop;
newOvalRight = ovalRight;
newOvalBottom = ovalBottom;
}
if((offset - oldOffset) >= 10 && gradient == 0){
Log.i("qq", "移动距离大于10时绘制一次");
newOvalLeft = ovalLeft - offset;
newOvalTop = currY - offset;
newOvalRight = ovalRight + offset;
newOvalBottom = ovalBottom - offset - offset;
rectF.set(newOvalLeft, newOvalTop, newOvalRight, newOvalBottom);
canvas.drawOval(rectF, paint);
oldOffset = offset;
gradient = 1;
}else if((offset - oldOffset) >= 7 && gradient == 1){
Log.i("qq", "移动距离大于7时绘制一次");
newOvalLeft = ovalLeft - offset;
newOvalTop = currY - offset;
newOvalRight = ovalRight + offset;
newOvalBottom = ovalBottom - offset - offset;
rectF.set(newOvalLeft, newOvalTop, newOvalRight, newOvalBottom);
canvas.drawOval(rectF, paint);
gradient = 2;
oldOffset = offset;
}else if((offset - oldOffset) >= 4 && gradient == 2){
Log.i("qq","移动距离大于4时绘制一次");
newOvalLeft = ovalLeft - offset;
newOvalTop = currY - offset;
newOvalRight = ovalRight + offset;
newOvalBottom = ovalBottom - offset - offset;
rectF.set(newOvalLeft, newOvalTop, newOvalRight, newOvalBottom);
canvas.drawOval(rectF, paint);
gradient = 3;
oldOffset = offset;
}else if((offset - oldOffset) >= 2 && gradient == 3){
Log.i("qq","移动距离大于2时绘制一次");
newOvalLeft = ovalLeft - offset;
newOvalTop = currY - offset;
newOvalRight = ovalRight + offset;
newOvalBottom = ovalBottom - offset - offset;
rectF.set(newOvalLeft, newOvalTop, newOvalRight, newOvalBottom);
canvas.drawOval(rectF, paint);
gradient = -1;
oldOffset = offset;
}else{
Log.i("qq", "移动绘制一次");
rectF.set(newOvalLeft, newOvalTop, newOvalRight, newOvalBottom);
canvas.drawOval(rectF, paint);
}
Log.i("qq", "圆挤压成椭圆,方向向北 currY=" + currY + " offset=" + offset);
Log.i("qq", "圆挤压成椭圆,方向向北 newOvalLeft=" + newOvalLeft + " newOvalTop=" + newOvalTop + " newOvalRight=" + newOvalRight + " newOvalBottom=" + newOvalBottom);
}*/
}

/**
* 1.根据重力感应移动小球
* 2.一般重力感应使用的重力加速,这样的话每次移动距离不是固定的
* 3.此应用,将速度固定。
* 4.小球碰撞到边缘时,会发生形变。(小球压缩形变)
* 5.可以点击按钮添加,发生形变后回弹效果
*/
class AnimationActivity : Activity(), View.OnClickListener {
private var sensorManager: SensorManager? = null
private var sensor: Sensor? = null
private var init = false
// 因为布局是填充父窗体的,且设置了出掉状态栏,所有求出的宽高就是屏幕的宽高。
private var containerWidth = 0
private var containerHeight = 0
// 小球的宽高
private val ballWidth = 360
private val ballHeight = 360
// 自定义球
private lateinit var ball: BallViewTwo
// 小球的起始位置
private var ballX = 100f
private var ballY = 100f
private var currentState = -1 // 初始方向
private var shotDirection = -1 // 小球发生碰撞时的那刻的方向
// 初始化 东 西 南 北 四个方向
private val NORTH = 0
private val EAST = 1
private val SOUTH = 2
private val WEST = 3
private var constantsSpeed = 100 // 每次斜边移动的距离
private val SPEED = 10 // 比例
private var canMove = true // 小球是否可以移动
private val durationPiece = 150 // 执行动画的时间块
private var duration = 450 // 初始速度下的执行时间
private var isShotBack = false
/**
* -1: 小球正常移动
* 0: 小球正在碰撞
* 1: 小球碰撞刚结束
*/
private var shot = -1
/** Called when the activity is first created. */
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
window.setFlags(
WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN
)
setContentView(R.layout.activity_animation)
// 初始化重力感应
sensorManager = getSystemService(SENSOR_SERVICE) as SensorManager
sensor = sensorManager!!.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
}
private fun init() {
val container = findViewById(R.id.ball_container)
findViewById(R.id.accelerate).setOnClickListener(this) // 加速移动
findViewById(R.id.reduce).setOnClickListener(this) // 减少移动
findViewById(R.id.normal).setOnClickListener(this) // 正常速度移动
findViewById(R.id.isShowBack).setOnClickListener(this) // 是否弹一弹
containerWidth = container.width
containerHeight = container.height
ball = findViewById(R.id.ball)
/**
* 碰撞监听
*/
ball.setShotOver(object : BallViewTwo.ShotOver {
override fun bubbleShotStart(direction: Int) {
shotDirection = direction
shot = 0 // 正在压缩变形
canMove = false
// Log.i("shotDirection", "小球发生碰撞时的方向==" + shotDirection);
}
override fun bubbleShotEnd() {
shot = 1 // 刚压缩变形结束
}
})
}
/**
* 移动小球
* @param x
* @param y
*/
private fun moveTo(x: Float, y: Float) {
ballX += x
ballY += y
if (ballX < 0) {
ballX = 0f
}
if (ballY  containerWidth - ballWidth) {
ballX = (containerWidth - ballWidth).toFloat()
}
if (ballY > containerHeight - ballHeight) {
ballY = (containerHeight - ballHeight).toFloat()
}
ball.moveTo(ballX.toInt(), ballY.toInt(), currentState, duration, isShotBack)
}
private val listener = object : SensorEventListener {
override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {
// TODO Auto-generated method stub
}
override fun onSensorChanged(event: SensorEvent) {
if (!init) return
currentState = calc(event)
// 如果当前方向不等于碰撞方向 并且圆刚碰撞结束
if (shotDirection != currentState && shot == 1) {
canMove = true
}
Log.i("direction", "当前方向==$currentState canMove=$canMove")
// 可以移动的话才计算移动速度,调用移动方法
if (canMove) {
// 如果刚碰撞结束,根据位置,将其挪动一段距离
if (shot == 1) {
when (shotDirection) {
0 -> moveTo(0f, 20f)
1 -> moveTo(-20f, 0f)
2 -> moveTo(0f, -20f)
3 -> moveTo(20f, 0f)
}
shot = -1
// 直接移动小球
} else {
constantSpeed(event)
}
} else {
Log.i("qq", "正在执行“弹”,所以先不移动小球")
}
}
}
/**
* 计算x,y轴应该移动的值(为了使每次移动距离保持不变)
* @param event
*/
private fun constantSpeed(event: SensorEvent) {
val x = event.values[SensorManager.DATA_X] * SPEED
val y = event.values[SensorManager.DATA_Y] * SPEED
val tan = x / y
val movey: Double
val movex: Double
if (x == 0f && y != 0f) {
movex = 0.0
movey = constantsSpeed.toDouble()
} else if (x != 0f && y == 0f) {
movex = constantsSpeed.toDouble()
movey = 0.0
} else if (x == 0f && y == 0f) {
movex = 0.0
movey = 0.0
} else {
val temp = constantsSpeed / (tan * tan + 1)
movey = Math.sqrt(temp.toDouble()) // 开根号
movex = movey * tan
}
moveTo(
(if (x < 0) -Math.abs(movex) else Math.abs(movex)).toFloat(),
(if (y  0 -> WEST
x  EAST
else -> -1
}
tempVertical = when {
y > 0 -> SOUTH
y  NORTH
else -> -1
}
currentState = when {
tempHorizontal == EAST && tempVertical == NORTH ->
if (Math.abs(x) > Math.abs(y)) EAST else NORTH
tempHorizontal == EAST && tempVertical == SOUTH ->
if (Math.abs(x) > Math.abs(y)) EAST else SOUTH
tempHorizontal == WEST && tempVertical == NORTH ->
if (Math.abs(x) > Math.abs(y)) WEST else NORTH
tempHorizontal == WEST && tempVertical == SOUTH ->
if (Math.abs(x) > Math.abs(y)) WEST else SOUTH
else -> currentState
}
return currentState
}
override fun onDestroy() {
super.onDestroy()
unregister()
}
override fun onPause() {
super.onPause()
unregister()
}
override fun onRestart() {
super.onRestart()
register()
}
override fun onResume() {
super.onResume()
register()
}
override fun onClick(v: View) {
when (v.id) {
R.id.accelerate -> adjustSpeedAndDuration(5)
R.id.reduce -> adjustSpeedAndDuration(-5)
R.id.normal -> {
constantsSpeed = 10
duration = 450
}
R.id.isShowBack -> isShotBack = !isShotBack
}
}
/**
* 改变小球的移动速度和变形时间
* 因为移动速度越快,碰撞时间越短
*/
private fun adjustSpeedAndDuration(change: Int) {
constantsSpeed += change
duration = if (change < 0) {
duration + durationPiece
} else {
duration - durationPiece
}
when {
constantsSpeed  {
constantsSpeed = 5
duration = 750
}
constantsSpeed >= 25 -> {
constantsSpeed = 25
duration = 150
}
}
}
}

 xml


本站无任何商业行为
个人在线分享 » Android中球体碰撞代码分享-kotlin,入门版
E-->