我正在使用JComponent
with HTML inside it(特别是JLabel
)来显示和更改可能需要换行的文本。不幸的是,当我更改HTML时会出现明显的闪烁,因为它正在做两个布局+绘制周期而不是一个。有没有办法避免用错误的布局绘制JLabel
?我在jframe.revalidate();
之后尝试拨打setText
,但没有帮助。
通过每秒更改HTML来演示闪烁的示例代码。我在Thread.sleep
期间添加了一个人工paintComponent
来模拟大型布局树或昂贵的绘画,以便在这个小窗口中可以看到闪烁。在屏幕截图中,左边是正确布局的标签,右边是在闪烁中间捕获的错误截图。
import java.awt.Container;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.WindowConstants;
public class HtmlJLabelFlicker {
static final String text =
"<html>This label is being set to a new value " +
"that needs to wrap. Unfortunately, this causes " +
"two layout + paint cycles instead of just one, " +
"which can cause flicker if the text doesn\u2019t " +
"change very much between refreshes. ";
public static void createUI() {
final JLabel label = new JLabel(text);
final Timer timer = new Timer(1000, new ActionListener() {
int n;
@Override public void actionPerformed(ActionEvent e) {
label.setText(text + n++);
}
});
timer.start();
JFrame jframe = new JFrame();
jframe.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
jframe.addWindowListener(new WindowAdapter() {
@Override public void windowClosed(WindowEvent e) {
timer.stop();
}
});
jframe.setSize(300, 300);
Container content = jframe.getContentPane();
content.add(label);
jframe.setVisible(true);
}
public static void main(String[] args) throws Exception {
SwingUtilities.invokeAndWait(new Runnable() {
@Override public void run() {
createUI();
}
});
}
}
答案 0 :(得分:6)
当我在将JLabel插入树中后尝试scrollRectToVisible时,我遇到了与OP相同的问题。问题是在布局的getPreferredSize()期间,包含HTML的JLabel在需要换行之前不知道有多少水平空间可用。它只是返回一个宽大的,假设它根本不需要包装。然后,在绘制期间,JLabel的内部FlowView将“修复”布局并设置其实际所需高度,该高度在下一次布局迭代时被缓存并返回。
我使用的解决方案是调用JLabel.paint,因为似乎没有其他方法可以告诉它完成使用实际可用宽度进行布局。
除此之外,WPF’s MeasureOverride,JavaFX’s prefHeight with contentBias和Android’s measure等其他用户界面系统会让度量传递可用空间,以便它们不会出现此问题。
以下是防止闪烁的相关代码:
label.setText(text + n++);
// revalidate, but do so synchronously.
Container validateRoot = label;
while (! validateRoot.isValidateRoot()) {
Container parent = validateRoot.getParent();
if (parent == null)
break;
validateRoot = parent;
}
// This first validate() call may be excluded if the width is already correct
validateRoot.validate();
NoopGraphics g = new NoopGraphics(0, 0, label.getWidth(), label.getHeight(), label.getGraphicsConfiguration(), false, false);
label.paint(g);
validateRoot.validate();
// Now you can use the measured bounds for e.g. scrollRectToVisible
NoopGraphics.java:
import java.awt.AlphaComposite;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.Paint;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.RenderingHints.Key;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.ImageObserver;
import java.awt.image.RenderedImage;
import java.awt.image.renderable.RenderableImage;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
import java.util.Collections;
import java.util.Map;
import javax.swing.SwingUtilities;
/**
* A subclass of Graphics2D that returns the correct FontMetrics but does not
* actually paint anything.
*
* @see <a
* href="http://stackoverflow.com/questions/16227877/how-to-update-a-jcomponent-with-html-without-flickering">How
* to update a JComponent with HTML without flickering?</a>
*/
public class NoopGraphics extends Graphics2D {
private Font font;
private Color color = Color.BLACK;
private final Rectangle clip;
private Stroke stroke;
private Paint paint;
private Color background;
private AffineTransform transform = new AffineTransform();
private final RenderingHints renderingHints = new RenderingHints(Collections.<Key,Object>emptyMap());
private Composite composite = AlphaComposite.SrcOver;
private boolean isAntiAliased;
private boolean usesFractionalMetrics;
private GraphicsConfiguration graphicsConfiguration;
public static GraphicsConfiguration getDefaultScreenGraphicsConfiguration() {
GraphicsEnvironment graphicsEnvironment = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice graphicsDevice = graphicsEnvironment.getDefaultScreenDevice();
GraphicsConfiguration graphicsConfiguration = graphicsDevice.getDefaultConfiguration();
return graphicsConfiguration;
}
public NoopGraphics(int x, int y, int width, int height) {
this(x, y, width, height, getDefaultScreenGraphicsConfiguration(), false, false);
}
public NoopGraphics(int x, int y, int width, int height, GraphicsConfiguration graphicsConfiguration, boolean isAntiAliased, boolean usesFractionalMetrics) {
this.graphicsConfiguration = graphicsConfiguration;
this.isAntiAliased = isAntiAliased;
this.usesFractionalMetrics = usesFractionalMetrics;
this.clip = new Rectangle(x, y, width, height);
}
@Override public void setXORMode(Color c1) {}
@Override public void setPaintMode() {}
@Override public Font getFont() {return font;}
@Override public void setFont(Font font) {this.font=font;}
@Override public Color getColor() {return color;}
@Override public void setColor(Color c) {this.color=c;}
@Override public void setClip(int x, int y, int width, int height) {
}
@Override public void setClip(Shape clip) {this.clip.setRect(clip.getBounds());}
@Override public FontMetrics getFontMetrics(Font f) {
// http://stackoverflow.com/questions/2753514/java-friendlier-way-to-get-an-instance-of-fontmetrics
return new Canvas(graphicsConfiguration).getFontMetrics(f);
}
@Override public Rectangle getClipBounds() {return clip.getBounds();}
@Override public Shape getClip() {return clip;}
@Override public void fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) {}
@Override public void fillRect(int x, int y, int width, int height) {}
@Override public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints) {}
@Override public void fillOval(int x, int y, int width, int height) {}
@Override public void fillArc(int x, int y, int width, int height, int startAngle, int arcAngle) {}
@Override public void drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) {}
@Override public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints) {}
@Override public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints) {}
@Override public void drawOval(int x, int y, int width, int height) {}
@Override public void drawLine(int x1, int y1, int x2, int y2) {}
@Override public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, Color bgcolor, ImageObserver observer) {return true;}
@Override public boolean drawImage(Image img, int dx1,
int dy1, int dx2, int dy2, int sx1, int sy1,
int sx2, int sy2, ImageObserver observer) {
return drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, null, observer);
}
@Override public boolean drawImage(Image img, int x, int y, int width, int height, Color bgcolor, ImageObserver observer) {
return false;
}
@Override public boolean drawImage(Image img, int x, int y, int width, int height, ImageObserver observer) {
return drawImage(img, x, y, width, height, null, observer);
}
@Override public boolean drawImage(Image img, int x, int y, Color bgcolor, ImageObserver observer) {
return false;
}
@Override public boolean drawImage(Image img, int x, int y, ImageObserver observer) {
return drawImage(img, x, y, null, observer);
}
@Override public void drawArc(int x, int y, int width, int height, int startAngle, int arcAngle) {}
@Override public void dispose() {}
@Override public Graphics create() {return this;}
@Override public void copyArea(int x, int y, int width, int height, int dx, int dy) {}
@Override public void clipRect(int x, int y, int width, int height) {SwingUtilities.computeIntersection(x, y, width, height, this.clip);}
@Override public void clearRect(int x, int y, int width, int height) {}
@Override public void translate(double tx, double ty) {getTransform().translate(tx, ty);}
@Override public void translate(int x, int y) {translate((double)x, (double)y);}
@Override public void transform(AffineTransform Tx) {getTransform().concatenate(Tx);}
@Override public void shear(double shx, double shy) {getTransform().shear(shx, shy);}
@Override public void scale(double sx, double sy) {getTransform().scale(sx, sy);}
@Override public void setTransform(AffineTransform Tx) {this.transform = Tx;}
@Override public void setStroke(Stroke s) {this.stroke = s;}
@Override public void setRenderingHints(Map<?, ?> hints) {this.renderingHints.clear(); this.renderingHints.putAll(hints);}
@Override public void setRenderingHint(Key hintKey, Object hintValue) {this.renderingHints.put(hintKey, hintValue);}
@Override public void setPaint(Paint paint) {this.paint = paint;}
@Override public void setComposite(Composite comp) {this.composite = comp;}
@Override public void setBackground(Color color) {this.background = color;}
@Override public void rotate(double theta, double x, double y) {getTransform().rotate(theta, x, y);}
@Override public void rotate(double theta) {getTransform().rotate(theta);}
@Override public boolean hit(Rectangle rect, Shape s, boolean onStroke) {
return false;
}
@Override public AffineTransform getTransform() {return this.transform;}
@Override public Stroke getStroke() {return this.stroke;}
@Override public RenderingHints getRenderingHints() {return renderingHints;}
@Override public Object getRenderingHint(Key hintKey) {return renderingHints.get(hintKey);}
@Override public Paint getPaint() {return this.paint;}
@Override public FontRenderContext getFontRenderContext() {return new FontRenderContext(transform, isAntiAliased, usesFractionalMetrics);}
@Override public GraphicsConfiguration getDeviceConfiguration() {return graphicsConfiguration;}
@Override public Composite getComposite() {return composite;}
@Override public Color getBackground() {return background;}
@Override public void fill(Shape s) {}
@Override public void drawString(AttributedCharacterIterator iterator, float x, float y) {}
@Override public void drawString(AttributedCharacterIterator iterator, int x, int y) {drawString(iterator, (float)x, (float)y);}
@Override public void drawString(String str, float x, float y) {drawString(new AttributedString(str).getIterator(), x, y);}
@Override public void drawString(String str, int x, int y) {drawString(str, (float)x, (float)y);}
@Override public void drawRenderedImage(RenderedImage img, AffineTransform xform) {}
@Override public void drawRenderableImage(RenderableImage img, AffineTransform xform) {}
@Override public void drawImage(BufferedImage img, BufferedImageOp op, int x, int y) {}
@Override public boolean drawImage(Image img, AffineTransform xform, ImageObserver obs) {return false;}
@Override public void drawGlyphVector(GlyphVector g, float x, float y) {}
@Override public void draw(Shape s) {}
@Override public void clip(Shape s) {}
@Override public void addRenderingHints(Map<?, ?> hints) {renderingHints.putAll(hints);}
}
答案 1 :(得分:0)
您可以尝试使用所需的HTML创建新标签并替换旧标签。
创建并显示原始标签
final JLabel label = new JLabel(originalText);
Container content = jframe.getContentPane();
content.add(label);
创建新标签并替换旧标签
final JLabel newLabel = new JLabel(newtext);
content.remove(label);
content.add(newLabel);
content.validate(); // This will cause the container to show the new label.