公司要求我写小球碰撞,并有增加小球的效果
package com.meng.ballcollision;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.Log;
/**
* User: mengqingdong
* Date: 2018/5/21
* Time: 14:30
* Description:
*/
public class Ball {
public int id;
/**
* x轴速度 向右为正,向左为负
*/
public float vx = 1.0f;
/**
* y轴速度 向右为正,向左为负
*/
public float vy = 1.0f;
/**
* 圆心x坐标
*/
public float centerX;
/**
* 圆心y坐标
*/
public float centerY;
/**
* 圆半径
*/
public float radius = 150.0f;
/**
* 2个球碰撞的弹性
*/
public float spring = 1.0f;
/**
* 球与边界碰撞的弹性
*/
public float bounce = -1.0f;
/**
* 球的重力
*/
public float gravity = 0.00f;
/**
* paint
*/
public Paint paint;
/**
* 图片bitmap
*/
public Bitmap bitmap;
public RectF bitmapRectF;
public boolean isStatic;
public boolean isEnter;
public boolean isFirstRun;
public Ball(float radius, float centerX, float centerY) {
this.radius = radius;
this.centerX = centerX;
this.centerY = centerY;
}
public Ball(float radius, float centerX, float centerY, Paint paint) {
this.radius = radius;
this.centerX = centerX;
this.centerY = centerY;
this.paint = paint;
}
public Ball(float radius, float centerX, float centerY, Bitmap bitmap) {
this.radius = radius;
this.centerX = centerX;
this.centerY = centerY;
this.bitmap = bitmap;
}
public Ball(float radius, float centerX, float centerY, Paint paint, boolean isStatic, boolean isEnter, int id) {
this.radius = radius;
this.centerX = centerX;
this.centerY = centerY;
this.isStatic = isStatic;
this.isEnter = isEnter;
this.paint = paint;
this.id = id;
this.isFirstRun = true;
}
public Paint getPaint() {
return paint;
}
public void setPaint(Paint paint) {
this.paint = paint;
}
/**
* 优先使用bitmap绘制
*/
public void draw(Canvas canvas) {
if (null == bitmap) {
canvas.drawCircle(centerX, centerY, radius, paint);
} else {
bitmapRectF = new RectF(centerX - radius, centerY - radius, centerX + radius, centerY + radius);
canvas.drawBitmap(bitmap, null, bitmapRectF, null);
}
}
/**
* 计算小球是否越界,超出边界调整其位置并改变运动方向
*
* @param width 边界的宽
* @param height 边界的高
*/
public void calculateBallIsOutOfBounds(float width, float height) {
if (isStatic) {
float edgeDistance = centerX + radius - width * 0.7f;
if (edgeDistance > 0) {
//超出右边界
centerX = width * 0.7f - radius;
//向左移动
vx *= bounce;
}
edgeDistance = this.centerX - radius;
if (edgeDistance < width * 0.3f) {
//超出左边界
centerX = radius + width * 0.3f;
//向右移动
vx *= bounce;
}
edgeDistance = centerY + radius - height * 0.6f;
if (edgeDistance > 0) {
//超出下边界
centerY = height * 0.6f - radius;
vy *= bounce;
}
edgeDistance = centerY - radius;
if (edgeDistance < height * 0.4f) {
//超出上边界
centerY = radius + height * 0.4f;
vy *= bounce;
}
} else {
float edgeDistance = centerX + radius - width;
if (edgeDistance > 0) {
//超出右边界
centerX = width - radius;
//向左移动
vx *= bounce;
}
edgeDistance = this.centerX - radius;
if (edgeDistance < 0) {
// this.isEnter = false;
//超出左边界
centerX = radius;
//向右移动
vx *= bounce;
}
edgeDistance = centerY + radius - height;
if (edgeDistance > 0) {
// this.isEnter = false;
//超出下边界
centerY = height - radius;
vy *= bounce;
}
edgeDistance = centerY - radius;
if (edgeDistance < 0) {
// this.isEnter = false;
//超出上边界
centerY = radius;
vy *= bounce;
}
}
float edgeDistance = centerX + radius - width;
if (edgeDistance > 0) {
//超出右边界
centerX = width - radius;
//向左移动
vx *= bounce;
}
edgeDistance = this.centerX - radius;
if (edgeDistance < 0) {
//超出左边界
centerX = radius;
//向右移动
vx *= bounce;
}
edgeDistance = centerY + radius - height;
if (edgeDistance > 0) {
//超出下边界
centerY = height - radius;
vy *= bounce;
}
edgeDistance = centerY - radius;
if (edgeDistance < 0) {
//超出上边界
centerY = radius;
vy *= bounce;
}
// }
}
/**
* 计算小球下一步的位置
*/
public void calculateNextPositionOfBall() {
// this.vy += gravity;
// this.centerX += vx;
// this.centerY += vy;
if (isEnter) {
if (this.isFirstRun) {
vx = 10;
vy = 10;
this.isFirstRun = false;
}
this.centerX += vx;
this.centerY += vy;
} else {
// 限制最大速度
float maxSpeed = 1.5f; // 设置一个最大速度值
float speed = (float) Math.sqrt(vx * vx + vy * vy);
if (speed > maxSpeed) {
this.vx = (vx / speed) * maxSpeed;
this.vy = (vy / speed) * maxSpeed;
}
this.centerX += vx;
this.centerY += vy;
Log.i("赵云" + this.id, this.id + " vx:" + vx + " vy:" + vy + " isEnter:" + isEnter);
}
}
}
package com.meng.ballcollision;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
* User: mengqingdong
* Date: 2018/5/21
* Time: 14:42
* Description: 小球碰撞
*/
public class BallsCollisionView extends View {
/**
* 小球半径 px
*/
private float radius = 150.0f;
/**
* 小球数量
*/
private int ballCount = 1;
/**
* 小球集合
*/
List<Ball> ballList = new ArrayList<>();
public BallsCollisionView(Context context) {
this(context, null);
}
public BallsCollisionView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public BallsCollisionView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
try {
createBalls(getMeasuredWidth(), getMeasuredHeight());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 初始化碰撞小球,默认随机位置,初始化时小球不可以重叠
* 如果想生成不同样式的小球需要重写此方法
*
* @param width view width
* @param height view height
*/
public List<Ball> createBalls(float width, float height) throws Exception {
float minX = radius;
float maxX = width - radius;
float minY = radius;
float maxY = height - radius;
Paint paint = createBallPaint();
int i = 0;
while (ballList.size() < ballCount) {
float centerX = getRandomIntByRange((int) minX, (int) maxX);
float centerY = getRandomIntByRange((int) minY, (int) maxY);
if (!ballIntersectList(radius, centerX, centerY, ballList)) {
Ball ball = new Ball(i == 0 ? radius : radius / 2, centerX, centerY, paint, i == 0 ? true : false, false, i);
ballList.add(ball);
}
i++;
}
return ballList;
}
public void addBall() {
float centerX = 0f;
float centerY = 0f;
try {
int randomNumber = (int) (Math.random() * 4);
int randomX = (int) (Math.random() * getWidth());
int randomY = (int) (Math.random() * getHeight());
switch (randomNumber) {
case 0:
//从左边进入
centerX = -radius / 2;
centerY = randomY;
break;
case 1:
//从上边进入
centerX = randomX;
centerY = -radius / 2;
break;
case 2:
//从右边进入
centerX = getWidth() + radius / 2;
centerY = randomY;
break;
case 3:
//从下边进入
centerX = randomX;
centerY = getHeight() + radius / 2;
break;
default:
break;
}
Paint paint = createBallPaint();
if (!ballIntersectList(radius, centerX, centerY, ballList)) {
Ball ball = new Ball(ballList.size() == 0 ? radius : radius / 2, centerX, centerY, paint, false, true, ballList.size() + 1);
ballList.add(ball);
}
} catch (Exception e) {
e.printStackTrace();
}
invalidate();
}
private Paint createBallPaint() {
Paint paint = new Paint();
paint.setColor(Color.parseColor("#55943a21"));
return paint;
}
/**
* 判断当前要生成的小球是否与其他球相交
*
* @param radius 要生成小球的半径
* @param centerX 要生成小球圆心 x 坐标
* @param centerY 要生成小球圆心 y 坐标
* @param ballList 被比较小球集合
* @return 有一个相交返回true
*/
private boolean ballIntersectList(float radius, float centerX, float centerY, List<Ball> ballList) {
for (Ball temp : ballList) {
if (ballIntersect(radius, centerX, centerY, temp)) {
return true;
}
}
return false;
}
/**
* 获取指定范围内整数 rangeStart要小于rangeEnd
*
* @param rangeStart 整数开始值
* @param rangeEnd 整数结束值
*/
private int getRandomIntByRange(int rangeStart, int rangeEnd) throws Exception {
if (rangeStart >= rangeEnd) {
throw new Exception("rangeStart should less then rangeEnd");
}
Random random = new Random();
return random.nextInt(rangeEnd) % (rangeEnd - rangeStart + 1) + rangeStart;
}
/**
* 判断2个小球是否相交
*
* @param ball 第一个小球
* @param radius 第二个小球的半径
* @param centerX 第二个小球圆心 x 坐标
* @param centerY 第二个小球圆心 y 坐标
* @return 相交返回true
*/
private boolean ballIntersect(float radius, float centerX, float centerY, Ball ball) {
double dx = centerX - ball.centerX;
double dy = centerY - ball.centerY;
double dist = Math.sqrt(dx * dx + dy * dy);
return ball.radius + radius > dist;
}
/**
* 判断2个小球是否相交
*
* @param ball1 第一个小球
* @param ball2 第二个小球
* @return 相交返回true
*/
private boolean ballIntersect(Ball ball1, Ball ball2) {
double dx = ball2.centerX - ball1.centerX;
double dy = ball2.centerY - ball1.centerY;
double dist = Math.sqrt(dx * dx + dy * dy);
return ball1.radius + ball2.radius > dist;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (ballList.size() == 0) {
return;
// try {
// initBall(getWidth(), getHeight());
// } catch (Exception e) {
// e.printStackTrace();
// }
}
hitBall();
for (Ball ball : ballList) {
ball.calculateNextPositionOfBall();
ball.calculateBallIsOutOfBounds(getWidth(), getHeight());
}
for (Ball ball : ballList) {
ball.draw(canvas);
}
postInvalidate();
}
/**
* 一个球与当前球碰撞
*/
public void hitBall() {
int num = ballList.size();
for (int i = 0; i < num; i++) {
Ball ball1 = ballList.get(i);
for (int j = i + 1; j < num; j++) {
Ball ball2 = ballList.get(j);
double dx = ball2.centerX - ball1.centerX;
double dy = ball2.centerY - ball1.centerY;
double dist = Math.sqrt(dx * dx + dy * dy);
double misDist = ball1.radius + ball2.radius;
if (dist < misDist) {
double angle = Math.atan2(dy, dx);
double tx = ball1.centerX + Math.cos(angle) * misDist;
double ty = ball1.centerY + Math.sin(angle) * misDist;
double ax = (tx - ball2.centerX) * ball1.spring;
double ay = (ty - ball2.centerY) * ball1.spring;
ball1.vx -= ax;
ball1.vy -= ay;
ball2.vx += ax;
ball2.vy += ay;
ball1.isEnter = false;
ball2.isEnter = false;
}
}
}
}
}
package com.meng.ballcollision;
import android.annotation.SuppressLint;
import android.os.Bundle;
import android.view.View;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
@SuppressLint({"MissingInflatedId", "LocalSuppress"}) BallsCollisionView ballsView = findViewById(R.id.ballsView);
ballsView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ballsView.addBall();
}
});
}
}
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<com.meng.ballcollision.BallsCollisionView
android:id="@+id/ballsView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
大球在中间固定范围内动。其它小球随意移动