Java一个简单的反弹动画练习
文章目录
- 说明
- 代码详解
- 创建窗体代码
- 创建绘图板
- 创建线程
- 运行结果
- 完整代码
说明
做了一个小球和星型做反弹动画的窗体作为练习,分享给大家,为了方便和我一样的小白可以看的比较明白,所以尽量详细的标注了注释,希望能帮到同样在学习路上的朋友
代码详解
创建窗体代码
public class AnimationJFrame extends JFrame {
//实例化属性
private final DrawCircleAndStar drawCircleAndStar = new DrawCircleAndStar();//实例化图形绘制
private final JButton jButton = new JButton();//实例化按钮
private final AnimationRun animationRun = new AnimationRun();//实例化线程
//设置绘图全局属性
private int circleX = 0;//圆形的初始位置X坐标
private int circleY = 10;//圆形的初始位置Y坐标
private int circleXDirection = 1;//圆形运动X轴方向
private int circleYDirection = 1;//圆形运动Y轴方向
private int starX = 355;//星型的初始位置X坐标
private int starY = 200;//星型的初始位置Y坐标
private int starXDirection = 1;//星形运动X轴方向
private int starYDirection = 1;//星形运动Y轴方向
//构造方法构造窗体
public AnimationJFrame(){
//窗体基本设置
Container conn = getContentPane();//建立窗体容器
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//设置窗体关闭方式
setBounds(300,300,400,400);//设置窗体位置及大小
setResizable(false);//窗体大小不可改变
setLayout(null);//清空窗体布局管理器,不采用默认布局
//创建动画布局
JPanel jPanel = new JPanel();//实例化布局
jPanel.setLayout(null);//清空布局的布局管理器,不采用默认布局
jPanel.setBounds(10, 10, 365, 300);//动画布局的位置及大小
jPanel.setBorder(BorderFactory.createLineBorder(Color.BLACK));//设置动画布局的边框
jPanel.setBackground(Color.LIGHT_GRAY);//设置动画布局的底色
//创建动画图形的标签容器
JLabel jLabel = new JLabel();//实例化标签
jLabel.setBounds(0,0,365,300);//设置动画标签的大小和位置
//设置绘图标签
drawCircleAndStar.setBackground(new Color(0,0,0,0));//设置绘图标签的背景透明
drawCircleAndStar.setSize(365,300);//设置绘图标签的大小
jLabel.add(drawCircleAndStar);//添加绘图板到标签
jPanel.add(jLabel);//添加动画标签到动画布局
//设置按键
jButton.setText("开始");//设置按键显示文字
jButton.setFont(new Font("黑体", Font.PLAIN, 14));//设置按键文字的字体
jButton.setBounds(275, 320, 100, 22);//设置按键的位置和大小
//添加原件到窗体容器
conn.add(jPanel);//添加动漫布局到容器
conn.add(jButton);//添加按钮到容器
//给按钮添加监听
jButton.addActionListener(e -> {
if (e.getActionCommand().equals("开始")){
jButton.setText("暂停一下");//更改按键文字
animationRun.start();//启动线程
}else if (e.getActionCommand().equals("暂停一下")){
jButton.setText("继续");
animationRun.pause();//暂停线程
}else if (e.getActionCommand().equals("继续")){
jButton.setText("暂停一下");
animationRun.restart();//重启线程
}
});
}
这里需要说明的是,代码中直接把窗体创建在构造方法中,但是直接在构造方法中设置窗体只适用于线程较简单的程序,如果在正式的项目中,应该避免这种写法,因为swing是线程不安全的,应该对窗体单独建立窗体方法容器,例如:
public AnimationJFrame() {
SwingUtilities.invokeLater(() -> {
initUI();
});
}
private void initUI() {
//代码块...........
}
创建绘图板
//创建绘图板
class DrawCircleAndStar extends JLabel{
@Override
public void paintComponent(Graphics g){//重写paintComponent
super.paintComponent(g);//继承父类的构造方法
Graphics2D graphics2D = (Graphics2D) g;
graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);//设置绘图抗锯齿
//绘制圆形
graphics2D.setColor(Color.RED);//设置填充颜色,红色
graphics2D.fillOval(circleX,circleY,15,15);//设置圆形的位置和大小
//绘制星型
graphics2D.setColor(Color.BLUE);//设置填充色,蓝色
//设置星型的属性
int radius = 10;//设置星型的大小
int[] xPoints = new int[10];//星型顶点的X坐标
int[] yPoints = new int[10];//星型顶点的Y坐标
//计算星型顶点的坐标及初始角度
for (int i = 0; i < 10; i++) {
double baseAngle = i * 2 * Math.PI / 10 + Math.PI / 2;//初始角度
if (i % 2 == 0) {//外围顶点的坐标
xPoints[i] = starX + (int) (radius * Math.cos(baseAngle));
yPoints[i] = starY - (int) (radius * Math.sin(baseAngle));
} else {//内部顶点的坐标
xPoints[i] = starX + (int) (radius * Math.cos(baseAngle) * 0.5);
yPoints[i] = starY - (int) (radius * Math.sin(baseAngle) * 0.5);
}
}
Polygon star = new Polygon(xPoints, yPoints, 10);//绘制星型
graphics2D.fillPolygon(star);//填充星型
}
}
创建绘图板,别的没有什么可说的,这里切记一点,无论绘图板继承自布局还是标签,相对的布局和标签是没有大小的,必须在窗体设置中,为它们设定尺寸,比如本代码中的
drawCircleAndStar.setBackground(new Color(0,0,0,0));//设置绘图标签的背景透明
drawCircleAndStar.setSize(365,300);//设置绘图标签的大小
如果不设置大小,将无法将绘图板图形显示到容器
创建线程
//创建运行线程
class AnimationRun extends Thread{
boolean flag = false;//设置挂起标志
synchronized void pause(){//暂停方法
flag = true;
}
synchronized void restart(){//重启方法
notifyAll();
flag = false;
}
//重写run
@Override
public void run(){
//动画运行
while (true) {
//挂起区
synchronized (this){
while (flag){
try {
wait();//等待挂起
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
//判断圆形运动方向
if (circleX == 0) {
circleXDirection = 1;
} else if (circleX == 350) {
circleXDirection = -1;
}
if (circleY == 0) {
circleYDirection = 1;
} else if (circleY == 285) {
circleYDirection = -1;
}
//判断星型运动方向
if (starX == 10){
starXDirection = 1;
}else if (starX == 355){
starXDirection = -1;
}
if (starY == 10){
starYDirection = 1;
}else if (starY == 290){
starYDirection = -1;
}
//设置绘图延迟
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//计算新的圆形位置和星型的位置
circleX += circleXDirection;
circleY += circleYDirection;
starX += starXDirection;
starY += starYDirection;
//重绘绘图板
drawCircleAndStar.repaint();
}
}
}
synchronized
相对来说并不是最优的选择,消耗较高,建议优化,另外一个这里重写的不是paint
而是重写的paintComponent
因为重写paintComponent
只绘制图形主体,不会影响边框和背景,建议单独绘制元素的时候使用paintComponent
而不是paint
。
运行结果
完整代码
import javax.swing.*;
import java.awt.*;
public class AnimationJFrame extends JFrame {
//实例化属性
private final DrawCircleAndStar drawCircleAndStar = new DrawCircleAndStar();//实例化图形绘制
private final JButton jButton = new JButton();//实例化按钮
private final AnimationRun animationRun = new AnimationRun();//实例化线程
//设置绘图全局属性
private int circleX = 0;//圆形的初始位置X坐标
private int circleY = 10;//圆形的初始位置Y坐标
private int circleXDirection = 1;//圆形运动X轴方向
private int circleYDirection = 1;//圆形运动Y轴方向
private int starX = 355;//星型的初始位置X坐标
private int starY = 200;//星型的初始位置Y坐标
private int starXDirection = 1;//星形运动X轴方向
private int starYDirection = 1;//星形运动Y轴方向
//构造方法构造窗体
public AnimationJFrame(){
//窗体基本设置
Container conn = getContentPane();//建立窗体容器
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//设置窗体关闭方式
setBounds(300,300,400,400);//设置窗体位置及大小
setResizable(false);//窗体大小不可改变
setLayout(null);//清空窗体布局管理器,不采用默认布局
//创建动画布局
JPanel jPanel = new JPanel();//实例化布局
jPanel.setLayout(null);//清空布局的布局管理器,不采用默认布局
jPanel.setBounds(10, 10, 365, 300);//动画布局的位置及大小
jPanel.setBorder(BorderFactory.createLineBorder(Color.BLACK));//设置动画布局的边框
jPanel.setBackground(Color.LIGHT_GRAY);//设置动画布局的底色
//创建动画图形的标签容器
JLabel jLabel = new JLabel();//实例化标签
jLabel.setBounds(0,0,365,300);//设置动画标签的大小和位置
//设置绘图标签
drawCircleAndStar.setBackground(new Color(0,0,0,0));//设置绘图标签的背景透明
drawCircleAndStar.setSize(365,300);//设置绘图标签的大小
jLabel.add(drawCircleAndStar);//添加绘图板到标签
jPanel.add(jLabel);//添加动画标签到动画布局
//设置按键
jButton.setText("开始");//设置按键显示文字
jButton.setFont(new Font("黑体", Font.PLAIN, 14));//设置按键文字的字体
jButton.setBounds(275, 320, 100, 22);//设置按键的位置和大小
//添加原件到窗体容器
conn.add(jPanel);//添加动漫布局到容器
conn.add(jButton);//添加按钮到容器
//给按钮添加监听
jButton.addActionListener(e -> {
if (e.getActionCommand().equals("开始")){
jButton.setText("暂停一下");//更改按键文字
animationRun.start();//启动线程
}else if (e.getActionCommand().equals("暂停一下")){
jButton.setText("继续");
animationRun.pause();//暂停线程
}else if (e.getActionCommand().equals("继续")){
jButton.setText("暂停一下");
animationRun.restart();//重启线程
}
});
}
//创建绘图板
class DrawCircleAndStar extends JLabel{
@Override
public void paintComponent(Graphics g){//重写paintComponent
super.paintComponent(g);//继承父类的构造方法
Graphics2D graphics2D = (Graphics2D) g;
graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);//设置绘图抗锯齿
//绘制圆形
graphics2D.setColor(Color.RED);//设置填充颜色,红色
graphics2D.fillOval(circleX,circleY,15,15);//设置圆形的位置和大小
//绘制星型
graphics2D.setColor(Color.BLUE);//设置填充色,蓝色
//设置星型的属性
int radius = 10;//设置星型的大小
int[] xPoints = new int[10];//星型顶点的X坐标
int[] yPoints = new int[10];//星型顶点的Y坐标
//计算星型顶点的坐标及初始角度
for (int i = 0; i < 10; i++) {
double baseAngle = i * 2 * Math.PI / 10 + Math.PI / 2;//初始角度
if (i % 2 == 0) {//外围顶点的坐标
xPoints[i] = starX + (int) (radius * Math.cos(baseAngle));
yPoints[i] = starY - (int) (radius * Math.sin(baseAngle));
} else {//内部顶点的坐标
xPoints[i] = starX + (int) (radius * Math.cos(baseAngle) * 0.5);
yPoints[i] = starY - (int) (radius * Math.sin(baseAngle) * 0.5);
}
}
Polygon star = new Polygon(xPoints, yPoints, 10);//绘制星型
graphics2D.fillPolygon(star);//填充星型
}
}
//创建运行线程
class AnimationRun extends Thread{
boolean flag = false;//设置挂起标志
synchronized void pause(){//暂停方法
flag = true;
}
synchronized void restart(){//重启方法
notifyAll();
flag = false;
}
//重写run
@Override
public void run(){
//动画运行
while (true) {
//挂起区
synchronized (this){
while (flag){
try {
wait();//等待挂起
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
//判断圆形运动方向
if (circleX == 0) {
circleXDirection = 1;
} else if (circleX == 350) {
circleXDirection = -1;
}
if (circleY == 0) {
circleYDirection = 1;
} else if (circleY == 285) {
circleYDirection = -1;
}
//判断星型运动方向
if (starX == 10){
starXDirection = 1;
}else if (starX == 355){
starXDirection = -1;
}
if (starY == 10){
starYDirection = 1;
}else if (starY == 290){
starYDirection = -1;
}
//设置绘图延迟
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//计算新的圆形位置和星型的位置
circleX += circleXDirection;
circleY += circleYDirection;
starX += starXDirection;
starY += starYDirection;
//重绘绘图板
drawCircleAndStar.repaint();
}
}
}
public static void main(String[] args) {
new AnimationJFrame().setVisible(true);//启动程序
}
}