JScrollPane - 防止顶部的可见组件移动



我目前正在尝试实现聊天视图。该聊天有消息气泡,有点像WhatsApp或其他您习惯的东西。所有消息都在JScrollPane内的JPanel内。消息的长度是可变的,并且位于一个JTextArea中,因此高度和宽度是可变的。每当JScrollPane的宽度发生变化时,JViewPort的大小就会发生变化。由于 y 坐标保持不变,但消息的高度保持不变,因此它们在可见视口内移动。更改JScrollPane的大小时,是否可以简单地将顶部的消息保留在原处?

我的第一个想法是通过向JScrollPane的垂直JScrollBar添加AdjustmentListener来跟踪顶部的组件,然后使用ComponentListener#componentResized调整JScrollPane的 y 坐标。然而,这种方法对我来说似乎有点笨拙,我想知道这样做最干净的方法是什么。也许我想多了,但我希望这将是一个相对正常的用例,因此有一个简单的解决方法。

这是我用例的一个最小示例:

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.LayoutManager;
import java.awt.Rectangle;
import javax.swing.Box;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.ScrollPaneConstants;
import javax.swing.Scrollable;
public class Main
{
public static void main( String[] args ) throws Exception
{
final JPanel layout = new ScrollablePanel( new GridBagLayout() );
GridBagConstraints constraints = new GridBagConstraints();
constraints.gridx = 0;
constraints.fill = GridBagConstraints.HORIZONTAL;
constraints.weightx = 1.0;
for ( int i = 0; i < 20; i++ )
{
layout.add( createMessage( "Marcel",
"Lorem mauris sea aliquam ut justo splendide rhoncus ipsum leo ipsum impedit duo graeci insolens est mea dicunt kasd mus nullam elementum ei tincidunt ullamcorper cetero et aliquyam uonsetetur imperdiet kasd at adipisci nec justo amet duo aliquam at rhoncus nullam splendide facilisi invidunt antiopam duo et splendide amet te vitae partiendo at per inimicus imperdiet rhoncus labore usu vel assentior cursus kasd kasd semper duo vitae mauris his qui aliquyam uonsetetur dicunt eu in habeo takimata definiebas splendide duo kasd qui duo et ullamcorper dicunt Vulputate uonsetetur ullamcorper mauris sea eleifend duo ei definiebas kasd vitae graeci labore inimicus usu vis semper assentior ne odio elementum elementum imperdiet temporibus habeo est ullamcorper semper odio arcu cetero partiendo his eteu iusto corrumpit mus eteu corpora penatibus ut qui pretium te corrumpit his aenean voluptua ipsum his Vulputate usu est insolens assentior arcu et sea aliquyam dicunt inimicus ut nullam ei mauris vis semper duo insolens repudiandae vitae mauris repudiandae definiebas est nec ius labore iusto nec usu dicta ullamcorper in tincidunt mauris voluptua ipsum ut Ut ne dicta aenean eam insolens elementum vitae elementum officiis imperdiet assentior ut ut aliquyam rhoncus vis his invidunt eos est eteu temporibus ei temporibus",
"20.19.2019", false ), constraints, 0 );
}
JFrame frame = new JFrame();
frame.getContentPane()
.add( new JScrollPane( layout, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER ) );
frame.setSize( 400, 300 );
frame.setVisible( true );
}
private static Component createMessage( String author, String message, String date, boolean left )
{
JPanel messagePanel = new JPanel( new GridBagLayout() )
{
@Override
public Dimension getMinimumSize()
{
//Making sure that the panel can shrink and not only grow.
final Dimension minimumSize = super.getMinimumSize();
minimumSize.width = 0;
return minimumSize;
}
};
final JLabel authorLabel = new JLabel( author );
final JLabel dateLabel = new JLabel( date );
final JTextArea messageText = new JTextArea( message );
messageText.setEditable( false );
messageText.setLineWrap( true );
GridBagConstraints constraints = new GridBagConstraints();
constraints.gridx = 0;
constraints.gridy = 0;
messagePanel.add( authorLabel, constraints );
constraints.gridx = 1;
constraints.anchor = GridBagConstraints.EAST;
messagePanel.add( dateLabel, constraints );
constraints.gridx = 0;
constraints.gridy = 1;
constraints.gridwidth = 2;
constraints.anchor = GridBagConstraints.WEST;
constraints.fill = GridBagConstraints.HORIZONTAL;
constraints.weightx = 1.0;
messagePanel.add( messageText, constraints );
final Box box = Box.createHorizontalBox();
final Box.Filler filler = new Box.Filler( new Dimension( 20, 0 ), new Dimension( 100, 0 ), new Dimension( 200, 0 ) );
box.add( left ? messagePanel : filler );
box.add( Box.createHorizontalGlue() );
box.add( left ? filler : messagePanel );
box.add( Box.createHorizontalGlue() );
return box;
}
private static class ScrollablePanel extends JPanel implements Scrollable
{
public ScrollablePanel( LayoutManager layout )
{
super( layout );
}
@Override
public Dimension getPreferredScrollableViewportSize()
{
return getLayout().preferredLayoutSize( this );
}
@Override
public int getScrollableUnitIncrement( final Rectangle visibleRect, final int orientation, final int direction )
{
return 20;
}
@Override
public int getScrollableBlockIncrement( final Rectangle visibleRect, final int orientation, final int direction )
{
return 20;
}
@Override
public boolean getScrollableTracksViewportWidth()
{
return true;
}
@Override
public boolean getScrollableTracksViewportHeight()
{
return false;
}
}
}

添加一个AdjustmentListener...然后使用ComponentListener#componentResized调整JScrollPane的 y 坐标。

也许另一种选择是覆盖ScrollablePanel#doLayout()方法:

private static class ScrollablePanel extends JPanel implements Scrollable {
public ScrollablePanel( LayoutManager layout ) {
super( layout );
}
@Override public void doLayout() {
Container p = SwingUtilities.getAncestorOfClass(JViewport.class, this);
if (!(p instanceof JViewport)) {
super.doLayout();
return;
}
JViewport vport = (JViewport) p;
Rectangle vrect = vport.getViewRect();
// search the message bubble that is at the top
Component tgt = null;
for (Component c: getComponents()) {
// not considered if the message bubble height is greater than the viewport height.
if (vrect.contains(c.getLocation())) {
tgt = c;
break;
}
}
// causes this container to lay out its components
super.doLayout();
// keep the message bubble that is at the top
if (tgt != null) {
Point vp = vport.getViewPosition();
vp.y = tgt.getY();
vport.setViewPosition(vp);
}
}
// ...

主2.java

import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.LayoutManager;
import java.awt.Point;
import java.awt.Rectangle;
import javax.swing.Box;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JViewport;
import javax.swing.ScrollPaneConstants;
import javax.swing.Scrollable;
import javax.swing.SwingUtilities;
public class Main2
{
public static void main( String[] args ) throws Exception
{
final JPanel layout = new ScrollablePanel( new GridBagLayout() );
GridBagConstraints constraints = new GridBagConstraints();
constraints.gridx = 0;
constraints.fill = GridBagConstraints.HORIZONTAL;
constraints.weightx = 1.0;
for ( int i = 0; i < 20; i++ )
{
layout.add( createMessage( "Marcel: " + i,
"Lorem mauris sea aliquam ut justo splendide rhoncus ipsum leo ipsum impedit duo graeci insolens est mea dicunt kasd mus nullam elementum ei tincidunt ullamcorper cetero et aliquyam uonsetetur imperdiet kasd at adipisci nec justo amet duo aliquam at rhoncus nullam splendide facilisi invidunt antiopam duo et splendide amet te vitae partiendo at per inimicus imperdiet rhoncus labore usu vel assentior cursus kasd kasd semper duo vitae mauris his qui aliquyam uonsetetur dicunt eu in habeo takimata definiebas splendide duo kasd qui duo et ullamcorper dicunt Vulputate uonsetetur ullamcorper mauris sea eleifend duo ei definiebas kasd vitae graeci labore inimicus usu vis semper assentior ne odio elementum elementum imperdiet temporibus habeo est ullamcorper semper odio arcu cetero partiendo his eteu iusto corrumpit mus eteu corpora penatibus ut qui pretium te corrumpit his aenean voluptua ipsum his Vulputate usu est insolens assentior arcu et sea aliquyam dicunt inimicus ut nullam ei mauris vis semper duo insolens repudiandae vitae mauris repudiandae definiebas est nec ius labore iusto nec usu dicta ullamcorper in tincidunt mauris voluptua ipsum ut Ut ne dicta aenean eam insolens elementum vitae elementum officiis imperdiet assentior ut ut aliquyam rhoncus vis his invidunt eos est eteu temporibus ei temporibus",
"20.19.2019", false ), constraints, 0 );
}
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane()
.add( new JScrollPane( layout, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER ) );
frame.setSize( 400, 300 );
frame.setVisible( true );
}
private static Component createMessage( String author, String message, String date, boolean left )
{
JPanel messagePanel = new JPanel( new GridBagLayout() )
{
@Override
public Dimension getMinimumSize()
{
//Making sure that the panel can shrink and not only grow.
final Dimension minimumSize = super.getMinimumSize();
minimumSize.width = 0;
return minimumSize;
}
};
final JLabel authorLabel = new JLabel( author );
final JLabel dateLabel = new JLabel( date );
final JTextArea messageText = new JTextArea( message );
messageText.setEditable( false );
messageText.setLineWrap( true );
GridBagConstraints constraints = new GridBagConstraints();
constraints.gridx = 0;
constraints.gridy = 0;
messagePanel.add( authorLabel, constraints );
constraints.gridx = 1;
constraints.anchor = GridBagConstraints.EAST;
messagePanel.add( dateLabel, constraints );
constraints.gridx = 0;
constraints.gridy = 1;
constraints.gridwidth = 2;
constraints.anchor = GridBagConstraints.WEST;
constraints.fill = GridBagConstraints.HORIZONTAL;
constraints.weightx = 1.0;
messagePanel.add( messageText, constraints );
final Box box = Box.createHorizontalBox();
final Box.Filler filler = new Box.Filler( new Dimension( 20, 0 ), new Dimension( 100, 0 ), new Dimension( 200, 0 ) );
box.add( left ? messagePanel : filler );
box.add( Box.createHorizontalGlue() );
box.add( left ? filler : messagePanel );
box.add( Box.createHorizontalGlue() );
return box;
}
private static class ScrollablePanel extends JPanel implements Scrollable
{
public ScrollablePanel( LayoutManager layout )
{
super( layout );
}
@Override public void doLayout() {
Container p = SwingUtilities.getAncestorOfClass(JViewport.class, this);
if (!(p instanceof JViewport)) {
super.doLayout();
return;
}
JViewport vport = (JViewport) p;
Rectangle vrect = vport.getViewRect();
Component tgt = null;
for (Component c: getComponents()) {
if (vrect.contains(c.getLocation())) {
tgt = c;
break;
}
}
super.doLayout();
if (tgt != null) {
Point vp = vport.getViewPosition();
vp.y = tgt.getY();
vport.setViewPosition(vp);
}
}
@Override
public Dimension getPreferredScrollableViewportSize()
{
return getLayout().preferredLayoutSize( this );
}
@Override
public int getScrollableUnitIncrement( final Rectangle visibleRect, final int orientation, final int direction )
{
return 20;
}
@Override
public int getScrollableBlockIncrement( final Rectangle visibleRect, final int orientation, final int direction )
{
return 20;
}
@Override
public boolean getScrollableTracksViewportWidth()
{
return true;
}
@Override
public boolean getScrollableTracksViewportHeight()
{
return false;
}
}
}

最新更新