有很多方法可以满足你的要求。我真的说不出哪种方式更好——使用
JList
,创建自己的组件,从头开始绘制整个组件或其他东西。
每种方法都有其优缺点:
-
列表框
渲染速度更快,代码更干净
-
自定义组件更易于编写、设置样式和收听各种事件
-
从头开始绘制组件很难,但可能会提供更好的渲染速度和可用性
我真的不这么认为
JTable
无论如何都会在这里帮助你
列表框
-它具有相同的渲染策略。是的,它有编辑器,但像按钮一样使用它们对我来说有点奇怪。
在与玩了大约15-20分钟之后
列表框
渲染器我从你的屏幕截图中制作了一个完整的列表副本(除了图标——我只是选择了自己的图标)。以下是以下示例的屏幕截图:
列表框
如果你知道如何使用它,它实际上是一个非常强大的工具。
以下是该示例的源代码:
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个单元格),但只有一个具有不同设置的真实按钮会被用来渲染所有这些单元格-这是
列表框
-它不会为每个单元创建大量组件,而是为每个单元重用单个渲染器。
因此,您必须将自己的鼠标侦听器添加到列表中,并处理列表中鼠标事件的坐标(这实际上并不难)。我在上面发布的例子中做到了这一点——小鼠标监听器可以捕捉十字“按钮”上的点击。
请注意,列表有自己的鼠标侦听器来选择单元格——您必须与这些侦听器“协作”,以避免您自己的列表中出现任何不当行为。
附言:我差点忘了,这里有两个我用过的图标:<
><
>
第二个是白色的(十字图标),所以我用大括号突出显示了它——不要错过!:)