package clock;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.Calendar;
import java.util.Date;
import java.text.*;
public class RunningLight extends JFrame {
public RunningLight() {
// 字型設定
Font font = new Font("微軟正黑體", Font.BOLD, 16);
// 日期時間格式化
Calendar cal = Calendar.getInstance();
SimpleDateFormat formatter = new SimpleDateFormat(
"EEEE, MMMM dd日, yyyy年 HH:mm:ss", Locale.TRADITIONAL_CHINESE);
String dateTime = formatter.format(cal.getTime());
// 自訂跑馬燈面板
MovingMessagePanel panel = new MovingMessagePanel(dateTime);
panel.setFont(font);
panel.setBackground(Color.BLACK);
panel.setForeground(Color.PINK);
add(panel);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
RunningLight frame = new RunningLight();
frame.setTitle("Java Swing 跑馬燈示範");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 150);
frame.setLocationRelativeTo(null); // 置中
frame.setVisible(true);
});
}
// 內部類:跑馬燈面板
static class MovingMessagePanel extends JPanel {
private String message = " ";
private int x = 0;
private final int y = 40;
public MovingMessagePanel(String message) {
this.message = message;
// 每 100ms 觸發重繪
Timer timer = new Timer(100, new TimerListener());
timer.start();
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g); // 清除背景
// 文字超出寬度 → 重置到左側外部
if (x > getWidth()) {
x = -100; // 文字寬度估值
}
x += 5; // 每幀移動 5 像素
g.drawString(message, x, y);
}
// Timer 事件監聽
class TimerListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
repaint(); // 觸發 paintComponent
}
}
}
}
| 技術 | 說明 | 關鍵代碼 |
|---|---|---|
| 自訂繪圖 | 繼承 JPanel,覆寫 paintComponent | g.drawString(message, x, y) |
| 動畫循環 | javax.swing.Timer 定時觸發 repaint | new Timer(100, listener).start() |
| 雙重緩衝 | JPanel 預設開啟,無閃爍 | 無需額外代碼 |
| EDT 安全 | 主程式用 invokeLater,Timer 事件在 EDT | SwingUtilities.invokeLater |
| 資源釋放 | 關閉視窗自動停止 (EXIT_ON_CLOSE) | Timer 需手動 stop() 更嚴謹 |
static class AdvancedMovingPanel extends JPanel {
private String message = " ";
private int x = 0;
private int speed = 5;
private int delay = 100;
private Timer timer;
private boolean running = true;
public AdvancedMovingPanel(String message) {
this.message = message;
timer = new Timer(delay, e -> {
if (running) repaint();
});
timer.start();
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
FontMetrics fm = g.getFontMetrics();
int textWidth = fm.stringWidth(message);
if (x > getWidth()) x = -textWidth;
x += speed;
g.drawString(message, x, 40);
}
public void setMessage(String msg) { this.message = msg; }
public void setSpeed(int pxPerFrame) { this.speed = pxPerFrame; }
public void setDelay(int ms) { timer.setDelay(ms); }
public void pause() { running = false; }
public void resume() { running = true; }
public void stop() { timer.stop(); }
}
super.paintComponent(g) 或關閉雙重緩衝System.nanoTime() 計算實際位移// JavaFX 更流暢、GPU 加速、聲明式
Timeline timeline = new Timeline(
new KeyFrame(Duration.millis(16), e -> {
// 更新位移
text.setX(text.getX() + 2);
if (text.getX() > width) text.setX(-textWidth);
})
);
timeline.setCycleCount(Animation.INDEFINITE);
timeline.play();
Swing 跑馬燈展示了自訂元件 + Timer 動畫 + EDT 繪圖的經典模式。雖 Swing 屬維護模式,但理解其繪圖機制、事件模型、執行緒規則,對維護舊系統、學習 GUI 基礎仍有價值。新專案建議使用 JavaFX 或 Web 技術棧。