JAVA.AWT.CANVAS使用

本文详细解析了Canvas与JPanel组件的区别,包括它们在Swing与AWT框架下的特性对比,以及在实际应用中的使用场景。重点讨论了Canvas的使用方法,包括paint、update和repaint方法的含义与作用,并通过示例代码展示了如何在动画渲染中避免线程同步问题。同时,文章提供了一个完整示例,展示了如何在JFrame中利用Canvas实现粒子动画,有效避免了界面假死的问题。
该组件继承自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;
	}

}







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


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


距离上次更新博客差不多是一年前的事情了,一年来,却没有什么大的长进,真是个悲伤的故事~
<think> 我们正在讨论如何使用java.awt.Font来设置间距。但是,请注意,java.awt.Font类本身并不直接提供设置字符间距(字间距)或行间距的方法。Font类主要用于描述字体、样式、大小等基本属性。 然而,我们可以通过其他方式来影响文本的间距: 1. 字符间距(字间距):在Java AWT中,我们可以通过使用TextLayout类或GlyphVector类来调整字符间距。 2. 行间距:行间距通常是由FontMetrics和布局容器(如TextArea、Label等)或自定义绘制时通过设置行高来控制。 根据引用[2]的提示,如果我们进行自定义绘制(如在Canvas或Panel上绘制),我们可以覆盖paintComponent方法(如果是Swing组件)或paint方法(如果是AWT组件),并在其中使用高级文本处理类来调整间距。 具体到设置字符间距,我们可以使用java.awt.font.TextAttribute类中的TRACKING(字距调整)属性。TRACKING属性是一个浮点数,用于调整字符之间的间距。 下面是一个使用Font的派生属性设置字符间距的示例: 步骤: 1. 创建一个基础字体(Font)。 2. 创建一个属性映射(Map),并设置TextAttribute.TRACKING属性(以及必要的字体属性)。 3. 使用这个属性映射创建一个新的字体(Font.deriveFont(Map))。 注意:TextAttribute.TRACKING的值是一个额外的间距因子,通常是一个很小的浮点数(例如0.1表示增加10%的间距,-0.1表示减少10%)。 示例代码(适用于Swing组件,覆盖paintComponent方法): ```java import javax.swing.*; import java.awt.*; import java.awt.font.TextAttribute; import java.util.HashMap; import java.util.Map; public class SpacingExample extends JPanel { @Override protected void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2d = (Graphics2D) g; Font baseFont = new Font("Serif", Font.PLAIN, 24); Map<TextAttribute, Object> attributes = new HashMap<>(); attributes.put(TextAttribute.FAMILY, baseFont.getFamily()); attributes.put(TextAttribute.SIZE, baseFont.getSize()); attributes.put(TextAttribute.TRACKING, 0.2); // 增加20%的字符间距 Font fontWithSpacing = baseFont.deriveFont(attributes); g2d.setFont(fontWithSpacing); g2d.drawString("Hello, World with increased spacing", 50, 50); } public static void main(String[] args) { JFrame frame = new JFrame("Spacing Example"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.add(new SpacingExample()); frame.setSize(400, 200); frame.setVisible(true); } } ``` 在上面的代码中,我们创建了一个带有自定义字符间距的字体。注意,我们使用了TextAttribute.TRACKING,并设置了0.2(即20%的额外间距)。 关于行间距:在绘制多行文本时,行间距通常由FontMetrics中的getHeight()方法返回的值(包括字体高度和行间距)来控制。我们可以通过设置每一行文本的起始位置(y坐标递增值)来控制行间距。如果我们想要增加行间距,可以在每行之间增加额外的像素。 例如: ```java FontMetrics fm = g2d.getFontMetrics(font); int lineHeight = fm.getHeight() + extraSpacing; // extraSpacing为额外行间距 ``` 总结: - 字符间距:使用TextAttribute.TRACKING来调整。 - 行间距:通过控制绘制文本时的y坐标增量来调整(通常基于FontMetrics.getHeight()加上额外的像素)。 注意:上述方法适用于在自定义绘制时设置间距。如果是使用标准组件(如Button、Label等),则无法直接设置间距,因为它们的绘制是系统控制的。在这种情况下,可能需要使用自定义组件。 相关问题: 1.Java AWT中,如何调整文本的行间距? 2. 除了使用TextAttribute.TRACKING,还有哪些方法可以调整字符间距? 3. 如何为Swing组件中的按钮文本设置字符间距? </think> ### 使用 java.awt.Font 设置字符间距的方法 在 Java AWT 中,`java.awt.Font` 类本身**不直接提供字符间距(spacing)设置功能**。字体间距主要由字体本身的规格决定,但可以通过以下两种间接方式控制间距: #### 方法 1:使用 `TextAttribute.TRACKING`(推荐) 通过 `java.awt.font.TextAttribute` 设置字距(tracking),这会全局影响字符间距: ```java import java.awt.*; import java.awt.font.TextAttribute; import java.util.HashMap; import java.util.Map; // 创建基础字体 Font baseFont = new Font("Serif", Font.PLAIN, 12); // 添加TRACKING属性(范围:-0.1至0.5) Map<TextAttribute, Object> attributes = new HashMap<>(); attributes.put(TextAttribute.TRACKING, 0.2); // +20% 间距 attributes.put(TextAttribute.FAMILY, baseFont.getFamily()); attributes.put(TextAttribute.SIZE, baseFont.getSize()); // 生成带间距的字体 Font spacedFont = baseFont.deriveFont(attributes); ``` #### 方法 2:手动调整绘制位置 自定义绘制时通过 `drawString()` 的坐标控制间距: ```java @Override public void paint(Graphics g) { String text = "Hello"; int x = 10; int y = 50; for (char c : text.toCharArray()) { g.drawString(String.valueOf(c), x, y); x += g.getFontMetrics().charWidth(c) + 5; // 额外添加5像素间距 } } ``` #### ⚠️ 重要注意事项 1. **组件类型限制**: AWT 基础组件(如 `Button`, `Label`)不支持直接间距修改,需使用 `TextAttribute` 的自定义字体或改用 Swing 的 `JComponent` 2. **Swing 组件**: 对于 Swing 组件,优先使用 `JTextComponent` 及其样式模型: ```java JTextPane pane = new JTextPane(); SimpleAttributeSet attr = new SimpleAttributeSet(); StyleConstants.setTracking(attr, 0.3f); // Swing专用API pane.setParagraphAttributes(attr, false); ``` 3. **布局影响**: 在容器中可通过 `GridLayout` 的行/列间距 (`setHgap()`, `setVgap()`) 间接控制组件间距离[^3],但非文本内部间距 **参考建议**:如需精细控制文本渲染,请改用: - `GlyphVector` 进行字形级操作 - `TextLayout` 处理复杂文本布局 - 迁移到 Swing 的 `JComponent` 及其高级文本 API --- ### 相关问题 1. 如何在 Swing 的 `JLabel` 中调整字符间距? 2. `TextAttribute.TRACKING` 和 CSS 的 `letter-spacing` 有何异同? 3. 使用 `GlyphVector` 精确控制字符位置的实现步骤是什么? 4. AWT 与 Swing 在文本渲染支持上有哪些核心区别?[^2]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值