我们要使用的Java来画图,要用到的就是AWT和Swing中,核心就是Graphics2D的这个类,我们使用的Java画圆飘动。
最终的效果图如下:
在这里我们先缕缕思路,我们要在窗口中画,那么我们就要有一个窗口(JFrame的),在窗口中我们需要一个画布(JPanel中),还需要这个圆(圆的对象)
要实现绘制,我们就要了解Java的绘图时的双缓存机制,这在JPanel中中就可以直接设置
我们首先来实现最基础的数据类,因为我们要画圆,所以我们就要创建圆这种类
import java.awt.Point;
//圆的类
public class Circle{
public int x,y;//外部不但能读取,在移动中还能修改
private int r; //半径
public int vx,vy;//速度
public boolean isFilled;//记录圆是否为实心
public int getR() {
return r;
}
public Circle(int x, int y, int r, int vx, int vy) {
super();
this.x = x;
this.y = y;
this.r = r;
this.vx = vx;
this.vy = vy;
}
//移动圆
public void move(int minx,int miny,int maxx,int maxy) {
x+=vx;
y+=vy;
checkCollision(minx, miny, maxx, maxy);//防撞击检测
}
//检测碰撞
private void checkCollision(int minx,int miny,int maxx,int maxy){
if(x-r<minx) { x=r; vx=-vx;} //碰到了左边缘,让他向相反的方向
if(x-r>=maxx) {x=maxx;vx=-vx;}
if(y-r<miny) {y=r;vy=-vy;}
if(y-r>=maxy) {y=maxy-r;vy=-vy;}
}
//检查传入点是否在圆中
public boolean contain(Point p){
return (x-p.x)*(x-p.x)+(y-p.y)*(y-p.y)<=r*r;
}
}
这里我们已经将之后要用到的所有的方法都写入到类中,这里所写的方法都是对圆的操作
当我们成功创建一个圆的对象后,我们先创建一个工具类来方便调用,在这个工具类中实现对图形的各种绘制
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.Ellipse2D;
//工具类
public class AlgoVisHelper {
//用户不能实例化该类
private AlgoVisHelper() {}
//设置画笔宽度
public static void setStrokeWidth(Graphics2D g2d,int w) {
int strokewidth=w;
g2d.setStroke(new BasicStroke(strokewidth,BasicStroke.CAP_ROUND,BasicStroke.JOIN_ROUND));
//cap_round 结束时以圆形修饰 join_round 将角以弧线相连接
}
//绘制一个空心的圆
public static void strokeCiecle(Graphics2D g2d,int x,int y,int r){
Ellipse2D circle=new Ellipse2D.Double(x-r,y-r,2*r,2*r);//x-r和y-r确定的是圆左上角的坐标,2*r表示的是它的长度和宽度
g2d.draw(circle);//绘制
}
//绘制一个实心的圆
public static void fillCiecle(Graphics2D g2d,int x,int y,int r){
Ellipse2D circle=new Ellipse2D.Double(x-r,y-r,2*r,2*r);//x-r和y-r确定的是圆左上角的坐标,2*r表示的是它的长度和宽度
g2d.fill(circle);//绘制实心的圆
}
//设置颜色
public static void setColor(Graphics2D g2d,Color color) {
g2d.setColor(color);
}
//暂停一段时间
public static void pause(int t) {
try {
Thread.sleep(t);
} catch (InterruptedException e) {
System.out.println("Error in sleeping");
}
}
}
这个工具类不能被实例化
当工具类创建好后,我们就要创建用于展示的窗口,这个窗口我们的思路是,首先JFrame的窗口,然后是创建画布,最后是在画布上进行绘画
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class AlgoFrame extends JFrame{
private int canvasWidth;//窗口的宽
private int canvasHeight;//窗口的高
public AlgoFrame(String title,int canvasWidth,int canvasHeight){
super(title);//调用父类,设置窗口的名字
this.canvasHeight=canvasHeight;
this.canvasWidth=canvasWidth;
//画布
AlgoCanvas canvas=new AlgoCanvas(); //创建画布,画布是就是Jpanel的
setContentPane(canvas);//将canvas设置为主要的内容面板
pack();//自动布局整理
this.setSize(canvasWidth,canvasHeight);
setResizable(false);//禁止放大
setVisible(true);
}
public AlgoFrame(String title){
this(title,1024,768);
}
public int getCanvasWidth() {
return canvasWidth;
}
public int getCanvasHeight() {
return canvasHeight;
}
//此处得到数据对象并且进行刷新
private Circle[] circles;
public void render(Circle[] circles) {
this.circles=circles;
repaint();//将jf进行重新刷新包括刷新画布
}
private class AlgoCanvas extends JPanel{//JP默认支持双缓存
public AlgoCanvas() {
super(true);//这里打开了双缓存
}
//绘制函数
@Override
public void paintComponent(Graphics g){//在这个函数里进行绘制
super.paintComponents(g);
Graphics2D g2d=(Graphics2D) g;//强转
//开启抗锯齿
RenderingHints hit=new RenderingHints(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
g2d.addRenderingHints(hit);
//此处调用工具类
AlgoVisHelper.setStrokeWidth(g2d,1);//设置笔画的宽度
AlgoVisHelper.setColor(g2d, Color.blue);//设置画笔颜色
for(Circle circle:circles){
if(circle.isFilled) {
AlgoVisHelper.fillCiecle(g2d, circle.x, circle.y, circle.getR());
}else {
AlgoVisHelper.strokeCiecle(g2d, circle.x, circle.y,circle.getR());
}
}
}
//获得画布的长和宽 Dimension是对长和宽的封装
@Override
public Dimension getPreferredSize(){
return new Dimension(canvasWidth, canvasHeight);
}
}
}
在这里要说明一下绘制函数的paintComponent,在这里我们也可以使用油漆()函数,前面的渲染类中有一个重绘的方法,每次调用它,JAVA系统就会调用的update()方法,更新()方法又会调用paint()方法,实现绘制。而这里所用的paintComponent与paint的关系就是家具和房子的关系,paint就相当于房子,paint就相当于家具,在执行paint()方法时,它会依次调用paintComponent(),paintBorder(),paintChildren(),我们在这里只是对这个局部进行布局,所以就使用paintComponent方法。
最后我们要对这个窗口和对象进行粘接
import java.awt.EventQueue;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
//操作层,将上面的数据和视图进行联系
public class AlgoVisualizer {
private Circle[] circles;
private AlgoFrame frame;//之前已经创建好的frame类
private boolean isAnimated=true;//动画是否在进行
public AlgoVisualizer(int sceneWidth,int sceneHeight,int N) {
//初始化圆
circles=new Circle[N];
int R=50;
for(int i=0;i<N;i++) {
int x=(int)(Math.random()*(sceneWidth-2*R))+R;
int y=(int)(Math.random()*(sceneHeight-2*R))+R;
int vx=(int)(Math.random()*11)-5;
int vy=(int)(Math.random()*11)-5;
circles[i]=new Circle(x, y, R, vx, vy);
}
EventQueue.invokeLater(()->{
frame=new AlgoFrame("A",sceneWidth,sceneHeight);
frame.addKeyListener(new AlgoKeyListener());
frame.addMouseListener(new AlgoMouseListener());
new Thread(()->{
run();
}).start();
});
}
//动画逻辑封装
private void run() {
while(true) {
//绘制数据
frame.render(circles);
AlgoVisHelper.pause(20);
//更新数据
if(isAnimated)
for(Circle circle:circles)
circle.move(0,0,frame.getCanvasWidth(),frame.getCanvasWidth());//让圆动
}
}
//创建键盘监控类
private class AlgoKeyListener extends KeyAdapter{
@Override
public void keyReleased(KeyEvent event) {
if(event.getKeyChar()==' ') {
isAnimated=!isAnimated;
}
}
}
//添加鼠标的响应事件
private class AlgoMouseListener extends MouseAdapter{
@Override
public void mousePressed(MouseEvent event) {
// Point point = event.getPoint();//获得鼠标点击位置的坐标
event.translatePoint(0,-(frame.getBounds().height-frame.getCanvasHeight()));
// System.out.println(point.toString());
for(Circle circle:circles) {
if(circle.contain(event.getPoint())) {
circle.isFilled=!circle.isFilled;//将实心和空心进行变换
}
}
}
}
public static void main(String[] args) {
new AlgoVisualizer(800,800,5);
}
}
有关在绘图时要使用多线程,是为了防止事件派发阻塞,因为绘图特别占内存,所以使用多线程防止阻塞。