无法理解 Graphics.getClipBounds() 和视口

Trouble understanding Graphics.getClipBounds() and viewports

我试图更好地理解视口,所以我制作了一个可以放置在 JScrollPane 的行 header 视图中的垂直时间栏。它有效,但是当我进一步调查时,它会在向下滚动时出现,它会绘制不可见的组件区域。我只希望它根据 Graphics.getClipBounds() 绘制可见区域,但随着我继续向下滚动,它会绘制越来越多的组件,直到在底部,打​​印输出表明我正在绘制整个高度组件。

我对 topMillis 的计算似乎有问题,但 endMillis(基于 topMillis)看起来是正确的。

查看问题 运行 程序并注意向下滚动时 topMillis 和 endMillis 之间的差异增加。我希望差异保持不变,因为那应该是用户可见的区域。

顺便提一下,有没有更有效的方法来绘制这个组件?简单的方法是每次都绘制整个组件。如果时间范围太大,那会让人望而却步。我这样做的方式应该更有效,因为我们只绘制用户可见的内容,而不管时间范围有多大。但我的方法与数据面板的大小直接相关,这似乎有问题。有没有一种方法可以使我的组件仅与行 header 视口的大小相同,但仍能准确反映用户在滚动窗格中滚动的内容?

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;

public class TimeBarTest {

   private static final int DATA_HEIGHT = 1000;

   private final JScrollPane mScrollPane;

   private TimeBar mRowHeaderView;

   TimeBarTest() {

      JPanel dataPanel = new JPanel();
      dataPanel.setPreferredSize(new Dimension(0, DATA_HEIGHT));

      mScrollPane = new JScrollPane(dataPanel);
      mScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
      mScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);

      JButton cornerButton = new JButton("Hi");
      cornerButton.addActionListener(pEvent -> {
         mRowHeaderView.setTime(System.currentTimeMillis());
         mScrollPane.getVerticalScrollBar().setValue(0);
      });
      cornerButton.setPreferredSize(new Dimension(20, 20));

      JPanel columnHeader = new JPanel(new BorderLayout());
      columnHeader.setPreferredSize(new Dimension(0, 30));
      columnHeader.setBorder(BorderFactory.createLineBorder(Color.GRAY));
      columnHeader.add(new JLabel("Column Header", SwingConstants.CENTER), BorderLayout.CENTER);

      mRowHeaderView = new TimeBar(DATA_HEIGHT);

      mScrollPane.setCorner(ScrollPaneConstants.UPPER_LEFT_CORNER, cornerButton);
      mScrollPane.setRowHeaderView(mRowHeaderView);
      mScrollPane.setColumnHeaderView(columnHeader);

      JPanel contentPane = new JPanel(new BorderLayout());
      contentPane.setPreferredSize(new Dimension(500, 475));
      contentPane.add(mScrollPane, BorderLayout.CENTER);

      JFrame frame = new JFrame();
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.setContentPane(contentPane);

      frame.pack();
      frame.setLocationRelativeTo(null);
      frame.setVisible(true);
   }

   public static void main(String... args) {
      SwingUtilities.invokeLater(() -> new TimeBarTest());
   }

   private class TimeBar extends JComponent {

      private final int MAJOR_TICK_LENGTH = 8;
      private final int MINOR_TICK_LENGTH = 4;

      private final int MINUTES_PER_MAJOR = 5;
      private final int MINUTES_PER_MINOR = 1;
      private final long MILLIS_PER_PIXEL = 4000;

      private final long MILLIS_PER_MAJOR = TimeUnit.MINUTES.toMillis(MINUTES_PER_MAJOR);
      private final long MILLIS_PER_MINOR = TimeUnit.MINUTES.toMillis(MINUTES_PER_MINOR);

      private final SimpleDateFormat mSimpleDateFormat = new SimpleDateFormat("hh:mm");

      private long mTime;

      TimeBar(int pHeight) {
         setPreferredSize(new Dimension(50, pHeight));

         mTime = System.currentTimeMillis();
      }

      public void setTime(long pTime) {
         mTime = pTime;
         repaint();
      }

      @Override
      protected void paintComponent(Graphics pGraphics) {
         super.paintComponent(pGraphics);

         pGraphics.setColor(Color.black);

         Rectangle clipBounds = pGraphics.getClipBounds();
         Rectangle visibleRect = getVisibleRect();

         // Determine the start and end time based on the visible area.
         long topMillis = mTime - (clipBounds.y * MILLIS_PER_PIXEL);
         long endMillis = topMillis - ((clipBounds.y + clipBounds.height) * MILLIS_PER_PIXEL);

         // Determine where we should start drawing the ticks.
         long startMillis = topMillis - (topMillis % MILLIS_PER_MINOR);

         System.out.println("    clipBounds=" + clipBounds);
         System.out.println("   visibleRect=" + visibleRect);
         SimpleDateFormat dateFormat = new SimpleDateFormat("hh:mm:ss");
         System.out.println("      origTime=" + dateFormat.format(new Date(mTime)));
         System.out.println("       topTime=" + dateFormat.format(new Date(topMillis)));
         System.out.println("   startMillis=" + dateFormat.format(new Date(startMillis)));
         System.out.println("       endTime=" + dateFormat.format(new Date(endMillis)));
         System.out.println("     topMillis=" + topMillis);
         System.out.println("   startMillis=" + startMillis);
         System.out.println("     endMillis=" + endMillis);
         System.out.println("millisPerMajor=" + MILLIS_PER_MAJOR);
         System.out.println("millisPerMinor=" + MILLIS_PER_MINOR);

         // Draw the ticks and labels backwards through time.
         for (long i = startMillis; i >= endMillis; i -= MILLIS_PER_MINOR) {
            int pixel = (int) ((topMillis - i) / MILLIS_PER_PIXEL);
            System.out.println("pixel=" + pixel);

            if (i % MILLIS_PER_MAJOR == 0) {
               String text = mSimpleDateFormat.format(new Date(i));
               pGraphics.drawString(text, 1, (int) (pixel + 4));
               pGraphics.drawLine(clipBounds.width, (int) pixel, clipBounds.width - MAJOR_TICK_LENGTH, (int) pixel);
            } else {
               pGraphics.drawLine(clipBounds.width, (int) pixel, clipBounds.width - MINOR_TICK_LENGTH, (int) pixel);
            }
         }
      }
   }
}

我认为第一期只是代数问题。

您将显示的开始时间是 mTime - clipBounds.y*MILLIS_PER_PIXEL,最后时间是 mTime - (clipBounds.y + clipBounds.height)*MILLIS_PER_PIXEL。如果你想把它写成开始时间的函数。

endMillis = topMillis - clipBounds.height*MILLIS_PER_PIXEL;

下一期稍微基础一些。您不想根据视口的位置更改绘图。本质上,你会画出整个场景,但正如你所说,时间可能会非常长,你可以进行一些手动剪辑。

因此您的坐标将基于 mTime 而不是开始时间。

int pixel = (int) ((mTime-i) / MILLIS_PER_PIXEL);

如果你不剪辑,你会从 mTime 一直到时间结束,这不会改变。