【问题标题】:JScrollPane - prevent visible component at top to moveJScrollPane - 防止顶部的可见组件移动
【发布时间】:2023-03-30 01:08:01
【问题描述】:

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

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

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

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;
    }
  }
}

【问题讨论】:

  • 我的第一个想法是…… - 对我来说听起来很合理。我不知道有任何内置的 API 支持这样的东西。
  • 感谢您的想法 :)

标签: java swing scroll resize responsive


【解决方案1】:

添加一个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);
      }
    }
// ...

Main2.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;
    }
  }
}

【讨论】:

  • 有时间我会看看这个方法的。
猜你喜欢
  • 2018-09-15
  • 2022-11-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-03-27
  • 1970-01-01
相关资源
最近更新 更多