実装したい動作は、デフォルトで 1:1 の縮尺でビューを表示することです。コンテナーが (ユーザーが親 JFrame を動的にサイズ変更することによって) 大きくなる場合、ビューはより大きな領域に収まるようにスケーリングする必要があります。小さくする場合は、小さい領域に合わせてビューを拡大縮小する必要があります (上限まで)。最小サイズよりも小さい場合、スクロール バーはビューポートと共に表示され、最小縮尺で表示されるビュー内の移動をサポートする必要があります。
サイズ変更がいつ発生したかを判断するために JScrollPane と ComponentListener を使用して、機能が不十分な実装が現在あります。新しいサイズに基づいて、ビューが収まるように描画するための新しいスケール係数が設定され (最小スケールまで)、ビューの preferredSize が設定され、revalidate() が呼び出されます。問題は、これにより「ジッター」表示が発生することです。スクロールバーの表示を避けるために新しいスケールが適用されるポイントを超えてサイズ変更すると、スクロールバーが表示されてから消えます。これは、私のアプローチが予測的ではなく事後対応的であるためだと思います。つまり、LayoutManager はビューの preferredSize のみを見て、それに基づいてレイアウトを実行します。私' m JScrollPane のサイズ変更をリッスンし、サイズが小さすぎる場合にのみ、ビューの preferredSize を変更し、revalidate() の呼び出しでコンテナーを再度レイアウトします。少なくとも私はそう理解しています。
JScrollPane、JViewport、およびそれぞれの LayoutManager のソースを確認してきました。この時点で、私は 1 つ以上をサブクラス化して、より良い (予測的で、よりスムーズなサイジングを実現する) 実装を検討しています。これは、他の人が以前に実装したに違いない動作のようです。既存の Containers/LayoutManagers/methods を使用して行う別の方法はありますか?異なる LnF またはプラットフォーム間で意図しない動作をサブクラス化して危険にさらすことなく、これを行うことはできますか?
編集: ScrollPaneLayout のプロトタイプ サブクラスをハッキングしました。スクロールバーを追加する前に、ビューの最小サイズをチェックします。これは機能しています (ビューポートがビューの優先サイズよりも小さい場合にスクロールバーを表示する元の動作ではなく、ビューポートがビューの最小サイズよりも小さくなるまでスクロールバーが表示されないという点で) が、スクロールバーが表示されると、ビューはこれは最小サイズではなく推奨サイズです。ViewportLayout クラスもハックする必要があるかもしれませんが、これはすぐに堅牢になるとは思えません。
編集:
私は基本に戻って提案を再試行し、成功しました。ScrollPaneLayout と ViewportLayout のデフォルトの動作をオーバーロードしようとした私の試みと、それらの間の相互作用は正しい方向ではありませんでした。最初の試行の後、LayoutManagers が処理を行った後に不適切なサイズ設定を修正するための「リアクティブ」アプローチのちらつきと不安定性を回避する方法はないと確信しました。幸いなことに、どちらの LayoutManager もサブクラス化せずにこれを機能させる方法があります。Scrollable
インターフェイス(以前は正しく行っていましたが、動作させるために他の変更を加えていませんでした)。実際には、ビューポートのサイズに応じて true/false を返すように 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