Java样式JList用于聊天

时间:2013-09-02 15:28:25

标签: java swing jlist

我正在试图弄清楚如何将样式添加到JList中以使其具有功能(如果选择了项目则关闭按钮)(如果收到新消息,则显示新消息图标),如下所示:

http://cdn.livechatinc.com/website/media/img/devices/windows/main-feature_v3.png

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

任何想法如何实现?

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

1 个答案:

答案 0 :(得分:10)

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

每种方法都有自己的优点和缺点:

  • JList渲染速度更快,代码更清晰
  • 自定义组件更易于编写,设计和收听各种活动
  • 从头开始绘制组件很难,但可能会提供更好的渲染速度和可用性

我真的认为JTable无论如何都会比JList更好地帮助你 - 它具有相同的渲染策略。是的,它有编辑器,但像按钮这样使​​用它们对我来说似乎有些奇怪。

使用JList渲染器播放约15-20分钟后,我从屏幕截图中制作了完整的列表副本(除了图标 - 我只选择了自己的照片)。以下是下面列出的示例的屏幕截图:

enter image description here

JList实际上是一个非常强大的工具,如果您知道如何使用它。

以下是该示例的源代码:

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的高级知识,否则您将无法完全模仿源示例。但是,如果你想在最后创建一些非常好的用户界面,那么这就是你必须要知道的。

无论如何,JList与此案例的唯一问题是用于渲染单元格的组件不是“活动”,这意味着您可以将一些按钮放入渲染器,但它不会充当按钮 - 最后它将是该按钮的简单图像。当然,您不会从这样的按钮收到任何事件。

你可以看到的另外一件事就是列表中的10个按钮(例如,如果列表中有10个单元格),但只有一个具有不同设置的真实按钮将用于渲染所有这些单元格 - 这是主要的优化JList - 它不会为每个单元格创建大量组件,它会为每个单元格重复使用单个渲染器。

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

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

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