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 by <code>DigitalClock</code> and
others.
*/
publicabstractclass Clock extends JComponent {
属性也是:
/**
* Font rendering context - assumes no
default transform, anti-aliasing
* active and fractional
metrics allowed.
*/
publicstaticfinal FontRenderContext
frc = new FontRenderContext(null,
true, true);
/**
* The calendar instance
for this clock.
*/
protected Calendar calendar;
/**
* @see #getBgImage()
*/
protected Image bgImage;
和数字时钟完全一样,提供基本属性和文本显示和绘制的信息容器.
再看AnalogClock:
/**
* To implement a analog-type clock.
*/
publicclass AnalogClock extends Clock implements ActionListener {
它有两个属性:
/**
* Parts to construct this
clock.
*/
private Parts parts = null;
/**
* A timer to run in a
independent thread.
*/
private Timer timer = null;
一个是定时刷新时间的Timer,一个是时钟的样式.
具体方法有,
1.复写addNotify和removeNotify方法控制Timer的启动和终止.
/**
* @see
java.awt.Component#addNotify()
*/
@Override
publicvoid addNotify() {
super.addNotify();
timer.start();
}
/**
* @see
java.awt.Component#removeNotify()
*/
@Override
publicvoid removeNotify() {
timer.stop();
super.removeNotify();
}
2.复写getPreferredSize方法使组件自动适应大小.
/**
*/
@Override
public Dimension getPreferredSize() {
Dimension size = getSize();
size.width = parts.getSize().width;
size.height = parts.getSize().height + MARGIN;
return size;
}
3.复写paintComponent使修正外观
@Override
publicvoid paintComponent(Graphics g) {
4.实现Timer必须的actionPerformed方法,做定时任务
/**
* Do transformation based
on current precise time when display.
*/
@Override
publicvoid actionPerformed(ActionEvent e) {
主要操作是取得当前时间,更新组件:
parts.doTransform(hour, minute, second, millisecond);
repaint();
// Resize this clock in time
setSize(getPreferredSize());
还有最主要的构造函数,组件的外观通过它传入,
/**
* Constructor:<br>
* Creates an analog-type clock by
using given parts.
*/
public AnalogClock(Parts parts) {
并且把Timer初始化:
timer = new Timer(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.
*/
publicabstractclass Parts
extends JComponent {
再看看它的属性:
/**
* Coloring scheme for the
parts.
*/
protected BasicColor colors;
/**
* Size of this parts.
*/
protected Dimension size;
/**
* Clock face.
*/
protected Shape dial;
分别控制时钟的各个颜色,大小,和外观样式.
然后是方法,它提供一个虚方法给具体类实现:
/**
*
Changes positions of hour hand, minute hand, second hand and * decisecond hand based on current time.
*/
publicabstractvoid doTransform(int hour, int minute, int second,
int millisecond);
这个方法主要是按给定的时间值得出指针在时钟上的位置和角度.
接着是RotateParts类:
/**
* This class defines a classical clock
behavior by using rotation pattern, *as we all know in common sense.
*/
publicabstractclass RotateParts extends Parts {
再看看它的属性:
/**
* 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.
*/
public AffineTransform getTransform() {
return AffineTransform.getRotateInstance(0, x, y);
}
这个方法是提供默认的指针的位置,即绕圆心(0,0)点旋转0度,即12点位置.
接着
/**
* Sets rotation algorithm
by given value.
*/
publicvoid setToRotation(AffineTransform af, double value, int grad) {
af.setToRotation(value * (2 * Math.PI / grad), x, y);
}
这个方法根据给定的具体值(这里可以理解为当前具体时间的时、分或者秒)和总的时间划分(12或者60)算出需要旋转的角度,然后绕圆心(x,y)旋转.
最后是
/**
* Gets a rotation
transform by given parameters.
*/
public AffineTransform getRotateInstance(int grad, int seq) {
return getRotateInstance(x, y, grad, seq);
}
/**
* Get a rotation transform
by given parameters.
*/
publicstatic AffineTransform getRotateInstance(float x, float y, int grad, int seq) {
return AffineTransform.getRotateInstance((2
* Math.PI / grad) *
seq, x, y);
}
这个是根据指定的值和总值以及中心点取得映射变换的实例.
接着就是重要的BasicParts类了
/**
* To
implement a classical analog-type clock face, except definitely *describing the hands
shape.<br>
*/
publicabstractclass BasicParts
extends RotateParts {
它是钟表刻度的继承,继承它就可以实现自己的指针钟表了.
先看它的属性:
/**
* Hour hand.
*/
protected Shape hourHand;
/**
* Minute hand.
*/
protected Shape minuteHand;
/**
* Second hand.
*/
protected Shape secondHand;
/**
* Hour hand behavior
controller.
*/
protected AffineTransform hourTransform;
/**
* Minute hand behavior
controller.
*/
protected AffineTransform minuteTransform;
/**
* Second hand behavior
controller.
*/
protected AffineTransform secondTransform;
这6个属性提供时分秒三个时针的形状和绘制映射类,通过它们可以对钟表进行绘制.
/**
* Moves all parts, to
leave some margin.
*/
protectedtransient AffineTransform trans;
这个属性是在对时分秒指针绘制时提供变换的.
/**
* Arabic time punctualities.
*/
publicstaticfinal String[] ARABIC = { "12", "1", "2", "3", "4", "5", "6","7", "8", "9", "10", "11" };
/**
* Roman time punctualities.
*/
publicstaticfinal String[] ROMAN = { "XII", "I", "II", "III", "IV", "V","VI", "VII", "VIII", "IX", "X", "XI" };
这两个常量是提供表盘的刻度显示的,也可以自己定义一个12位的数组代替.
再看它的构造函数
/**
* Constructor: Joins every
parts in a entire analog-type clock.
*/
protected BasicParts(Shape dial, Shape hourHand, Shape minuteHand,
Shape secondHand, String[] numbers, BasicColor colors)
throws Exception {
需要传入外围图形、时分秒图形、刻度数字和各部分颜色.当然可以传入new GeneralPath()
在以后再具体描绘它们.
/**
* Initializes hand
transformation.
*/
protectedvoid initTransform() {
hourTransform = getTransform();
minuteTransform = getTransform();
secondTransform = getTransform();
}
这个是初始化时分秒绘制映射类的.默认让它们都指向12点方向.
/**
* Default algorithm for
hands's action trace.
*/
@Override
publicvoid doTransform(int hour, int minute, int second, int millisecond) {
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.
*/
protectedvoid drawNumber(Graphics g, String number, Font font) {
BasicColor c = (BasicColor) colors;
AttributedString num = new AttributedString(number);
if (font != null) {
num.addAttribute(TextAttribute.FONT, font);
}
drawNumber(g,
num, x, y - radius, c.numbers);
}
/**
* Draws a number at 12
o'clock.
*/
publicstaticvoid drawNumber(Graphics g, AttributedString number, float x, float y, Color color) {
if (number != null) {
Graphics2D g2 = (Graphics2D) g;
g2.setPaint(color);
g2.drawString(number.getIterator(),
x, y);
}
}
是按指定的属性在表盘上画刻度的.
最后是重要的paintComponent方法了
@Override
publicvoid paintComponent(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.
*/
publicfinalclass MyParts
extends BasicParts {
首先还是看它的属性:
/**
* Radius of the clock
face.
*/
protectedfloatradius;
这个是定义钟表的半径.
/**
* 12 hour ticks.
*/
protected Shape tick;
/**
* Other 48 minute ticks
not at time punctualities.
*/
private GeneralPath smallTick;
这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.
*/
publicstaticvoid drawTicks(Graphics
g, Shape tick, int tickNumber,
float x, float y, 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.
*/
privatevoid createHand(Shape hand, float x, float y, float radius,
float widthPercent, float lengthPercent, float marginPercent,
float firstWidthPercent, float firstLengthPercent,
float secondWidthPercent, float secondLengthPercent) {
这个是绘制时针和分针的,形状是尾部粗尖端细
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.
*/
publicstaticvoid createHand(Shape
hand, float x, float y, float radius, float widthPercent, float lengthPercent, float marginPercent) {
这个是绘制秒针的,粗细均匀,比较简单
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
*/
privatevoid
drawNumbers(Graphics g, String[] numbers, float marginPercent, Font font) {
以3点举例,先算角度:
float cZero1 = (float) Math.cos((2 * Math.PI / 12) * 3);
再把数字转为属性串,取得宽度:
num = new
AttributedString(numbers[p]);
num.addAttribute(TextAttribute.FONT, font);
layout = new
TextLayout(numbers[p], font, Clock.frc);
float width = layout.getBounds().getBounds().width;
然后算出坐标:
float px = (float) (x + trans.getTranslateX() + radius
* (1 + marginPercent) * sin);
最后调用父类绘制方法绘制:
super.drawNumber(g, num, px, py, color);
接着是初始化方法,它把指针和表盘大小,位置都进行了初始化:
/**
* To initialize some
parameters and every parts shape.
*/
protectedvoid initialize() {
首先算圆心和半径:
x = xNW + width / 2;
y = yNW + width / 2;
radius = width / 2 - 5;
然后画时针:
设定各个百分比位置,然后调用时针方法
float hWidthOfRadius =
0.08f;
float hLengthOfRadius =
0.7f;
createHand(hourHand, x, y, radius, hWidthOfRadius, hLengthOfRadius,
hMarginOfRadius,
fstWidthOfRadius, fstLengthOfRadius,
sndWidthOfRadius,
sndLengthOfRadius);
其它指针也是类似画出.
最后是复写paintComponent方法,当属性变更时重新绘制指针时钟:
/**
* Paint ticks and time punctualities.
*/
@Override
publicvoid paintComponent(Graphics g) {
在里面进行了指针数字和刻度绘制方法的调用
// Draw 12 numbers by using
specific font
drawNumbers(g,
numbers, marginOfRadius, new Font("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
*/
private AnalogClock getColorfulClock() {
if (colorfulClock == null) {
try {
colorfulClock = new AnalogClock(new MyParts());
} catch (Exception e) {
e.printStackTrace();
}
}
returncolorfulClock;
}
就可以使用了.