如何在不损失更高分辨率的情况下,在视网膜显示器上使用Java Swing加倍缓冲区?
我在JLayer子类中使用双缓冲图形在JavaSwing应用程序中实现一个简单的滑动动画。它在较旧的显示器上运行良好,但当我在视网膜显示器上运行它时,屏幕在动画开始时会失去双倍的分辨率,在动画结束时会恢复分辨率。我不知道如何在动画中保持较高的分辨率 我的动画方法最初是这样的:如何在不损失更高分辨率的情况下,在视网膜显示器上使用Java Swing加倍缓冲区?,java,swing,jlayer,Java,Swing,Jlayer,我在JLayer子类中使用双缓冲图形在JavaSwing应用程序中实现一个简单的滑动动画。它在较旧的显示器上运行良好,但当我在视网膜显示器上运行它时,屏幕在动画开始时会失去双倍的分辨率,在动画结束时会恢复分辨率。我不知道如何在动画中保持较高的分辨率 我的动画方法最初是这样的: private void animate() { Timer timer = new Timer(frameMillis, null); final ActionListener actionListener =
private void animate() {
Timer timer = new Timer(frameMillis, null);
final ActionListener actionListener = (evt) -> { /* omitted for brevity */ };
timer.addActionListener(actionListener);
int imageType = BufferedImage.TYPE_INT_ARGB;
upcomingScreen = new BufferedImage(liveComponent.getWidth(), liveComponent.getHeight(), imageType);
Graphics2D graphics2D = (Graphics2D) upcomingScreen.getGraphics();
liveComponent.paint(graphics2D); // liveComponent is a JComponent
graphics2D.dispose();
timer.start();
}
我试着将图像大小加倍,但没用
upcomingScreen = new BufferedImage(liveComponent.getWidth()*2, liveComponent.getHeight()*2, imageType);
为了反映这些变化,我在LayerUI中通过加倍xLimit
,width
,height
,更改了绘图代码:
public void paint(final Graphics g, final JComponent c) {
if (isAnimating) {
int xLimit = (c.getWidth()*2 * frame) / maxFrames;
int width = c.getWidth()*2;
int height = c.getHeight()*2;
g.drawImage(uScreen, 0, 0, xLimit, height, 0, 0, xLimit, height, c);
g.drawImage(pScreen, xLimit, 0, width, height, xLimit, 0, width, height, c);
} else {
super.paint(g, c);
}
}
这没用。不管有没有最后的变化,它都会画相同的图,这毫无意义
下面是一个说明问题的类:
/**
* <p>Created by IntelliJ IDEA.
* <p>Date: 5/2/20
* <p>Time: 10:25 AM
*
* @author Miguel Mu\u00f1oz
*/
@SuppressWarnings({"HardcodedLineSeparator", "StringConcatenation", "HardCodedStringLiteral", "DuplicatedCode"})
public final class SwipeViewTest extends JPanel {
public static final String text1 = "Demo of Swipe View.\n\nThe swipe button will toggle between two pages of text. It has a built-in " +
"special effect, which is a swipe. When you hit the swipe button, it should flip between two pages of text. This worked fine on " +
"the older displays, but for some reason, on a Retina display, the text briefly switches to low resolution as the swipe proceeds, " +
"then switches back once it has finished. This code is written for retina displays. I don't know if it will work for the older, " +
"low resolution displays.\n\nYou can watch it swipe by hitting the space bar or by clicking the swipe button.";
public static final String text2 = "Demo of Swipe View.\n\nThis is the second page of the swipe-text demo. The change in resolution is " +
"most easily noticed when watching the line at the top, which doesn't change as the swipe is performed.";
private final SwipeView<TestView> swipeView;
private final TestView testView;
public static void main(String[] args) {
JFrame frame = new JFrame("SwipeView demo");
SwipeViewTest comp = new SwipeViewTest();
comp.install();
frame.add(comp);
frame.setLocationByPlatform(true);
frame.pack();
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setVisible(true);
}
private boolean page1 = true;
private SwipeViewTest() {
super(new BorderLayout());
testView = new TestView();
swipeView = SwipeView.wrap(testView, 1000);
add(BorderLayout.CENTER, swipeView.getLayer());
}
private void install() {
JButton jButton = new JButton("Swipe");
jButton.addActionListener(this::doSwipe);
add(jButton, BorderLayout.PAGE_END);
AncestorListener ancestorListener = new AncestorListener() {
@Override
public void ancestorAdded(final AncestorEvent event) {
JComponent button = event.getComponent();
button.requestFocus();
button.removeAncestorListener(this); // execute only once.
}
@Override public void ancestorRemoved(final AncestorEvent event) { }
@Override public void ancestorMoved(final AncestorEvent event) { }
};
jButton.addAncestorListener(ancestorListener);
}
private void doSwipe(ActionEvent ignored) {
swipeView.swipeLeft(this::flipPage);
}
private void flipPage() {
page1 = !page1;
if (page1) {
testView.setText(text1);
} else {
testView.setText(text2);
}
}
private static class TestView extends JPanel {
private final JTextArea textArea;
TestView() {
super(new BorderLayout());
textArea = new JTextArea(20, 40);
JScrollPane scrollPane = new JScrollPane(textArea, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
textArea.setLineWrap(true);
textArea.setWrapStyleWord(true);
textArea.setEditable(false);
textArea.setText(text1);
add(scrollPane, BorderLayout.CENTER);
}
private void setText(String text) {
textArea.setText(text);
}
}
/**
* SwipeView adds a swipe special effect to a Component. This draws a swipe-right or swipe-left effect on a chosen
* action. It also optionally supports a repeated action when the mouse is held down.
* <p>
* This class is very specific right now, but I hope to generalize it for other special effects later.
* <p>Created by IntelliJ IDEA.
* <p>Date: 4/4/18
* <p>Time: 12:38 AM
*
* @author Miguel Mu\u00f1oz
*/
@SuppressWarnings("MagicNumber")
public static final class SwipeView<C extends JComponent> extends LayerUI<C> {
public static <J extends JComponent> SwipeView<J> wrap(J view, int durationMillis) {
JLayer<J> jLayer = new JLayer<>(view);
final SwipeView<J> ui = new SwipeView<>(view, jLayer, durationMillis);
jLayer.setUI(ui);
return ui;
}
private final C liveComponent;
private Image priorScreen = null;
private Image upcomingScreen = null;
private final JLayer<C> layer;
private boolean isAnimating = false;
private SwipeDirection swipeDirection = SwipeDirection.SWIPE_RIGHT;
private final int maxFrames;
// Calculated:
@SuppressWarnings("FieldCanBeLocal")
private final int frameMillis;
private int frame = 0;
private final long startTime = System.currentTimeMillis();
private SwipeView(C view, JLayer<C> theLayer, int animationDurationMillis) {
super();
liveComponent = view;
layer = theLayer;
maxFrames = (30 * animationDurationMillis) / 1000;
frameMillis = animationDurationMillis / maxFrames;
}
public JLayer<C> getLayer() { return layer; }
/**
* Perform the specified operation with a swipe-right special effect. This is often used in an ActionListener:
* <pre>
* first.addActionListener((e) -> swipeView.swipeRight(recordModel::goFirst));
* </pre>
* Here, the Action listener will perform a Swipe-right after executing the goFirst() method of recordModel.
*
* @param operation The operation
*/
@SuppressWarnings("WeakerAccess")
public void swipeRight(Runnable operation) {
swipe(operation, SwipeDirection.SWIPE_RIGHT);
}
/**
* Perform the specified operation with a swipe-left special effect. This is often used in an ActionListener:
* <pre>
* first.addActionListener((e) -> swipeView.swipeLeft(recordModel::goFirst));
* </pre>
* Here, the Action listener will perform a Swipe-Left after executing the goFirst() method of recordModel.
*
* @param operation The operation
*/
@SuppressWarnings("WeakerAccess")
public void swipeLeft(Runnable operation) {
swipe(operation, SwipeDirection.SWIPE_LEFT);
}
private void swipe(Runnable operation, SwipeDirection swipeDirection) {
prepareToAnimate(swipeDirection);
operation.run();
animate();
}
// @SuppressWarnings({"HardCodedStringLiteral", "HardcodedFileSeparator"})
@Override
public void paint(final Graphics g, final JComponent c) {
if (isAnimating) {
int xLimit = (c.getWidth() * 2 * frame) / maxFrames;
if (swipeDirection == SwipeDirection.SWIPE_LEFT) {
xLimit = (c.getWidth() * 2) - xLimit;
}
int width = c.getWidth() * 2;
int height = c.getHeight() * 2;
// //noinspection UseOfSystemOutOrSystemErr
// System.out.printf("Dimensions: Frame: %d/%d (at %d) xLimit: %4d (%4d x %4d) (from %4d x %4d) Animating: %b%n",
// frame, maxFrames, System.currentTimeMillis() - startTime, xLimit, width, height, c.getWidth(), c.getHeight(), isAnimating);
assert upcomingScreen != null;
assert priorScreen != null;
Image pScreen = Objects.requireNonNull(priorScreen);
Image uScreen = Objects.requireNonNull(upcomingScreen);
if (swipeDirection == SwipeDirection.SWIPE_RIGHT) {
g.drawImage(uScreen, 0, 0, xLimit, height, 0, 0, xLimit, height, c);
g.drawImage(pScreen, xLimit, 0, width, height, xLimit, 0, width, height, c);
} else {
g.drawImage(uScreen, xLimit, 0, width, height, xLimit, 0, width, height, c);
g.drawImage(pScreen, 0, 0, xLimit, height, 0, 0, xLimit, height, c);
}
} else {
super.paint(g, c);
}
}
private void prepareToAnimate(SwipeDirection swipeDirection) {
this.swipeDirection = swipeDirection;
isAnimating = true;
frame = 0;
// Save current state
priorScreen = new BufferedImage(liveComponent.getWidth() * 2, liveComponent.getHeight() * 2, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics2D = (Graphics2D) priorScreen.getGraphics();
liveComponent.paint(graphics2D);
graphics2D.dispose();
}
private void animate() {
Timer timer = new Timer(frameMillis, null);
final ActionListener actionListener = (evt) -> {
frame++;
layer.repaint();
if (frame == maxFrames) {
frame = 0;
isAnimating = false;
timer.stop(); // Investigate: Am I leaking timers?
}
};
timer.addActionListener(actionListener);
upcomingScreen = new BufferedImage(liveComponent.getWidth() * 2, liveComponent.getHeight() * 2, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics2D = (Graphics2D) upcomingScreen.getGraphics();
liveComponent.paint(graphics2D);
graphics2D.dispose();
timer.start();
}
}
public static enum SwipeDirection {
@SuppressWarnings("JavaDoc") SWIPE_RIGHT,
@SuppressWarnings("JavaDoc") SWIPE_LEFT
}
}
/**
*由IntelliJ IDEA创建。
*日期:5/2/20
*时间:上午10:25
*
*@author Miguel Mu\u00f1oz
*/
@SuppressWarnings({“HardcodedLineSeparator”、“StringConcatenation”、“HardCodedStringLiteral”、“DuplicatedCode”})
公共最终类SwipeViewTest扩展了JPanel{
public static final String text1=“滑动视图的演示。\n\n滑动按钮将在两页文本之间切换。它有一个内置的”+
特殊效果,即滑动。当您点击滑动按钮时,它应该在两页文本之间翻转。这在+
较旧的显示器,但由于某些原因,在视网膜显示器上,文本在滑动过程中会短暂切换为低分辨率+
“然后在完成后切换回去。这段代码是为视网膜显示器编写的。我不知道它是否适用于老年人。”+
“低分辨率显示。\n\n您可以通过点击空格键或单击“滑动”按钮来观看它的滑动。”;
public static final String text2=“滑动视图演示。\n\n这是滑动文本演示的第二页。分辨率更改为”+
“最容易被注意到的是看顶部的线条,它不会随着刷卡的进行而改变。”;
私人最终SwipeView SwipeView;
私有最终测试视图测试视图;
公共静态void main(字符串[]args){
JFrame=新JFrame(“SwipeView演示”);
SwipeViewTest comp=新的SwipeViewTest();
comp.install();
帧。添加(comp);
frame.setLocationByPlatform(真);
frame.pack();
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setVisible(true);
}
私有布尔值page1=true;
专用SwipeViewTest(){
超级(新边框布局());
testView=newtestview();
swipeView=swipeView.wrap(testView,1000);
添加(BorderLayout.CENTER,swipeView.getLayer());
}
私有void安装(){
JButton JButton=新的JButton(“滑动”);
addActionListener(this::dosweep);
添加(jButton,BorderLayout.PAGE_END);
AncestorListener AncestorListener=新的AncestorListener(){
@凌驾
添加了公共无效的AncestorEvent(最终AncestorEvent事件){
JComponent button=event.getComponent();
requestFocus()按钮;
button.removancestorlistener(this);//只执行一次。
}
@重写公共无效的AnceStoreRemoved(最终AnceStoreEvent事件){}
@覆盖公共无效的ancestorMoved(最终AnceStoreEvent事件){}
};
addAncestorListener(ancestorListener);
}
私有void doswip(忽略ActionEvent){
swipeView.swipleft(this::flipage);
}
私人股本{
第1页=!第1页;
如果(第1页){
setText(text1);
}否则{
setText(text2);
}
}
私有静态类TestView扩展了JPanel{
私人最终JTextArea textArea;
TestView(){
超级(新边框布局());
textArea=新的JTextArea(20,40);
JScrollPane scrollPane=新的JScrollPane(textArea,JScrollPane.VERTICAL\u SCROLLBAR\u ALWAYS,JScrollPane.HORIZONTAL\u SCROLLBAR\u NEVER);
textArea.setLineWrap(true);
textArea.setWrapStyleWord(true);
textArea.setEditable(false);
textArea.setText(text1);
添加(滚动窗格,BorderLayout.CENTER);
}
私有void setText(字符串文本){
textArea.setText(text);
}
}
/**
*SwipeView向组件添加滑动特殊效果。这会在所选组件上绘制向右滑动或向左滑动效果
*操作。它还可以选择在按住鼠标时支持重复操作。
*
*这个类现在非常具体,但我希望在以后的其他特效中推广它。
*由IntelliJ IDEA创建。
*日期:2018年4月4日
*时间:上午12:38
*
*@author Miguel Mu\u00f1oz
*/
@抑制警告(“MagicNumber”)
公共静态最终类SwipeView扩展了{
公共静态SwipeView wrap(J视图,整数持续时间毫秒){
JLayer JLayer=新JLayer(视图);
最终SwipeView ui=新SwipeView(视图、jLayer、持续时间毫秒);
jLayer.setUI(ui);
返回用户界面;
}
私有最终C-liveComponent;
私有图像优先屏幕=空;
私有图像upcomingScreen=null;
私有层;
私有布尔isAnimating=false;
专用SwipeDirection SwipeDirection=SwipeDirection.SWIPE_RIGHT;
私有最终整数最大帧;
//计算:
@抑制警告(“FieldCanBeLocal”)
私有最终整数帧;
私有整数帧=0;
private final long startTime=System.currentTimeMillis();
专用SwipeView(C视图、JLayer-theLayer、int-animationDurationMillis){
超级();
liveComponent=视图;
层=层;
最大帧=(30*animationDurationMillis)/1000;
frameMillis=动画持续时间Millis/maxFrames;
}
public JLayer getLayer(){return layer;}
/**
*使用向右滑动的特殊效果执行指定的操作。这通常在ActionListener中使用:
*
*first.addActionListener((e)->swipeView.SwiperRight(recordModel::goFirst));
*
*这里是A
//priorScreen = new BufferedImage(liveComponent.getWidth() * 2, liveComponent.getHeight() * 2, BufferedImage.TYPE_INT_ARGB);
priorScreen = new BufferedImage(liveComponent.getWidth() * 2, liveComponent.getHeight() * 2, BufferedImage.TYPE_INT_RGB);
private static final int SCALE = calculateScaleForDefaultScreen();
private static int calculateScaleForDefaultScreen() {
// scale will be 2.0 for a Retina screen, and 1.0 for an older screen
double scale = GraphicsEnvironment.getLocalGraphicsEnvironment()
.getDefaultScreenDevice()
.getDefaultConfiguration()
.getDefaultTransform()
.getScaleX(); // Requires Java 9+ to work. Compiles under Java 8 but always returns 1.0.
//noinspection NumericCastThatLosesPrecision
return (int) Math.round(scale);
}
Graphics2D graphics2D = (Graphics2D) priorScreen.getGraphics();
graphics2D.scale(SCALE, SCALE);
liveComponent.paint(graphics2D); // paint the current state of liveComponent into the image
graphics2D.dispose();
Graphics2D graphics2D = (Graphics2D) upcomingScreen.getGraphics();
graphics2D.scale(SCALE, SCALE);
liveComponent.paint(graphics2D); // paint the upcoming state of liveComponent into the image
graphics2D.dispose();
if (swipeDirection == SwipeDirection.SWIPE_RIGHT) {
g.drawImage(uScreen, 0, 0, xLimit, height, 0, 0, xLimit*SCALE, height*SCALE, c);
g.drawImage(pScreen, xLimit, 0, width, height, xLimit*SCALE, 0, width*SCALE, height*SCALE, c);
} else {
g.drawImage(uScreen, xLimit, 0, width, height, xLimit*SCALE, 0, width*SCALE, height*SCALE, c);
g.drawImage(pScreen, 0, 0, xLimit, height, 0, 0, xLimit*SCALE, height*SCALE, c);
}