0

我正在学习使用 java 线程,所以我决定制作一个简单的弹跳球程序。但是,程序显示了多个线程,但只有一个利用了窗口大小,其他球被限制在一个区域。

我尝试为每个球的 JPanel 和不同的布局设置大小,但这些布局不起作用。

弹跳球.java

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;
import javax.swing.*;

public class BouncingBall extends JFrame {

    ArrayList<Ball> balls = new ArrayList<Ball>();

    //GUI Elements
    JLabel lblCount;
    JButton btn= new JButton("Stop");

    BouncingBall() {
//        setDefaultLookAndFeelDecorated(true);
        setTitle("BouncingBall");

        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setSize(300, 200);
        for (int i = 0; i < 5; i++) {
            balls.add(new Ball());
        }
        setLayout(new FlowLayout());
        setContentPane(balls.get(0));
        balls.get(0).init();
        for (Ball b : balls
        ) {
            System.out.println(b.getHeight());

            if (b != balls.get(0)) {
                b.init();
                balls.get(0).add(b);
            }
        }

        this.add(btn,BorderLayout.SOUTH);
        btn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                for (Ball b :balls
                ) {
                    b.stopMoving();
                }
            }
        });
        addMouseListener(new MouseListener() {
            @Override
            public void mouseClicked(MouseEvent e) {
                for (Ball b :balls
                     ) {
                    b.startMoving();
                }
            }

            @Override
            public void mousePressed(MouseEvent e) {
            }

            @Override
            public void mouseReleased(MouseEvent e) {
            }

            @Override
            public void mouseEntered(MouseEvent e) {
            }

            @Override
            public void mouseExited(MouseEvent e) {
            }
        });
        this.setVisible(true);
    }

    public static void main(String[] args) {
        new BouncingBall();
    }
}

Ball.java


import javax.swing.*;
import java.awt.*;

public class Ball extends JPanel implements Runnable {
    // Box height and width
    int width;
    int height;

    // Ball Size
    float radius = 5;
    float diameter = radius * 2;

    // Center of Call
    float X = radius + 50;
    float Y = radius + 20;

    // Direction
    float dx;
    float dy;

    //Vars
    int count = 0;
    float[] colorHSB = new float[3];
    boolean moving = false;

    //Thread
    Thread t;

    Ball() {

        dx = (float) Math.random() * 10;
        dy = (float) Math.random() * 10;
        width = getWidth();
        height = getHeight();

        for (int i = 0; i < 3; i++) {
            colorHSB[i] = (float) Math.random() * 255;
        }
        t = new Thread(this);

    }

    void init() {
        t.start();
    }

    public void run() {
        while (true) {

            width = getWidth();
            height = getHeight();
            if (moving){
                X = X + dx;
                Y = Y + dy;
            }

            if (X - radius < 0) {
                dx = -dx;
                X = radius;
                addCount();
            } else if (X + radius > width) {
                dx = -dx;
                X = width - radius;
                addCount();
            }

            if (Y - radius < 0) {
                dy = -dy;
                Y = radius;
                addCount();
            } else if ((Y + radius) > height) {
                dy = -dy;
                Y = height - radius;
                addCount();
            }
            repaint();

            try {
                Thread.sleep(50);
            } catch (InterruptedException ex) {
            }
        }
    }

    public void startMoving() {
        moving = true;
    }

    public void stopMoving(){
        moving=false;
    }

    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.setColor(Color.getHSBColor(colorHSB[0], colorHSB[1], colorHSB[2]));
        g.fillOval((int) (X - radius), (int) (Y - radius), (int) diameter, (int) diameter);
    }

    public void addCount() {
        count++;
        System.out.println(count);
    }
}

程序运行的照片

在此处输入图像描述

它应该利用整个窗口显示所有在框架周围弹跳的球。

4

1 回答 1

1

我的回答是基于MCV 模型。这将模型、视图和控制器之间的职责分开。
每一个(M、V 和 C)都成为一个定义明确的单一职责类。起初,类的数量以及它们之间的关系可能看起来令人费解。在研究并理解了结构之后,您意识到它实际上将您尝试解决的“问题”划分为更小且更易于处理的部分。

球可以是模型的简单示例。它实际上是一个 pojo,包含视图绘制球所需的所有信息:

//a model representing ball
class Ball  {

    //Ball attributes
    private static final int SIZE = 10;  //diameter
    private int x, y;  // Position
    private final Color color;
    private Observer observer;  //to be notified on changes

    Ball() {

        Random rnd = new Random();
        color = new Color(rnd.nextInt(256), rnd.nextInt(256), rnd.nextInt(256));
    }

    Color getColor() {
        return color;
    }

    int getSize(){
        return SIZE;
    }

    synchronized int getX() {
        return x;
    }

    synchronized void setX(int x) {
        this.x = x;
        notifyObserver();
    }

    synchronized int getY() {
        return y;
    }

    synchronized void setY(int y) {
        this.y = y;
        notifyObserver();
    }

    void registerObserver(Observer observer){
        this.observer = observer;
    }

    void notifyObserver(){
        if(observer == null) return;
        observer.onObservableChanged();
    }
}

请注意,您可以将 an 注册ObserverBall. AnObserver定义为:

//listening interface. Implemented by View and used by Ball to notify changes
interface Observer {
    void onObservableChanged();
}

它用于Ball通知观察者发生了变化。ABall也有一些synchronizedgetter 和 setter,所以它的属性可以被多个线程访问。

我们还应该定义一个Model,另一个 pojo,它是封装视图所需的所有信息的类:

//view model: hold info that view needs
class Model {

    private final ArrayList<Ball> balls;
    private final int width, height;

    Model(){
        balls = new ArrayList<>();
        width = 300; height = 200;
    }

    boolean addBall(Ball ball){
        return balls.add(ball);
    }

    List<Ball> getBalls() {
        return new ArrayList<>(balls); //return a copy of balls
    }

    int getWidth() {
        return width;
    }

    int getHeight() {
        return height;
    }
}

A View,顾名思义就是:

class View {

    private final BallsPane ballsPane;

    View(Model model){
        ballsPane = new BallsPane(model);
    }

    void createAndShowGui(){
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLocationRelativeTo(null);
        frame.add(ballsPane);
        frame.pack();
        frame.setResizable(false);
        frame.setVisible(true);
    }

    Observer getObserver(){
        return ballsPane;
    }
}

class BallsPane extends JPanel implements Observer {

    private final Model model;

    BallsPane(Model model){
        this.model = model;
        setPreferredSize(new Dimension(model.getWidth(), model.getHeight()));
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        for(Ball b : model.getBalls()){
            g.setColor(b.getColor());
            g.fillOval(b.getX(), b.getY(), b.getSize(), b.getSize());
        }
    }

    @Override
    public void onObservableChanged() {
        repaint(); //when a change was notified
    }
}

请注意,View(实际上BallsPane)实现了Observer. 它将观察(或监听) 中的变化Ball,并通过调用 来响应每一个变化repaint()

由于每个Ball都有synchronized位置 (x,y) getter 和 setter,您可以更改这些属性:

class BallAnimator implements Runnable{

    private final Ball ball;
    private final int maxX, maxY;
    private final Random rnd;
    private boolean moveRight = true,  moveDown = true;
    private static final int STEP =1, WAIT = 40;

    BallAnimator(Ball ball, int maxX, int maxY) {
        this.ball = ball;
        this.maxX = maxX;
        this.maxY = maxY;
        rnd = new Random();
        ball.setX(rnd.nextInt(maxX - ball.getSize()));
        ball.setY(rnd.nextInt(maxY - ball.getSize()));
        new Thread(this).start();
    }

    @Override
    public void run() {

        while(true){

            int dx = moveRight ? STEP : -STEP ;
            int dy = moveDown  ? STEP : -STEP ;

            int newX = ball.getX() + dx;
            int newY = ball.getY() + dy;

            if(newX + ball.getSize()>= maxX || newX <= 0){
                newX = ball.getX() - dx;
                moveRight = ! moveRight;
            }

            if(newY +ball.getSize()>= maxY || newY <= 0){
                newY = ball.getY() - dy;
                moveDown = ! moveDown;
            }

            ball.setX(newX);
            ball.setY(newY);

            try {
                Thread.sleep(WAIT);
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            }
        }
    }
}


添加控制器并将其放在一起:BouncingBalls充当控制器。它“连接”解决方案的不同部分。
为了方便和简单,可以将以下整个代码复制粘贴到一个名为 BouncingBalls.java 的文件中并运行。

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class BouncingBalls{

    BouncingBalls(int numOfBalls) {

        Model model = new Model();
        View view = new View(model);;

        for (int i = 0; i < numOfBalls; i++) {
            Ball b = new Ball(); //construct  a ball
            model.addBall(b);    //add it to the model 
            b.registerObserver(view.getObserver());  //register view as an observer to it 
            new BallAnimator(b, model.getWidth(), model.getHeight()); //start a thread to update it 
        }

        view.createAndShowGui();
    }

    public static void main(String[] args) {
        new BouncingBalls(5);
    }
}

//listening interface. Implemented by View and used by Ball to notify changes
interface Observer {
    void onObservableChanged();
}

class View {

    private final BallsPane ballsPane;

    View(Model model){
        ballsPane = new BallsPane(model);
    }

    void createAndShowGui(){
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLocationRelativeTo(null);
        frame.add(ballsPane);
        frame.pack();
        frame.setResizable(false);
        frame.setVisible(true);
    }

    Observer getObserver(){
        return ballsPane;
    }
}

class BallsPane extends JPanel implements Observer {

    private final Model model;

    BallsPane(Model model){
        this.model = model;
        setPreferredSize(new Dimension(model.getWidth(), model.getHeight()));
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        for(Ball b : model.getBalls()){
            g.setColor(b.getColor());
            g.fillOval(b.getX(), b.getY(), b.getSize(), b.getSize());
        }
    }

    @Override
    public void onObservableChanged() {
        repaint(); //when a change was notified
    }
}

//view model: hold info that view needs
class Model {

    private final ArrayList<Ball> balls;
    private final int width, height;

    Model(){
        balls = new ArrayList<>();
        width = 300; height = 200;
    }

    boolean addBall(Ball ball){
        return balls.add(ball);
    }

    List<Ball> getBalls() {
        return new ArrayList<>(balls); //return a copy of balls
    }

    int getWidth() {
        return width;
    }

    int getHeight() {
        return height;
    }
}

//a model representing ball
class Ball  {

    //Ball attributes
    private static final int SIZE = 10;  //diameter
    private int x, y;  // Position
    private final Color color;
    private Observer observer;  //to be notified on changes

    Ball() {

        Random rnd = new Random();
        color = new Color(rnd.nextInt(256), rnd.nextInt(256), rnd.nextInt(256));
    }

    Color getColor() {
        return color;
    }

    int getSize(){
        return SIZE;
    }

    synchronized int getX() {
        return x;
    }

    synchronized void setX(int x) {
        this.x = x;
        notifyObserver();
    }

    synchronized int getY() {
        return y;
    }

    synchronized void setY(int y) {
        this.y = y;
        notifyObserver();
    }

    void registerObserver(Observer observer){
        this.observer = observer;
    }

    void notifyObserver(){
        if(observer == null) return;
        observer.onObservableChanged();
    }
}

class BallAnimator implements Runnable{

    private final Ball ball;
    private final int maxX, maxY;
    private final Random rnd;
    private boolean moveRight = true,  moveDown = true;
    private static final int STEP =1, WAIT = 40;

    BallAnimator(Ball ball, int maxX, int maxY) {
        this.ball = ball;
        this.maxX = maxX;
        this.maxY = maxY;
        rnd = new Random();
        ball.setX(rnd.nextInt(maxX - ball.getSize()));
        ball.setY(rnd.nextInt(maxY - ball.getSize()));
        new Thread(this).start();
    }

    @Override
    public void run() {

        while(true){

            int dx = moveRight ? STEP : -STEP ;
            int dy = moveDown  ? STEP : -STEP ;

            int newX = ball.getX() + dx;
            int newY = ball.getY() + dy;

            if(newX + ball.getSize()>= maxX || newX <= 0){
                newX = ball.getX() - dx;
                moveRight = ! moveRight;
            }

            if(newY +ball.getSize()>= maxY || newY <= 0){
                newY = ball.getY() - dy;
                moveDown = ! moveDown;
            }

            ball.setX(newX);
            ball.setY(newY);

            try {
                Thread.sleep(WAIT);
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            }
        }
    }
}


在此处输入图像描述

于 2019-06-23T09:47:01.470 回答