Java Swing 中的跑马灯效果

发布于 2024-09-16 11:56:29 字数 28 浏览 9 评论 0原文

如何在Java Swing中实现跑马灯效果

How can I implement Marquee effect in Java Swing

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

心不设防 2024-09-23 11:56:30

下面是一个使用 javax.swing.Timer 的示例。

Marquee.png

import java.awt.EventQueue;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;

/** @see http://stackoverflow.com/questions/3617326 */
public class MarqueeTest {

    private void display() {
        JFrame f = new JFrame("MarqueeTest");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        String s = "Tomorrow, and tomorrow, and tomorrow, "
        + "creeps in this petty pace from day to day, "
        + "to the last syllable of recorded time; ... "
        + "It is a tale told by an idiot, full of "
        + "sound and fury signifying nothing.";
        MarqueePanel mp = new MarqueePanel(s, 32);
        f.add(mp);
        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
        mp.start();
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                new MarqueeTest().display();
            }
        });
    }
}

/** Side-scroll n characters of s. */
class MarqueePanel extends JPanel implements ActionListener {

    private static final int RATE = 12;
    private final Timer timer = new Timer(1000 / RATE, this);
    private final JLabel label = new JLabel();
    private final String s;
    private final int n;
    private int index;

    public MarqueePanel(String s, int n) {
        if (s == null || n < 1) {
            throw new IllegalArgumentException("Null string or n < 1");
        }
        StringBuilder sb = new StringBuilder(n);
        for (int i = 0; i < n; i++) {
            sb.append(' ');
        }
        this.s = sb + s + sb;
        this.n = n;
        label.setFont(new Font("Serif", Font.ITALIC, 36));
        label.setText(sb.toString());
        this.add(label);
    }

    public void start() {
        timer.start();
    }

    public void stop() {
        timer.stop();
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        index++;
        if (index > s.length() - n) {
            index = 0;
        }
        label.setText(s.substring(index, index + n));
    }
}

Here's an example using javax.swing.Timer.

Marquee.png

import java.awt.EventQueue;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;

/** @see http://stackoverflow.com/questions/3617326 */
public class MarqueeTest {

    private void display() {
        JFrame f = new JFrame("MarqueeTest");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        String s = "Tomorrow, and tomorrow, and tomorrow, "
        + "creeps in this petty pace from day to day, "
        + "to the last syllable of recorded time; ... "
        + "It is a tale told by an idiot, full of "
        + "sound and fury signifying nothing.";
        MarqueePanel mp = new MarqueePanel(s, 32);
        f.add(mp);
        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
        mp.start();
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                new MarqueeTest().display();
            }
        });
    }
}

/** Side-scroll n characters of s. */
class MarqueePanel extends JPanel implements ActionListener {

    private static final int RATE = 12;
    private final Timer timer = new Timer(1000 / RATE, this);
    private final JLabel label = new JLabel();
    private final String s;
    private final int n;
    private int index;

    public MarqueePanel(String s, int n) {
        if (s == null || n < 1) {
            throw new IllegalArgumentException("Null string or n < 1");
        }
        StringBuilder sb = new StringBuilder(n);
        for (int i = 0; i < n; i++) {
            sb.append(' ');
        }
        this.s = sb + s + sb;
        this.n = n;
        label.setFont(new Font("Serif", Font.ITALIC, 36));
        label.setText(sb.toString());
        this.add(label);
    }

    public void start() {
        timer.start();
    }

    public void stop() {
        timer.stop();
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        index++;
        if (index > s.length() - n) {
            index = 0;
        }
        label.setText(s.substring(index, index + n));
    }
}
傾旎 2024-09-23 11:56:30

我知道这是一个迟到的答案,但我刚刚看到另一个关于已关闭的字幕的问题,因为它被认为是该答案的重复。

所以我想我应该添加我的建议,该建议采用与此处建议的其他答案不同的方法。

MarqueePanel 滚动面板上的组件,而不仅仅是文本。因此,这使您可以充分利用任何 Swing 组件。通过添加带有文本的 JLabel 可以使用简单的选取框。更高级的选取框可能会使用带有 HTML 的 JLabel,这样您就可以为文本使用不同的字体和颜色。您甚至可以添加带有图像的第二个组件。

I know this is a late answer, but I just saw another question about a marquee that was closed because it was considered a duplicate of this answer.

So I thought I'd add my suggestion which takes a approach different from the other answers suggested here.

The MarqueePanel scrolls components on a panel not just text. So this allows you to take full advantage of any Swing component. A simple marquee can be used by adding a JLabel with text. A fancier marquee might use a JLabel with HTML so you can use different fonts and color for the text. You can even add a second component with an image.

坐在坟头思考人生 2024-09-23 11:56:30

基本答案是您将文本/图形绘制到位图中,然后实现一个以一定量绘制位图偏移的组件。通常选取框/滚动条向左滚动,因此偏移量增加,这意味着位图绘制在 -offset 处。您的组件运行一个定期触发的计时器,增加偏移量并使自身无效,以便重新绘制。

像包装这样的事情处理起来有点复杂,但相当简单。如果偏移量超过位图宽度,则将其重置回 0。如果偏移量 + 组件宽度 >位图宽度 您从位图的开头开始绘制组件的其余部分。

一个像样的股票行情指示器的关键是使滚动尽可能平滑且无闪烁。因此,可能有必要考虑对结果进行双缓冲,首先将滚动位绘制到位图中,然后一次性渲染它,而不是直接绘制到屏幕中。

Basic answer is you draw your text / graphic into a bitmap and then implement a component that paints the bitmap offset by some amount. Usually marquees / tickers scroll left so the offset increases which means the bitmap is painted at -offset. Your component runs a timer that fires periodically, incrementing the offset and invalidating itself so it repaints.

Things like wrapping are a little more complex to deal with but fairly straightforward. If the offset exceeds the bitmap width you reset it back to 0. If the offset + component width > bitmap width you paint the remainder of the component starting from the beginning of the bitmap.

The key to a decent ticker is to make the scrolling as smooth and as flicker free as possible. Therefore it may be necessary to consider double buffering the result, first painting the scrolling bit into a bitmap and then rendering that in one go rather than painting straight into the screen.

这个俗人 2024-09-23 11:56:30

下面是我整理的一些代码,供您入门。我通常会采用 ActionListener 代码并将其放入某种 MarqueeController 类中,以使此逻辑与面板分开,但这是关于组织 MVC 架构的不同问题,并且在一个足够简单的类中,例如这或许并不是那么重要。

还有各种动画库可以帮助您做到这一点,但我通常不喜欢将库包含到项目中只是为了解决这样的一个问题。

public class MarqueePanel extends JPanel {
  private JLabel textLabel;
  private int panelLocation;
  private ActionListener taskPerformer;
  private boolean isRunning = false;

  public static final int FRAMES_PER_SECOND = 24;
  public static final int MOVEMENT_PER_FRAME = 5;

  /**
   * Class constructor creates a marquee panel.
   */

  public MarqueePanel() {
    this.setLayout(null);
    this.textLabel = new JLabel("Scrolling Text Here");
    this.panelLocation = 0;
    this.taskPerformer = new ActionListener() {
      public void actionPerformed(ActionEvent evt) {
        MarqueePanel.this.tickAnimation();
      }
    }
  }

  /**
   * Starts the animation.
   */

  public void start() {
    this.isRunning = true;
    this.tickAnimation();
  }

  /**
   * Stops the animation.
   */

  public void stop() {
    this.isRunning = false;
  }

  /**
   * Moves the label one frame to the left.  If it's out of display range, move it back
   * to the right, out of display range.
   */

  private void tickAnimation() {
    this.panelLocation -= MarqueePanel.MOVEMENT_PER_FRAME;
    if (this.panelLocation < this.textLabel.getWidth())
      this.panelLocaton = this.getWidth();
    this.textLabel.setLocation(this.panelLocation, 0);
    this.repaint();
    if (this.isRunning) {
      Timer t = new Timer(1000 / MarqueePanel.FRAMES_PER_SECOND, this.taskPerformer);
      t.setRepeats(false);
      t.start();
    }
  }
}

Here is some code that I threw together to get you started. I normally would take the ActionListener code and put that in some sort of MarqueeController class to keep this logic separate from the panel, but that's a different question about organizing the MVC architecture, and in a simple enough class like this it may not be so important.

There are also various animation libraries that would help you do this, but I don't normally like to include libraries into projects only to solve one problem like this.

public class MarqueePanel extends JPanel {
  private JLabel textLabel;
  private int panelLocation;
  private ActionListener taskPerformer;
  private boolean isRunning = false;

  public static final int FRAMES_PER_SECOND = 24;
  public static final int MOVEMENT_PER_FRAME = 5;

  /**
   * Class constructor creates a marquee panel.
   */

  public MarqueePanel() {
    this.setLayout(null);
    this.textLabel = new JLabel("Scrolling Text Here");
    this.panelLocation = 0;
    this.taskPerformer = new ActionListener() {
      public void actionPerformed(ActionEvent evt) {
        MarqueePanel.this.tickAnimation();
      }
    }
  }

  /**
   * Starts the animation.
   */

  public void start() {
    this.isRunning = true;
    this.tickAnimation();
  }

  /**
   * Stops the animation.
   */

  public void stop() {
    this.isRunning = false;
  }

  /**
   * Moves the label one frame to the left.  If it's out of display range, move it back
   * to the right, out of display range.
   */

  private void tickAnimation() {
    this.panelLocation -= MarqueePanel.MOVEMENT_PER_FRAME;
    if (this.panelLocation < this.textLabel.getWidth())
      this.panelLocaton = this.getWidth();
    this.textLabel.setLocation(this.panelLocation, 0);
    this.repaint();
    if (this.isRunning) {
      Timer t = new Timer(1000 / MarqueePanel.FRAMES_PER_SECOND, this.taskPerformer);
      t.setRepeats(false);
      t.start();
    }
  }
}
小姐丶请自重 2024-09-23 11:56:30

将 JLabel 添加到框架或面板。

ScrollText s=   new ScrollText("ello Everyone.");
jLabel3.add(s);


public class ScrollText extends JComponent {
private BufferedImage image;

private Dimension imageSize;

private volatile int currOffset;

private Thread internalThread;

private volatile boolean noStopRequested;

public ScrollText(String text) {
currOffset = 0;
buildImage(text);

setMinimumSize(imageSize);
setPreferredSize(imageSize);
setMaximumSize(imageSize);
setSize(imageSize);

noStopRequested = true;
Runnable r = new Runnable() {
  public void run() {
    try {
      runWork();
    } catch (Exception x) {
      x.printStackTrace();
    }
  }
};

internalThread = new Thread(r, "ScrollText");
internalThread.start();
}

private void buildImage(String text) {
RenderingHints renderHints = new RenderingHints(
    RenderingHints.KEY_ANTIALIASING,
    RenderingHints.VALUE_ANTIALIAS_ON);

renderHints.put(RenderingHints.KEY_RENDERING,
    RenderingHints.VALUE_RENDER_QUALITY);

BufferedImage scratchImage = new BufferedImage(1, 1,
    BufferedImage.TYPE_INT_RGB);

Graphics2D scratchG2 = scratchImage.createGraphics();
scratchG2.setRenderingHints(renderHints);

Font font = new Font("Serif", Font.BOLD | Font.ITALIC, 24);

FontRenderContext frc = scratchG2.getFontRenderContext();
TextLayout tl = new TextLayout(text, font, frc);
Rectangle2D textBounds = tl.getBounds();
int textWidth = (int) Math.ceil(textBounds.getWidth());
int textHeight = (int) Math.ceil(textBounds.getHeight());

int horizontalPad = 600;
int verticalPad = 10;

imageSize = new Dimension(textWidth + horizontalPad, textHeight
    + verticalPad);

image = new BufferedImage(imageSize.width, imageSize.height,
    BufferedImage.TYPE_INT_RGB);

Graphics2D g2 = image.createGraphics();
g2.setRenderingHints(renderHints);

int baselineOffset = (verticalPad / 2) - ((int) textBounds.getY());

g2.setColor(Color.BLACK);
g2.fillRect(0, 0, imageSize.width, imageSize.height);

g2.setColor(Color.GREEN);
tl.draw(g2, 0, baselineOffset);

// Free-up resources right away, but keep "image" for
// animation.
scratchG2.dispose();
scratchImage.flush();
g2.dispose();
 }
public void paint(Graphics g) {
// Make sure to clip the edges, regardless of curr size
g.setClip(0, 0, imageSize.width, imageSize.height);

int localOffset = currOffset; // in case it changes
g.drawImage(image, -localOffset, 0, this);
g.drawImage(image, imageSize.width - localOffset, 0, this);

// draw outline
g.setColor(Color.black);
g.drawRect(0, 0, imageSize.width - 1, imageSize.height - 1);
  }
private void runWork() {
while (noStopRequested) {
  try {
    Thread.sleep(10); // 10 frames per second

    // adjust the scroll position
    currOffset = (currOffset + 1) % imageSize.width;

    // signal the event thread to call paint()
    repaint();
  } catch (InterruptedException x) {
    Thread.currentThread().interrupt();
  }
  }
 }

public void stopRequest() {
noStopRequested = false;
internalThread.interrupt();
}

public boolean isAlive() {
return internalThread.isAlive();
}


}

Add a JLabel to your frame or panel.

ScrollText s=   new ScrollText("ello Everyone.");
jLabel3.add(s);


public class ScrollText extends JComponent {
private BufferedImage image;

private Dimension imageSize;

private volatile int currOffset;

private Thread internalThread;

private volatile boolean noStopRequested;

public ScrollText(String text) {
currOffset = 0;
buildImage(text);

setMinimumSize(imageSize);
setPreferredSize(imageSize);
setMaximumSize(imageSize);
setSize(imageSize);

noStopRequested = true;
Runnable r = new Runnable() {
  public void run() {
    try {
      runWork();
    } catch (Exception x) {
      x.printStackTrace();
    }
  }
};

internalThread = new Thread(r, "ScrollText");
internalThread.start();
}

private void buildImage(String text) {
RenderingHints renderHints = new RenderingHints(
    RenderingHints.KEY_ANTIALIASING,
    RenderingHints.VALUE_ANTIALIAS_ON);

renderHints.put(RenderingHints.KEY_RENDERING,
    RenderingHints.VALUE_RENDER_QUALITY);

BufferedImage scratchImage = new BufferedImage(1, 1,
    BufferedImage.TYPE_INT_RGB);

Graphics2D scratchG2 = scratchImage.createGraphics();
scratchG2.setRenderingHints(renderHints);

Font font = new Font("Serif", Font.BOLD | Font.ITALIC, 24);

FontRenderContext frc = scratchG2.getFontRenderContext();
TextLayout tl = new TextLayout(text, font, frc);
Rectangle2D textBounds = tl.getBounds();
int textWidth = (int) Math.ceil(textBounds.getWidth());
int textHeight = (int) Math.ceil(textBounds.getHeight());

int horizontalPad = 600;
int verticalPad = 10;

imageSize = new Dimension(textWidth + horizontalPad, textHeight
    + verticalPad);

image = new BufferedImage(imageSize.width, imageSize.height,
    BufferedImage.TYPE_INT_RGB);

Graphics2D g2 = image.createGraphics();
g2.setRenderingHints(renderHints);

int baselineOffset = (verticalPad / 2) - ((int) textBounds.getY());

g2.setColor(Color.BLACK);
g2.fillRect(0, 0, imageSize.width, imageSize.height);

g2.setColor(Color.GREEN);
tl.draw(g2, 0, baselineOffset);

// Free-up resources right away, but keep "image" for
// animation.
scratchG2.dispose();
scratchImage.flush();
g2.dispose();
 }
public void paint(Graphics g) {
// Make sure to clip the edges, regardless of curr size
g.setClip(0, 0, imageSize.width, imageSize.height);

int localOffset = currOffset; // in case it changes
g.drawImage(image, -localOffset, 0, this);
g.drawImage(image, imageSize.width - localOffset, 0, this);

// draw outline
g.setColor(Color.black);
g.drawRect(0, 0, imageSize.width - 1, imageSize.height - 1);
  }
private void runWork() {
while (noStopRequested) {
  try {
    Thread.sleep(10); // 10 frames per second

    // adjust the scroll position
    currOffset = (currOffset + 1) % imageSize.width;

    // signal the event thread to call paint()
    repaint();
  } catch (InterruptedException x) {
    Thread.currentThread().interrupt();
  }
  }
 }

public void stopRequest() {
noStopRequested = false;
internalThread.interrupt();
}

public boolean isAlive() {
return internalThread.isAlive();
}


}
荒芜了季节 2024-09-23 11:56:30

这应该是@camickr MarqueePanel 的改进。请参阅上文。

将鼠标事件映射到添加到 MarqueePanel 的特定组件

重写 MarqueePanel 的 add(Component comp) 以引导组件的所有鼠标事件

这里的问题是如何处理从单个组件激发的 MouseEvents成分。
我的方法是从添加的组件中删除鼠标侦听器,并让 MarqueePanel 将事件重定向到正确的组件。

就我而言,这些组件应该是链接。

    @Override
    public Component add(Component comp) {
        comp = super.add(comp);

        if(comp instanceof MouseListener)
             comp.removeMouseListener((MouseListener)comp);

        comp.addMouseListener(this);

        return comp;
    }

然后将组件x映射到MarqueePanel x,最后是正确的组件

@Override
public void mouseClicked(MouseEvent e)
{
    Component source = (Component)e.getSource();
    int x = source.getX() + e.getX();
    int y = source.getY();

    MarqueePanel2 marqueePanel = (MarqueePanel2) ((JComponent)e.getSource()).getParent();
    double x2 = marqueePanel.getWidth();
    double x1 = Math.abs(marqueePanel.scrollOffset);



    if(x >= x1 && x <= x2)
    {
        System.out.println("Bang " + x1);
        Component componentAt = getComponentAt(x+marqueePanel.scrollOffset, y);

        if(comp instanceof MouseListener)
             ((MouseListener) componentAt).mouseClicked(e);

        System.out.println(componentAt.getName());
    }
    else
    {
        return;
    }


    //System.out.println(x);
}

This is supposed to be an improvement of @camickr MarqueePanel. Please see above.

To map mouse events to the specific components added to MarqueePanel

Override add(Component comp) of MarqueePanel in order to direct all mouse events of the components

An issue here is what do do with the MouseEvents fired from the individual components.
My approach is to remove the mouse listeners form the components added and let the MarqueePanel redirect the event to the correct component.

In my case these components are supposed to be links.

    @Override
    public Component add(Component comp) {
        comp = super.add(comp);

        if(comp instanceof MouseListener)
             comp.removeMouseListener((MouseListener)comp);

        comp.addMouseListener(this);

        return comp;
    }

Then map the component x to a MarqueePanel x and finally the correct component

@Override
public void mouseClicked(MouseEvent e)
{
    Component source = (Component)e.getSource();
    int x = source.getX() + e.getX();
    int y = source.getY();

    MarqueePanel2 marqueePanel = (MarqueePanel2) ((JComponent)e.getSource()).getParent();
    double x2 = marqueePanel.getWidth();
    double x1 = Math.abs(marqueePanel.scrollOffset);



    if(x >= x1 && x <= x2)
    {
        System.out.println("Bang " + x1);
        Component componentAt = getComponentAt(x+marqueePanel.scrollOffset, y);

        if(comp instanceof MouseListener)
             ((MouseListener) componentAt).mouseClicked(e);

        System.out.println(componentAt.getName());
    }
    else
    {
        return;
    }


    //System.out.println(x);
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文