难以理解Graphics.getClipBounds()和视口

问题描述 投票:1回答:1

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

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

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

附带说明,有没有更有效的绘制方法?简单的方法是每次绘制整个组件。如果时间范围太大,那将变得令人望而却步。我这样做的方式应该更高效,因为无论时间范围多大,我们都只会绘制用户可见的内容。但是我的方法直接与数据面板的大小有关,这似乎是有问题的。有没有一种方法可以使我的组件成为行标题视口的大小,但仍可以准确反映用户在滚动窗格中滚动的内容?

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);
            }
         }
      }
   }
}
java graphics2d
1个回答
0
投票

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

您将显示的开始时间为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一直到结束为止,并且不会改变。

© www.soinside.com 2019 - 2024. All rights reserved.