无法理解 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
一直到时间结束,这不会改变。
我试图更好地理解视口,所以我制作了一个可以放置在 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
一直到时间结束,这不会改变。