在Java的图形化窗体界面上实现图像简单的运动功能

如何在窗体上实现图像运动?比如,在窗体上画一个黑色的点,然后让这个点以一定的方向运动。请往下看:
一、可视化窗体的生成
首先我们需要在电脑屏幕上面生成一个窗体,可以设定大小以及关闭按钮。
代码如下所示:

import javax.swing.JFrame;
public class GUI extends JFrame{
    /**简单生成一个窗体即可
     */
    private static final long serialVersionUID = 1L;
    public GUI() {
        setTitle("生成窗体");
        setSize(1000, 1000);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setVisible(true);
    }
    public static void main(String[] args) {
        new GUI();
    }
}

运行之后屏幕上就弹出了一个空的窗体
空的窗体
二、在窗体中画一个圆点
要画图形,先简要介绍一下Graphics类。Graphics类是所有图形上下文的抽象基类,允许应用程序绘制到在各种设备上实现的组件以及屏幕外图像。我们可以把他比喻成一个画笔。在之前生成的窗体中,我们并没有创建一个Graphics对象,但是我们需要注意的是,窗体自带一个Graphics对象,也就是窗体已经有一个画笔可以在窗体上画图了,但在代码中没有显示。我们需要利用窗体自带的画笔,怎么办呢?

  • 在类方法中添加一个方法 paint();
  • 在方法体中调用画笔的方法
    public void paint(Graphics g) {
        super.paint(g);
        g.fillOval(100, 100, 10, 10);//给窗体画圆点
    }

这里的paint实际上是JFrame父类的一个方法,在此进行重写,重写时记得先调用父类的方法体内容。在调用Graphics方法给窗体画图形时,会在底层调用paint方法载入画笔进行绘图(个人理解,如有不正欢迎批判指正)。如果在此处的GUI类中不加入paint方法,利用如下方式,再调用g的方法,则画不出图形。

Graphics g;
g = GUI.getGraphics;
//在不加入paint方法的情况下

运行效果如下:
画圆点

三、让点动起来

  1. 不做其他处理,单纯通过不断的改变fillOval方法的坐标画点.
    为了使过程直观可控,我们添加一个按钮事件,功能是:点一下按钮,就开始不断的画点。最简单的方法是利用循环,不断的改变fillOval方法的坐标,使得画出的点的位置看上去像是连续变化。
    (Tip:关于按钮添加和功能触发的原理,可参考接口与事件
    添加的按钮监听器类,并修改一下GUI的构造方法:
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.Graphics;

public class ButtonListener implements ActionListener{
    Graphics g;

    public ButtonListener(Graphics g) {//利用构造方法把窗体的Graphics画笔给按钮事件监听器使用
        this.g = g;       
    }
    @Override
    public void actionPerformed(ActionEvent e) {
        for (int i = 0; i < 10000; i++) {
            g.fillOval(i, 500, 30, 30);
        }
    }
}

//========================分割线=======================
    public GUI() {
        setTitle("生成窗体");
        setSize(1000, 1000);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        FlowLayout fl = new FlowLayout();
        setLayout(fl);
        JButton btn = new JButton("画图");
        add(btn);
        setVisible(true);
        ButtonListener bl = new ButtonListener(this.getGraphics());
        //注意这里获得Graphics的方法要放在setVisible后,否则画笔还未加载,方法无效
        btn.addActionListener(bl);
    }

运行之后,你将看到如下效果:
在这里插入图片描述
别眨眼,我们可以看见这个线条飞快地从左往右画过去,实现了点动成线的效果。但是,我想实现单个圆点从左往右运动,怎么做呢?
一个简答的办法就是:边画边擦除。也就是说,画上一个点之后,再拿原来的背景把点盖住,再在下一个坐标画点,再盖住,再在下一个坐标画点…这样我们就可以在屏幕上只看见一个点在运动了。

  • 修改一下按钮事件的方法:
    public void actionPerformed(ActionEvent e) {
        for (int i = 0; i < 10000; i++) {
            g.setColor(Color.BLACK);
            g.fillOval(i, 500, 30, 30);   //在下一个坐标画点
            g.setColor(Color.white);
            g.fillRect(0, 0, 1000, 1000); //使用白色背景擦除
        }
    }

再来看看运行效果:
在这里插入图片描述
这里就只看得见白色背景,看不见圆点。这是因为循环执行的速度过快,人眼帧数较低所,圆点的画面来不及被捕捉,就又被白色背景盖住了。为了显示效果,在画圆点后添加sleep即可。

    public void actionPerformed(ActionEvent e) {
        for (int i = 0; i < 10000; i++) {
            g.setColor(Color.BLACK);
            g.fillOval(i, 500, 30, 30);
            try {
                Thread.sleep(10);
            } catch (InterruptedException e1) {
                e1.printStackTrace();
            }
            g.setColor(Color.white);
            g.fillRect(0, 0, 1000, 1000);
        }
    }

在这里插入图片描述

  • 这样就显示出了一个圆点运动的效果!但是,这时候点右上角的关闭按钮,却关不掉窗口,因为程序要等到这个时间(循环)执行完毕再进行下一条指令。并且发现小球有时候会闪动。

四、使用多个线程进行画图

  • 对于想随时关闭窗口的问题,我们可以采用多线程的方式,把画图的方法放在线程里面。
  • 可以通过多线程同时在屏幕上画多个小球(每按一下按钮就新启动一个线程)
  • 修改代码如下:
import java.awt.Graphics;
import java.awt.Color;

public class MoveThread extends Thread{
    Graphics g;

    public MoveThread(Graphics g) {
        this.g = g;
    }

    public void run() {
        for (int i = 0; i < 10000; i++) {
            g.setColor(Color.BLACK);
            g.fillOval(i, 500, 30, 30);
            try {
                Thread.sleep(10);
            } catch (InterruptedException e1) {
                e1.printStackTrace();
            }
            g.setColor(Color.white);
            g.fillRect(0, 0, 1000, 1000);
        }
    }
}
===================分割线=====================
    public void actionPerformed(ActionEvent e) {
        new MoveThread(g).start();
    }

效果如下所示:
在这里插入图片描述
这里出现了几个问题:

  • 添加多个小球后,小球闪动更加明显。因为多个线程一起在这个窗体上进行画点-覆盖刷新-画点-覆盖刷新…的操作,并且每个线程开始的时间不一样,导致覆盖刷新不同步。
  • 背景也出现闪动,因为这么多个线程共用一个窗体的Graphics画笔,有一个线程画圆点时执行g.setColor(color.black)方法,把画笔设成黑色,其他线程这时正好在画背景,这样就把背景画成了黑色。

解决方法:

  • 启用线程队列,将所有添加的圆点放在一个List中,只需启动一个线程来把这个队列的元素画出来就可以了。
  • 用一个自定义的图片当作背景。
  • 我们需要把圆点单独作为一个类Point,并在GUI类中创建一个Point类型的List,然后用一个线程不断遍历这个List把点画出并进行背景覆盖刷新。
//GUI类:
ArrayList<Point> list = new ArrayList<>();
    public GUI() {
        setTitle("生成窗体");
        setSize(1000, 1000);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        FlowLayout fl = new FlowLayout();
        setLayout(fl);
        JButton btn = new JButton("画图");
        add(btn);
        setVisible(true);
        ButtonListener bl = new ButtonListener(this.getGraphics(),list);//注意这里获得Graphics的方法要放在setVisible后,否则画笔还未加载,方法无效
        btn.addActionListener(bl);
        new MoveThread(this.getGraphics(),list).start();
    }
//Point类:
import java.awt.Color;
import java.awt.Graphics;

public class Point {
    int x, y; //横纵坐标

    public Point() {}
    
    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public void move() {
        x += 1;
    }

    public void draw(Graphics g) {
        g.setColor(Color.black);
        g.fillOval(x, y, 30, 30);
    }
}
//线程类:
import java.awt.Graphics;
import java.util.ArrayList;
import javax.swing.ImageIcon;

public class MoveThread extends Thread{
    Graphics g;
    ArrayList<Point> list;
    ImageIcon bgm = new ImageIcon("D:\\Download\\远山.jpg");
    public MoveThread(Graphics g, ArrayList<Point> list) {//利用构造方法传参
        this.g = g;
        this.list = list;
    }

    public void run() {
        while (true) {
            for (int i = 0; i < list.size(); i++) {
                list.get(i).move();
                list.get(i).draw(g);
            }
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            g.drawImage(bgm.getImage(), 0, 100, null);
        }
    }
}
//按钮事件监听器类:
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.awt.Graphics;

public class ButtonListener implements ActionListener{
    Graphics g;
    ArrayList<Point> list;
    public ButtonListener(Graphics g, ArrayList<Point> list) {  //利用构造方法把窗体的Graphics画笔给按钮事件监听器使用
        this.g = g;
        this.list = list;
    }
    @Override
    public void actionPerformed(ActionEvent e) {
        list.add(new Point(0, 500));
    }
}
  • 再来看看效果:
    在这里插入图片描述
    这样就OK了,尽管还有略微的屏幕闪烁。
    (未完待续…)
  • 5
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值