天天看点

用java画图

我们要使用的Java来画图,要用到的就是AWT和Swing中,核心就是Graphics2D的这个类,我们使用的Java画圆飘动。

最终的效果图如下:

用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);
	}
	
}
           

有关在绘图时要使用多线程,是为了防止事件派发阻塞,因为绘图特别占内存,所以使用多线程防止阻塞。