线程---简单弹球
一般的程序,就是从头至尾地执行代码,完成了一个方法,接下来完成后面紧接着的方法,但是,一般解决实际问题不会单纯地按顺序操作,很有可能在同一时间执行多种方法,就像一个人,在某一个时刻不会只做一件事,他可能一边听歌,一边写总结•••那么这样就需要用到多线程操作。
实现线程有两种方式,一种是继承Thread类,一种是实现Runnable接口,这里主要先研究第一种情况继承Thread类。
步骤如下:
1. 先定义一个新类继承Thread类,在该类中,重写父类的run()方法,实际上要完成的方法就是重写的run()方法。
2. 实例化一个上述类对象,调用其start()方法,线程即准备就绪(个人理解就是开始了)。
注意:一个上述类对象只能调用一次start()方法,如果调用多次,系统将报错;
当run()中的过程结束,这个线程即结束。(当然也可以用stop()方法强制结束线程)
public class DrawTest extends JFrame{
接下来,是利用线程,实现一个简单的弹球:
这里定义一个DrawTest类来继承JFrame,小球将在该类的窗体上运动:
public class DrawTest extends JFrame{
public static void main(String[] args){
DrawTest test =new DrawTest();
test.shower();
}
//构造一个画板
public void shower(){
this.setTitle("神奇小球");
this.setSize(600,600);
this.setLocationRelativeTo(null);
this.setDefaultCloseOperation(3);
this.setLayout(new FlowLayout());
//设置执行按钮
JButton jb1=new JButton("执行");
jb1.setActionCommand("执行");
jb1.setPreferredSize(new Dimension(100,50));
this.add(jb1);
//设置暂停按钮
JButton jb2=new JButton("暂停");
jb2.setActionCommand("暂停");
jb2.setPreferredSize(new Dimension(100,50));
this.add(jb2);
//设置开始按钮
JButton jb3=new JButton("开始");
jb3.setActionCommand("开始");
jb3.setPreferredSize(new Dimension(100,50));
this.add(jb3);
//设置删除按钮
JButton jb4=new JButton("删除");
jb4.setActionCommand("删除");
jb4.setPreferredSize(new Dimension(100,50));
this.add(jb4);
this.setVisible(true);
drawListener1 A=new drawListener1(this);//这里将整个窗体传入,是为了后面得到画布所需要,将在后面解释
//添加监听器
jb1.addActionListener(A);
jb2.addActionListener(A);
jb3.addActionListener(A);
jb4.addActionListener(A);
}
}
因为有四个按钮,实现的是四个不同的功能,按道理来说要设置四个不同的监听器加入才行,但是这样代码太过繁琐,下面是一个实现监听器的类,在此类中用if语句判断,从而达到一个监听器多用的作用。
实现监听器的代码如下:
public class drawListener1 implements ActionListener {
private JFrame jf;
// 用一个数组队列来存放线程对象
private List<drawThread> list = new ArrayList<drawThread>();
public drawListener1(JFrame jf) {
this.jf = jf;
}
public void actionPerformed(ActionEvent e) {
// 执行增加一个小球
if (e.getActionCommand().equals("执行")) {
System.out.println("执行了!");
drawThread threadA = new drawThread(this.jf.getGraphics());
threadA.setStart(true);
threadA.start();
list.add(threadA);
//这里用到了线程,每点击“执行”一次,将实例化一个线程对像,这样可以让多个方法同时进行(多个小球同时运动)
}
// 暂停当前画布上所有的小球的运动
if (e.getActionCommand().equals("暂停")) {
for (int i = 0; i < list.size(); i++) {
drawThread thread = list.get(i);
thread.setStart(false);
}
}
// 开始当前画布上所有的小球的运动
if (e.getActionCommand().equals("开始")) {
for (int i = 0; i < list.size(); i++) {
drawThread thread = list.get(i);
thread.setStart(true);
}
}
// 清楚最后一次执行的小球
if (e.getActionCommand().equals("删除")) {
if (list.size() > 0) {
drawThread thread = list.get(list.size() - 1);
thread.clear();
thread.stop();
list.remove(list.size() - 1);
System.out.println(list.size());
}
}
}
}
接下来是实现线程中的run()方法:
// 构造函数,传入画布
private Graphics g;
public drawThread(Graphics g) {
this.g = g;
}
// 定义一个开始的开关,当其为真时可以执行
private boolean start = false;
// 定义小球初始半径
private int radius = 30;
// 分别定义小球x,y方向上的速度
private int speedX = 2;
private int speedY = 3;
// 定义一个控制x方向上的运动方向(0表示向左,1表示向右)
private int directX = 1;
// 定义一个控制y方向上的运动方向(0表示向上,1表示向下)
private int directY = 1;
// 定义小球的初始位置
private int X = 0, Y = 50;
// 定义小球的颜色
private Color color;
// 定义随机数,保存颜色
private int t1, t2, t3;
// 设置开关
public void setStart(boolean start) {
this.start = start;
System.out.println(start);
}
// 得到开关当前值
public boolean getStart() {
return start;
}
// 得到当前的X Y值
public int getX() {
return X;
}
public int getY() {
return Y;
}
// 得到当前的半径值
public int getRadius() {
return radius;
}
// 得到当前的画布对象
public Graphics getGra() {
return g;
}
// 重写run方法
public void run() {
java.util.Random rd = new java.util.Random();
if (start) {
// 设置小球的颜色以及速度
color = new Color(t1 = rd.nextInt(220), t2 = rd.nextInt(220),
t3 = rd.nextInt(220));
speedX = rd.nextInt(6) + 2;
speedY = rd.nextInt(6) + 3;
while (true) {
// 得到小球当前的坐标
moveXY();
// 延时
try {
Thread.sleep(50);
} catch (Exception e) {
}
// 擦除上次画的小球,画出本次小球
clear();
fill();
// 暂停时重绘小球
repaint();
}
}
}
// 小球的运动的方向
public void moveXY() {
if (start) {
if (X <= radius) {
directX = 1;
} else if (X >= 600 - radius) {
directX = 0;
}
if (Y <= radius + 70) {
directY = 1;
} else if (Y >= 600 - radius) {
directY = 0;
}
// 确定小球x方向上的运动方向
switch (directX) {
case 0:
X -= speedX;
break;
case 1:
X += speedX;
}
// 确定小球y方向上的运动方向
switch (directY) {
case 0:
Y -= speedY;
break;
case 1:
Y += speedY;
}
}
}
// 擦除小球
public void clear() {
if (directX == 1 && directY == 1) {
g.clearRect(X - speedX - 1, Y - speedY - 1, radius, radius);
} else if (directX == 1 && directY == 0) {
g.clearRect(X - speedX - 1, Y + speedY + 1, radius, radius);
} else if (directX == 0 && directY == 1) {
g.clearRect(X + speedX + 1, Y - speedY - 1, radius, radius);
} else if (directX == 0 && directY == 0) {
g.clearRect(X + speedX + 1, Y + speedY + 1, radius, radius);
}
}
// 画小球
public void fill() {
int t = radius,temp=t1;
for (int i = 0; i < radius; i++) {
temp=temp+10;
if (temp < 256 && t2 < 256 && t3 < 256) {
g.setColor(new Color(temp, t2, t3));
g.fillOval(X + i, Y + i, t--, t--);
}
}
}
// 暂停时的重绘
public void repaint() {
if (!start) {
int t = radius,temp=t1;
for (int i = 0; i < radius; i++) {
temp=temp+10;
if (temp < 256 && t2 < 256 && t3 < 256) {
g.setColor(new Color(temp, t2, t3); g.fillOval(X + i, Y + i, t--, t--);
}
}
}
}
}
完成以上代码,一个简单的弹球项目就完成了,能实现的功能有:加入一个球在窗体内运动(如下加入三个球):
[img]http://dl2.iteye.com/upload/attachment/0087/1363/45845eaf-51c4-340e-8661-ae758d94da23.png[/img]
让窗体上所有的球静止
让窗体上所有的球开始运动
删除最后一次执行的小球(原本三个球只剩下了两个):
[img]http://dl2.iteye.com/upload/attachment/0087/1365/9b86e239-9969-384c-a49a-54194fe1b01e.png[/img]
在开头曾经说到传画布的问题,在以前的项目里得出的经验是在窗体类中得到其画布,传入到监听器中,又将监听器中的画布传入到线程中。而本代码的思路却有所不同,是先将窗体随想传入到监听器中,再在监听器中通过drawThread(this.jf.getGraphics());语句使线程获得画布,这样有区别吗。答案是毫无疑问的,有!
大家可以试一试,如果使用传统方法,在执行多个小球运动时,会出现不和谐的印记(就像是没有擦干净,此处不再附图),而用本代码的方法将解决该问题。
这是为什么呢。
如果使用传统方法,将窗体的画布直接传入线程,那么多个线程对像将在同一个画布上执行方法,这样会出现线程中经常出现的资源共享的问题,但是,用本代码的方法,每次传入的画布是 窗体对象.getGraphics(),实际上,多次调用getGraphics()方法,得到的画布将不是同一个画布,这样就解决了资源共享的问题。
当然,该程序还有很多的不足,如代码不精炼,没有添加背景,区域大小不能随窗体的扩大而改变,没有球与球之间碰撞的效果,当然在后续时间我将完成这一任务。
一般的程序,就是从头至尾地执行代码,完成了一个方法,接下来完成后面紧接着的方法,但是,一般解决实际问题不会单纯地按顺序操作,很有可能在同一时间执行多种方法,就像一个人,在某一个时刻不会只做一件事,他可能一边听歌,一边写总结•••那么这样就需要用到多线程操作。
实现线程有两种方式,一种是继承Thread类,一种是实现Runnable接口,这里主要先研究第一种情况继承Thread类。
步骤如下:
1. 先定义一个新类继承Thread类,在该类中,重写父类的run()方法,实际上要完成的方法就是重写的run()方法。
2. 实例化一个上述类对象,调用其start()方法,线程即准备就绪(个人理解就是开始了)。
注意:一个上述类对象只能调用一次start()方法,如果调用多次,系统将报错;
当run()中的过程结束,这个线程即结束。(当然也可以用stop()方法强制结束线程)
public class DrawTest extends JFrame{
接下来,是利用线程,实现一个简单的弹球:
这里定义一个DrawTest类来继承JFrame,小球将在该类的窗体上运动:
public class DrawTest extends JFrame{
public static void main(String[] args){
DrawTest test =new DrawTest();
test.shower();
}
//构造一个画板
public void shower(){
this.setTitle("神奇小球");
this.setSize(600,600);
this.setLocationRelativeTo(null);
this.setDefaultCloseOperation(3);
this.setLayout(new FlowLayout());
//设置执行按钮
JButton jb1=new JButton("执行");
jb1.setActionCommand("执行");
jb1.setPreferredSize(new Dimension(100,50));
this.add(jb1);
//设置暂停按钮
JButton jb2=new JButton("暂停");
jb2.setActionCommand("暂停");
jb2.setPreferredSize(new Dimension(100,50));
this.add(jb2);
//设置开始按钮
JButton jb3=new JButton("开始");
jb3.setActionCommand("开始");
jb3.setPreferredSize(new Dimension(100,50));
this.add(jb3);
//设置删除按钮
JButton jb4=new JButton("删除");
jb4.setActionCommand("删除");
jb4.setPreferredSize(new Dimension(100,50));
this.add(jb4);
this.setVisible(true);
drawListener1 A=new drawListener1(this);//这里将整个窗体传入,是为了后面得到画布所需要,将在后面解释
//添加监听器
jb1.addActionListener(A);
jb2.addActionListener(A);
jb3.addActionListener(A);
jb4.addActionListener(A);
}
}
因为有四个按钮,实现的是四个不同的功能,按道理来说要设置四个不同的监听器加入才行,但是这样代码太过繁琐,下面是一个实现监听器的类,在此类中用if语句判断,从而达到一个监听器多用的作用。
实现监听器的代码如下:
public class drawListener1 implements ActionListener {
private JFrame jf;
// 用一个数组队列来存放线程对象
private List<drawThread> list = new ArrayList<drawThread>();
public drawListener1(JFrame jf) {
this.jf = jf;
}
public void actionPerformed(ActionEvent e) {
// 执行增加一个小球
if (e.getActionCommand().equals("执行")) {
System.out.println("执行了!");
drawThread threadA = new drawThread(this.jf.getGraphics());
threadA.setStart(true);
threadA.start();
list.add(threadA);
//这里用到了线程,每点击“执行”一次,将实例化一个线程对像,这样可以让多个方法同时进行(多个小球同时运动)
}
// 暂停当前画布上所有的小球的运动
if (e.getActionCommand().equals("暂停")) {
for (int i = 0; i < list.size(); i++) {
drawThread thread = list.get(i);
thread.setStart(false);
}
}
// 开始当前画布上所有的小球的运动
if (e.getActionCommand().equals("开始")) {
for (int i = 0; i < list.size(); i++) {
drawThread thread = list.get(i);
thread.setStart(true);
}
}
// 清楚最后一次执行的小球
if (e.getActionCommand().equals("删除")) {
if (list.size() > 0) {
drawThread thread = list.get(list.size() - 1);
thread.clear();
thread.stop();
list.remove(list.size() - 1);
System.out.println(list.size());
}
}
}
}
接下来是实现线程中的run()方法:
// 构造函数,传入画布
private Graphics g;
public drawThread(Graphics g) {
this.g = g;
}
// 定义一个开始的开关,当其为真时可以执行
private boolean start = false;
// 定义小球初始半径
private int radius = 30;
// 分别定义小球x,y方向上的速度
private int speedX = 2;
private int speedY = 3;
// 定义一个控制x方向上的运动方向(0表示向左,1表示向右)
private int directX = 1;
// 定义一个控制y方向上的运动方向(0表示向上,1表示向下)
private int directY = 1;
// 定义小球的初始位置
private int X = 0, Y = 50;
// 定义小球的颜色
private Color color;
// 定义随机数,保存颜色
private int t1, t2, t3;
// 设置开关
public void setStart(boolean start) {
this.start = start;
System.out.println(start);
}
// 得到开关当前值
public boolean getStart() {
return start;
}
// 得到当前的X Y值
public int getX() {
return X;
}
public int getY() {
return Y;
}
// 得到当前的半径值
public int getRadius() {
return radius;
}
// 得到当前的画布对象
public Graphics getGra() {
return g;
}
// 重写run方法
public void run() {
java.util.Random rd = new java.util.Random();
if (start) {
// 设置小球的颜色以及速度
color = new Color(t1 = rd.nextInt(220), t2 = rd.nextInt(220),
t3 = rd.nextInt(220));
speedX = rd.nextInt(6) + 2;
speedY = rd.nextInt(6) + 3;
while (true) {
// 得到小球当前的坐标
moveXY();
// 延时
try {
Thread.sleep(50);
} catch (Exception e) {
}
// 擦除上次画的小球,画出本次小球
clear();
fill();
// 暂停时重绘小球
repaint();
}
}
}
// 小球的运动的方向
public void moveXY() {
if (start) {
if (X <= radius) {
directX = 1;
} else if (X >= 600 - radius) {
directX = 0;
}
if (Y <= radius + 70) {
directY = 1;
} else if (Y >= 600 - radius) {
directY = 0;
}
// 确定小球x方向上的运动方向
switch (directX) {
case 0:
X -= speedX;
break;
case 1:
X += speedX;
}
// 确定小球y方向上的运动方向
switch (directY) {
case 0:
Y -= speedY;
break;
case 1:
Y += speedY;
}
}
}
// 擦除小球
public void clear() {
if (directX == 1 && directY == 1) {
g.clearRect(X - speedX - 1, Y - speedY - 1, radius, radius);
} else if (directX == 1 && directY == 0) {
g.clearRect(X - speedX - 1, Y + speedY + 1, radius, radius);
} else if (directX == 0 && directY == 1) {
g.clearRect(X + speedX + 1, Y - speedY - 1, radius, radius);
} else if (directX == 0 && directY == 0) {
g.clearRect(X + speedX + 1, Y + speedY + 1, radius, radius);
}
}
// 画小球
public void fill() {
int t = radius,temp=t1;
for (int i = 0; i < radius; i++) {
temp=temp+10;
if (temp < 256 && t2 < 256 && t3 < 256) {
g.setColor(new Color(temp, t2, t3));
g.fillOval(X + i, Y + i, t--, t--);
}
}
}
// 暂停时的重绘
public void repaint() {
if (!start) {
int t = radius,temp=t1;
for (int i = 0; i < radius; i++) {
temp=temp+10;
if (temp < 256 && t2 < 256 && t3 < 256) {
g.setColor(new Color(temp, t2, t3); g.fillOval(X + i, Y + i, t--, t--);
}
}
}
}
}
完成以上代码,一个简单的弹球项目就完成了,能实现的功能有:加入一个球在窗体内运动(如下加入三个球):
[img]http://dl2.iteye.com/upload/attachment/0087/1363/45845eaf-51c4-340e-8661-ae758d94da23.png[/img]
让窗体上所有的球静止
让窗体上所有的球开始运动
删除最后一次执行的小球(原本三个球只剩下了两个):
[img]http://dl2.iteye.com/upload/attachment/0087/1365/9b86e239-9969-384c-a49a-54194fe1b01e.png[/img]
在开头曾经说到传画布的问题,在以前的项目里得出的经验是在窗体类中得到其画布,传入到监听器中,又将监听器中的画布传入到线程中。而本代码的思路却有所不同,是先将窗体随想传入到监听器中,再在监听器中通过drawThread(this.jf.getGraphics());语句使线程获得画布,这样有区别吗。答案是毫无疑问的,有!
大家可以试一试,如果使用传统方法,在执行多个小球运动时,会出现不和谐的印记(就像是没有擦干净,此处不再附图),而用本代码的方法将解决该问题。
这是为什么呢。
如果使用传统方法,将窗体的画布直接传入线程,那么多个线程对像将在同一个画布上执行方法,这样会出现线程中经常出现的资源共享的问题,但是,用本代码的方法,每次传入的画布是 窗体对象.getGraphics(),实际上,多次调用getGraphics()方法,得到的画布将不是同一个画布,这样就解决了资源共享的问题。
当然,该程序还有很多的不足,如代码不精炼,没有添加背景,区域大小不能随窗体的扩大而改变,没有球与球之间碰撞的效果,当然在后续时间我将完成这一任务。