我想要实现的行为是默认以 1:1 的比例显示视图。如果其容器变大(通过用户动态调整父 JFrame 的大小),则应缩放视图以适应更大的区域;如果做得更小,则应缩放视图以适应较小的区域 - 达到限制。当小于最小尺寸时,滚动条应与视口一起出现,以支持在以最小比例显示的视图周围导航。
我现在有一个运行不佳的实现,使用 JScrollPane 和 ComponentListener 来确定何时发生调整大小。基于新的大小,设置新的比例因子以绘制适合的视图(直到最小比例),设置视图的 preferredSize 并调用 revalidate()。问题是这会导致“抖动”显示;当调整大小超过应用新比例以避免显示滚动条的点时,滚动条出现,然后消失。我相信这是因为我的方法是被动的,而不是预测的。也就是说,LayoutManager 只是查看视图的 preferredSize 并基于它执行布局。一世' m 监听 JScrollPane 的调整大小,当它太小时才更改视图的首选大小,然后通过调用 revalidate() 再次布局容器。至少我是这么理解的。
我一直在查看 JScrollPane、JViewport 和它们各自的 LayoutManagers 的源代码。在这一点上,我正在考虑将一个或多个子类化以获得更好的(可预测的,导致更平滑的大小调整)实现。这似乎是其他人之前必须实现的行为。是否有另一种方法可以使用现有的 Containers/LayoutManagers/methods 来做这不会在不同的 LnF 或平台上进行子类化和冒意外行为的风险?
编辑:我已经破解了 ScrollPaneLayout 的原型子类。它在添加滚动条之前检查视图的最小尺寸。这是有效的(因为滚动条在视口小于视图的最小尺寸之前不会出现,而不是在视口小于视图的首选尺寸时显示滚动条的原始行为)但是当显示滚动条时视图认为它仍然是首选尺寸,而不是最小尺寸。我可能也不得不修改 ViewportLayout 类,但这很快就变成了我怀疑它是否健壮的东西。
编辑:
我回到基础并再次尝试了这些建议,并且成功了。我试图重载 ScrollPaneLayout 和 ViewportLayout 的默认行为,它们之间的交互方向不正确。在我第一次尝试之后,我确信在 LayoutManager 完成他们的工作之后,我无法避免我的“反应式”方法在修复不正确的大小时出现闪烁和不稳定性。幸运的是,有一种方法可以在不继承任何 LayoutManager 的情况下完成这项工作 - 如上所述,它是通过实现Scrollable
界面(我之前已经正确完成,但没有进行其他更改以使其工作)。诀窍确实是实现 getScrollableTracksViewport() 以便它根据视口大小返回真/假。除了我正在做的其他计算之外,我还没有做的是更新视图的preferredSize。这是关键的一步。(请注意,我还依赖于侦听 ComponentListener.componentResized() 通知来触发正确设置返回值所需的计算)以下是功能代码,感谢您的帮助。
@SuppressWarnings("serial")
class ScalingScreenPanel extends JScrollPane {
private static final Dimension PREFERRED_SIZE = new Dimension(800,200);
private static ScalingScreen sScreen;
public ScalingScreenPanel() {
setPreferredSize(PREFERRED_SIZE);
getViewport().addComponentListener(new ComponentAdapter() {
public void componentResized(ComponentEvent event) {
sScreen.calculateSizes(event.getComponent().getSize());
}
});
setViewportView(sScreen=new ScalingScreen());
}
public void setShow(Show pShow) {
sScreen.setShow(pShow);
}
} // class PreviewScreenPanel
@SuppressWarnings("serial")
public abstract class ScalingScreen extends JPanel implements Scrollable {
private static final Dimension PREFERRED_SIZE = new Dimension(1000,100);
private static final JLabel EMPTY_PANEL = new JLabel("Empty",SwingConstants.CENTER);
private static final int DEFAULT_SIZE = 7;
private static final int MINIMUM_SIZE = 5;
private static final int SPACING = 3;
private static final int MINIMUM_PITCH = MINIMUM_SIZE+SPACING; // Do not modify directly
private static final int DEFAULT_PITCH = DEFAULT_SIZE+SPACING; // Do not modify directly
protected int cSize;
protected int cPitch;
protected Dimension cOrigin;
private Dimension cMinimumScreenSize;
private Dimension cDefaultScreenSize;
protected Dimension cCurrentScreenSize;
private boolean cEnableVerticalScrollbar = false, cEnableHorizontalScrollbar = false;
protected Dimension cGridSize = null;
ScalingScreen() {
cOrigin = new Dimension(0,0);
add(EMPTY_PANEL);
}
public void setShow(Show pShow) {
remove(EMPTY_PANEL);
cGridSize = new Dimension(pShow.dimension());
cMinimumScreenSize = new Dimension(cGridSize.width*MINIMUM_PITCH+SPACING,cGridSize.height*MINIMUM_PITCH+SPACING);
cDefaultScreenSize = new Dimension(cGridSize.width*DEFAULT_PITCH+SPACING,cGridSize.height*DEFAULT_PITCH+SPACING);
setMinimumSize(cMinimumScreenSize);
setPreferredSize(cDefaultScreenSize);
calculateSizes(getSize());
repaint();
}
public void calculateSizes(Dimension pViewportSize) {
if (cGridSize==null) return;
cPitch = Math.max(MINIMUM_PITCH,Math.min((pViewportSize.width-SPACING)/cGridSize.width,(pViewportSize.height-SPACING)/cGridSize.height));
cSize = cPitch - SPACING;
cOrigin = new Dimension((pViewportSize.width-(cPitch*cGridSize.width))/2,(pViewportSize.height-(cPitch*cGridSize.height))/2);
cCurrentScreenSize = new Dimension(Math.max(pViewportSize.width,cMinimumScreenSize.width),Math.max(pViewportSize.height,cMinimumScreenSize.height));
Dimension preferredSize = new Dimension();
if (pViewportSize.width<cMinimumScreenSize.width) {
cOrigin.width = 0;
cEnableHorizontalScrollbar = true;
preferredSize.width = cMinimumScreenSize.width;
} else {
cOrigin.width = (pViewportSize.width-(cPitch*cGridSize.width))/2;
cEnableHorizontalScrollbar = false;
preferredSize.width = cDefaultScreenSize.width;
}
if (pViewportSize.height<cMinimumScreenSize.height) {
cOrigin.height = 0;
cEnableVerticalScrollbar = true;
preferredSize.height = cMinimumScreenSize.height;
} else {
cOrigin.height = (pViewportSize.height-(cPitch*cGridSize.height))/2;
cEnableVerticalScrollbar = false;
preferredSize.height = cDefaultScreenSize.height;
}
setPreferredSize(preferredSize);
repaint();
}
// Methods to implement abstract Scrollable interface
@Override
public Dimension getPreferredScrollableViewportSize() {
return getPreferredSize();
}
@Override
public boolean getScrollableTracksViewportHeight() {
return !cEnableVerticalScrollbar;
}
@Override
public boolean getScrollableTracksViewportWidth() {
return !cEnableHorizontalScrollbar;
}
@Override
public int getScrollableBlockIncrement(Rectangle pVisibleRect, int pOrientation, int pDirection) {
switch (pOrientation) {
case SwingConstants.VERTICAL:
return pVisibleRect.height/2;
case SwingConstants.HORIZONTAL:
return pVisibleRect.width/2;
default:
return 0;
}
}
@Override
public int getScrollableUnitIncrement(Rectangle pVisibleRect,
int pOrientation, int pDirection) {
switch (pOrientation) {
case SwingConstants.VERTICAL:
return 1;
case SwingConstants.HORIZONTAL:
return 1;
default:
return 0;
}
}
@Override
public void paintComponent(Graphcs g) {
// custom drawing stuff
}
} // class ScalingScreen