1. 通过自定义View绘制小球
创建Ball类,封装与小球有关的属性元素
在构造方法中随机生成0-360的方向值
package com.pcf.randomball.bean;
import java.util.HashMap;
import java.util.Random;
public class Ball {
int radius;//半径大小 单位px
int color;//色值
int alpha;//透明度
int x;//圆心x坐标
int y;//圆心y坐标
int degree;//方向
int speed//速度 1-3
int axisX;//X轴矢量方向值
int axisY;//Y轴矢量方向值
public Ball() {
//生成一个在0-360范围内的随机数 为小球的方向
this.degree = new Random().nextInt(360);
//生成一个在1-3范围内的随机数 为小球的速度
this.speed = new Random().nextInt(3)+1;
}
public Ball(int radius, int color, int alpha, int x, int y) {
this.radius = radius;
this.color = color;
this.alpha = alpha;
this.x = x;
this.y = y;
//生成一个在0-360范围内的随机数 为小球的方向
this.degree = new Random().nextInt(360);
//生成一个在1-3范围内的随机数 为小球的速度
this.speed = new Random().nextInt(3)+1;
if (degree == 0) {
axisX = 0;
axisY = 1;
} else if (degree == 90) {
axisX = 1;
axisY = 0;
} else if (degree == 180) {
axisX = 0;
axisY = -1;
} else if (degree == 270) {
axisX = -1;
axisY = 0;
} else if (degree > 0 && degree < 90) {
axisX = 1;
axisY = 1;
} else if (degree > 90 && degree < 180) {
axisX = 1;
axisY = -1;
} else if (degree > 180 && degree < 270) {
axisX = -1;
axisY = -1;
} else if (degree > 270 && degree < 360) {
axisX = -1;
axisY = 1;
}
}
public int getRadius() {
return radius;
}
public void setRadius(int radius) {
this.radius = radius;
}
public int getColor() {
return color;
}
public void setColor(int color) {
this.color = color;
}
public int getAlpha() {
return alpha;
}
public void setAlpha(int alpha) {
this.alpha = alpha;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public int getDegree() {
return degree;
}
public void setDegree(int degree) {
this.degree = degree;
}
public long getSpeed() {
return speed;
}
public void setSpeed(long speed) {
this.speed = speed;
}
public int getAxisX() {
return axisX;
}
public void setAxisX(int axisX) {
this.axisX = axisX;
}
public int getAxisY() {
return axisY;
}
public void setAxisY(int axisY) {
this.axisY = axisY;
}
}
创建自定义RandomView类继承自View类
实现构造方法与OnDraw OnLayout OnMeasure方法
在OnLayout方法中获取控件实际宽高
在OnDraw方法里绘制圆形小球
package com.pcf.randomball.bean;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.View;
/**
* 随机小球View
* */
public class RandomView extends View {
public RandomView(Context context) {
super(context);
}
public RandomView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public RandomView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public RandomView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Ball ball = new Ball(50, Color.BLUE, 600, width / 2, height / 2)
//实例化画笔对象
Paint paint = new Paint();
//给画笔设置颜色
paint.setColor(ball.getColor());
paint.setAlpha(ball.getAlpha());
//设置画笔属性
paint.setStyle(Paint.Style.FILL);//画笔属性是实心
paint.setStrokeWidth(1);//设置画笔粗细
/**四个参数:
* 参数一:圆心的x坐标
* 参数二:圆心的y坐标
* 参数三:圆的半径
* 参数四:定义好的画笔
**/
canvas.drawCircle(ball.getX(), ball.getY(), ball.getRadius(), paint);
}
}
2. 定时更新小球位置,让小球运动
在Ball类的构造方法里面,通过生成随机数0-360赋值运动方向
在直角坐标系中把0-360°分成四等份,均分在四个象限中
通过角度判断小球的运动方向,每个角限都是90°
无法直接对View进行斜线的移动
把斜线的移动分解成竖向与横向的移动
沿X轴的,Y轴的进行分解
把90°分成了10等份,并分解到X轴与Y轴
开启线程定时更新小球位置并重新绘制小球位置
PS:后面会把小球运动的方案改为以速度为基础
package com.pcf.randomball.view;
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 com.pcf.randomball.bean.Ball;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class RandomView extends View {
private int height;
private int width;
private long maxBallNumber = -1;
private List<Ball> ballList = new ArrayList();
private String TAG = "RandomLayout";
private MyThread thread;
public RandomView(Context context) {
super(context);
}
public RandomView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public RandomView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public RandomView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Log.d(TAG, "onMeasure ");
width = getMeasuredWidth();
height = getMeasuredHeight();
Log.d(TAG, "width " + width);
Log.d(TAG, "height " + height);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
Log.d(TAG, "onLayout ");
ballList.add(new Ball(50, Color.BLUE, 600, width / 2, height / 2));
//避免重复创建线程
if (thread == null) {
thread = new MyThread();
}
thread.start();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.d(TAG, "onDraw ");
for (int i = 0; i < ballList.size(); i++) {
Ball ball = ballList.get(i);
//实例化画笔对象
Paint paint = new Paint();
//给画笔设置颜色
paint.setColor(ball.getColor());
paint.setAlpha(ball.getAlpha());
//设置画笔属性
paint.setStyle(Paint.Style.FILL);//画笔属性是实心
paint.setStrokeWidth(1);//设置画笔粗细
/*四个参数:
参数一:圆心的x坐标
参数二:圆心的y坐标
参数三:圆的半径
参数四:定义好的画笔*/
canvas.drawCircle(ball.getX(), ball.getY(), ball.getRadius(), paint);
}
}
public long getMaxBallNumber() {
return maxBallNumber;
}
public void setMaxBallNumber(long maxBallNumber) {
this.maxBallNumber = maxBallNumber;
}
/**
* 往布局添加小球
*/
public void addBall(Ball ball) {
if (ball != null && ballList.size()<maxBallNumber) {
ballList.add(ball);
}
}
/**
* 从布局移除小球
*/
public void removeBall(Ball ball) {
if (ball != null)
ballList.remove(ball);
}
public void actionBall() {
for (int i = 0; i < ballList.size(); i++) {
Ball ball1 = ballList.get(i);
int x1 = ball1.getX();
int y1 = ball1.getY();
int degree = ball1.getDegree();
//按照0-360度划分四个象限
if (degree >= 0 && degree < 90) {
//按10等份去分配X轴与Y轴的位移值
int xL = degree / 9;
int yL = 10 - degree / 9;
//第一象限
ball1.setX(x1 + xL);
ball1.setY(y1 + yL);
} else if (degree >= 90 && degree < 180) {
//第二象限 碰撞到布局边界
int xL = 10 - (degree / 9 - 10);
int yL = (degree / 9 - 10);
ball1.setX(x1 + xL);
ball1.setY(y1 + yL);
} else if (degree >= 180 && degree < 270) {
//第三象限 碰撞到布局边界
int xL = (degree / 9 - 20);
int yL = 10 - (degree / 9 - 20);
ball1.setX(x1 - xL);
ball1.setY(y1 + yL);
} else if (degree >= 270 && degree < 360) {
//第四象限 碰撞到布局边界
int xL = 10 - (degree / 9 - 30);
int yL = (degree / 9 - 30);
ball1.setX(x1 - xL);
ball1.setY(y1 - yL);
}
}
}
private class MyThread extends Thread {
@Override
public void run() {
while (true) {
actionBall();
//通知更新界面,会重新调用onDraw()函数
postInvalidate();
try {
sleep(10);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if(thread!=null)
thread.stop();
}
}
3. 边界检测,小球碰撞到布局边界时更改小球运动方向
根据入射角与反射角与平面夹角大小相等的原理
可以计算出在不同象限与边界碰撞后的运动方向
package com.pcf.randomball.view;
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 com.pcf.randomball.bean.Ball;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class RandomView extends View {
private int height;
private int width;
private long maxBallNumber = -1;
private List<Ball> ballList = new ArrayList();
private String TAG = "RandomLayout";
private MyThread thread;
public RandomView(Context context) {
super(context);
}
public RandomView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public RandomView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public RandomView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Log.d(TAG, "onMeasure ");
width = getMeasuredWidth();
height = getMeasuredHeight();
Log.d(TAG, "width " + width);
Log.d(TAG, "height " + height);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
Log.d(TAG, "onLayout ");
ballList.add(new Ball(50, Color.BLUE, 600, width / 2, height / 2));
//避免重复创建线程
if (thread == null) {
thread = new MyThread();
}
thread.start();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.d(TAG, "onDraw ");
for (int i = 0; i < ballList.size(); i++) {
Ball ball = ballList.get(i);
//实例化画笔对象
Paint paint = new Paint();
//给画笔设置颜色
paint.setColor(ball.getColor());
paint.setAlpha(ball.getAlpha());
//设置画笔属性
paint.setStyle(Paint.Style.FILL);//画笔属性是实心
paint.setStrokeWidth(1);//设置画笔粗细
/*四个参数:
参数一:圆心的x坐标
参数二:圆心的y坐标
参数三:圆的半径
参数四:定义好的画笔*/
canvas.drawCircle(ball.getX(), ball.getY(), ball.getRadius(), paint);
}
}
public long getMaxBallNumber() {
return maxBallNumber;
}
public void setMaxBallNumber(long maxBallNumber) {
this.maxBallNumber = maxBallNumber;
}
/**
* 往布局添加小球
*/
public void addBall(Ball ball) {
if (ball != null && ballList.size()<maxBallNumber) {
ballList.add(ball);
}
}
/**
* 从布局移除小球
*/
public void removeBall(Ball ball) {
if (ball != null)
ballList.remove(ball);
}
public void actionBall() {
for (int i = 0; i < ballList.size(); i++) {
Ball ball1 = ballList.get(i);
int x1 = ball1.getX();
int y1 = ball1.getY();
int degree = ball1.getDegree();
//按照0-360度划分四个象限
if (degree >= 0 && degree < 90) {
//按10等份去分配X轴与Y轴的位移值
int xL = degree / 9;
int yL = 10 - degree / 9;
//第一象限 碰撞到布局右边界
if (ball1.getX() + ball1.getRadius() >= width) {
ball1.setDegree(360 - degree);
ball1.setX(x1 - ball1.getRadius());
} else {
if (x1 + ball1.getRadius() + 1 + xL >= width) {
ball1.setX(width - ball1.getRadius());
} else {
ball1.setX(x1 + 1 + xL);
}
}
//碰撞到布局上边界
if (ball1.getY() - ball1.getRadius() <= 0) {
ball1.setDegree(180 - degree);
ball1.setY(y1 + 1 + yL);
} else {
if (y1 - ball1.getRadius() - 1 - yL <= 0) {
ball1.setY(ball1.getRadius());
} else {
ball1.setY(y1 - 1 - yL);
}
}
} else if (degree >= 90 && degree < 180) {
//第二象限 碰撞到布局边界
int xL = 10 - (degree / 9 - 10);
int yL = (degree / 9 - 10);
if (ball1.getX() + ball1.getRadius() >= width) {
ball1.setDegree(180 - degree + 180);
ball1.setX(x1 - ball1.getRadius());
} else {
if (x1 + ball1.getRadius() + 1 + xL >= width) {
ball1.setX(width - ball1.getRadius());
} else {
ball1.setX(x1 + 1 + xL);
}
}
//碰撞到布局边界
if (ball1.getY() + ball1.getRadius() >= height) {
ball1.setDegree(90 - degree + 90);
ball1.setY(y1 - 1 - yL);
} else {
if (y1 + ball1.getRadius() + 1 + yL >= height) {
ball1.setY(height - ball1.getRadius());
} else {
ball1.setY(y1 + 1 + yL);
}
}
} else if (degree >= 180 && degree < 270) {
//第三象限 碰撞到布局边界
int xL = (degree / 9 - 20);
int yL = 10 - (degree / 9 - 20);
if (ball1.getX() - ball1.getRadius() <= 0) {
ball1.setDegree(360 - degree);
ball1.setX(x1 + 1 + xL);
} else {
if (x1 - ball1.getRadius() - 1 - xL <= 0) {
ball1.setX(ball1.getRadius());
} else {
ball1.setX(x1 - 1 - xL);
}
}
//碰撞到布局边界
if (ball1.getY() + ball1.getRadius() >= height) {
ball1.setDegree(270 + 270 - degree);
ball1.setY(y1 - 1 - yL);
} else {
if (x1 + ball1.getRadius() + 1 + yL >= height) {
ball1.setY(height - ball1.getRadius());
} else {
ball1.setY(y1 + 1 + yL);
}
}
} else if (degree >= 270 && degree < 360) {
//第四象限 碰撞到布局边界
int xL = 10 - (degree / 9 - 30);
int yL = (degree / 9 - 30);
if (ball1.getX() - ball1.getRadius() <= 0) {
ball1.setDegree(360 - degree);
ball1.setX(x1 + 1 + xL);
} else {
if (x1 - ball1.getRadius() - 1 - xL <= 0) {
ball1.setX(ball1.getRadius());
} else {
ball1.setX(x1 - 1 - xL);
}
}
//碰撞到布局边界
if (ball1.getY() - ball1.getRadius() <= 0) {
ball1.setDegree(360 - degree + 180);
ball1.setY(y1 + 1 + yL);
} else {
if (y1 - ball1.getRadius() - 1 - yL <= 0) {
ball1.setY(ball1.getRadius());
} else {
ball1.setY(y1 - 1 - yL);
}
}
}
}
}
private class MyThread extends Thread {
@Override
public void run() {
while (true) {
actionBall();
//通知更新界面,会重新调用onDraw()函数
postInvalidate();
try {
sleep(10);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if(thread!=null)
thread.stop();
}
}
4. GitHubDemo
GitHubDemo
https://github.com/pengchengfuGit/randomball
5. 相关链接
https://www.cnblogs.com/xieyuan/archive/2012/11/27/3787450.html