[本讲的知识要点]:Graphics类、paint()格式及绘制的时机。Java中的文本、图形、图像绘制、声音、动画等编程技术。
4.1 Java 中的图形、文本、图像绘制编程的相关知识
4.1.1 有关的类及绘制函数:主要涉及java.awt包中的Graphics类;编程的基本方法是调用Graphics类中的相应的绘制图形、绘制文本、绘制图像等函数以实现在图形窗口下输出图形、文本、图像等。
4.1.2编程方法:先利用(在组件的paint()函数内)或获取(在组件的其它函数内如事件响应函数内)java.awt包中的Graphics类对象(它代表当前组件如窗口的绘图环境),然后调用Graphics类中相应的绘制函数来实现输出。
4.1.3 绘制的时机:
① 组件外形发生变化时(如窗口的大小、位置、图标化等显示区域更新时),AWT自动从高层直到叶结点组件相应地调用各组件的paint()方法,但这可能有一个迟后感。
① 程序员也可直接调用某一个组件的repaint()(系统再调用update()以清除背景区域,系统再调用paint()实现输出;如直接调用paint()将保留上次的屏幕输出,故不直接调用paint()),以立即更新外观(如在添加新的显示内容后)。
要点F如果要求保留上次的输出结果时可以调用paint(),而不要求保留上次的输出结果只希望用户能看到最新的输出结果时可以调用repaint()。
③ AWT响应外部的事件时(如首先显示于屏幕或需刷新时),AWT也会直接调用repaint()函数。
4.1.4如何获取Graphics类对象并绘制: 绘制之前,必须先获取Graphics类对象,因为它包含有当前组件的绘图环境,从而实现在当前组件(如图形窗口)内输出。
方法一:在paint()内则可通过其形参(它代表当前组件的绘图环境),从而调用Graphics类中的相关的绘制成员函数。
public void paint(Graphics g)
{ g.drawString("Java Text",x,y);
}
方法二:先在组件类(如Applet或窗框Frame的其他成员函数)中调用getGraphics()来获取当前组件的绘图环境,再强制绘制输出,从而可在paint()函数之外(如事件响应函数内)产生绘制动作。
public void actionPerformed(ActionEvent e)
{ Graphics g=getGraphics(); //获取当前组件的绘图环境
g.drawString("Java Text",x,y); //强制在当前组件内绘制输出
}
4.2 Java 中文本字串的编程输出显示
4.2.1 文本控制台方式下输出文字串:System.out.print(“待输出的文字串”);
4.2.2 图形窗口环境下输出文字串:应调用Graphics 类中相应的输出函数:g.drawString(“待输出的文字串”,x, y); //注意其中的X,Y是指定文本的左下角的坐标位置
4.2.3图形、文字的颜色编程:可以利用Color类设置或获取某一容器组件内的图形、文字的颜色。
① 编程方法:利用Color类创建出一Color类对象并设置颜色特征参数(r、g、b 三基色的分量);调用Graphics类中的setColor()将用户的颜色加入到系统中;再调用绘制函数输出图形、文字等。
② 实例:public void paint(Graphics g)
{ Color myColor= new Color(red, green, blue);
g.setColor(myColor);
g.drawString(“这是Java中的带颜色的文字串”, 100,100) ;
g.drawRect( 10,10,100 ,100) ;
}
要点F了解颜色的变化规律。JDK Color类中预定义了13种常量颜色(如: Color.red、Color.green等);对 Java的组件也可借助于setBackground()/setForeground()、getBackground()/getForeground()来改变或获取组件的前景和背景色。
4.2.4 图形文字的字体编程:可以利用Font类设置或获取某一容器组件内的文字字体。
4.2.4.1 编程方法:利用Font类创建出一Font类对象并设置字体特性参数;调用Graphics类中的setFont()将用户字体加入到系统中;再调用绘制函数输出文字(也可以设置按钮等基本控件的字体 OK.setFont())。
4.2.4.2 实例:public void paint(Graphics g)
{ Font myFont= new Font(“Dialog”, Font.BOLD, 20);
g.setFont(myFont);
g.drawString(“这是Java中的Dialog字体的文字串”, 100,100) ;
}
4.2.4.3 获得当前主机系统字体:编程GraphicsEnvironment环境类。
GraphicsEnvironment gEnvironment= GraphicsEnvironment.getLocalGraphicsEnvironment();
String [ ]fontNames= gEnvironment .getAvailableFontFamilyNames();
选学内容$: 字体的尺寸FontMetrics。drawString()函数中的字符位置坐标x、y含义(字符串的左下角);获知字符或字符串的宽度(stringWidth() 、charWidth())。获取文字字体的尺寸方法: FontMetrics fm=g.getFontMetrics();
fm.getHeight();
4.3 Java 中的图形编程
(1)Graphics 类中的图形函数(参见类库手册):可以将 java.awt.Graphics 支持的非特性方法划分为三个常规类别之下:
l 跟踪形状轮廓的绘制方法(点、线和面):
drawBytes()
drawChars()
drawImage()
drawPolygon()
drawPolyline()
drawString()
drawLine(int x1, int y1, int x2, int y2); //画直线
drawRect(int x, int y, int w, int h); //画矩形
drawRoundRect(int x, int y, int w, int h, int w1, int h1); //画圆角矩形;
drawArc(int x,int y,int width, int height, int startAngle, int arcAngle); //画圆弧线(圆弧角度逆时针为正)
drawOval(int x,int y,int width, int height); //画椭圆
draw3DRect(int x, int y, int w, int h, boolean flag);
//画三维矩形(先设置作图颜色为亮灰色g.setColor(Color.lightGray);然后再画它)
l 填充形状轮廓的绘制方法:
fill3DRect()
fillArc()
fillPolygon()
fillRoundRect()
fillRect(int x, int y, int w, int h); //画填充矩形
fillOval(int x,int y,int width, int height); //画填充椭圆(填充的颜色由g.setColor()决定)
l 其它杂项方法
copyArea(int x, int y,int width, int height, int dx, int dy);
clearRect(int x, int y,int width, int height);
translate()
实例Ë 设计一个画直方图的Java Applet程序
1 import java.applet.*;
2 import java.awt.*;
3 public class LifeCycle extends Applet
4 { public void paint(Graphics g)
5 { g.drawLine(20,200,300,200); //画出X坐标轴
6 g.drawLine(20,200,20,20); //画出Y坐标轴
7 g.drawLine(20,170,15,170);
8 g.drawLine(20,140,15,140); //画出Y坐标轴上的数据的各个刻度线,共5条
9 g.drawLine(20,110,15,110);
10 g.drawLine(20,80,15,80);
11 g.drawLine(20,50,15,50);
12 g.drawString(“Init()”,25,213); //在X坐标轴上显示出各个名称的文字
13 g.drawString(“Start()”,75,213);
14 g.drawString(“Stop()”,125,213);
15 g.drawString(“Destroy()”,175,213);
16 g.drawString(“Paint()”,235,213);
17 g.fillRect(25,200-InitCnt*30,40, InitCnt*30); //画填充矩形作为直方条,但放大30倍
18 g.fillRect(75,200-StartCnt*30,40, StartCnt*30);
19 g.fillRect(125,200-StopCnt*30,40, StopCnt*30);
20 g.fillRect(175,200-DestroyCnt*30,40, DestroyCnt*30);
21 g.fillRect(235,200-PaintCnt*30,40, PaintCnt*30);
22 }
23 }
实例Ë 设计一个自由笔(铅笔)以产生手画线的程序。
public boolean mouseDrag(Event evt, int x, int y)
{ Graphics g=getGraphics() ; //在鼠标的mouseDrag事件中画出鼠标的当前位置的点
g.setColor(Color.red) ;
g.drawLine(x, y, x, y ) ;
return true;
}
实例Ë自定义出一个按纽效果的图形
public void paint(Graphics g)
{ g.setColor(Color.LIGHT_GRAY);
g.fillRect(1,1,100-2,100-2);
g.draw3DRect(0,0,100-1,100-1,true);
g.setColor(this.getForeground());
g.drawRect(2,2,100-4,100-4);
}
画圆弧
g.drawArc(10,40,70,70,0,90); //在原点为(10,40),半径为70,起始角度为0度,逆时针转90度的圆弧
g.fillArc(100,40,70,70,0,90); //在原点为(100,40),半径为70,起始角度为0度,逆时针转90度的圆弧
g.drawArc(10,100,70,80,0,-90); //在原点为(10,100),长轴为80,短轴为70,起始角度为0度,顺时针转90度的弧
g.fillArc(100,100,70,80,0,-90); //在原点为(100,100),长轴为80,短轴为70,起始角度为0度,顺时针转90度的填充弧
画多边形
int xArr[] = { 78,188,194,284,106,116,52 };
int yArr[] = { 66,148,72,140,216,160,212 };
int numPoints=xArr.length; //获得x,y坐标对数组的长度
g.drawPolygon( xArr, yArr, numPoints);
4.4 Java Applet中声音编程
4.4.1 声音文件的格式:Java2目前支持*.au(适用于Sun Solaris机)、*.wav(适用于PC机) 、*.aiff(适用于Macintosh机)、*.MIDI 格式。
主要的声音文件类型的说明如下:
AU - (扩展名为AU或SND)适用于短的声音文件,为Solaris和下一代机器的通用文件格式,也是JAVA平台的标准的音频格式。AU类型文件使用的三种典型音频格式为: 8位μ-law类型(通常采样频率为8kHz), 8位线性类型,以及16位线性类型。
WAV - (扩展名为WAV)由 Microsoft和 IBM共同开发,对WAV的支持已经被加进Windows 95并且被延伸到Windows 98. WAV文件能存储各种格式包括μ-law,a-law和 PCM (线性)数据。他们几乎能被所有支持声音的Windows应用程序播放。
AIFF - (扩展名为AIF或IEF)音频互换文件格式是为Macintosh计算机和Silicon Graphics (SGI)计算机所共用的标准音频文件格式。AIFF和 AIFF-C几乎是相同的,除了后者支持例如μ-law和 IMA ADPCM类型的压缩。
MIDI - (扩展名为MID)乐器数字接口MIDI是为音乐制造业所认可的标准,主要用于控制诸如合成器和声卡之类的设备。
4.4.2编程要点:先加载声音文件;再播放它。
4.4.3编程方法:
① 方法一:在Applet程序中直接调用play()函数。play(getDocumentBase(), “mySound .au”) ;但此方法的缺点是用户无法控制它,因为加载声音文件到内存中后将马上播放它。。
② 方法二:AudioClip sound= getAudioClip( getDocumentBase(), “mySound . au”);
sound.play(); //或 sound.stop();
注意:此方法的优点是声音文件的加载与播放分开,从而可控。
实例Ë
import java.applet.*;
import java.awt.*;
import java.awt.event.*;
public class AudioClipDemo extends Applet implements ActionListener
{ Button PlayButton,LoopButton,StopButton;
AudioClip sound;
public void init()
{ //以下代码为生成三个按钮并添加ActionListener接口中的事件
PlayButton=new Button("开始播放");
add(PlayButton);
PlayButton.addActionListener(this);
LoopButton=new Button("循环播放");
add(LoopButton);
LoopButton.addActionListener(this);
StopButton=new Button("终止播放");
add(StopButton);
StopButton.addActionListener(this);
sound= getAudioClip(getDocumentBase(), "computer.au"); //加载声音文件
}
public void actionPerformed(ActionEvent e) //用户的鼠标行为事件(单击)
{ //以下的代码是识别用户按下那个按钮,并根据所按的按钮不同相应地进行操作
if(e.getSource()==PlayButton)
{ sound.play(); //开始播放声音
}
else if(e.getSource()==StopButton)
{ sound.stop(); //终止播放声音
}
else if(e.getSource()==LoopButton)
{ sound.loop(); //循环播放声音
}
}
}
4.5 Java中图像编程
4.5.1 有关的包与类:在java.awt包、java.awt.image包、java.applet包中都有与图像有关的类Image。
要点F图像编程的基本过程是:先加载图像(将它转化为Image类对象);再跟踪图像的加载过程;最后显示出图像;图像文件格式目前支持*.gif、*.jpeg、*.png等。
4.5.2 加载图像:在Applet及Application程序中载入图像的方式。
① Java Applet中加载图像:直接调用Image myImage = getImage(URL address, imageFileName);
② Java Application中加载图像:利用Toolkit 类中的getImage()。
如:Image myImage =Toolkit .getDefaultToolkit(). getImage(imageFileName); 加载图像。
4.5.3 跟踪图像载入:通过对图像进行跟踪,可以在图像完全加载完毕之后再显示出图像,因为AWT是异步在后台完成图像的加载,主进程继续执行程序;如果此时就显示出图像,将导致部分图像显示;特别是在网络速度较慢时更应该考虑它,故应该跟踪图像的载入过程,一但发现图像载入完毕,再显示出图像。
① 跟踪图像载入方式一:利用MediaTracker类来跟踪图像(适用于多幅图象、动画图象的跟踪)
MediaTracker tracker =new MediaTracker(this);
tracker.addImage(img,0); //将需要跟踪的图像加人到跟踪器中并指示其编号
try
{ m_Graphics. drawString(“正在加载图像,请等待。。。”, 50,50) ; //未加载完毕时显示等待提示信息
tracker.waitForAll(); //等待图象加载完毕
}
catch (InterruptedException e)
{
}然后再显示出图象
② 跟踪图像载入方式二:利用ImageObserver接口来实时跟踪图像载入情况(适用于静态单幅大图象的跟踪):该方法的编程基本思路为
(1)在本类中实现ImageObserver接口并重写imageUpdate()方法,在其中实时监视图像载入情况,如果图像载入完毕将返回false表示不再需要跟踪(if(info &ALLBITS)),而其他状态下都返回true表示需要继续跟踪图像的载入。
(2)一旦图像加载完毕,AWT会自动调用本类(组件类)的repaint()来显示出图像。
import java.awt.*;
import java.awt.image.*;
import java.applet.*;
public class ImageObserverTest extends Applet implements ImageObserver
{ Image img;
public void init()
{ img=getImage(getDocumentBase(),"myGif.gif"); //加载图像
}
public void paint(Graphics g)
{ //显示出图像并指定图像观察者对象为本Applet程序对象
g.drawImage(img, 0,0, this);
}
public boolean imageUpdate(Image img, int infoflag, int x, int y, int width, int height)
{ if((infoflag & ALLBITS)==0) //识别图像是否加载完毕
return true; //图像未加载完毕时将返回true,意味需要再跟踪
else
return false; //加载完毕后将返回false,意味不需要再跟踪
}
}
4.5.4显示出图像:调用Graphics类中的drawImage(Image image , int x, int y , ImageObserver observer) 显示出图像,其中第四个参数可以为null(不跟踪图像载入时)或this(跟踪图像载入并且跟踪者为本程序)。
4.6 Java 中的动画编程
4.6.1 动画编程的原理:画出一系列的图像帧,利用人眼的视觉暂停来造成运动的感觉。
4.6.2 编程实现的手段:利用线程来达到循环显示输出,使动作不断地重复产生。
4.6.3 动画的质量:除了图片本身的好坏外,动画中动作的平滑程度也是一大关键(每秒所播放的图像数较多,动画中动作的平滑程度则较高,一般应该为10~20帧/秒);动画的速度则可利用Java线程中的threadObj.sleep()来延迟以控制动画的速度。
4.6.4常见的动画效果及实现原理:
① 运动效果:平动---通过在线程体循环中改变文字在显示时(g.drawString())x,y坐标位置或g.drawImage()中的x,y坐标位置来实现;规则运动---通过在线程体循环中将x, y之间满足一定的数学规则或方程便可以达到。
② 变焦(由远到近-----文字由小到大;由近到远-----文字由大到小):通过在线程体循环中不断地改变文字的字体大小或图像的大小来实现。
③ 淡入淡出:通过在线程体循环中不断地改变文字的颜色并将颜色按某种规则变化来实现。
4.6.5 动画的图像载入编程技巧:
技巧一:为利用一个for()循环来将图片全部载入,因而各个图像文件名一般可采用数字编码来命名它。
技巧二:利用getImage()函数并用Image数组来存放各个Image对象。
技巧三:在线程体内将当前动画图像帧数加一,然后调用repaint()方法输出当前图像帧。
技巧四:为使人眼能正确地区分出各个图像帧,应该休眠线程一段时间。
4.6.6 动画的载体:文字或图像两种形式
实例Ë :文字淡入淡出动画的编程实现(以下只给出部分源程序,全部源程序请见TxtColor.java文件)。
(1)原理:利用new创建一个线程,用drawString()在屏幕指定位置显示出文字,然后让线程休眠一段时间,再改变文字显示的位置或颜色,最后再显示文字(其中包括边界判断、重设坐标等)。
(2)编程技巧:可利用Graphics类中的clipRect()函数用于限定绘制范围,从而减少清屏的区域以避免图像闪烁。
1 import java.awt.*;
2 import java.applet.*;
3 public class TxtColor extends java.applet.Applet implements Runnable
4 { Thread textThread=null;
5 int Red, Green,Blue;
6 int stringX, stringY;
7 String Text1="大家好,How Are You !";
8 Dimension rectSize;
9 FontMetrics fm;
10 public void init()
11 { Red=255;
12 Green=255;
13 Blue=255; //初始化各个颜色值
14 setFont(new Font("Helvetica",Font.BOLD,16)); //生成一种字体并使用它
15 fm=getFontMetrics(getFont()); //获得该字体的尺寸参数
16 rectSize=size(); //获得Applet的当前的尺寸参数(宽度和高度值)
17 rectSize.width=fm.stringWidth(Text1);//获得字串在该字体时的宽度尺寸参数
18 resize(rectSize.width,rectSize.height); //重置Applet的宽和高度值)
19 stringY=rectSize.height; //定位起始位置
20 stringX=0;
21 setBackground(Color.white); //设置背景色
22 }
23 public void start()
24 { if (textThread==null)
25 { textThread=new Thread(this,"text"); //实例化线程
26 textThread.start(); //启动改线程
27 }
28 }
29 public void run() // 识别当前线程是否正在运行
30 { while (Thread.currentThread()==textThread)
31 { repaint(); //更新Applet所在的区域
32 try { //捕获InterruptedException异常
33 textThread.sleep(150); //延迟
34 }
35 catch(InterruptedException e)
36 {
37 }
38 }
39 }
40 public void update(Graphics g)
41 //替换基类的update()以消除闪烁,不清除整个背景
42 {g.clipRect(stringX,stringY-fm.getAscent(),rectSize.width,fm.getHeight()*3);
43 g.clearRect(stringX,stringY-fm.getAscent(),rectSize.width,fm.getHeight()*3);
44 paint(g);
45 }
46 //必须重写出paint()函数,因为在Applet初始显示时将直接调用它
47 public void paint(Graphics g)
48 { if (stringY<=0) //字符串Y坐标是否为0(移到顶部?)
49 { stringY=rectSize.height; //重置字符串到最初的位置(底部)
50 }
51 stringY--; //开始颜色变化
52 Color textColor=new Color(Red,Green,Blue);
53 //利用当前颜色分量重组合成一种新颜色
54 g.setColor(textColor); //将文字改为该颜色显示
55 g.drawString(Text,stringX,stringY); //在指定位置处重新显示出该文字
56 if (stringY<rectSize.height/2)
57 { if(Green>=255)
58 { Green=255;
59 Red=255;
60 Blue=255;
61 }
62 else
63 { Green=Green+3;
64 Red=Red+3;
65 Blue=Blue+3;
66 }
67 }
68 else
69 { if(Green<=0)
70 { Green=0;
71 Red=0;
72 Blue=0;
73 }
74 else
75 { Green=Green-3;
76 Red=Red-3;
77 Blue=Blue-3;
78 }
79 }
80 public void stop()
81 { textThread=null; //清除线程
82 }
83 public boolean mouseEnter(Event event,int x,int y) //事件响应函数
84 { showStatus("Your Mouse Is Enter Applet Panel !");
85 return true;
86 }
87 }
选学内容$:在动画编程过程中所应注意的问题,动画图像闪烁的解决方法:
(1)图像闪烁的原因:在paint()方法中如果计算较为复杂,计算和绘画的时间超过了屏幕的刷新周期,则帧的第一部分在一个刷新周期绘制,其余部分在下一个刷新周期绘制,在帧的不同部分之间产生时间间隔,造成闪烁(本例因地球的图像较小,没有感觉出闪烁现象)。
① 图像闪烁的解决方法之一:替换基类的update()(缺省实现是清除原背景,然后再调用paint()),不清除背景或者只在必要时才清除整个背景(将绘制代码放入update()内,在paint()内再调用update()--因为AWT在初始绘制时或重显示时将会直接调用paint())。
public void paint(Graphics g)
{ update();
}
public void update(Graphics g)
{ if (m_fAllLoaded)
{ Rectangle r = g.getClipRect();
g.clearRect(r.x, r.y, r.width, r.height); //缩小清除的屏幕范围
displayImage(g);
}
else
g.drawString("Loading images...", 10, 20);
}
② 图像闪烁的解决方法之二:双缓冲区技术:主要原理是创建一个后台图象,将一帧画入图象,然后调用drawImage() 将整个图象一次画到屏幕上去。好处是大部分绘制是离屏的。将离屏图象一次 绘至屏幕上比直接在屏幕上绘制要有效得多。双缓冲可以使动画平滑,但有一个缺点,要分配一张后台图象,如果图象相当大,这将需要很大一块内存。当你使用双缓冲技术时,应重载update()。
public void update(Graphics g)
{ Dimension d=size();
Image offScrImage=createImage(d.width,d.height);
Graphics offScrGraphics=offScrImage.getGraphics();
offScrGraphics.drawImage(image,0,0,this);
g.drawImage(offScrImage,0,0,this);
}
[下一讲的预习要点]:Java异常编程规则、 try、catch、finally语句