版权声明:任何获得matrix授权的网站,转载时请务必保留以下作者信息和链接
作者:suli2921
原文:http://www.matrix.org.cn/resource/article/2007-04-20/java+grab_2a31448a-eed8-11db-9270-6dd444a118cb.html
关键字:java grab;游戏开发
最简单的 api
不管计算机运行得有多快,我们却总是在等待某个任务的完成,比如,下载大个的文件、执行彻底搜索或者进行复杂的数学计算。 在这些费时的任务完成时,许多 java 程序都会用一些花哨的方式提示用户,普遍方法是使用可以听得见的警告。
java 提供了许多声音 api 可以用于创建有声警告。 可以使用 java speech api 告诉用户任务已经结束。 如果您希望任务完成时播放音效或音乐,java sound api 是一个不错的选择。 然而,因为 java speech 需要额外的分发文件,而 java sound 需要相当复杂的代码,您可能就希望使用 audio clip api 了。
audio clip api 基于 java.applet.audioclip 和 java.applet.applet 方法,例如:public static final audioclip newaudioclip(url url)。 虽然此 api 比 java speech 和 java sound 更易于使用,但只用它来播放一段简单的声音也太过大材小用了。 对于这种简单的任务,还是考虑使用 java 最简单的声音 api 吧。
最简单的声音 api 由 java.awt.toolkit 的 public abstract void beep() 方法构成。 当调用此方法时,将发出简单的“哔跸”声。 为了展示 beep() 的用法,我创建了一个 calcpi 应用程序,为 pi 计数。 请看列表 1。
列表 1 calcpi.java
// calcpi.java
import java.awt.toolkit;
import java.math.bigdecimal;
public class calcpi
{
/* constants used in pi computation */
private static final bigdecimal zero = bigdecimal.valueof (0);
private static final bigdecimal one = bigdecimal.valueof (1);
private static final bigdecimal four = bigdecimal.valueof (4);
/* rounding mode to use during pi computation */
private static final int roundingmode = bigdecimal.round_half_even;
/* digits of precision after the decimal point */
private static int digits;
public static void main (string [] args)
{
if (args.length != 1)
{
system.err.println ("usage: java calcpi digits");
return;
}
int digits = 0;
try
{
digits = integer.parseint (args [0]);
}
catch (numberformatexception e)
{
system.err.println (args [0] + " is not a valid integer");
return;
}
system.out.println (computepi (digits));
toolkit.getdefaulttoolkit ().beep ();
}
/*
* compute the value of pi to the specified number of
* digits after the decimal point. the value is
* computed using machin's formula:
*
* pi/4 = 4*arctan(1/5) - arctan(1/239)
*
* and a power series expansion of arctan(x) to
* sufficient precision.
*/
public static bigdecimal computepi (int digits)
{
int scale = digits + 5;
bigdecimal arctan1_5 = arctan (5, scale);
bigdecimal arctan1_239 = arctan (239, scale);
bigdecimal pi = arctan1_5.multiply (four).
subtract (arctan1_239).multiply (four);
return pi.setscale (digits, bigdecimal.round_half_up);
}
/*
* compute the value, in radians, of the arctangent of
* the inverse of the supplied integer to the specified
* number of digits after the decimal point. the value
* is computed using the power series expansion for the
* arc tangent:
*
* arctan(x) = x - (x^3)/3 + (x^5)/5 - (x^7)/7 +
* (x^9)/9 ...
*/
public static bigdecimal arctan (int inversex, int scale)
{
bigdecimal result, numer, term;
bigdecimal invx = bigdecimal.valueof (inversex);
bigdecimal invx2 = bigdecimal.valueof (inversex * inversex);
numer = one.divide (invx, scale, roundingmode);
result = numer;
int i = 1;
do
{
numer = numer.divide (invx2, scale, roundingmode);
int denom = 2 * i + 1;
term = numer.divide (bigdecimal.valueof (denom), scale, roundingmode);
if ((i % 2) != 0)
result = result.subtract (term); else
result = result.add (term);
i++;
}
while (term.compareto (zero) != 0);
return result;
}
}
列表 1 使用一种算法来计算 pi,该算法是早在 18 世纪初期由英国数学家 john machin 发明的。 算法首先计算 pi/4 = 4*arctan(1/5)-arctan(1/239),然后将结果乘以 4 得出 pi 的值。 由于 arc (inverse) tangent 是使用一系列庞大的 term 来计算的, term 的数量越大得出的 pi 值越准确(小数点后显示的位数)
注意
列表 1 的大部分代码引用自 sun 的远程方法调用教程的“创建一个客户端程序”部分。
此算法的实现依赖于 java.math.bigdecimal 和一个 arc-tangent 方法。 虽然 java se 5.0 等高级版本的 bigdecimal 包括常量 zero 和 one,这些常量在 java 1.4 中是不存在的。 同样,number-of-digits 命令行参数用于确定 arc-tangent 的数量和 pi 的精确度。
java calcpi 0
3
java calcpi 1
3.1
java calcpi 2
3.14
java calcpi 3
3.142
java calcpi 4
3.1416
java calcpi 5
3.14159
本文中更重要的部分是 toolkit.getdefaulttoolkit ().beep ();,该语句用于在计算结束时发出“哗哗”的声音。 由于数字参数越大造成的计算时间越长,此声音可以让您知道 pi 的计算何时结束。 如果一声“哗”响不够用,可以按照如下方法创建其他的音效:
toolkit tk = toolkit.getdefaulttoolkit ();
for (int i = 0; i < number_of_beeps; i++)
{
tk.beep ();
// on windows platforms, beep() typically
// plays a wave file. if beep() is called
// before the wave sound finishes, the
// second wave sound will not be heard. a
// suitable delay solves this problem.
// (i'm not sure if this problem occurs
// on other platforms.)
try
{
thread.sleep (beep_delay);
}
catch (interruptedexception e)
{
}
}
窗口居中
使您的 java 程序看起来更加专业的一种方法就是使其对话框窗口(例如,一个 "about" 窗口)位于父窗口的中间。 可以使用 java.awt.window 的 public void setlocationrelativeto(component c) 方法完成此任务,该方法将相对于组件参数 ―null 来创建一个居于屏幕中间的窗口。 请看列表 2。
列表 2 aboutbox1.java
// aboutbox1.java
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;
public class aboutbox1
{
public static void main (string [] args)
{
final jframe frame = new jframe ("aboutbox1");
frame.setdefaultcloseoperation (jframe.exit_on_close);
jpanel panel = new jpanel ()
{
{
jbutton btnwindow = new jbutton ("window center");
actionlistener l = new actionlistener ()
{
public void actionperformed (actionevent e)
{
new aboutbox (frame, "w").setvisible (true);
}
};
btnwindow.addactionlistener (l);
add (btnwindow);
jbutton btnscreen = new jbutton ("screen center");
l = new actionlistener ()
{
public void actionperformed (actionevent e)
{
new aboutbox (frame, "s").setvisible (true);
}
};
btnscreen.addactionlistener (l);
add (btnscreen);
}
};
frame.getcontentpane ().add (panel);
// frame.setlocationrelativeto (null);
frame.pack ();
// frame.setlocationrelativeto (null);
frame.setvisible (true);
}
}
class aboutbox extends jdialog
{
aboutbox (jframe frame, string centermode)
{
super (frame, "aboutbox", true /* modal */);
final jbutton btnok = new jbutton ("ok");
btnok.addactionlistener (new actionlistener ()
{
public void actionperformed (actionevent e)
{
dispose ();
}
});
getcontentpane ().add (new jpanel () {{ add (btnok); }});
pack ();
setlocationrelativeto (centermode.equals ("w") ? frame : null);
}
}
listing 2's aboutbox1 创建了一个 gui,它的两个按钮建立了一个 "about" 对话框,该对话框通过 setlocationrelativeto() 方法位于应用程序主窗口或屏幕的中心位置。 frame.pack (); 之前被注释掉的一行无法起到使主窗口居中的作用,因为主窗口的大小还没有确定。 但是,被注释掉的第二行起到了使窗口居中的作用。
getcontentpane ().add (new jpanel () {{ add (btnok); }}); 也许看起来有点奇怪,因为它嵌套了许多括号。 本质上该语句可以理解为,创建一个内部匿名类(该类扩展自 javax.swing.jpanel)的对象,通过由内层括号对标识的 object block initializer 为此对象添加一个按钮,然后将对象添加到对话框的 content pane 中。
添加阴影
如果您想突出显示一个 "about" 对话框的标题文字,可以考虑以一定的偏移量和指定的颜色绘制背景文字以达到投放“阴影”的效果。 选择一个适当的颜色做为背景字的颜色,注意与前景文字和背景的颜色搭配。 然后使用反失真技术使边缘上的小锯齿变得平滑。 效果如图 1 所示:
图 1:阴影可以强调对话框的标题
图 1 展示了一个具有蓝色文字、黑色阴影和白色背景的 "about" 对话框。 该对话框是在 aboutbox2 程序的 aboutbox(jframe frame, string centermode) 构造函数内创建的。 由于该程序的代码与 aboutbox1.java 极为相似,所以我只给出其构造函数:
aboutbox (jframe frame, string centermode)
{
super (frame, "aboutbox", true /* modal */);
// add a panel that presents some text to the dialog box's content pane.
getcontentpane ().add (new jpanel ()
{
final static int shadow_offset = 3;
{
// establish the drawing panel's preferred
// size.
setpreferredsize (new dimension (250, 100));
// create a solid color border that both
// surrounds and is part of the drawing
// panel. select the panel background
// color that is appropriate to this look
// and feel.
color c =
uimanager.getcolor ("panel.background");
setborder (new matteborder (5, 5, 5, 5, c));
}
public void paintcomponent (graphics g)
{
// prevent jagged text.
((graphics2d) g).setrenderinghint
(renderinghints.key_antialiasing,
renderinghints.value_antialias_on);
// because the border is part of the panel,
// we need to make sure that we don't draw
// over it.
insets insets = getinsets ();
// paint everything but the border white.
g.setcolor (color.white);
g.fillrect (insets.left, insets.top,
getwidth ()-insets.left-
insets.right,
getheight ()-insets.top-
insets.bottom);
// select an appropriate text font and
// obtain the dimensions of the text to be
// drawn (for centering purposes). the
// getstringbounds() method is used instead
// of stringwidth() because antialiasing is
// in effect -- and the documentation for
// stringwidth() recommends use of this
// method whenever the antialiasing or
// fractional metrics hints are in effect.
g.setfont (new font ("verdana",
font.bold,
32));
fontmetrics fm = g.getfontmetrics ();
rectangle2d r2d;
r2d = fm.getstringbounds ("about box", g);
int width = (int)((rectangle2d.float) r2d)
.width;
int height = fm.getheight ();
// draw shadow text that is almost
// horizontally and vertically (the
// baseline) centered within the panel.
g.setcolor (color.black);
g.drawstring ("about box",
(getwidth ()-width)/2+
shadow_offset,
insets.top+(getheight()-
insets.bottom-insets.top)/2+
shadow_offset);
// draw blue text that is horizontally and
// vertically (the baseline) centered
// within the panel.
g.setcolor (color.blue);
g.drawstring ("about box",
(getwidth ()-width)/2,
insets.top+(getheight()-
insets.bottom-insets.top)/2);
}
}, borderlayout.north);
final jbutton btnok = new jbutton ("ok");
btnok.addactionlistener (new actionlistener ()
{
public void actionperformed (actionevent e)
{
dispose ();
}
});
getcontentpane ().add (new jpanel () {{ add (btnok); }},
borderlayout.south);
pack ();
setlocationrelativeto (centermode.equals ("w") ? frame : null);
}
除了向您介绍如何在 jpanel 子类组件的 public void paintcomponent(graphics g) 方法中呈现阴影以外,构造函数还揭示了一个技巧:使用 uimanager.getcolor("panel.background") 获取与对话框背景色匹配的组件边框的颜色(即现在的外观)。
超级链接和启动浏览器
许多程序都会在 "about" 对话框中呈现超级链接。单击超级链接时,程序将启动默认浏览器,并为用户打开应用程序的网站。我想 "about" 对话框中的超级链接是可以用类来描述的,所以我创建了一个 aboutbox3 应用程序来说明其可行性。请阅读以下代码:
aboutbox (jframe frame, string centermode)
{
super (frame, "aboutbox", true /* modal */);
// create a pane that presents this dialog box's text. surround the pane
// with a 5-pixel empty border.
pane pane = new pane (5);
pane.setpreferredsize (new dimension (250, 100));
// create a title with a drop shadow for the pane.
font font = new font ("verdana", font.bold, 32);
pane.textnode tn = pane.new textnode ("about box", font, color.blue,
pane.textnode.centerx,
pane.textnode.centery, color.black);
pane.add (tn);
// create a link for the pane.
font = new font ("verdana", font.bold, 12);
tn = pane.new textnode ("jeff friesen", font, color.blue,
pane.textnode.centerx, 80,
null, "http://www.javajeff.mb.ca", color.red);
pane.add (tn);
// add pane to the center region of the dialog box's content pane.
getcontentpane ().add (pane);
// create a button for disposing the dialog box.
final jbutton btnok = new jbutton ("ok");
btnok.addactionlistener (new actionlistener ()
{
public void actionperformed (actionevent e)
{
dispose ();
}
});
// add button via an intermediate panel that causes button to be laid
// out at its preferred size to the south region of the dialog box's
// content pane.
getcontentpane ().add (new jpanel () {{ add (btnok); }},
borderlayout.south);
// resize all components to their preferred sizes.
pack ();
// center the dialog box with respect to the frame window or the screen.
setlocationrelativeto (centermode.equals ("w") ? frame : null);
}
aboutbox(jframe frame, string centermode)构造函数创建了一个 pane 组件,来描述一个用于绘制文字的区域。该组件的 pane(int bordersize) 构造函数使用 bordersize 参数,来识别组件边框的大小(以像素为单位)-- 绘制区域的大小等于 pane 的大小减去边框大小:
pane (int bordersize)
{
// create a solid color border that both surrounds and is part of the
// this component. select the panel background color that is appropriate
// to this look and feel.
setborder (new matteborder (bordersize, bordersize, bordersize,
bordersize,
uimanager.getcolor ("panel.background")));
}
该组件将文字存储为 pane.textnode 对象的数组列表。每个 textnode 描述一个文字条目,并且创建自三个构造函数之一。最简单的构造函数是 textnode(string text, font font, color color, int x, int y),它用于创建一个非超级链接且没有阴影的文字节点。使用的五个参数是:
text 指定要绘制的文字
font 指定要使用的字体
font 指定要使用的文字颜色
x 指定第一个字符的起始列
y 指定每个字符基线所在的行
第二简单的构造函数是:textnode(string text, font font, color color, int x, int y, color shadowcolor).除了上述参数外,还需要为该函数指定 shadowcolor,即阴影的颜色。如果传递 null,则不呈现阴影,这样一来该构造函数就与前一构造函数一样了。这两个构造函数都调用下面的第三个构造函数:
textnode (string text, font font, color color, int x, int y,
color shadowcolor, string url, color activelinkcolor)
{
this.text = text;
this.font = font;
this.color = color;
this.x = x;
this.y = y;
this.shadowcolor = shadowcolor; this.url = url;
this.activelinkcolor = activelinkcolor;
if (url != null)
{
addmouselistener (new mouseadapter ()
{
public void mousepressed (mouseevent e)
{
int mx = e.getx ();
int my = e.gety ();
if (mx >= textnode.this.x &&
mx < textnode.this.x+width &&
my > textnode.this.y-height &&
my <= textnode.this.y)
{
active = false;
repaint ();
}
}
public void mousereleased (mouseevent e)
{
int mx = e.getx ();
int my = e.gety ();
if (mx >= textnode.this.x &&
mx < textnode.this.x+width &&
my > textnode.this.y-height &&
my <= textnode.this.y)
{
active = true;
repaint ();
launcher.
launchbrowser (textnode.this.url);
}
}
});
addmousemotionlistener (new mousemotionlistener ()
{
public void mousemoved (mouseevent e)
{
int mx = e.getx ();
int my = e.gety ();
if (mx >= textnode.this.x &&
mx < textnode.this.x+width &&
my > textnode.this.y-height &&
my <= textnode.this.y)
{
if (!active)
{
active = true;
repaint ();
}
}
else
{
if (active)
{
active = false;
repaint ();
}
}
}
public void mousedragged (mouseevent e)
{
}
});
}
}
保存参数后,该构造函数会在 pane 中注册一个鼠标监听器(假设 url 不为 null)。这些侦听器将判断鼠标指针是否位于超级链接文本上。如果是,则操纵 active 变量,将呈现该节点及其他节点,并启动浏览器。
class launcher
{
static void launchbrowser (string url)
{
try
{
// identify the operating system.
string os = system.getproperty ("os.name");
// launch browser with url if windows. otherwise, just output the url
// to the standard output device.
if (os.startswith ("windows"))
runtime.getruntime ()
.exec ("rundll32 url.dll,fileprotocolhandler " + url);
else
system.out.println (url);
}
catch (ioexception e)
{
system.err.println ("unable to launch browser");
}
}
}
pane 的 public void paintcomponent(graphics g) 方法将在该组件及其文本节点呈现的时候被调用。该方法启用反失真技术(防止文字出现锯齿),获取组件的插入位置(所以文本不会落在边框上),清理绘制区域使其成为白色,并呈现数组列表存储的每个文字节点。
public void paintcomponent (graphics g)
{
// prevent jagged text.
((graphics2d) g).setrenderinghint (renderinghints.key_antialiasing,
renderinghints.value_antialias_on);
// because the border is part of the panel, we need to make sure that we
// don't draw over it.
insets insets = getinsets ();
// paint everything but the border white.
g.setcolor (color.white);
g.fillrect (insets.left, insets.top, getwidth ()-insets.left-insets.right,
getheight ()-insets.top-insets.bottom);
// render all nodes.
iterator iter = nodes.iterator ();
while (iter.hasnext ())
{
textnode tn = (textnode) iter.next ();
tn.render (g, insets);
}
}
每个文本节点都是由 textnode 的 void render(graphics g, insets insets) 方法呈现的。该方法首先确定了字体,然后调用私有的 strdim() 方法来获取要绘制文字的规格尺寸等。然后呈现文本(可以选择阴影、超级链接等属性):
void render (graphics g, insets insets)
{
g.setfont (font);
dimension d = strdim (g, text);
width = (int) d.width;
height = (int) d.height;
// always drop the drop shadow (if specified) first.
<
闽公网安备 35060202000074号