[Java] 指針型時鐘

其實這是高中時的一個題目,當時是在玩 Visual Basic,當時沒有完成。現在用 java GUI 完成,而 JFrame 和 Graphics2D 的知識來自 YouTuber Bro Code,寫的還蠻順的,20年前抓取系統時間都還是自00:00:00到現在經過的秒數,要自己換算時、分與秒,現在 java 的 LocalTime().now() 都有很方便的 getHour()、getMinute()等功能,剩下的就是將時間換成坐標畫圖了。

坐標的部份問題是,一般數學上的極坐標和直角坐標轉換,是依據直角坐標上有向角的定義,標準位置角是從 x 軸正向逆時針旋轉,而且 x 軸以右為正,y 軸以上為正。

只是畫刻度倒沒什麼關係,還是可以用一般直角坐標上的換算,但是指針的走向就不行了,時鐘上是從 π/2 而且是順時針旋轉,再加上電腦上的 y 軸是往下為正(左上 left top 為 (0, 0),x 往右增加,y 往下增加)所以要想個辦法轉換成電腦上的,數學上極坐標[ r , θ ] 換成直角坐標是( r cos θ , r sin θ ),如下左圖:

而電腦上是上右圖。整理一下差別:

數學上:

  • x 軸向右為正,y 軸向上為正
  • 角度逆時針為正角
  • 從 x 軸正向開始為始邊(時鐘3點方向開始)

電腦上模擬時鐘:

  • x 軸向右為正,y 軸向下為正
  • 角度順時針為正角
  • 從 y 軸負向開始為始邊(時鐘12點方向開始)

我的想法是若 y 反方向改為向下為正,那轉換就改為 ( h + r cos θ , k - r sin θ ) ,此處 ( h , k ) 為平移,也就是圓參數式的圓心。再來順時針的問題,就將 θ 改為 - θ,負角就是改為逆時針取角。最後角度的起始點是 12 點,也就是一般直角坐標的90度,那就將角度加上 π/2 ,這樣會得到 t 秒時的坐標為 ( h + r cos ( π/2 - ( 2πt/60)) , k - r cos ( π/2 - ( 2πt/60)))

到此大致比較大的問題都解決了,剩下就是一些微調和新功能,例如:將 timer 設為 100 也就是 1/10 秒跳一次,這樣秒針走起來更流暢,為了加這個功能還將取秒 getSecond() 多了 getNano() ;還有刻度跟著秒針加寬,這樣一來秒針指到哪個刻度更加清楚;最後是考量刻度畫好之後就不需要每次 timer 都重畫一遍,想說刻度與指針分開在兩個 panel 可以節省資源,無奈兩個 panel 有覆蓋問題而作罷。

一個初學可以練習的問題。

<

記錄一下在裡面用到的功能:

在 JFrame 或 JPanel 上畫圓的 java.awt.Graphics.drawOval(int x, int y, int width, int height) 和畫直線的 java.awt.Graphics.drawLine(int x1, int y1, int x2, int y2)

例如:
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.BasicStroke;
import java.awt.Color;

public void paint(Graphics g) {
	super.paint(g);
	Graphics2D g2D = (Graphics2D)g;
	g2D.setStroke(new BasicStroke(2));	//設定粗細
	g2D.drawOval(20,20,540,540);		//從(20,20)畫一個直徑540的圓
	g2D.setColor(new Color(209,152,38));	//設定顏色
	g2D.drawLine(295,284,385,125);		//從(295,284)畫直線到(385,125)
}

計時的 javax.swing.Timer(int delay, ActionListener listener) 和取現在時間的 java.time.LocalTime.now()

例如:
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.event.ActionEvent;
import java.awt.event.ActionListener;
import java.time.LocalTime;

public class ClockExample extends JPanel implements ActionListener {
	Timer timer;

	ClockExample() {
		timer = new Timer(100, this);		//每1/10秒計一次,聽取這個panel
		timer.start();				//計時器啓動

		int nowHour = LocalTime.now().getHour();	//取得現在時間的時
		int nowMinute = LocalTimer.now().getMinute();	//取得現在時間的分
	}     
	@Override
	public void actionPerformed(ActionEvent e) {
		repaint();	// 計時器每跳一次就作 Graphics2D 的重畫
	}			// 將取時間與繪圖都放入 paint(Graphics e){ } 之中
}

留言