一個繼承自 JLabel 的 MoveLabel 組件,文字在水平方向無限循環滾動,支持自定義速度、顏色、字體,可直接嵌入 Swing 界面作為通告欄、狀態提示等。
JLabel 實現 Runnable:複用標籤佈局/字體/顏色屬性,自帶線程驅動動畫paintComponent(Graphics g):自定義繪製邏輯,計算文字 X 座標實現位移repaint():每 50ms 更新位移並觸發重繪,Thread.sleep 控制幀率import javax.swing.*;
import java.awt.*;
public class MarqueeLabelDemo extends JFrame {
public MarqueeLabelDemo() {
super("跑馬燈標籤演示");
setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(400, 150);
setLocationRelativeTo(null);
setLayout(new BorderLayout());
MoveLabel label = new MoveLabel("歡迎使用 Java Swing 跑馬燈組件 - 支持自定義速度、顏色、字體...");
label.setFont(new Font("微軟雅黑", Font.BOLD, 18));
label.setForeground(Color.WHITE);
label.setBackground(new Color(0x2C3E50));
label.setOpaque(true);
label.setPreferredSize(new Dimension(380, 50));
add(label, BorderLayout.CENTER);
setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(MarqueeLabelDemo::new);
}
/** 跑馬燈標籤核心類 */
static class MoveLabel extends JLabel implements Runnable {
private final Thread animator;
private int x = 0; // 當前文字 X 偏移
private int textWidth = 0; // 文字像素寬度
public MoveLabel(String text) {
super(text);
this.animator = new Thread(this);
this.animator.setDaemon(true); // 隨 JVM 退出
this.animator.start();
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
// 計算文字寬度(首次/字體變更時)
FontMetrics fm = g2.getFontMetrics(getFont());
textWidth = fm.stringWidth(getText());
int h = getHeight();
int baseline = (h - fm.getHeight()) / 2 + fm.getAscent();
// 背景填充
g2.setColor(getBackground());
g2.fillRect(0, 0, getWidth(), h);
// 繪製主文字
g2.setColor(getForeground());
g2.drawString(getText(), x, baseline);
// 繪製銜接副本(無縫循環關鍵)
if (x + textWidth < getWidth()) {
g2.drawString(getText(), x + textWidth + 10, baseline); // 10px 間隙
}
}
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
x -= 2; // 每幀左移 2px,速度可通過 setSpeed(int px) 控制
if (x < -textWidth - 10) { // 完全移出後重置
x = getWidth();
}
repaint(); // 觸發 EDT 重繪
try {
Thread.sleep(50); // ~20 FPS
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 保留中斷狀態
break;
}
}
}
// 擴展:動態調速
public void setSpeed(int pixelsPerFrame) {
// 可通過修改 run() 中的 x -= speed 實現,此處略
}
}
}
單次繪製無法實現「文字剛移出屏幕右側,左側立即出現」的效果。核心邏輯:
// 主文字位置 x
g.drawString(text, x, y);
// 副本文字位置 x + textWidth + gap
if (x + textWidth < getWidth()) {
g.drawString(text, x + textWidth + gap, y);
}
當主文字尚未完全移出,副本已從右側進入,視覺上形成連續流動。
repaint() 將繪製任務投遞到 EDT 隊列,由 paintComponent 執行實際繪製FontMetrics 緩存:文字寬度僅在字體/內容變更時重新計算RenderingHints.KEY_TEXT_ANTIALIASING 提升視覺質量setDaemon(true) 避免主窗口關閉後動畫線程阻塞 JVM 退出| 功能 | 實現方案 |
|---|---|
| 垂直滾動 | 修改 y 座標,paintComponent 中計算行高 |
| 顏色漸變 | g2.setPaint(new GradientPaint(...)) 繪製文字 |
| 滾動暫停/恢復 | 增加 volatile boolean paused 标志,run() 中 wait/notify |
| 富文本/圖片混排 | 改用 JEditorPane + HTML,或自定義 Icon 實現 |
| 多行跑馬燈 | 維護 List<String> lines,逐行繪製,行間距固定 |
setOpaque(true) + 背景色對比度,或 getWidth() 為 0(布局未完成),可在 addComponentListener 中延遲啟動動畫paintComponent 外直接調用 getGraphics()FontMetrics.stringWidth() 已正確支持雙字節字符,無需特殊處理本文實現的 MoveLabel 組件,通過 繼承 JLabel + 實現 Runnable + 重寫 paintComponent + 雙重繪製 四步,打造出輕量級、可復用、視覺流暢的跑馬燈效果。代碼結構清晰,擴展性強,可直接作為項目通用組件庫收錄。掌握「後台線程計算座標 + EDT 繪製」這一 Swing 動畫核心模式,舉一反三可實現進度條動畫、粒子特效、圖表動態渲染等豐富視覺效果。