第15章 图形编程
在Java的开发中,图形编程是一个比较重要的环节,一般的效果图及页面几乎都是通过Applet来实现,并且是一个单独的界面显示小程序,可以做成各种动画效果或是与网页有关的,如电子相册、QQ欢迎动画等。
实例137 多变的按钮
在本实例中,主要是实现多变的按钮,它是一个用Java Applet实现页面响应鼠标事件的例子。可以进行鼠标移入、移出、单击事件等操作并实现响应鼠标移入、移出、单击事件的功能,可以让图形界面更加美观。
技术要点
多变的按钮的技术要点如下:
• Applet的声音播放AudioClip类的使用。
• 鼠标事件的监听功能。
• MediaTracker类的使用。
实现步骤
(1)新建一个类名为Change_Button.java。
(2)代码如下所示:
package chp15; import java.applet.*; import java.awt.*; import java.awt.event.*; import java.net.*; public class Change_Button extends Applet implements MouseListener {//页面响应鼠标事件 private int width, height; //声明int类变量 private Image image, img1, img2, img3; //声明Image类型变量 private Label la = new Label("多变的按钮"); //创建一个带初始值的Label对象 private Graphics grap; //声明Graphics变量 private MediaTracker media; //声明MediaTracker变量 private AudioClip audioA, audioB; //声明AudioClip变量 public void init() { //为Applet初始化 audioA = getAudioClip(this.getCodeBase(), "lx.wav"); //创建audioA对象 audioB = getAudioClip(getCodeBase(), "hh.wav"); //创建audioB对象 width = getSize().width; //返回Applet的宽度 height = getSize().height; //返回Applet的高度 image = createImage(width, height); //根据参数创建一个Image对象 grap = image.getGraphics(); //根据图像创建Graphics对象 media = new MediaTracker(this); //MediaTracker对象实例化 img1 = getImage(getCodeBase(), "b1.jpg"); //根据参数创建Image对象 media.addImage(img1, 0); //将img1放入media对象中 img2 = getImage(getCodeBase(), "b3.jpg"); media.addImage(img2, 1); img3 = getImage(getCodeBase(), "b2.jpg"); media.addImage(img3, 2); try { media.waitForAll(); //等待media加载所有的图像 } catch (InterruptedException e) { } la.setSize(100, 20); la.setForeground(Color.pink); //设置标签的前景颜色 this.add(la, BorderLayout.NORTH); //将标签组件加载到Applet中 addMouseListener(this); //为Applet添加鼠标监听事件 } public void start() { //开始Applet程序 grap.drawImage(img1, 0, 0, width, height, this); //根据给定的参数绘制图像 repaint(); } public void mouseClicked(MouseEvent e) { //鼠标单击事件 } public void mousePressed(MouseEvent e) { //鼠标按下事件 grap.drawImage(img3, 0, 0, width, height, this); //当鼠标被按下时所绘制的图像 audioA.stop(); //audioA停止播放声音 audioB.play(); //audioB开始播放声音 la.setBackground(Color.black); //设置标签的背景颜色 la.setForeground(Color.yellow); //设置标签的前景颜色 la.setText("audioB is playing"); //设置标签中要显示的内容 this.add(la, BorderLayout.NORTH); //添加标签组件 repaint(); //重新绘制组件 } public void mouseReleased(MouseEvent e) { //鼠标释放事件 grap.drawImage(img2, 0, 0, width, height, this); repaint(); audioB.stop(); audioA.play(); la.setBackground(Color.yellow); la.setForeground(Color.black); la.setText("audioA is playing"); this.add(la, BorderLayout.NORTH); } public void mouseEntered(MouseEvent e) { //鼠标进入Applet所触发的事件 grap.drawImage(img2, 0, 0, width, height, this); repaint(); } public void mouseExited(MouseEvent e) { //鼠标离开Applet所触发的事件 grap.drawImage(img1, 0, 0, width, height, this); repaint(); } public void paint(Graphics g) { g.drawImage(image, 0, 0, width, height, this); } }
(3)运行结果的初始界面如图15-1所示。当鼠标进入界面区域中时,出现如图15-2所示界面。当长按鼠标时,出现如图15-3所示界面。当释放鼠标时,出现如图15-4所示界面。
图15-1 初始界面
图15-2鼠标进入界面
图15-3长按鼠标界面
图15-4释放鼠标界面
源程序解读
本实例采用了Java小应用程序Java Applet。
(1)创建一个带初始值的Label对象:
private Label la = new Label("多变的按钮");
(2)Applet的声音播放AudioClip类的使用:使用Applet播放声音时,需先定义AudioClip对象,getAudioClip方法能把声音赋予AudioClip对象,如果仅想播放一遍声音,应调用AudioClip类的play方法,如果想循环播放,应选用AudioClip类的loop方法。
(3)MediaTracker类的使用:Java专门提供了用于跟踪包括图像和声音等多媒体对象的ImageObserver类和MediaTracker类,将MediaTracker对象实例化。
media = new MediaTracker(this);
实例138 自制对话框
本实例介绍如何定义适合自己的组件,本实例自定义一个对话框,在对话框中输入文本,单击“确定”按钮后将输入的文本显示到主窗口中,当单击“保存”按钮时,会将主窗口中的内容保存到通过文件对话框选择的文件中。
技术要点
自制对话框的技术要点如下:
• 文本域JTextArea可以显示多行文本,可以使用append()方法向文本域中追加内容。
• 自定义对话框继承JDialog,通过JDialog的setVisible方法显示和隐藏自定义对话框。
• 通过JFileChooser创建一个文件选择器。
• 通过JFileChooser对象的showSaveDialog方法可以弹出一个保存的对话框。
实现步骤
(1)新建一个类名为DialogDemo.java。
(2)代码如下所示:
import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.File; import java.io.FileOutputStream; import javax.swing.JButton; import javax.swing.JDialog; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JMenuBar; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.JTextField; /** * 使用对话框。功能介绍:界面包括一个文本域、一个添加内容按钮和一个保存按钮,单击添加内容按钮弹出一个对话框, * 在对话框中输入的字符串将在文本域中显示;单击保存按钮弹出一个保存对话框,将文本域保存在指定的文件中,若该文件不存在,则自动创建文件并保存 */ public class DialogDemo extends JFrame implements ActionListener { private Simple_Dialog simple_dialog; //声明Simple_Dialog类对象 private JTextArea area; //声明JTextArea变量 String lineSeparator; //文本域中行之间的分隔符 public DialogDemo() { super("对话框示例"); //调用父类的构造方法 area = new JTextArea(5, 30); //创建一个能显示5行30个字符的文本域 area.setEditable(false); //文本域的状态为不可修改 JMenuBar bar = new JMenuBar(); //创建一个空的菜单栏 getContentPane().add("North", bar); //将菜单栏添加到容器中 getContentPane().add("Center", new JScrollPane(area)); //将带滚动条的文本添加到容器中 JButton button = new JButton("添加内容"); //添加一个按钮,单击按钮弹出对话框 button.setActionCommand("b1"); //设置激发操作事件的命令名称为b1 button.addActionListener(this); //添加单击监听事件 JButton saveButton = new JButton("保存"); //同理 saveButton.setActionCommand("save"); //同理 saveButton.addActionListener(this); //同理 JPanel panel = new JPanel(); //创建一个JPanel对象 panel.add(button); //将按钮添加到面板中 panel.add(saveButton); getContentPane().add("South", panel); //获取文本域中行之间的分隔符。这里调用了系统的属性 lineSeparator = System.getProperty("line.separator"); this.pack(); //调整窗体布局大小 } public void actionPerformed(ActionEvent event) { //单击按钮时根据不同的命令名称进行不同的操作 File file; //声明File对象 if (event.getActionCommand().equals("b1")) { //如果单击添加按钮 if (simple_dialog == null) { //弹出一个对话框 simple_dialog = new Simple_Dialog(this, "输入对话框"); } simple_dialog.setVisible(true); //设置对话框可见 } if (event.getActionCommand().equals("save")) { //如果单击保存按钮 JFileChooser fc = new JFileChooser(); //创建一个文件选择器 fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); //设置文件选择模式 fc.setDialogType(JFileChooser.SAVE_DIALOG); //设置对话框类型 fc.showSaveDialog(this); //弹出一个Save File文件选择器对话框 try { file = fc.getSelectedFile(); //返回选中的文件 if (!file.exists()) { //判断该文件是否存在 file.createNewFile(); //若不存在则创建 } String s = area.getText(); //返回文本区域中的内容 FileOutputStream fou = new FileOutputStream(file); //创建一个文件输出流 byte[] by = s.getBytes(); //将字符串转换成字节数组 fou.write(by); //将字节数组中的内容写入指定的文件中 } catch (Exception e) { } } } public void setText(String text) { area.append(text + lineSeparator); //添加内容到文本域的后面,每次都新起一行 } public static void main(String args[]) { DialogDemo window = new DialogDemo(); window.setVisible(true); window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } } /** * 自定义对话框。对话框包括一个Label、一个文本框和两个按钮。 */ class Simple_Dialog extends JDialog implements ActionListener { JTextField text; //文本框,用于输入字符串 DialogDemo p; //对话框的父窗体 JButton comfig; //"确定"按钮 /** 构造函数,参数为父窗体和对话框的标题 */ Simple_Dialog(JFrame prent_Frame, String title) { //调用父类的构造函数,第三个参数用false表示允许激活其他窗体,为true表示不能够激活其他窗体 super(prent_Frame, title, false); p = (DialogDemo) prent_Frame; //添加Label和输入文本框 JPanel pl = new JPanel(); JLabel label = new JLabel("请输入要添加的文本:"); pl.add(label); text = new JTextField(30); text.addActionListener(this); pl.add(text); getContentPane().add("Center", pl); //添加确定和取消按钮 JPanel pl1 = new JPanel(); pl1.setLayout(new FlowLayout(FlowLayout.RIGHT)); JButton cancelButton = new JButton("取 消"); cancelButton.addActionListener(this); comfig = new JButton("确 定"); comfig.addActionListener(this); pl1.add(comfig); pl1.add(cancelButton); getContentPane().add("South", pl1); pack(); //调整对话框布局大小 } /** 事件处理 */ public void actionPerformed(ActionEvent event) { Object source = event.getSource(); if ((source == comfig)) { //如果确定按钮被按下,则将文本框的文本添加到父窗体的文本域中 p.setText(text.getText()); } text.selectAll(); setVisible(false); //隐藏对话框 } }
(3)运行结果:单击“添加内容”按钮,如图15-5所示。
(4)当单击“确定”按钮之后,就出现一个新的页面,如图15-6所示。
图15-5 添加按钮操作图
图15-6 添加内容之后的页面
(5)单击“保存”按钮,如图15-7所示。
(6)打开保存的文件,可以看到一个文件,如图15-8所示。
图15-7 保存按钮操作图
图15-8 文件的页面
源程序解读
1. Simple_Dialog类
(1)自定义组件必须继承一个标准的组件,在标准组件上进行功能改进比自己完全写一个组件容易得多。Simple_Dialog继承JDialog,使得主窗口可以调用Simple_Dialog的setVisible方法控制何时显示、隐藏对话框。
(2)自定义组件的构造方法必须先调用父类的构造方法。super(prent-Frame, title, false);语句初始化了JDialog对象,其中第三个参数为false表示该对话框不是总在最前,即当对话框被显示时,可以激活程序中的其他窗口;当第三个参数为true时,表示该对话框始终在最前,不能够激活其他窗口。
(3)ActionEvent的getSource方法可以获得事件发生的源对象。如果是“确定”按钮被单击,则调用主窗口的setText方法,传入对话框中文本框的文本,在父窗口的setText方法中通过JTextArea的append方法将传入的文本追加显示在文本域中。
2. DialogDemo类
(1)DialogDemo类继承JFrame实现ActionListener,在自身的构造方法中,可以调用父类的构造方法“super("对话框示例");”语句初始化JFrame对象。
(2)在actionPerformed方法中,ActionEvent的getActionCommand()可以得到标识事件命令的字符串标志。如果是“保存”按钮被单击,则创建文件选择器,根据选择的路径创建文件对象,然后通过FileOutputStream写入文件对象中。
实例139 模仿QQ空间的电子相册
QQ空间的电子相册大家都不陌生。本实例就是通过使用Java Applet实现电子相册效果。首先建立一个下拉框,改变下拉框的内容,页面上就会显示不同的图片。
技术要点
模仿QQ空间的电子相册的技术要点如下:
• 各种事件的使用,如ActionEvent、MouseAdapter等。
• 类java.awt.Component中方法boolean action(Event evt,Object what)的使用。
实现步骤
(1)新建一个类名为Picture.java。
(2)代码如下所示:
import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.ArrayList; import javax.swing.*; public class Picture extends JApplet implements ActionListener { private JLabel[] lab = new JLabel[6]; //定义一个JLabel数组,其长度为6 private ImageIcon imic; //声明ImageIcon变量 private ImageIcon[] icn = new ImageIcon[6];//创建一个ImageIcon数组,其长度为6 private JPanel pl, pl1; //定义JPanel变量 ArrayList list; //声明ArrayList变量 private JButton btn[] = new JButton[3]; //定义一个JButton数组,其长度为3 private Image image; //声明Image变量 private int key = 0; //定义int类型变量 private Graphics grapcs; //定义Graphics变量 public void init() { //Applet初始化 F_init(); //调用自定义F_init()方法 this.setSize(450, 450); //设置Applet的大小 this.setVisible(true); //设置组件可见性 image = createImage(getSize().width - 105, getSize().height - 140); //确定画图像的位置 grapcs = image.getGraphics(); //返回一个Graphics对象 image = icn[0].getImage(); //返回此图标的Image grapcs.drawImage(image, 100, 100, this); //确定绘制image的起点 repaint(); //重新调用paint方法 } public void actionPerformed(ActionEvent e) { //处理单击的监听事件 if (e.getActionCommand().equals("b1")) { //如果单击"上一张"按钮 int k = getKey(); //得到一个k值 if (k != 0) { //如果k不等于0 imic = (ImageIcon) list.get(k - 1); //通过k值到ArrayList中取出相应的ImageIcon setKey(k - 1); //将k-1重新赋给k image = imic.getImage(); //返回此图标的Image repaint(); //重新调用paint方法 } } if (e.getActionCommand().equals("b2")) { //如果单击"下一张"按钮 //其余的代码可以参考上面的 int k = getKey(); if (k != 5) { imic = (ImageIcon) list.get(k + 1); setKey(k + 1); image = imic.getImage(); repaint(); } } if (e.getActionCommand().equals("b3")) { //如果单击"返回"按钮 imic = (ImageIcon) list.get(0); setKey(0); image = imic.getImage(); repaint(); } } public void paint(Graphics g) { //绘制图像 F_init(); g.drawImage(image, 120, 120, this); } public void F_init() { //主要是对各组件进行布局和初始化的作用 pl = new JPanel(new GridLayout(1, 6)); //创建一个网格布局的面板pl pl1 = new JPanel(); //创建面板pl1 list = new ArrayList(); //创建ArrayList对象 for (int i = 0; i < lab.length; i++) { icn[i] = new ImageIcon("D:\\workspace\\Yin\\TP" + (i + 1) + ".jpg"); //创建ImageIcon对象 lab[i] = new JLabel(icn[i]); //创建带图标的标签 pl.add(lab[i], i); //将标签组件添加到面板中 list.add(i, icn[i]); //将图标对象添加到ArrayList中 } //对每个标签进行鼠标单击事件监听 lab[0].addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent e) { if (e.getButton() == 1) { //如果鼠标左键被按下 imic = (ImageIcon) list.get(0); //取出ArrayList中0位置上的图标 image = imic.getImage(); //返回此图标的图像 repaint(); setKey(0); } } }); lab[1].addMouseListener(new MouseAdapter() { //同上 public void mouseClicked(MouseEvent e) { if (e.getButton() == 1) { imic = (ImageIcon) list.get(1); image = imic.getImage(); repaint(); setKey(1); } } }); lab[2].addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent e) { if (e.getButton() == 1) { imic = (ImageIcon) list.get(2); image = imic.getImage(); repaint(); setKey(2); } } }); lab[3].addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent e) { if (e.getButton() == 1) { imic = (ImageIcon) list.get(3); image = imic.getImage(); repaint(); setKey(3); } } }); lab[4].addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent e) { if (e.getButton() == 1) { imic = (ImageIcon) list.get(4); image = imic.getImage(); repaint(); setKey(4); } } }); lab[5].addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent e) { if (e.getButton() == 1) { imic = (ImageIcon) list.get(5); image = imic.getImage(); repaint(); setKey(5); } } }); //定义按钮 btn[0] = new JButton("前一张"); btn[0].setActionCommand("b1"); btn[0].addActionListener(this); btn[1] = new JButton("下一张"); btn[1].setActionCommand("b2"); btn[1].addActionListener(this); btn[2] = new JButton("返回"); btn[2].setActionCommand("b3"); btn[2].addActionListener(this); pl1.add(btn[0], 0); pl1.add(btn[1], 1); pl1.add(btn[2], 2); this.add(pl, BorderLayout.NORTH); this.add(pl1, BorderLayout.SOUTH); } public int getKey() { return key; } public void setKey(int key) { this.key = key; } }
(3)运行结果的初始页面,如图15-9所示。
(4)单击“下一张”按钮就会跳到下一个图片中,如图15-10所示。
图15-9 电子相册
图15-10 单击“下一张”按钮出现的页面
源程序解读
(1)绘制程序中的图像:
public void paint(Graphics g) { F_init(); g.drawImage(image, 120, 120, this); }
(2)方法action()的使用:在Java Applet小程序中,可以通过action()方法来获得Java Applet小程序运行时所发生的事件,如单击按钮、键入文本、使用鼠标或执行任何界面相关的动作等。
(3)鼠标事件:程序中通过MouseAdapter将图片关联起来,单击鼠标时就会触动相应的鼠标事件,并执行操作。
实例140 会动的七彩文字
会动的七彩文字在网页上经常可以看到,现在采用Java Applet这个效果。可以想到文字的颜色是不停改变的,并产生波浪效果,这样就可以使页面中的文字更加美观,并且可以调整读者的积极性,让读者在开发中有一个轻松的心情。
技术要点
会动的七彩文字的技术要点如下:
• 类Font的使用。
• 类FontMetrics的使用。
• 文字的颜色及速度控制。
实现步骤
(1)新建一个类名为QC_Text.java。
(2)代码如下所示:
import java.awt.*; import java.applet.*; public class QC_Text extends Applet implements Runnable { //有线程运行接口 String str = null; int d = 1; int h = 18; int v = 18; Thread thread = null; //设置一个线程 char[] ch; int p = 0; Image image; Graphics gphics; Color[] color; private Font f; //字体 private FontMetrics fm; //字模 public void init() { str = "有志者事竟成"; //设置七彩文字内容 this.setSize(500, 200); //设置Applet的大小 setBackground(Color.black); //设置背景颜色 ch = new char[str.length()]; ch = str.toCharArray(); //将字符串中的各个字符保存到数组中 image = createImage(getSize().width, getSize().height); gphics = image.getGraphics(); f = new Font("", Font.BOLD, 30); fm = getFontMetrics(f); //获得指定字体的字体规格 gphics.setFont(f); //设置组件的字体 float hue; color = new Color[str.length()]; //颜色的色元 for (int i = 0; i < str.length(); i++) { hue = ((float) i) / ((float) str.length()); color[i] = new Color(Color.HSBtoRGB(hue, 0.8f, 1.0f)); //颜色分配 } } public void start() { //线程开始的类 if (thread == null) { //如果线程为空 thread = new Thread(this); //开始新的线程 thread.start(); //开始 } } //终止线程 public void stop() { if (thread != null) { //如果线程不为空 thread.stop(); //终止线程,使它为空 thread = null; } } //运行线程 public void run() { while (thread != null) { try { thread.sleep(200); //让线程沉睡200毫秒 } catch (InterruptedException e) { } repaint(); //重新绘制界面 } } public void update(Graphics g) { //重写update方法,解决闪烁问题 int x, y; double a; gphics.setColor(Color.black); gphics.fillRect(0, 0, getSize().width, getSize().height); p += d; p %= 7; //主要控制字的速度,被除数越小,速度越快 //System.out.println(p+" p1"); for (int i = 0; i < str.length(); i++) { a = ((p - i * d) % 7) / 4.0 * Math.PI; //主要控制弧度,被除数越小,弧度越大 x = 30 + fm.getMaxAdvance() * i + (int) (Math.cos(a) * h); //求x坐标值 y = 80 + (int) (Math.sin(a) * v); //求y坐标值 gphics.setColor(color[(p + i) % str.length()]); gphics.drawChars(ch, i, 1, x, y); } paint(g); } public void paint(Graphics g) { g.drawImage(image, 0, 0, this); } }
(3)运行结果的初始页面如图15-11所示。
(4)程序运行的下一个页面如图15-12所示。
图15-11 会动的七彩文字
图15-12 文字进入下一秒时的轨迹
源程序解读
(1)字体类(Font类)代表字体。通过类Graphics或组件的方法getFont()或setFont()可以获取或设置当前使用的字体(Font类的对象)。在上面的实例中,可以看到Font类的使用。
f = new Font("", Font.BOLD, 30); fm = getFontMetrics(f);
(2)类Graphics2D也继承了类Graphics的两个方法getFont()和setFont()。
(3)字模(FontMetrics类):FontMetrics类表示字模,这个类提供了一系列方法,通过调用它们,可以得到用某种字体表示的一个字符串在屏幕上的特定尺寸。
fm = getFontMetrics(f); gphics.setFont(f);
实例141 模仿3D渐层效果
3D渐层效果可以使读者更直观地看到文字或图片的立体效果,本实例主要采用Java Applet实现文字的3D效果,并随着时间的改变,不停地改变字体的颜色。
技术要点
模仿3D渐层效果的技术要点如下:
• 随机产生RGB颜色值。
• 使用for循环实现3D文字渐层显示。
• 设置绘图的字体。
实现步骤
(1)新建一个类名为TextOf3D.java。
(2)代码如下所示:
import java.awt.*; import javax.swing.JApplet; public class TextOf3D extends JApplet implements Runnable { private Image image; //声明image变量 private Image image_1; //声明image_1变量 private Graphics gp; //声明绘图对象 private Thread thread = null; //声明线程 private MediaTracker tracker; //声明媒体跟踪器 private int height, width; //声明int变量 private String text; //声明String变量text private Font font; //声明Font对象 public void init() { //Applet初始化 this.setSize(200, 100); //设置初始大小 width = this.getWidth(); //得到容器的宽 height = this.getHeight(); //获取容器的高 this.setBackground(Color.lightGray); //设置容器的背景色 image = createImage(width, height); //根据当前的宽和高创建一个图像 text = "welcome"; //String变量text赋值 String str = "中华儿女"; //创建一个有初始值的String变量 if (str != null) text = str; //在str的值不为空的前提下,将str的值赋给text font = new Font("仿宋_GB2312", Font.BOLD, 30); //创建一个Font对象 tracker = new MediaTracker(this); //创建一个MediaTracker对象 tracker.addImage(image, 0); //将image加载到tracker中 try { tracker.waitForID(0); //等待加载Id为0的图像 } catch (InterruptedException e) { } image_1 = createImage(width, height); //根据当前的宽和高创建一个图像 gp = image_1.getGraphics(); //根据图像image_1,获得Graphics对象 } public void start() { //启动线程 if (thread == null) { thread = new Thread(this); thread.start(); } } public void run() { //运行线程 int x = 20; //设置绘制图像的X坐标 int y = height / 2; //设置绘制图像的Y坐标 int R, G, B; //设置RGB颜色值 gp.setFont(font); //设置绘图的字体 while (thread != null) { //在启动线程的前提下 //随机生成Color颜色值 R = (int) (255 * Math.random()); G = (int) (255 * Math.random()); B = (int) (255 * Math.random()); try { thread.sleep(2000); //线程休眠2000毫秒 } catch (InterruptedException ex) { } gp.setColor(Color.black); //设置绘图的背景色为黑色 gp.fillRect(0, 0, width, height); //设置所绘矩形的大小和位置 repaint(); //重新绘制此组件 for (int i = 0; i < 10; i++) { gp.setColor(new Color((255 - (0 + R) * i / 10), (255 - (0 + G) * i / 10), (255 - (0 + B) * i / 10))); //根据RGB设置当前的绘图颜色 gp.drawString(text, x - i, y - i); //根据当前字体和颜色绘制由指定string给定的文本 repaint(); //重新绘制此组件 try { thread.sleep(60); //线程休眠60毫秒 } catch (InterruptedException e) { } } } } public void paint(Graphics g) { //绘图方法 g.drawImage(image_1, 0, 0, this); } }
(3)运行结果如图15-13所示,程序运行2秒后的页面如图15-14所示。程序再运行2秒后的页面如图15-15所示。
图15-13 模仿3D渐层效果
图15-14 2秒后的文字
图15-15 再过2秒后的页面
源程序解读
(1)3D文字效果的实现,使文字颜色伴随时间改变。利用随机数定义颜色:public void init(),利用这个构造方法来初始化文字,得到容器的宽和高,同时也为下面的程序做了声明。
(2)根据当前的值获得设置绘图颜色:
gp.setColor(newColor((255 - (0 + R) * i / 10),(255 - (0 + G)* i / 10),(255 - (0 + B) * i / 10)));
(3)运用线程来启动小程序,实现程序的效果,展现3D文字。
实例142 模仿QQ空间的欢迎动画
对于QQ空间相信每个人都不会感到陌生。在一个完美的空间,用户都喜欢有一个欢迎画面,欢迎画面既起到了宣传产品的作用,同时也标志了应用程序是否成功启动。本实例将展示一个欢迎画面,可以指定欢迎画面用的图片和显示的时间,当用户单击画面时,画面关闭;当显示时间到期时,画面自动关闭。
技术要点
制作欢迎画面的技术要点如下:
• 由于JWindow是一种没有标题、没有边框的窗口,因此它是实现欢迎画面的最佳组件。
• 掌握通过图片文件的目录路径可以构造ImageIcon对象,利用ImageIcon对象可以构造标签JLabel。此时调用JLabel的getPreferredSize方法将得到标签的最佳大小,也就是图片载入标签中的最佳尺寸。
• 掌握Toolkit类的构造方式和getScreenSize方法的使用。
• 掌握使用MouseAdapter事件处理器为组件添加鼠标事件。其中,它的mousePressed方法是在鼠标按键被按下时调用。
• 掌握SwingUtilities的invokeAndWait或者invokeLater方法。
实现步骤
(1)新建一个类名为Desktop_Window.java。
(2)代码如下所示:
package chp15; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.Toolkit; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import javax.swing.ImageIcon; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JWindow; import javax.swing.SwingUtilities; /** * 本例实现一个欢迎画面,常用作应用软件的启动画面。 */ public class Desktop_Window extends JWindow implements Runnable { Thread waitThread; final int rest; final Thread closerThread; /**构造函数image_url:欢迎画面所用的图片,frame:欢迎画面所属的窗体,waitTime:欢迎画面显示的事件*/ public Desktop_Window(String image_url, JFrame frame, int waitTime) { super(frame); //建立一个标签,标签中显示图片。将标签放在欢迎画面中间 JLabel label = new JLabel(new ImageIcon(image_url)); getContentPane().add(label, BorderLayout.CENTER); pack(); //获取屏幕的分辨率大小 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); Dimension labelSize = label.getPreferredSize(); //获取标签大小 //将欢迎画面放在屏幕中间 setLocation(screenSize.width / 2 - (labelSize.width / 2), screenSize.height / 2 - (labelSize.height / 2)); //增加一个鼠标事件处理器,如果用户用鼠标单击了欢迎画面,则关闭 addMouseListener(new MouseAdapter() { public void mousePressed(MouseEvent e) { setVisible(false); dispose(); } }); rest = waitTime; /**Swing线程在同一时刻仅能被一个线程所访问。 *一般来说,这个线程是事件派发线程(event-dispatching thread)。 * 如果需要从事件处理(event-handling)或绘制代码以外的地方访问UI, * 则可以使用SwingUtilities类的invokeLater()或invokeAndWait()方法。 */ closerThread = new Thread() { //关闭欢迎画面的线程 public void run() { setVisible(false); dispose(); //释放资源 } }; waitThread = new Thread(); //等待关闭欢迎画面的线程 setVisible(true); waitThread.start(); //启动等待关闭欢迎画面的线程 } public static void main(String[] args) throws FileNotFoundException { JFrame frame = new JFrame("自制欢迎动画"); frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); Desktop_Window dw = new Desktop_Window("D:/abc/wel.jpg", frame, 5000); frame.pack(); frame.setVisible(true); } public void run() { try { Thread.sleep(rest); //当显示了rest后,尝试关闭欢迎画面 SwingUtilities.invokeAndWait(closerThread); } catch (Exception e) { e.printStackTrace(); } } }
(3)运行结果如图15-16所示。
图15-16 欢迎动画
源程序解读
(1)通过ImageIcon把图片装入到缓存中,再用ImageIcon对象构造标签。通过JLabel的getPreferredSize()方法获得标签的最佳大小,也就是图片的最佳大小。
(2)获取屏幕的分辨率大小:
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
(3)为JWindow注册鼠标事件处理器,在mousePressed方法中关闭窗口,即当用户单击欢迎画面时,画面消失。setVisible 方法传入false表示隐藏欢迎画面,dispose方法表示销毁窗口资源。
(4)closerRunner对象是一个线程,它完成关闭欢迎画面的功能。
(5)关闭欢迎画面:
public void run() { try { Thread.sleep(rest); SwingUtilities.invokeAndWait(closerThread);
(6)Swing线程在同一时刻仅能被一个线程所访问,而这个线程就是事件派发线程,它是执行绘制和事件处理的线程。例如,paint和actionPerformed方法会自动在事件派发线程中执行。
实例143 百叶窗效果
百叶窗效果是一种渐变的效果,让图片逐渐地映入眼帘,本实例是利用Java Applet动态改变显示的图片,并产生百叶窗的效果。希望通过这个实例能带给读者一点启发。百叶窗效果可以是多种,读者可以根据自己的需要,举一反三。
技术要点
百叶窗效果的技术要点如下:
• 类PixelGrabber的使用。
• 使用类MemoryImageSource创建图像。
• MediaTracker对象的使用。
实现步骤
(1)新建一个类名为Blinds.java。
(2)代码如下所示:
package chp15; import java.awt.*; import java.applet.*; import java.net.MalformedURLException; import java.net.URL; import java.awt.image.*; public class Blinds extends Applet implements Runnable { private Image IMG[], image; //声明Image数组和变量 private MediaTracker tracker; //声明MediaTracker变量 private int width, height, image_count = 8, image2, image3; //声明int类型的变量 private Thread thread; //声明Thread变量 private int delay = 3000; //定义int变量,初始值为3000 private int p, p_1[], p_2[], p_3[], p_4[], p_5[], p_6[], p_7[], p_8[], p_A[], p_B[]; //声明int型数组,用于接收生成图像的像素 public void init() { this.setBackground(Color.black); //设置背景颜色为黑色 this.setSize(320, 250); //设置边框的宽和高 IMG = new Image[image_count]; //创建数组长度为image_count的Image数组 tracker = new MediaTracker(this); //创建MediaTracker对象 String s = ""; for (int i = 0; i < image_count; i++) { s = "D://abc//" + (i + 1) + ".jpg"; URL url; try { url = new URL("file:" + s); //创建URL对象 IMG[i] = getImage(url); //创建Image对象 tracker.addImage(IMG[i], 0); //将Image对象添加到tracker的指定位置中 } catch (MalformedURLException e) { e.printStackTrace(); } } try { tracker.waitForID(0); //等待加载ID为0的所有图像 } catch (InterruptedException e) { } width = IMG[0].getWidth(this); //得到此图片的宽 height = IMG[0].getHeight(this); //得到此图片的高 p = width * height; //宽和高的乘积 p_1 = new int[p]; //创建长度为p的int数组 PixelGrabber PG1 = new PixelGrabber(IMG[0], 0, 0, width, height, p_1,0, width); //创建一个PixelGrabber对象,以便从指定的图像中将像素矩形部分 (x, y, w,h) 抓取到给定的数组中 try { PG1.grabPixels(); //请求Image开始传递像素,并等待传递完相关矩形中的所有像素,或者等待到超时期已过 } catch (InterruptedException ex) { } p_2 = new int[p]; PixelGrabber PG2 = new PixelGrabber(IMG[1], 0, 0, width, height, p_2, 0, width); try { PG2.grabPixels(); } catch (InterruptedException ex) { } p_3 = new int[p]; PixelGrabber PG3 = new PixelGrabber(IMG[2], 0, 0, width, height, p_3, 0, width); try { PG3.grabPixels(); } catch (InterruptedException ex) { } p_4 = new int[p]; PixelGrabber PG4 = new PixelGrabber(IMG[3], 0, 0, width, height, p_4, 0, width); try { PG4.grabPixels(); } catch (InterruptedException ex) { } p_5 = new int[p]; PixelGrabber PG5 = new PixelGrabber(IMG[4], 0, 0, width, height, p_5, 0, width); try { PG5.grabPixels(); } catch (Exception ex) { } p_6 = new int[p]; PixelGrabber PG6 = new PixelGrabber(IMG[5], 0, 0, width, height, p_6, 0, width); try { PG6.grabPixels(); } catch (InterruptedException ex) { } p_7 = new int[p]; PixelGrabber PG7 = new PixelGrabber(IMG[6], 0, 0, width, height, p_7, 0, width); try { PG7.grabPixels(); } catch (InterruptedException ex) { } p_8 = new int[p]; PixelGrabber PG8 = new PixelGrabber(IMG[7], 0, 0, width, height, p_8, 0, width); try { PG8.grabPixels(); } catch (Exception ex) { } image2 = 0; p_A = new int[p]; p_B = new int[p]; image = IMG[0]; thread = new Thread(this); //创建线程 thread.start(); //启动线程 } public void paint(Graphics g) //绘制组件 { g.drawImage(image, 0, 0, this); } public void update(Graphics g) //更新组件 { paint(g); } public void run() //运行线程 { if (thread == null) { thread = new Thread(this); thread.start(); } while (true) //控制图片和图片之间的过渡 { try { thread.sleep(delay); //线程休眠delay毫秒 image3 = ((image2 + 1) % image_count);//指向当前图片的下一张图片 if (image2 == 0) { System.arraycopy(p_1, 0, p_A, 0, p); //从下标为0的p_1数组中复制一个数组,存放到p_A数组中下标为0的位置中,其数组长度为p System.arraycopy(p_2, 0, p_B, 0, p); //同上 image = createImage(new MemoryImageSource(width, height, p_A, 0, width)); //根据指定的图像生成器创建一幅图像 repaint(); } if (image2 == 1) { System.arraycopy(p_2, 0, p_A, 0, p); System.arraycopy(p_3, 0, p_B, 0, p); image = createImage(new MemoryImageSource(width, height, p_A, 0, width)); repaint(); } if (image2 == 2) { System.arraycopy(p_3, 0, p_A, 0, p); System.arraycopy(p_4, 0, p_B, 0, p); image = createImage(new MemoryImageSource(width, height, p_A, 0, width)); repaint(); } if (image2 == 3) { System.arraycopy(p_4, 0, p_A, 0, p); System.arraycopy(p_5, 0, p_B, 0, p); image = createImage(new MemoryImageSource(width, height, p_A, 0, width)); repaint(); } if (image2 == 4) { System.arraycopy(p_5, 0, p_A, 0, p); System.arraycopy(p_6, 0, p_B, 0, p); image = createImage(new MemoryImageSource(width, height, p_A, 0, width)); repaint(); } if (image2 == 5) { System.arraycopy(p_6, 0, p_A, 0, p); System.arraycopy(p_7, 0, p_B, 0, p); image = createImage(new MemoryImageSource(width, height, p_A, 0, width)); repaint(); } if (image2 == 6) { System.arraycopy(p_7, 0, p_A, 0, p); System.arraycopy(p_8, 0, p_B, 0, p); image = createImage(new MemoryImageSource(width, height, p_A, 0, width)); repaint(); } if (image2 == 7) { System.arraycopy(p_8, 0, p_A, 0, p); System.arraycopy(p_1, 0, p_B, 0, p); image = createImage(new MemoryImageSource(width, height, p_A, 0, width)); repaint(); } while (true) //控制叶宽 { for (int i = 0; i < (int) (height / 10); i++) { try { thread.sleep(30); //线程休眠30毫秒 for (int j = 0; j < height; j += (int) (height / 10)) { for (int k = 0; k < width; k++) { p_A[width * (j + i) + k] = p_B[width * (j + i) + k];//进行数组间的复制 } } } catch (InterruptedException e) { } image = createImage(new MemoryImageSource(width, height, p_A, 0, width)); repaint(); } break; } image2 = image3; repaint(); } catch (InterruptedException e) { } } } }
(3)运行结果的初始页面如图15-17所示。
(4)运行之后的页面如图15-18所示。
图15-17 百叶窗
图15-18 百叶窗运行后的页面
源程序解读
(1)类PixelGrabber的使用:获得了图像后,可以通过java.awt.image.PixelGrabber包中的PixelGrabber方法将图像中的像素信息完全读取出来。用法如下:
PixelGrabber PG1 = new PixelGrabber(IMG[0], 0, 0, width, height, p_1,0, width);
(2)MediaTracker对象的使用:在语句tracker = new MediaTracker(this);之后就将Image对象添加到MediaTracker中,并对其操作。
(3)通过启动线程来将图片进行获取及制作,最终运行出百叶窗效果。
实例144 闪电效果
本实例是一个Java小应用程序(Applet)的例子。使用线程技术,最终运行小程序就可以看到闪电效果,并且可以是多种颜色的,随时变化。
技术要点
闪电效果的技术要点如下:
• 类Graphics的使用。
• 类Image的使用。
实现步骤
(1)新建一个类名为LightnING.java。
(2)代码如下所示:
package chp15; import java.applet.Applet; import java.awt.Color; import java.awt.Graphics; import java.awt.Image; public class LightnING extends Applet implements Runnable { //在applet中支持线程,需要实现Runnable接口 private Thread thread = null; //applet支持的线程 private int no_Light = 0; //没有闪电的标志变量:0表示没有闪电 private int Light = 1; //有闪电的标志变量 private int[] light; //声明int型数组light private int[] array_1; //声明int型数组array_1 private int[] array_2; //声明int型数组array_2 private Image T_image, image; //声明Image变量T_image, image private int delay = 3; //延长的时间倍数 public void init() { //初始化applet this.setSize(640, 480); String imageName = "city.gif"; image = getImage(getCodeBase(), imageName); //创建int型数组,定义其长度为getSize().height light = new int[getSize().height]; array_1 = new int[getSize().height]; array_2 = new int[getSize().height]; T_image = this.createImage(getSize().width, getSize().height); //根据指定的参数创建一幅图像 } public void paint(Graphics g) { int i, t; if (no_Light == 0) //没有闪电 { g.setColor(Color.black); //设背景色为黑色 g.fillRect(0, 0, getSize().width, getSize().height); //填充背景色 g.drawImage(image, 0, 0, this); //输出city.gif } else //有闪电 { switch (Light) { case 1: g.setColor(new Color(240, 255, 255)); break; //设背景色为蓝色 case 2: g.setColor(new Color(176, 48, 96)); break; //设背景色为红色 case 3: g.setColor(new Color(250, 255, 240)); break; //设背景色为黄色 case 4: g.setColor(new Color(240, 255, 240)); break; //设背景色为绿色 case 5: g.setColor(new Color(248, 248, 255)); break; //设背景色为紫色 default: g.setColor(new Color(245, 245, 245)); break; //设背景色为白色 } g.fillRect(0, 0, getSize().width, getSize().height); //填充背景色 t = (int) (0.6F * getSize().height); //输出闪电图像 for (i = 1; i < getSize().height; i++) { if (i < t) { //输出闪电周围的灰色矩形 g.setColor(Color.white); g.drawRect(light[i] - 4, i, 3, 1); g.drawRect(light[i] + 1, i, 3, 1); g.setColor(Color.orange); g.drawRect(light[i] - 1, i, 1, 1); g.drawRect(light[i] + 1, i, 1, 1); } switch (Light) { case 1: g.setColor(new Color(0, 0, 255)); break; //蓝色闪电 case 2: g.setColor(new Color(255, 0, 0)); break; //红色闪电 case 3: g.setColor(new Color(255, 215, 0)); break; //黄色闪电 case 4: g.setColor(new Color(0, 255, 0)); break; //绿色闪电 case 5: g.setColor(new Color(160, 32, 240)); break; //紫色闪电 default: g.setColor(new Color(225, 225, 225)); break; //白色闪电 } g.drawLine(light[i], i, light[i - 1], i - 1); if (array_1[i] >= 0) { //输出闪电折线 g.drawLine(array_1[i], i, array_1[i - 1], i - 1); } if (array_2[i] >= 0) { //输出闪电折线 g.drawLine(array_2[i], i, array_2[i - 1], i - 1); } } g.drawImage(image, 0, 0, this); //输出城市图像city.gif Light = (int) ((Math.random() * 10) + 1); } } void drawT_image() { //调用paint()方法 Graphics g; g = T_image.getGraphics(); paint(g); } public void start() { //启动applet,创建并启动线程 if (thread == null) { thread = new Thread(this); thread.start(); } } public void stop() { //停止运行线程 if (thread != null) { thread.stop(); thread = null; } } void createLight() { //生成闪电的坐标数组数据 int i; int bs1, bs2; //开始位置的坐标 int be1, be2; //结束位置的坐标 light[0] = (int) (Math.random() * getSize().width); //随机产生闪电出现的位置 array_1[0] = light[0]; array_2[0] = light[0]; bs1 = (int) (Math.random() * getSize().height) + 1; bs2 = (int) (Math.random() * getSize().height) + 1; be1 = bs1 + (int) (0.5 * Math.random() * getSize().height) + 1; be2 = bs2 + (int) (0.5 * Math.random() * getSize().height) + 1; for (i = 1; i < getSize().height; i++) { light[i] = light[i - 1] + ((Math.random() > 0.5) ? 1 : -1); array_1[i] = light[i]; array_2[i] = light[i]; } for (i = bs1; i < getSize().height; i++) { array_1[i] = array_1[i - 1] + ((Math.random() > 0.5) ? 2 : -2); } for (i = bs2; i < getSize().height; i++) { array_2[i] = array_2[i - 1] + ((Math.random() > 0.5) ? 2 : -2); } for (i = be1; i < getSize().height; i++) { array_1[i] = -1; } for (i = be2; i < getSize().height; i++) { array_2[i] = -1; } } public void run() { //启动进程 Graphics g; while (true) { try { //输出图像 drawT_image(); g = this.getGraphics(); g.drawImage(T_image, 0, 0, this); Thread.sleep((int) (delay * 1000 * Math.random())); //线程休眠,时间随机产生 no_Light = 1; //无闪电标记变量置位 createLight(); //创建闪电 //输出图像 drawT_image(); g = this.getGraphics(); g.drawImage(T_image, 0, 0, this); Thread.sleep(1000); //线程休眠1秒 no_Light = 0; //无闪电标记变量置位 } catch (InterruptedException e) { stop(); //发生异常,停止线程 } } } }
(3)运行结果的初始页面如图15-19所示。过2秒后就会出现一个新的闪电页面,如图15-20所示。
图15-19 闪电效果
图15-20 黄色闪电的效果
源程序解读
(1)初始化Applet小程序,加载图片:
Image=getImage(getCodeBase(),"xx.gif");
(2)实现闪电的特效。线程休眠,时间随机产生,之后就确定闪电的坐标位置,并通过线程来运行,最后确定闪电的精确位置。
Thread.Sleep((int)(Integer.parseInt(delay)*1000*Math.radom()));
(3)输出闪电以及图像使用Paint()方法来画出闪电的图像,并随之输出相应颜色的闪电。
(4)生成闪电的坐标数组数据,随机产生闪电出现的位置:
light[i]= light[i-1]+((Math.random()>0.5)?i-1); b1[i]= light[i]; b2[i]= light[i];
实例145 模拟放大镜效果
大家都了解放大镜的基本功能,就是将小的字或图放大,能让人看得清楚。本实例就是一个Java小应用程序(Applet)的例子。使用线程技术,运行小程序就会看到一个图片放大镜的特效,随着放大镜的移动,放大框内的图像就会被放大。
技术要点
模拟放大镜效果的技术要点如下:
• 鼠标移动事件。
• 双缓冲技术处理图像。
• 类MediaTracker的使用。
实现步骤
(1)新建一个类名为Max_Glass.java。
(2)代码如下所示:
package chp15; import java.applet.Applet; import java.awt.Color; import java.awt.Graphics; import java.awt.Image; import java.awt.MediaTracker; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionListener; public class Max_Glass extends Applet implements MouseMotionListener { Graphics g; Image image; Image back_Image; String name; MediaTracker tracker; //声明媒体跟踪器tracker int Glass_X = 0, Glass_Y = 0; //放大镜初始位置 int Glass_W = 100, Glass_H = 100; //放大镜宽度、高度 int width, height; //声明背景图片的宽度和高度 public void init() { //初始化applet //加载图片 this.setSize(600, 300); g = getGraphics(); name = "grid.jpg"; tracker = new MediaTracker(this); back_Image = getImage(getCodeBase(), name); image = createImage(250, 100); //设置放大后的图像的大小 Graphics offg = image.getGraphics(); offg.drawImage(back_Image, 0, 0, this); addMouseMotionListener(this); //添加鼠标事件监听 } public void mouseDragged(MouseEvent e) { //鼠标拖拽事件处理 } public void mouseMoved(MouseEvent e) { //处理鼠标移动事件 reprintGlass(Glass_X, Glass_Y, e.getX(), e.getY()); //通过鼠标位置设置放大镜的位置 //设置放大镜的当前位置 Glass_X = e.getX(); Glass_Y = e.getY(); //若放大镜溢出applet则进行调整 if (Glass_X > (width - Glass_W / 2)) Glass_X = width - Glass_W / 2; if (Glass_Y > (height - Glass_H / 2)) Glass_Y = height - Glass_H / 2; printGlass(); //调用自定义方法—输出放大镜 } void printGlass() { Graphics temp = g.create(); //复制g的一个实例 temp.clipRect(Glass_X, Glass_Y, Glass_W, Glass_H);//为temp限制一个矩形区域 temp.drawImage(back_Image, -Glass_X, -Glass_Y, width * 2, height * 2, null); //输出放大后的图像 g.setColor(Color.black); //设置放大镜边框的颜色 g.drawRect(Glass_X, Glass_Y, Glass_W - 1, Glass_H - 1);//输出放大镜边框 } void reprintGlass(int X, int Y, int new_X, int new_Y) { //清除已经画过的矩形框和放大的图像 Graphics temp = g.create(); //同上 if (new_X <= X && new_Y <= Y) { temp.clipRect(new_X, new_Y + Glass_H, Glass_W + X - new_X, Y - new_Y); temp.drawImage(image, 0, 0, null); temp = g.create(); temp.clipRect(new_X + Glass_W, new_Y, X - new_X, Glass_H + Y - new_Y); temp.drawImage(image, 0, 0, null); } else if (new_X > X && new_Y <= Y) { temp.clipRect(X, new_Y + Glass_H, Glass_W + new_X - X, Y - new_Y); temp.drawImage(image, 0, 0, null); temp = g.create(); temp.clipRect(X, new_Y, new_X - X, Glass_H + Y - new_Y); temp.drawImage(image, 0, 0, null); } else if (new_X > X && new_Y > Y) { temp.clipRect(X, Y, Glass_W + new_X - X, new_Y - Y); temp.drawImage(image, 0, 0, null); temp = g.create(); temp.clipRect(X, Y, new_X - X, Glass_H + new_Y - Y); temp.drawImage(image, 0, 0, null); } else { temp.clipRect(new_X, Y, Glass_W + X - new_X, new_Y - Y); temp.drawImage(image, 0, 0, null); temp = g.create(); temp.clipRect(new_X + Glass_W, Y, X - new_X, Glass_H + new_Y - Y); temp.drawImage(image, 0, 0, null); } } public boolean imageUpdate(Image img, int infoflags, int x, int y, int w, int h) {//判断infoflags参数是否已完全加载了图像,是则返回false;否则返回true if (infoflags == ALLBITS) { //ALLBITS指示现在已完成了一幅以前绘制的静态图像,并且可以其最终形式再次绘制它 width = back_Image.getWidth(this); height = back_Image.getHeight(this); image = createImage(width + Glass_W / 2, height + Glass_H / 2); Graphics offg = image.getGraphics(); offg.setColor(Color.white); offg.fillRect(0, 0, width + Glass_W / 2, height + Glass_H / 2); offg.drawImage(back_Image, 0, 0, this); repaint(); return false; } else return true; } public void paint(Graphics g) { g.drawImage(back_Image, 0, 0, this); //输出背景图片 printGlass(); //画放大镜 } }
(3)运行结果如图15-21所示。
图15-21 放大镜效果
源程序解读
(1)Mouse移动事件,鼠标移动主要通过接口MouseMotionListener来实现,mouse-Dragged()当用户按下鼠标按钮,并在松开之前进行移动时发生;在mouseDragged()后松开鼠标不会导致mouseClicked();mouseMoved()当鼠标在组件上移动而不是拖动时发生。
(2)初始化Applet,addMouseMotion-Listener(this):添加鼠标事件监听。
(3)实现放大镜特效,通过该构造方法void printGlass()复制g的一个实例:
• temp=g.create():为temp限制一个矩形区域。
• temp.clipRect(boxX, boxY, boxW, boxH):输出放大后的图像。
• temp.drawImage(back,-boxX,-boxY,width*2,height*2,mull):输出放大镜边框。
• g.setColor(Color.white):g.drawRect(boxX, boxY,boxW-1,boxH-1)。
实例146 水面倒影
水面倒影在生活中可以见到,就是在水中呈现出一个物体的倒影。本实例是一个Java小应用程序(Applet)的例子。使用线程技术,运行小程序就会显示一个水面倒影的特效。
技术要点
水面倒影的技术要点如下:
• 类Math的使用。
• 类MediaTracket的使用。
• 类Graphics的使用。
• 水波特效。
实现步骤
(1)新建一个类名为Reflection.java。
(2)代码如下所示:
package chp15; import java.applet.Applet; import java.awt.Graphics; import java.awt.Image; import java.awt.MediaTracker; public class Reflection extends Applet implements Runnable { Thread thread = null; //声明线程 private Graphics g, inv_g; //定义绘制正常图像和倒立后图像的Graphics对象 private Image image, invimage;//image用于载入正常图像,invimage用于载入倒立后的图像 private int inv; //应用于形成倒立影像的变量 private int image_W = 0, image_H = 0; //定义装载图片宽和高的变量 private boolean load_Flag = false; //定义标志,其作用是标志加载图片是否完毕 private final int fre = 14; //定义水纹波动的频率,数值越大,波动越慢 private String picture_name = ""; //定义图片名字 public void init() { //初始化applet picture_name = "flaw.jpg"; } public void paint(Graphics g) { if (!load_Flag) //如果已经载入图片,则返回 return; if (invimage != null) { //输出倒影图片 g.drawImage(invimage, (-inv * image_W), image_H, this); g.drawImage(invimage, ((fre - inv) * image_W), image_H, this); } g.drawImage(image, 0, -1, this); //输出正向图片 } public void start() { //启动applet,创建并启动线程 if (thread == null) { thread = new Thread(this); thread.start(); } } public void run() { //启动线程 //加载图片 inv = 0; g = getGraphics(); MediaTracker imageTracker = new MediaTracker(this); image = getImage(this.getCodeBase(), picture_name); imageTracker.addImage(image, 0); try { imageTracker.waitForAll(); load_Flag = !imageTracker.isErrorAny();//检查媒体跟踪器跟踪的所有图像的错误状态 } catch (InterruptedException e) { } //图片宽度、图片高度 image_W = image.getWidth(this); image_H = image.getHeight(this); this.setSize(image_W, image_H * 2 - 20); creatWater(); //生成倒影 repaint(); //重新输出applet while (true) { try { if (!load_Flag) return; if (invimage != null) { g.drawImage(invimage, (-inv * image_W), image_H, this); g.drawImage(invimage, ((fre - inv) * image_W), image_H, this); } g.drawImage(image, 0, -1, this); if (++inv == fre) inv = 0; Thread.sleep(50); } catch (InterruptedException e) { stop(); } } } public void creatWater() { //产生水波特效 Image back = createImage(image_W, image_H + 1); Graphics graphics = back.getGraphics(); int phase = 0; int x, y; double p1; graphics.drawImage(image, 0, 1, this); for (int i = 0; i < (image_H >> 1); i++) { graphics.copyArea(0, i, image_W, 1, 0, image_H - i); graphics.copyArea(0, image_H - 1 - i, image_W, 1, 0, -image_H + 1 + (i << 1)); graphics.copyArea(0, image_H, image_W, 1, 0, -1 - i); } invimage = createImage((fre + 1) * image_W, image_H); inv_g = invimage.getGraphics(); inv_g.drawImage(back, fre * image_W, 0, this); for (phase = 0; phase < fre; phase++) { p1 = 2 * Math.PI * (double) phase / (double) fre; x = (fre - phase) * image_W; for (int i = 0; i < image_H; i++) { y = (int) ((image_H / 14) * ((double) i + 28.0) * Math.sin((double) ((image_H / 14) * (image_H - i)) / (double) (i + 1) + p1) / (double) image_H); if (i < -y) inv_g.copyArea(fre * image_W, i, image_W, 1, -x, 0); else inv_g.copyArea(fre * image_W, i + y, image_W, 1, -x, -y); } } graphics.drawImage(image, 0, 1, this); image = back; } }
(3)运行结果如图15-22所示。
图15-22 水面倒影
源程序解读
(1)先定义一个正常的图片和一个倒立的图片,初始化变量。
(2)启动线程,同时检查媒体跟踪器跟踪的所有图像的错误状态,如果有错误状态就会抛出异常,如果没有则程序就会向下进行,将上面的图片加载到程序中,再对线程进行控制,将图片生成倒影效果。最后关闭线程。
(3)实现水波特效。通过public void creatWater()方法可以产生倒影中的水波特效。
实例147 美丽的烟花
烟花是一个节日的象征,也是美的加忆,可以让人感觉到那一瞬间的美。本实例是一个Java小应用程序(Applet)的例子。使用线程技术,运行小程序就会显示一个烟花的特效。
技术要点
美丽的烟花的技术要点如下:
• 类Image的使用。
• 类Graphics的使用。
• 双缓冲技术处理闪烁。
实现步骤
(1)新建一个类名为MissileDemo.java。
(2)代码如下所示:
package chp15; import java.applet.Applet; import java.awt.Color; import java.awt.Graphics; import java.net.URL; import java.util.Random; public class MissileDemo extends Applet implements Runnable { public int speed, variability, Max_Number, Max_Energy, Max_Patch, Max_Length, G; public String sound; private int width, height; //获取当前容器边界的宽和高 private Thread thread = null; //设置线程 private BeaClassDemo bcd[]; //创建BeaClassDemo类数组bcd public void init() { //Applet初始化 int i; this.setSize(400, 400); //设置当前容器的宽和高 width = getSize().width - 1; height = getSize().height - 1; speed = 30; //烟花绽放的速度 variability = 10; Max_Number = 100; //可发出烟花的最大数目 Max_Energy = width + 50; Max_Patch = 80; //最大的斑点数 Max_Length = 200; //斑点的最大距离 G = 50; //向地面弯曲的力度 bcd = new BeaClassDemo[Max_Number]; //初始化BeaClassDemo数组 for (i = 0; i < Max_Number; i++) bcd[i] = new BeaClassDemo(width, height, G);//创建BeaClassDemo类对象 } public void start() { //启动线程 if (thread == null) { thread = new Thread(this); thread.start(); } } public void stop() { //停止线程 if (thread != null) { thread.stop(); thread = null; } } public void run() { int i; int E = (int) (Math.random() * Max_Energy * 3 / 4) + Max_Energy / 4 + 1; int P = (int) (Math.random() * Max_Patch * 3 / 4) //烟花的斑点数 + Max_Patch / 4 + 1; int L = (int) (Math.random() * Max_Length * 3 / 4) //烟花可发射出的距离 + Max_Length / 4 + 1; long S = (long) (Math.random() * 10000); //产生的随机数 boolean sleep; //休眠的标志 Graphics g = getGraphics(); URL u = null; while (true) { try { thread.sleep(1000 / speed); } catch (InterruptedException x) { } sleep = true; for (i = 0; i < Max_Number; i++) sleep = sleep && bcd[i].sleep; if (sleep && Math.random() * 100 < variability) { E = (int) (Math.random() * Max_Energy * 3 / 4) + Max_Energy / 4 + 1; P = (int) (Math.random() * Max_Patch * 3 / 4) + Max_Patch / 4 + 1; L = (int) (Math.random() * Max_Length * 3 / 4) + Max_Length / 4 + 1; S = (long) (Math.random() * 10000); } for (i = 0; i < Max_Number; i++) { if (bcd[i].sleep && Math.random() * Max_Number * L < 1) { bcd[i].init(E, P, L, S); bcd[i].start(); } bcd[i].show(g); } } } public void paint(Graphics g) { //绘制组件 g.setColor(Color.black); //设置背景颜色为黑 g.fillRect(0, 0, width + 1, height + 1); //根据参数画矩形 } } class BeaClassDemo { public boolean sleep = true; private int energy, patch, length, width, height, G, Xx, Xy, Ex[], Ey[], x, y, Red, Blue, Green, t; private Random random; //声明Random类对象 public BeaClassDemo(int a, int b, int g) { //类BeaClassDemo的构造方法 width = a; height = b; G = g; } public void init(int e, int p, int l, long seed) {//初始化 int i; //赋值运算 energy = e; patch = p; length = l; //创建一个带种子的随机数生成器 random = new Random(seed); Ex = new int[patch]; //初始化int数组Ex,其长度为patch Ey = new int[patch]; //初始化int数组Ey,其长度为patch //随机生成不透明的RGB颜色值 Red = (int) (random.nextDouble() * 128) + 128; Blue = (int) (random.nextDouble() * 128) + 128; Green = (int) (random.nextDouble() * 128) + 128; Xx = (int) (Math.random() * width / 2) + width / 4; Xy = (int) (Math.random() * height / 2) + height / 4; for (i = 0; i < patch; i++) { Ex[i] = (int) (Math.random() * energy) - energy / 2; Ey[i] = (int) (Math.random() * energy * 7 / 8) - energy / 8; } } public void start() { t = 0; sleep = false; } public void show(Graphics g) { //输出烟花 if (!sleep) //如果休眠状态为false if (t < length) { int i, c; double s; Color color; c = (int) (random.nextDouble() * 64) - 32 + Red; if (c >= 0 && c < 256) Red = c; c = (int) (random.nextDouble() * 64) - 32 + Blue; if (c >= 0 && c < 256) Blue = c; c = (int) (random.nextDouble() * 64) - 32 + Green; if (c >= 0 && c < 256) Green = c; color = new Color(Red, Blue, Green); for (i = 0; i < patch; i++) { s = (double) t / 100; x = (int) (Ex[i] * s); y = (int) (Ey[i] * s - G * s * s); g.setColor(color); g.drawLine(Xx + x, Xy - y, Xx + x, Xy - y); if (t >= length / 2) { int j; for (j = 0; j < 2; j++) { s = (double) ((t - length / 2) * 2 + j) / 100; x = (int) (Ex[i] * s); y = (int) (Ey[i] * s - G * s * s); g.setColor(Color.black); g.drawLine(Xx + x, Xy - y, Xx + x, Xy - y); } } } t++; } else { sleep = true; } } }
(3)运行结果的初始页面如图15-23所示。程序运行的第二个页面如图15-24所示。程序运行的第三个页面,如图15-25所示。
图15-23 烟花效果
图15-24 第二个页面
图15-25 第三个页面
源程序解读
(1)MissileDemo类随机生成烟花的颜色,这种随机的颜色可以通过RGB来实现颜色的分配。
(2)在运行烟花程序时,烟花绽放的颜色通过上面的方法可以控制,现在就得对烟花的速度进行一定的控制,主要是通过speed属性来控制。另外,就是对烟花的斑点数进行控制,可以参照下面的方法:
(Math.random() * Max_Patch * 3 / 4)
(3)最后就可以运行小程序,输出烟花,看到它绽放的效果。
实例148 开窗游戏
本实例实现一个开窗户的小游戏,有25个小窗户,单击一个窗户,周围窗户的图片就会发生变化,当所有的窗户变成开窗的图片时,游戏获胜。游戏过程中可以重置所有窗户到初始状态。
技术要点
开窗游戏的技术要点如下:
• 整个游戏的布局是以一个窗口中放置多个游戏面板的形式完成。
• 掌握JPanel类的使用,可以利用JPanel(LayoutManager layout)方法创建一个指定布局管理器的JPanel对象,然后将其他的组件按指定好的布局载入JPanel中。
• 将25个窗户按钮放在面板中,一个按钮对应一个窗户,用setActionCommand()方法设置按钮被单击时的动作命令。
• 由一个内部类Buttion_Pane专门处理事件,由于它继承了ActionListener接口,所以必须实现actionPerformed方法,在该方法中通过getActionCommand方法获取动作指令并执行相应的功能。
实现步骤
(1)新建一个类名为OpenWindowsGrame.java。
(2)代码如下所示:
package chp15; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; /** * 开窗户游戏。当单击游戏界面中任何一个具有窗户图标的按钮时, * 它周围的正方形按钮的图标就会发生变化, * 当全部正方形按钮的图标都变为开窗时,你就胜利了 */ public class OpenWindowsGrame extends JFrame { Buttion_Panel bp = new Buttion_Panel(); Control_Panel cp = new Control_Panel(bp); public OpenWindowsGrame() { super.setTitle("开窗户游戏"); JPanel panel = new JPanel(); //主面板,控制整体布局的面板 panel.setLayout(new BorderLayout()); panel.add(bp, "Center"); panel.add(cp, "South"); getContentPane().add(panel); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.pack(); this.setVisible(true); } public static void main(String[] args) { new OpenWindowsGrame(); } } /** 加载按钮的面板 */ class Buttion_Panel extends JPanel implements ActionListener { JButton[] bn = new JButton[25]; //包括25个窗户按钮 Icon icn = new ImageIcon("D:/abc/close.jpg"); boolean[] bl = new boolean[25]; //定义boolean类型的数组,用于接收开闭窗的状态 public Buttion_Panel() { this.setLayout(new GridLayout(5, 5)); //面板采用网格布局管理器 for (int i = 0; i < 25; i++) { bn[i] = new JButton(icn); bn[i].setActionCommand(String.valueOf(i)); bn[i].addActionListener(this); this.add(bn[i]); } this.setPreferredSize(new Dimension(350, 350)); //面板大小值为350*350 } /** 单击一个窗户按钮时引起开窗户事件 */ boolean f = false;//标志窗户的状态 Icon C_icn = new ImageIcon("D:/abc/close.jpg"); Icon O_icn = new ImageIcon("D:/abc/open.jpg"); public void actionPerformed(ActionEvent a) { f = !f; int x = Integer.parseInt(a.getActionCommand()); //获取被单击窗户的ID set_Select(x, f); //设置被单击窗户被选 isWin(); //判断是否胜利 } //当一个窗户被选中时进行的操作。需要改变周围窗户的图标 private void set_Select(int x, boolean flag) { if (x == 0) { change_Icon(bn[x], flag, x); change_Icon(bn[x + 1], flag, x + 1); change_Icon(bn[x + 5], flag, x + 5); } else if (x > 0 && x < 4) { change_Icon(bn[x], flag, x); change_Icon(bn[x - 1], flag, x - 1); change_Icon(bn[x + 1], flag, x + 1); change_Icon(bn[x + 5], flag, x + 5); } else if (x == 4) { change_Icon(bn[x], flag, x); change_Icon(bn[x - 1], flag, x - 1); change_Icon(bn[x + 5], flag, x + 5); } else if (x == 20) { change_Icon(bn[x], flag, x); change_Icon(bn[x - 5], flag, x - 5); change_Icon(bn[x + 1], flag, x + 1); } else if (x == 24) { change_Icon(bn[x], flag, x); change_Icon(bn[x - 5], flag, x - 5); change_Icon(bn[x - 1], flag, x - 1); } else if (x > 20 && x < 24) { change_Icon(bn[x], flag, x); change_Icon(bn[x - 5], flag, x - 5); change_Icon(bn[x - 1], flag, x - 1); change_Icon(bn[x + 1], flag, x + 1); } else if (x % 5 == 0) { change_Icon(bn[x], flag, x); change_Icon(bn[x - 5], flag, x - 5); change_Icon(bn[x + 1], flag, x + 1); change_Icon(bn[x + 5], flag, x + 5); } else if (x % 5 == 4) { change_Icon(bn[x], flag, x); change_Icon(bn[x - 5], flag, x - 5); change_Icon(bn[x - 1], flag, x - 1); change_Icon(bn[x + 5], flag, x + 5); } else { change_Icon(bn[x], flag, x); change_Icon(bn[x - 5], flag, x - 5); change_Icon(bn[x - 1], flag, x - 1); change_Icon(bn[x + 1], flag, x + 1); change_Icon(bn[x + 5], flag, x + 5); } } private void change_Icon(JButton bon, boolean flag, int i) {//改变周围窗户图标的方法 //如果窗户的状态是打开的,则给相应的按钮设置打开的图标 if (flag) { //flag:false表示关,true表示开 bl[i] = true; bon.setIcon(O_icn); } else { //如果窗户的状态是关闭的,则给相应的按钮设置关闭的图标 bon.setIcon(C_icn); bl[i] = false; } } //判断是否胜出 private void isWin() { int a = 1; //当25个窗户状态都变成true时,获胜 for (int j = 0; j < 25; j++) { if (bl[j]) { a++; } } if (a > 25) { JOptionPane.showMessageDialog(null, "恭喜过关"); } } } /** 控制面板 */ class Control_Panel extends JPanel implements ActionListener { JLabel label = new JLabel("开窗户游戏"); JButton restart = new JButton("重置"); //游戏重新开始按钮 Buttion_Panel bp; Icon icn = new ImageIcon("D:/abc/close.jpg"); //构造函数,参数为待控制的窗户面板 public Control_Panel(Buttion_Panel bp) { this.bp = bp; restart.addActionListener(this); this.add(label); this.add(restart); } //重设窗户面板,将所有窗户变成关闭状态 public void actionPerformed(ActionEvent a) { for (int i = 0; i < 25; i++) { bp.bn[i].setIcon(icn); } } }
(3)运行结果的初始页面如图15-26所示。程序运行的第二个页面如图15-27所示。通过全部关卡,就会出现一个页面,如图15-28所示。
图15-26 开窗户游戏界面
图15-27 第二个页面
图15-28 过关之后的页面
源程序解读
1. Buttion_Panel类
该类定义了游戏主面板。
(1)继承JPanel,采用了网格布局管理器,设置网格的行数和列数均为5,刚好容纳25个窗户按钮。
(2)窗户按钮上没有文本,通过JButton的setActionCommand方法为按钮设置动作命令,当用户单击按钮时,根据按钮的动作命令,判断哪个按钮被单击了。为按钮添加事件处理器actionPerformed方法,当按钮被单击时,由actionPerformed方法处理。该方法实现了ActionListener接口,表示它是一个事件处理器的类。当按钮被单击时,actionPerformed方法根据ActionEvent参数的getActionCommand方法定位引起事件的源对象,再调用set_Select方法改变源对象周围的窗户按钮的背景图片,最后调用isWin方法判断是否胜出。
(3)set_Select私有方法改变某个窗户的所有邻居窗户的背景图片。根据被单击窗户的序号,确定邻居窗户的序号,中间的窗户有四个邻居窗户,靠边的窗户有三个邻居窗户,四个角上的窗户只有2个邻居窗户。
(4)isWin方法依次遍历25个窗体按钮,如果它们的背景图片都为开窗状态,则表明游戏胜出。
(5)change_Icon方法是根据当前按钮的窗户的状态和ID来改变背景图片。
2. Control_Panel类
该类定义了控制面板,包含一个标签和“重置”按钮。
(1)Control_Pane构造方法实现了ActionListener接口,它的actionPerformed方法将所有25个窗户按钮的背景图片设置为关闭窗户。
(2)注册“重置”按钮的事件处理器为Reset对象,当单击它时,所有窗户背景图片变成关闭窗户。
3. OpenWindowsGrame类
该类是游戏的主运行类,在main方法中创建一个JFrame,通过JFrame的getContentPane方法获得窗口的容器,调用容器的add方法将游戏主面板放入到窗口中;通过setDefault-CloseOperation(JFrame.EXIT_ON_CLOSE)语句设置当用户单击窗口的关闭按钮时,退出游戏;调用JFrame的pack方法调整窗口中各组件到最佳大小;通过JFrame的setVisible方法显示主窗口。