我正在制作自定义 ListCellRenderer。我知道您可以为每个单独的单元格设置不同的尺寸。但现在我想为所选单元格设置不同的维度。不知何故,JList 在第一次必须为每个单元格计算边界时缓存每个单元格的维度。 这是我的代码:
public class Test {
static class Oh extends JPanel {
public Oh() {
setPreferredSize(new Dimension(100, 20));
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.WHITE);
g.fillRect(0, 0, getWidth(), getHeight());
}
}
static class Yeah extends JPanel {
private boolean isSelected;
public Yeah(boolean isSelected) {
setPreferredSize(new Dimension(100, 100));
this.isSelected = isSelected;
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
//setSize(100, 100); // doesn't change the bounds of the component
//setBounds(0, 0, 100, 100); // this doesn't do any good either.
if (isSelected) g.setColor(Color.GREEN);
else g.setColor(Color.BLACK);
g.fillRect(0, 0, getWidth(), getHeight());
}
}
public static void main(String[] args) {
JFrame f = new JFrame();
f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
f.setSize(800, 500);
Vector<Integer> ints = new Vector<Integer>();
for (int i = 0; i < 100; i++) {
ints.add(i);
}
JList list = new JList(ints);
list.setCellRenderer(new ListCellRenderer() {
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
if (isSelected || ((Integer) value) == 42) return new Yeah(isSelected);
else return new Oh();
}
});
//list.setPrototypeCellValue(null);
//list.setFixedCellHeight(-1);
f.add(new JScrollPane(list));
f.setVisible(true);
}
}
在评论中您可以看到我已经尝试过的内容。
我已经搜索了很长时间,发现了很多无用的文章,其中一些涉及 ListCellRenderer/动态高度,但它们之所以有效,是因为单个单元格的高度保持不变。我的高度在变化,我该怎么做?
最佳答案
基本上,问题有两个方面,都位于ui delegate
- 它无法在测量时将渲染器配置为其真实状态,即完全忽略选择(和焦点)
- 众所周知,它顽固地反对被迫重新计算缓存的单元格大小:它没有这样做的公共(public) API,并且仅在模型更改时自愿执行。
解决第一个问题的补救措施确实是渲染器:实现忽略给定的选定标志并查询列表以获取真正的选择,如@Andy 所述。在代码中,使用 OP 的组件
ListCellRenderer renderer = new ListCellRenderer() {
Yeah yeah = new Yeah(false);
Oh oh = new Oh();
@Override
public Component getListCellRendererComponent(JList list,
Object value, int index, boolean isSelected,
boolean cellHasFocus) {
// ignore the given selection index, query the list instead
if (list != null) {
isSelected = list.isSelectedIndex(index);
}
if (isSelected || ((Integer) value) == 42) {
yeah.isSelected = isSelected;
return yeah;
}
return oh;
}
};
list.setCellRenderer(renderer);
要解决第二个问题,自定义 ui 委托(delegate)(也如其他答案中所建议的那样)是一种可能的解决方案。虽然在一般情况下有些工作,但如果需要支持多个 LAF。
强制 ui 自动更新其缓存的侵入性较小但有点肮脏的方法是在 selectionChange 上发送伪造的 ListDataEvent:
ListSelectionListener l = new ListSelectionListener() {
ListDataEvent fake = new ListDataEvent(list, ListDataEvent.CONTENTS_CHANGED, -1, -1);
@Override
public void valueChanged(ListSelectionEvent e) {
JList list = (JList) e.getSource();
ListDataListener[] listeners = ((AbstractListModel) list.getModel())
.getListDataListeners();
for (ListDataListener l : listeners) {
if (l.getClass().getName().contains("ListUI")) {
l.contentsChanged(fake);
break;
}
}
}
};
list.addListSelectionListener(l);
BTW,SwingX的JXList|项目有一个自定义 ui 委托(delegate) - 主要用于支持排序/过滤 - 使用公共(public) api 重新计算缓存,然后上面的 ListSelectionListener 将被简化(和干净:-) 到
ListSelectionListener l = new ListSelectionListener() {
@Override
public void valueChanged(ListSelectionEvent e) {
((JXList) e.getSource()).invalidateCellSizeCache();
}
};
list.addListSelectionListener(l);
关于Java Swing : JList with ListCellRenderer selected item different height,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1282865/