3

我想要实现的行为是默认以 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
4

1 回答 1

3

基本方法是让您的自定义组件实现 Scrollable 并根据需要对 getTracksViewportWidth/-Height 进行编码。

编辑

实现它们以返回 true(从而禁用滚动条),直到达到最小比例,然后返回 false?

确切地说,这就是想法 - 但没有像我预期的那样工作:在达到最小值时的“转折点”,图像被缩放到它的首选,即使有一个自我调整的 getPrefScrollable (这似乎没有任何效果,它在开始时调用一次)

编辑 2

在 OP 的帮助下:

更新视图的preferredSize

终于明白了:(我最初的尝试把它颠倒了;-) 将 prefScrollable 保持为“真正的”pref 并让 pref 返回最小值或 pref,具体取决于滚动条是否可见。

public static class JImagePanel extends JPanel implements Scrollable {

    private BufferedImage image;

    public JImagePanel(BufferedImage image) {
        this.image = image;
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        BufferedImage scaled = //new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
                GraphicsUtilities.createCompatibleImage(getWidth(), getHeight());
        Graphics2D g2 = scaled.createGraphics();
        g2.drawImage(image, 0, 0, getWidth(), getHeight(), null);
        g.drawImage(scaled, 0, 0, this);
        g2.dispose();
    }

    /**
     * This method is used for laying out this container
     * inside the Viewport: let it return the "real" pref
     * or min, depending on whether or not the scrollbars
     * are showing.  
     */
    @Override
    public Dimension getPreferredSize() {
        Dimension size = getImageSize();
        if (!getScrollableTracksViewportWidth()) {
            size.width = getMinimumSize().width;
        }
        if (!getScrollableTracksViewportHeight()) {
            size.height = getMinimumSize().height;
        }
        return size;
    }

    @Override
    public Dimension getMinimumSize() {
        Dimension min = getImageSize();
        min.height /= 2;
        min.width /= 2;
        return min;
    }

    /**
     * This is used for laying out the scrollPane. Keep 
     * it fixed to "real" pref size.
     */
    @Override
    public Dimension getPreferredScrollableViewportSize() {
        return getImageSize();
    }

    /**
     * The unscaled image size (aka: "real" pref)
     */
    protected Dimension getImageSize() {
        return new Dimension(image.getWidth(), image.getHeight());
    }

    @Override
    public boolean getScrollableTracksViewportWidth() {
        return getParent() instanceof JViewport
                && getParent().getWidth() >= getMinimumSize().width;
    }

    @Override
    public boolean getScrollableTracksViewportWidth() {
        return getParent() instanceof JViewport
                && getParent().getWidth() >= getMinimumSize().width;
    }

    @Override
    public boolean getScrollableTracksViewportHeight() {
        return getParent() instanceof JViewport
              && getParent().getHeight() >= getMinimumSize().height;        }


    @Override
    public int getScrollableUnitIncrement(Rectangle visibleRect,
            int orientation, int direction) {
        return 10;
    }

    @Override
    public int getScrollableBlockIncrement(Rectangle visibleRect,
            int orientation, int direction) {
        return 100;
    }

}
于 2012-10-07T15:09:18.637 回答