该组件继承自java.awt.Component组件,在这点上与JPanel上是一致的.所以在JPanel拥有与Canvas一样的功能是很很正常的.
之前的一直无法理解为啥有了JPanel还要Canvas组件干啥,毕竟JPanel功能有Canvas的功能.现在的个人理解是JPanel更多的是一个容器的概念,更多的是充当容器的作用,其中有很多是在作图中是不需要的.
在网上看到一个关于Canvas与JPanel的区别评论如下:
 Canvas: AWT JPanel: Swing
Swing is based on AWT, so Canvas can be more lightweight and in lower layer.
If canvas meet all your requirements, just use Canvas.
PS: I don't think there would be too much expensive to use JPanel, just choose the one you like.

Canvas是AWT组件,JPanel是Swing组件,Swing组件是以AWT组件为基础的,从理论上来说,Canvas要比JPanel更轻量些.如果canvas能满足需求,就用canvas.
但是作者认为两者并没有太大的性能差异,所以,想用哪个就用哪个,开心就好.


上述评论链接:

http://stackoverflow.com/questions/29476468/difference-between-canvas-and-jpanel



Canvas使用.
1.paint方法
一般来说,我们是不能直接使用该类的,需要继承Canvas并重写其paint方法.因为Canvas源码是这么写的:



public void paint(Graphics g) {
    g.clearRect(0, 0, width, height);
}






可以看到,源码是直接做清屏操作,如果不重写,啥也干不了.
paint方法的具体使用,JDK 文档是这么写的:
调用此方法响应对 repaint 的调用。首先通过使用背景色填充 canvas 来清理它,然后通过调用此 canvas 的 paint 方法重绘它。注:重写此方法的应用程序应该调用 super.update(g),或者将上述功能合并到其自身的代码中。





2.update方法
与paint方法极其类似,源码如下:




public void update(Graphics g) {
    g.clearRect(0, 0, width, height);
    paint(g);
}






所以本质上update方法也是啥也没干,冏


3.repaint方法
该方法继承自Component组件.
JDK文档是这么说该方法的:
重绘此组件。
如果此组件是轻量级组件,则此方法会尽快调用此组件的 paint 方法。否则此方法会尽快调用此组件的 update 方法。


至此update、paint、repaint三个方法之间的默认调用关系很清楚了:
repaint---》update---》paint


repaint方式去调用update方式是通过EventQueue调用,也就是说通过AWT-EventQueue去调用.即开启了线程去重绘canvas.
repaint作用源码如下:




if (isVisible() && (this.peer != null) &&(width > 0) && (height > 0)) {
    PaintEvent e = new PaintEvent(this, PaintEvent.UPDATE,
    new Rectangle(x, y, width, height));
    Toolkit.getEventQueue().postEvent(e);
}





在对一个不断变化的动画来说,就会容易出现线程同步问题.
示例:


class Roll extends Thread {
		@Override
		public void run() {
			while (true) {
				 //创建粒子
				createGrain();
				//移动粒子
				 moveGrain();
				//重绘粒子
				canvas.repaint();
			}
		}
}




在上述例子中,粒子是在不断的变化的,存储在List中,但是由于没有重写repaint方法,在重绘粒子的时候是使用的是单独的线程去绘制粒子,而此时createGrain()与moveGrain()已经更改了存储粒子的List的了,此时会抛出如下异常.
Exception in thread "AWT-EventQueue-0" java.util.ConcurrentModificationException
异常


如果想要简单的解决该问题,重写repaint方法即可:




 public void repaint() {
	 Graphics g = this.getGraphics();
	 update(g);
 }






此外,如果不重写repaint方法,在绘制大量图案时,还容易造成界面假死的情况.
造成假死的原因个人理解是由于awt-EventQueue是负责创建用户界面和绘制图形类方法的,此时在该线程中绘制图像,导致程序没有空闲的时间去处理其他的界面事件,因此造成了假死.


完整使用示例:


CanvasTest.java:


package test;

import java.awt.Graphics;

public class CanvasTest {

	final int CREATE_GRAIN_NUMBER = 5000;
	JFrame frame;
	WorldCanvas canvas;
	List<Grain> grains;

	/**
	 * @wbp.parser.entryPoint
	 */
	public CanvasTest() {
		init();
		Graphics g = canvas.getGraphics();
		g.fillOval(20, 20, 20, 20);
		canvas.paint(g);
	}

	public void init() {
		grains = new ArrayList<Grain>();

		frame = new JFrame();

		JPanel panel = new JPanel();
		frame.getContentPane().add(panel, BorderLayout.NORTH);

		JButton startBtn = new JButton("开始");
		panel.add(startBtn);
		startBtn.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				// TODO Auto-generated method stub
				new Roll().start();
			}
		});

		canvas = new WorldCanvas();
		canvas.setGrains(grains);

		frame.getContentPane().add(canvas);
		frame.setSize(400, 400);
		frame.setVisible(true);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	}

	private void createGrain() {
		for (int i = 0; i < CREATE_GRAIN_NUMBER; i++) {
			grains.add(new Grain());
		}
	}

	private void moveGrain() {
		Grain grain;
		for (int i = 0; i < grains.size(); i++) {
			grain = grains.get(i);
			grain.move();
			if (grain.isDead()) {
				grains.remove(i);
			}
		}
	}

	class Roll extends Thread {
		@Override
		public void run() {
			while (true) {
				createGrain();
				moveGrain();
				canvas.repaint();
			}
		}
	}

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



Grain.java
package test;

public class Grain{
	private final int SPEED_X = 1;
	private final int SPEED_Y = 1;
	private final int MAX_X = 300;
	private final int MAX_Y = 300;
	private int pos_x;
	private int pos_y;
	private int radius = 5;
	private int time = 1;
	private boolean isDead = false;

	public Grain() {
		pos_x = generateRandom(0, 350);
		pos_y = generateRandom(0, 350);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see test.IGrain#move()
	 */
	public void move() {
		pos_x = pos_x + SPEED_X * time;
		pos_y = pos_y + SPEED_Y * time;

		if (pos_x > MAX_X || pos_y > MAX_Y) {
			setDead(true);
		}

	}

	private int generateRandom(int min, int max) {
		int random = (int) (Math.random() * (max - min)) + min;
		return random;
	}

	public int getPos_x() {
		return pos_x;
	}

	public void setPos_x(int pos_x) {
		this.pos_x = pos_x;
	}

	public int getPos_y() {
		return pos_y;
	}

	public void setPos_y(int pos_y) {
		this.pos_y = pos_y;
	}

	public boolean isDead() {
		return isDead;
	}

	private void setDead(boolean b) {
		isDead = true;
	}

	public int getRadius() {
		return radius;
	}

	public void setRadius(int radius) {
		this.radius = radius;
	}
}






WorldCanvas.java



package test;

import java.awt.Canvas;
import java.awt.Color;
import java.awt.Graphics;
import java.util.List;

public class WorldCanvas extends Canvas {
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	private List<Grain> grains;

	// public void update(Graphics g) {
	// g.clearRect(0, 0, 350, 350);
	// super.update(g);
	// }

	@Override
	public void repaint() {
		Graphics g = this.getGraphics();
		update(g);
	}

	@Override
	public void paint(Graphics g) {
		g.setColor(Color.blue);
		for (Grain grain : grains) {
			g.fillOval(grain.getPos_x(), grain.getPos_y(), grain.getRadius(),
					grain.getRadius());
		}
	}

	public List<Grain> getGrains() {
		return grains;
	}

	public void setGrains(List<Grain> grains) {
		this.grains = grains;
	}

}







貌似排版有点渣.......


经过一番折腾,好多了,看样子是使用的姿势不对呃~


距离上次更新博客差不多是一年前的事情了,一年来,却没有什么大的长进,真是个悲伤的故事~
Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐