【问题标题】:Marquee effect in Java SwingJava Swing 中的选取框效果
【发布时间】:2011-04-06 17:59:30
【问题描述】:

如何在 Java Swing 中实现 Marquee 效果

【问题讨论】:

    标签: java swing marquee


    【解决方案1】:

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

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

    一个像样的代码的关键是使滚动尽可能平滑和无闪烁。因此,可能需要考虑对结果进行双缓冲,首先将滚动位绘制到位图中,然后一次性渲染,而不是直接绘制到屏幕上。

    【讨论】:

      【解决方案2】:

      这是我为帮助您入门而整理的一些代码。我通常会采用 ActionListener 代码并将其放入某种 MarqueeController 类中,以使该逻辑与面板分开,但这是关于组织 MVC 架构的不同问题,在这样一个足够简单的类中它可能不是太重要了。

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

      公共类 MarqueePanel 扩展 JPanel { 私人 JLabel 文本标签; 私人int panelLocation; 私有 ActionListener 任务执行器; 私有布尔 isRunning = false; 公共静态最终 int FRAMES_PER_SECOND = 24; 公共静态最终 int MOVEMENT_PER_FRAME = 5; /** * 类构造函数创建一个选取框面板。 */ 公共选框面板(){ this.setLayout(null); this.textLabel = new JLabel("在此处滚动文本"); 这个.panelLocation = 0; this.taskPerformer = new ActionListener() { 公共无效actionPerformed(ActionEvent evt){ MarqueePanel.this.tickAnimation(); } } } /** * 开始动画。 */ 公共无效开始(){ this.isRunning = true; this.tickAnimation(); } /** * 停止动画。 */ 公共无效停止(){ this.isRunning = false; } /** * 将标签向左移动一帧。如果超出显示范围,请将其移回 * 向右,超出显示范围。 */ 私人无效tickAnimation(){ this.panelLocation -= MarqueePanel.MOVEMENT_PER_FRAME; if (this.panelLocation

      【讨论】:

      • 是否缺少某些部分?
      • 我仍然看到问题:我希望 MarqueePanel 扩展 JPanel,而 tickAnimation() 需要返回类型。另外,start()stop() 需要查看Timer。对不起,如果我狙击了这个问题。
      • 没问题。学过的知识。下次我要转储这样的解决方案 - 先测试它。 :)
      【解决方案3】:

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

      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));
          }
      }
      

      【讨论】:

      • +1 用于提供完整的可理解代码 + 正在运行的程序的屏幕截图 :)(正是我喜欢的方式!!!)我希望我可以 +100
      【解决方案4】:

      我知道这是一个迟到的答案,但我刚刚看到另一个关于已关闭选取框的问题,因为它被认为与此答案重复。

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

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

      【讨论】:

      • 我向选取框添加了带有动作的链接,但我无法让它们工作。鼠标事件没有进入正确的组件,有时会,有时不会。
      • @user905374,不支持。我有一个几年前玩过的版本,它确实试图将鼠标事件重新分配到正确的组件。不确定它的效果如何。如果您想试用此版本,请使用博客中的“联系我们”页面向我发送消息,我将向您发送此版本。
      • 感谢您的回答,是的,通过检查代码我很快意识到这不受支持。我想试试其他版本,我从“联系我们”页面向您发送了一条消息。谢谢。
      • 请在下面的回答中看到我的想法
      【解决方案5】:

      将 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();
      }
      
      
      }
      

      【讨论】:

        【解决方案6】:

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

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

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

        这里的一个问题是如何处理从各个组件触发的 MouseEvent。 我的方法是从添加的组件中删除鼠标侦听器,让 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);
        }
        

        【讨论】:

        • 对 MouseEvents 使用 SwingUtilities 中的方法而不是 getParent() 等
        • 我不确定在 SwingUtilities 中使用哪些方法
        • getDeepestComponentAt, convert(Xxx)Xxx 但请确保我的 cmets 这里是为了舒适,因为可能有 getParent().getParent() :-)
        猜你喜欢
        • 2011-09-05
        • 2023-04-08
        • 1970-01-01
        • 2011-12-03
        • 1970-01-01
        • 2010-09-30
        • 2014-07-26
        • 2015-01-24
        • 1970-01-01
        相关资源
        最近更新 更多