Java的指针时钟最基础的原理和数字时钟其实差不多,也是利用Swing的Timer计时,每隔一定时间重新绘制组件,最后重写paintComponent方法来更新界面.和之前介绍的时钟一样,为了保证时钟的正确启动和终止,需要重写组件的addNotify和removeNotify方法,在方法内加入Timer的启动和终止;最后也要重写组件getPreferredSize方法使组件的大小自动适应.
首先看最终的效果:
工程的目录:
其中timespinner包是时间的微调组件,这儿只是为了显示用的,和指针时钟无关,先不介绍了.
Clock包则是显示指针时钟的包,指针时钟的组件类是AnalogClock,它继承于Clock类,处理和数字时钟的基本一致,先看Clock的:
/**
* This bean to define basic properties and
behaviors of a clock, concrete
* instances will be implemented byDigitalClock
and
others.
*/
publicabstractclassClockextendsJComponent {
属性也是:
/**
* Font rendering context-assumes no
default transform, anti-aliasing
* active and fractional
metrics allowed.
*/
publicstaticfinalFontRenderContextfrc=newFontRenderContext(null,
true,true);
/**
* The calendar instance
for this clock.
*/
protectedCalendarcalendar;
/**
*@see#getBgImage()
*/
protectedImagebgImage;
和数字时钟完全一样,提供基本属性和文本显示和绘制的信息容器.
再看AnalogClock:
/**
* To implement a analog-type clock.
*/
publicclassAnalogClockextendsClockimplementsActionListener {
它有两个属性:
/**
* Parts to construct this
clock.
*/
privatePartsparts=null;
/**
* A timer to run in a
independent thread.
*/
privateTimertimer=null;
一个是定时刷新时间的Timer,一个是时钟的样式.
具体方法有,
1.复写addNotify和removeNotify方法控制Timer的启动和终止.
/**
*@seejava.awt.Component#addNotify()
*/
@Override
publicvoidaddNotify() {
super.addNotify();
timer.start();
}
/**
*@seejava.awt.Component#removeNotify()
*/
@Override
publicvoidremoveNotify() {
timer.stop();
super.removeNotify();
}
2.复写getPreferredSize方法使组件自动适应大小.
/**
*/
@Override
publicDimension getPreferredSize() {
Dimension size = getSize();
size.width=parts.getSize().width;
size.height=parts.getSize().height+MARGIN;
returnsize;
}
3.复写paintComponent使修正外观
@Override
publicvoidpaintComponent(Graphics g) {
4.实现Timer必须的actionPerformed方法,做定时任务
/**
* Do transformation based
on current precise time when display.
*/
@Override
publicvoidactionPerformed(ActionEvent e) {
主要操作是取得当前时间,更新组件:
parts.doTransform(hour, minute, second, millisecond);
repaint();
// Resize this clock in time
setSize(getPreferredSize());
还有最主要的构造函数,组件的外观通过它传入,
/**
* Constructor:
* Creates an analog-type clock by
using given parts.
*/
publicAnalogClock(Parts parts) {
并且把Timer初始化:
timer=newTimer(1000,this);
到现在为止,和时间设置相关的已经完成,剩下的就是传入组件的表现Parts,使画面呈现了.
指针时钟的呈现主要使用了Parts、RotateParts、BasicParts和MyParts四个类,它们是继承关系.
其中Parts是最基本的,它主要描绘指针时钟最外层的边框、指针时钟颜色和大小,,并且提供了虚的doTransform方法供子类实现绘制;
RotateParts在Parts的基础上提供了圆心和半径把数字时钟最外层的圆的属性提供出来,并提供了画刻度的方法,没有具体的绘制;
BasicParts是主要的绘制类,它完成了指针时钟显示的大部分工作,提供时钟上的数字和时分秒指针以及指针的变换器这些基本属性,并提供了绘制数字和指针在组件上的方法,简单的继承它就可以实现一个指针时钟了,只是不够美观;
MyParts是继承于BasicParts的类,它主要目的是把指针时钟做的更美观,并且定义时钟的基本大小,颜色等,提供了更好的绘制钟面上数字和指针的方法.
现在依次详细看看这些类:
首先是最基本的Parts
/**
* To represent all modules which a analog-type clock consists
of.
*/
publicabstractclassPartsextendsJComponent {
再看看它的属性:
/**
* Coloring scheme for the
parts.
*/
protectedBasicColorcolors;
/**
* Size of this parts.
*/
protectedDimensionsize;
/**
* Clock face.
*/
protectedShapedial;
分别控制时钟的各个颜色,大小,和外观样式.
然后是方法,它提供一个虚方法给具体类实现:
/**
*
Changes positions of hour hand, minute hand, second hand and * decisecondhand based on current time.
*/
publicabstractvoiddoTransform(inthour,intminute,intsecond,
intmillisecond);
这个方法主要是按给定的时间值得出指针在时钟上的位置和角度.
接着是RotateParts类:
/**
* This class defines a classical clock
behavior by using rotation pattern, *as we all know in common sense.
*/
publicabstractclassRotatePartsextendsParts {
再看看它的属性:
/**
* X coordinate of the
center.
*/
protectedfloatx;
/**
* Y coordinate of the
center.
*/
protectedfloaty;
/**
* Radius of the clock
face.
*/
protectedfloatradius;
分别给定了指针时钟的圆的圆心和半径,没有提供绘制方面的属性.
然后是方法,它提供了几个给定时间值换算为时钟位置的方法:
/**
* a rotation instance from
12 o'clock direction.
*/
publicAffineTransform getTransform() {
returnAffineTransform.getRotateInstance(0,x,y);
}
这个方法是提供默认的指针的位置,即绕圆心(0,0)点旋转0度,即12点位置.
接着
/**
* Sets rotation algorithm
by given value.
*/
publicvoidsetToRotation(AffineTransform af,doublevalue,intgrad) {
af.setToRotation(value * (2 * Math.PI/ grad),x,y);
}
这个方法根据给定的具体值(这里可以理解为当前具体时间的时、分或者秒)和总的时间划分(12或者60)算出需要旋转的角度,然后绕圆心(x,y)旋转.
最后是
/**
* Gets a rotation
transform by given parameters.
*/
publicAffineTransform getRotateInstance(intgrad,intseq) {
returngetRotateInstance(x,y, grad, seq);
}
/**
* Get a rotation transform
by given parameters.
*/
publicstaticAffineTransform getRotateInstance(floatx,floaty,intgrad,intseq) {
returnAffineTransform.getRotateInstance((2
* Math.PI/ grad) *
seq, x, y);
}
这个是根据指定的值和总值以及中心点取得映射变换的实例.
接着就是重要的BasicParts类了
/**
* To
implement a classical analog-type clock face, except definitely *describing the hands
shape.
*/
publicabstractclassBasicPartsextendsRotateParts {
它是钟表刻度的继承,继承它就可以实现自己的指针钟表了.
先看它的属性:
/**
* Hour hand.
*/
protectedShapehourHand;
/**
* Minute hand.
*/
protectedShapeminuteHand;
/**
* Second hand.
*/
protectedShapesecondHand;
/**
* Hour hand behavior
controller.
*/
protectedAffineTransformhourTransform;
/**
* Minute hand behavior
controller.
*/
protectedAffineTransformminuteTransform;
/**
* Second hand behavior
controller.
*/
protectedAffineTransformsecondTransform;
这6个属性提供时分秒三个时针的形状和绘制映射类,通过它们可以对钟表进行绘制.
/**
* Moves all parts, to
leave some margin.
*/
protectedtransientAffineTransformtrans;
这个属性是在对时分秒指针绘制时提供变换的.
/**
* Arabic time punctualities.
*/
publicstaticfinalString[]ARABIC= {"12","1","2","3","4","5","6","7","8","9","10","11"};
/**
* Roman time punctualities.
*/
publicstaticfinalString[]ROMAN= {"XII","I","II","III","IV","V","VI","VII","VIII","IX","X","XI"};
这两个常量是提供表盘的刻度显示的,也可以自己定义一个12位的数组代替.
再看它的构造函数
/**
* Constructor: Joins every
parts in a entire analog-type clock.
*/
protectedBasicParts(Shape dial, Shape hourHand, Shape minuteHand,
Shape secondHand, String[] numbers, BasicColor colors)
throwsException {
需要传入外围图形、时分秒图形、刻度数字和各部分颜色.当然可以传入newGeneralPath()
在以后再具体描绘它们.
/**
* Initializes hand
transformation.
*/
protectedvoidinitTransform() {
hourTransform= getTransform();
minuteTransform= getTransform();
secondTransform= getTransform();
}
这个是初始化时分秒绘制映射类的.默认让它们都指向12点方向.
/**
* Default algorithm for
hands's action trace.
*/
@Override
publicvoiddoTransform(inthour,intminute,intsecond,intmillisecond) {
if(hourTransform!=null&&minuteTransform!=null
&&secondTransform!=null) {
setToRotation(hourTransform,
hour + (minute + second / 60.0) / 60.0, 12);
setToRotation(minuteTransform, minute + second / 60.0, 60);
setToRotation(secondTransform, second, 60);
}
}
这个是父类的虚函数的实现,根据给定值旋转指定角度呈现给画面.
/**
* Draws a number at 12
o'clock.
*/
protectedvoiddrawNumber(Graphics g, String number, Font font) {
BasicColor c = (BasicColor)colors;
AttributedString num =newAttributedString(number);
if(font !=null) {
num.addAttribute(TextAttribute.FONT, font);
}
drawNumber(g,
num,x,y-radius, c.numbers);
}
/**
* Draws a number at 12
o'clock.
*/
publicstaticvoiddrawNumber(Graphics g, AttributedString number,floatx,floaty, Color color) {
if(number !=null) {
Graphics2D g2 = (Graphics2D) g;
g2.setPaint(color);
g2.drawString(number.getIterator(),
x, y);
}
}
是按指定的属性在表盘上画刻度的.
最后是重要的paintComponent方法了
@Override
publicvoidpaintComponent(Graphics g) {
它按照属性了上面取得的绘制映射类进行绘制
首先是绘制外围界面:
g2.setPaint(c.dail);
g2.fill(trans.createTransformedShape(dial));
g2.setPaint(Color.BLACK);
g2.draw(trans.createTransformedShape(dial));
然后绘制时分秒指针:
// Draw hour hand
g2.setPaint(c.hourHand);
g2.fill(trans.createTransformedShape(hourTransform
.createTransformedShape(hourHand)));
分秒基本和时的一样.
最后要看的类就是自己实现的MyParts类了,其实这里简单实现一个SimpleParts也可以的只是界面比较难看,如下图:
所以需要做漂亮点还是要自己去写一部分代码的.
先看继承关系
/**
* A piece of sample code to show how to
develop a nice-looking
analog-type
* clock by using this API.
*/
publicfinalclassMyPartsextendsBasicParts {
首先还是看它的属性:
/**
* Radius of the clock
face.
*/
protectedfloatradius;
这个是定义钟表的半径.
/**
* 12 hour ticks.
*/
protectedShapetick;
/**
* Other 48 minute ticks
not at time punctualities.
*/
privateGeneralPathsmallTick;
这2个是定义钟表的刻度,分别代表比较明显的12个整点刻度,和其它48个不明显的刻度.
/**
* X coordinate of left top
corner.
*/
privatestaticfloatxNW= 0;
/**
* Y coordinate of left top
corner.
*/
privatestaticfloatyNW= 0;
/**
* Width of the square.
*/
privatestaticfloatwidth= 170;
这2个属性分别代表距离中心的坐标和表的外围大小.
/**
* Additional margin size
in proportion of radius by percentage.
*/
privatestaticfloatmarginOfRadius= 0.1f;
这个属性代表空白区域的百分比.
然后是方法,先看画刻度的方法:
/**
* Draws ticks.
*/
publicstaticvoiddrawTicks(Graphics
g, Shape tick,inttickNumber,
floatx,floaty, AffineTransform trans, Color color) {
首先得到最基本的指针位置,默认指向12点位置:
AffineTransform at = AffineTransform.getRotateInstance(0, x, y);
然后取得偏移的角度:
at = RotateParts.getRotateInstance(x, y, tickNumber, p);
最后是绘制:
g2.fill(trans.createTransformedShape(at
.createTransformedShape(tick)));
再看绘制指针的方法:
/**
* Generate hour hand and
minute hand shape.
*/
privatevoidcreateHand(Shape hand,floatx,floaty,floatradius,
floatwidthPercent,floatlengthPercent,floatmarginPercent,
floatfirstWidthPercent,floatfirstLengthPercent,
floatsecondWidthPercent,floatsecondLengthPercent) {
这个是绘制时针和分针的,形状是尾部粗尖端细
h.moveTo(x, y);
h.curveTo(x - radius * (widthPercent / 2) * (firstWidthPercent / 2), y-
radius * marginPercent * (firstLengthPercent / 2), x – radius * (widthPercent /
2) * (secondWidthPercent / 2), y – radius * marginPercent *
(secondLengthPercent / 2), x, y – radius* lengthPercent);
/**
* Generates concrete hand
shape.
*/
publicstaticvoidcreateHand(Shape
hand,floatx,floaty,floatradius,floatwidthPercent,floatlengthPercent,floatmarginPercent) {
这个是绘制秒针的,粗细均匀,比较简单
h.moveTo(x - radius *
(widthPercent / 2), y + radius * marginPercent);
h.lineTo(x + radius *
(widthPercent / 2), y + radius * marginPercent);
再看绘制表上数字的方法
/**
*
An algorithm to locate time punctualities numbers on a round clock *face
*/
privatevoiddrawNumbers(Graphics g, String[] numbers,floatmarginPercent, Font font) {
以3点举例,先算角度:
floatcZero1 = (float) Math.cos((2 * Math.PI/ 12) * 3);
再把数字转为属性串,取得宽度:
num =newAttributedString(numbers[p]);
num.addAttribute(TextAttribute.FONT, font);
layout =newTextLayout(numbers[p], font, Clock.frc);
floatwidth = layout.getBounds().getBounds().width;
然后算出坐标:
floatpx = (float) (x+trans.getTranslateX() +radius
* (1 + marginPercent) * sin);
最后调用父类绘制方法绘制:
super.drawNumber(g, num, px, py, color);
接着是初始化方法,它把指针和表盘大小,位置都进行了初始化:
/**
* To initialize some
parameters and every parts shape.
*/
protectedvoidinitialize() {
首先算圆心和半径:
x=xNW+width/ 2;
y=yNW+width/ 2;
radius=width/ 2 - 5;
然后画时针:
设定各个百分比位置,然后调用时针方法
floathWidthOfRadius =
0.08f;
floathLengthOfRadius =
0.7f;
createHand(hourHand,x,y,radius, hWidthOfRadius, hLengthOfRadius,
hMarginOfRadius,
fstWidthOfRadius, fstLengthOfRadius,
sndWidthOfRadius,
sndLengthOfRadius);
其它指针也是类似画出.
最后是复写paintComponent方法,当属性变更时重新绘制指针时钟:
/**
* Paint ticks and time punctualities.
*/
@Override
publicvoidpaintComponent(Graphics g) {
在里面进行了指针数字和刻度绘制方法的调用
// Draw 12 numbers by using
specific font
drawNumbers(g,numbers,marginOfRadius,newFont("Ravie", Font.BOLD+ Font.ITALIC, 8));
// Draw 12 hour ticks, here
use SimpleParts
drawTicks(g,tick, max,x,y,trans, c.tick);
// Draw 48 minute ticks, here
use SimpleParts
drawTicks(g,smallTick, 60,x,y,trans, c.tick);
这个绘制类就完成了.
到此为止,所有的指针时钟的创立工作全部完成.
最后通过
/**
* This method shows how to
create a user defined analog-type clock
*/
privateAnalogClock getColorfulClock() {
if(colorfulClock==null) {
try{
colorfulClock=newAnalogClock(newMyParts());
}catch(Exception e) {
e.printStackTrace();
}
}
returncolorfulClock;
}
就可以使用了.