代码之家  ›  专栏  ›  技术社区  ›  Alosyius

Java风格的聊天JList

  •  3
  • Alosyius  · 技术社区  · 11 年前

    我正试图弄清楚如何将样式a功能添加到JList中,使其发挥功能(如果选择了项目,则关闭按钮)(如果收到新消息,则显示新消息图标),就像这里所做的那样:

    enter image description here
    (来源: livechatinc.com )

    我想到了“安娜、露西和乔”的名单

    有什么想法可以实现吗?

    编辑:切换到正确的图片:)

    1 回复  |  直到 11 年前
        1
  •  11
  •   Mikle Garin    10 年前

    有很多方法可以满足你的要求。我真的说不出哪种方式更好——使用 JList ,创建自己的组件,从头开始绘制整个组件或其他东西。

    每种方法都有其优缺点:

    • 列表框 渲染速度更快,代码更干净
    • 自定义组件更易于编写、设置样式和收听各种事件
    • 从头开始绘制组件很难,但可能会提供更好的渲染速度和可用性

    我真的不这么认为 JTable 无论如何都会在这里帮助你 列表框 -它具有相同的渲染策略。是的,它有编辑器,但像按钮一样使用它们对我来说有点奇怪。

    在与玩了大约15-20分钟之后 列表框 渲染器我从你的屏幕截图中制作了一个完整的列表副本(除了图标——我只是选择了自己的图标)。以下是以下示例的屏幕截图:

    enter image description here

    列表框 如果你知道如何使用它,它实际上是一个非常强大的工具。

    以下是该示例的源代码:

    import javax.swing.*;
    import java.awt.*;
    import java.awt.event.MouseAdapter;
    import java.awt.event.MouseEvent;
    import java.awt.geom.Area;
    import java.awt.geom.Ellipse2D;
    import java.awt.geom.RoundRectangle2D;
    
    /**
     * @author Mikle Garin
     * @see http://stackoverflow.com/a/18589264/909085
     */
    
    public class CustomListRenderer extends DefaultListCellRenderer
    {
        private static final ImageIcon crossIcon = new ImageIcon ( CustomListRenderer.class.getResource ( "cross.png" ) );
        private static final ImageIcon tipIcon = new ImageIcon ( CustomListRenderer.class.getResource ( "tip.png" ) );
    
        /**
         * Sample frame with list.
         *
         * @param args arguments
         */
        public static void main ( String[] args )
        {
            JFrame frame = new JFrame ( "Custom list renderer" );
    
            DefaultListModel model = new DefaultListModel ();
            model.addElement ( new CustomData ( new Color ( 209, 52, 23 ), 1, "Anna Williams" ) );
            model.addElement ( new CustomData ( new Color ( 135, 163, 14 ), 0, "Lucy Frank" ) );
            model.addElement ( new CustomData ( new Color ( 204, 204, 204 ), 0, "Joe Fritz" ) );
            model.addElement ( new CustomData ( new Color ( 90, 90, 90 ), 3, "Mikle Garin" ) );
    
            JList list = new JList ( model );
            list.setCellRenderer ( new CustomListRenderer ( list ) );
            list.setBorder ( BorderFactory.createEmptyBorder ( 5, 5, 5, 5 ) );
            frame.add ( list );
    
            frame.pack ();
            frame.setLocationRelativeTo ( null );
            frame.setDefaultCloseOperation ( JFrame.EXIT_ON_CLOSE );
            frame.setVisible ( true );
        }
    
        /**
         * Actual renderer.
         */
        private CustomLabel renderer;
    
        /**
         * Custom renderer constructor.
         * We will use it to create actual renderer component instance.
         * We will also add a custom mouse listener to process close button.
         *
         * @param list our JList instance
         */
        public CustomListRenderer ( final JList list )
        {
            super ();
            renderer = new CustomLabel ();
    
            list.addMouseListener ( new MouseAdapter ()
            {
                @Override
                public void mouseReleased ( MouseEvent e )
                {
                    if ( SwingUtilities.isLeftMouseButton ( e ) )
                    {
                        int index = list.locationToIndex ( e.getPoint () );
                        if ( index != -1 && list.isSelectedIndex ( index ) )
                        {
                            Rectangle rect = list.getCellBounds ( index, index );
                            Point pointWithinCell = new Point ( e.getX () - rect.x, e.getY () - rect.y );
                            Rectangle crossRect = new Rectangle ( rect.width - 9 - 5 - crossIcon.getIconWidth () / 2,
                                    rect.height / 2 - crossIcon.getIconHeight () / 2, crossIcon.getIconWidth (), crossIcon.getIconHeight () );
                            if ( crossRect.contains ( pointWithinCell ) )
                            {
                                DefaultListModel model = ( DefaultListModel ) list.getModel ();
                                model.remove ( index );
                            }
                        }
                    }
                }
            } );
        }
    
        /**
         * Returns custom renderer for each cell of the list.
         *
         * @param list         list to process
         * @param value        cell value (CustomData object in our case)
         * @param index        cell index
         * @param isSelected   whether cell is selected or not
         * @param cellHasFocus whether cell has focus or not
         * @return custom renderer for each cell of the list
         */
        @Override
        public Component getListCellRendererComponent ( JList list, Object value, int index, boolean isSelected, boolean cellHasFocus )
        {
            renderer.setSelected ( isSelected );
            renderer.setData ( ( CustomData ) value );
            return renderer;
        }
    
        /**
         * Label that has some custom decorations.
         */
        private static class CustomLabel extends JLabel
        {
            private static final Color selectionColor = new Color ( 82, 158, 202 );
    
            private boolean selected;
            private CustomData data;
    
            public CustomLabel ()
            {
                super ();
                setOpaque ( false );
                setBorder ( BorderFactory.createEmptyBorder ( 0, 36 + 5, 0, 40 ) );
            }
    
            private void setSelected ( boolean selected )
            {
                this.selected = selected;
                setForeground ( selected ? Color.WHITE : Color.BLACK );
            }
    
            private void setData ( CustomData data )
            {
                this.data = data;
                setText ( data.getName () );
            }
    
            @Override
            protected void paintComponent ( Graphics g )
            {
                Graphics2D g2d = ( Graphics2D ) g;
                g2d.setRenderingHint ( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
    
                if ( selected )
                {
                    Area area = new Area ( new Ellipse2D.Double ( 0, 0, 36, 36 ) );
                    area.add ( new Area ( new RoundRectangle2D.Double ( 18, 3, getWidth () - 18, 29, 6, 6 ) ) );
                    g2d.setPaint ( selectionColor );
                    g2d.fill ( area );
    
                    g2d.setPaint ( Color.WHITE );
                    g2d.fill ( new Ellipse2D.Double ( 2, 2, 32, 32 ) );
                }
    
                g2d.setPaint ( data.getCircleColor () );
                g2d.fill ( new Ellipse2D.Double ( 5, 5, 26, 26 ) );
                g2d.drawImage ( tipIcon.getImage (), 5 + 13 - tipIcon.getIconWidth () / 2, 5 + 13 - tipIcon.getIconHeight () / 2, null );
    
                if ( selected )
                {
                    g2d.drawImage ( crossIcon.getImage (), getWidth () - 9 - 5 - crossIcon.getIconWidth () / 2,
                            getHeight () / 2 - crossIcon.getIconHeight () / 2, null );
                }
                else if ( data.getNewMessages () > 0 )
                {
                    g2d.setPaint ( selectionColor );
                    g2d.fill ( new Ellipse2D.Double ( getWidth () - 18 - 5, getHeight () / 2 - 9, 18, 18 ) );
    
                    final String text = "" + data.getNewMessages ();
                    final Font oldFont = g2d.getFont ();
                    g2d.setFont ( oldFont.deriveFont ( oldFont.getSize () - 1f ) );
                    final FontMetrics fm = g2d.getFontMetrics ();
                    g2d.setPaint ( Color.WHITE );
                    g2d.drawString ( text, getWidth () - 9 - 5 - fm.stringWidth ( text ) / 2,
                            getHeight () / 2 + ( fm.getAscent () - fm.getLeading () - fm.getDescent () ) / 2 );
                    g2d.setFont ( oldFont );
                }
    
                super.paintComponent ( g );
            }
    
            @Override
            public Dimension getPreferredSize ()
            {
                final Dimension ps = super.getPreferredSize ();
                ps.height = 36;
                return ps;
            }
        }
    
        /**
         * Custom data for our list.
         */
        private static class CustomData
        {
            private Color circleColor;
            private int newMessages;
            private String name;
    
            public CustomData ( Color circleColor, int newMessages, String name )
            {
                super ();
                this.circleColor = circleColor;
                this.newMessages = newMessages;
                this.name = name;
            }
    
            private Color getCircleColor ()
            {
                return circleColor;
            }
    
            private int getNewMessages ()
            {
                return newMessages;
            }
    
            private String getName ()
            {
                return name;
            }
        }
    }
    

    是的,这个例子需要一些高级知识 Graphics2D ,否则您将无法完全模仿源代码示例。但是,如果你最终想创建一些真正好的UI,那么这正是你必须知道的。

    无论如何,唯一的问题是 列表框 在这种情况下,用于渲染单元的组件不是“活动的”,这意味着你可以在渲染器中放入一些按钮,但它不会充当按钮——它最终将是该按钮的简单图像。当然,您不会从这样的按钮收到任何事件。

    还有一件事,你可以在列表中看到10个按钮(例如,如果你在列表中有10个单元格),但只有一个具有不同设置的真实按钮会被用来渲染所有这些单元格-这是 列表框 -它不会为每个单元创建大量组件,而是为每个单元重用单个渲染器。

    因此,您必须将自己的鼠标侦听器添加到列表中,并处理列表中鼠标事件的坐标(这实际上并不难)。我在上面发布的例子中做到了这一点——小鼠标监听器可以捕捉十字“按钮”上的点击。

    请注意,列表有自己的鼠标侦听器来选择单元格——您必须与这些侦听器“协作”,以避免您自己的列表中出现任何不当行为。

    附言:我差点忘了,这里有两个我用过的图标:< enter image description here >< enter image description here >
    第二个是白色的(十字图标),所以我用大括号突出显示了它——不要错过!:)