4

我正在制作一个自定义 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/动态高度的东西,但它们之所以起作用,是因为各个单元格的高度保持不变。我的身高在变化,我该怎么做?

4

7 回答 7

8

基本上有两个方面的问题,都位于uidelegate中

  • 测量时无法将渲染器配置为真实状态,即完全忽略选择(和焦点)
  • 众所周知,它顽固地反对被迫重新计算缓存的单元格大小:它没有公共 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 委托(如其他答案中所建议的那样)是一种可能的解决方案。虽然在一般情况下有些工作,但如果需要支持多个 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);

顺便说一句,SwingX项目的 JXList 有一个自定义 ui 委托 - 主要用于支持排序/过滤 - 使用公共 api 重新计算缓存,然后上面的 ListSelectionListener 将被简化(并且干净:-) 为

    ListSelectionListener l = new ListSelectionListener() {
        @Override
        public void valueChanged(ListSelectionEvent e) {
            ((JXList) e.getSource()).invalidateCellSizeCache();
        }
    };
    list.addListSelectionListener(l);
于 2012-11-20T12:51:03.057 回答
5

我刚刚实现了这个功能。问题是,单元格渲染器被要求两次渲染单元格。在第一轮中,所有列表条目都在没有选择的情况下呈现,然后使用选择再次呈现选定的单元格。因此,如果您在第一轮中提供了首选大小,则会将其缓存并用于第二轮。

诀窍是忽略 中的isSelected布尔参数,getListCellRendererComponent并通过检查是否list.getSelectedIndices()包含给定索引来确定选择状态。

但是,我仍然有一个问题,在列表可见后,渲染组件的高度有时会变大/变小。用鼠标调整列表大小后,一切都很好了。我玩弄了验证/重新验证,重新绘制,缓存高度的重置,但没有任何效果。摇摆有时有点奇怪……

于 2012-11-20T10:49:02.203 回答
4

JList 无法根据选择或其他内容更改单元格的大小。该列表使用“缓存”大小。如果有新的 cellRenderer 提供,则重新计算此大小并将其应用于列表中的所有单元格。我认为原因是有很多条目的列表的性能。可能的解决方案是编写自己的 ListUI 实现,该实现能够为选定和未选定的单元格使用不同的大小。这也带来了通过对数或其他插值调整围绕选择的单元格大小的可能性。我希望你有一个很大的理由这样做。这是很多工作!

于 2009-08-16T08:04:26.523 回答
2

我一直在为这个愚蠢的 JList 行高问题而烦恼。我有一个单元格渲染器,它为每一行设置一个可变的行高 - 问题是 JList 保留了高度的缓存。

使用其他答案,我想我已经达到了圣杯。这里是:

使用 Jaap 创建的 BasicListUI 的简化版本:

public class BetterListUI extends BasicListUI {
    public void triggerUpdate() {
        updateLayoutState();
    }
}

然后当你创建一个 JList - 像这样扩展它:

betterListUI = new BetterListUI();
myJList = new JList() {
    @Override
    public void repaint(long tm, int x, int y, int width, int height) {
        betterListUI.triggerUpdate();
        super.repaint(tm, x, y, width, height);
    }
};
myJList.setUI(betterListUI);

根据您的情况,您可能需要在创建过程中对 triggerUpdate 进行保护。

于 2011-01-30T07:33:40.783 回答
1

感谢 Rastislav Komara,我已经能够很容易地解决这个问题:

我创建了一个扩展 BasicListUI 的内部类,并创建了在 ListSelectionListener.valueChanged 上调用的公共方法:

private class MyRenderer implements ListCellRenderer {
    public int listSelectedIndex = -1;

    public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected,
            boolean cellHasFocus) {
        if (index == listSelectedIndex)
            return new Yeah(isSelected);
        else
            return new Oh();
    }
}
MyRenderer lcr = new MyRenderer();
private class MyListUI extends BasicListUI {

    public void triggerUpdate() {
        lcr.listSelectedIndex = list.getSelectedIndex();
        updateLayoutState();
        list.revalidate();
    }
}

updateLayoutState 方法通常在 JList 高度发生变化时触发。我在这里做的唯一“疯狂”的事情是我的渲染器需要知道所选索引是什么。这是因为 updateLayoutState 方法在其高度计算中不使用选定的索引。不知何故,在 getListCellRendererComponent 中使用 list.getSelectedIndex() 效果不佳。

编辑:
还要检查 nevster 和 kleopatra 的分析器,它们看起来更聪明,先试试吧……

于 2009-08-16T12:57:22.800 回答
0

JList 可能正在“缓存”您的单元格渲染器。尝试附加一个 ListSelectionListener,并在选择更改时再次设置渲染器。

...
addListSelectionListener(new ListSelectionListener() {  
  public void valueChanged(ListSelectionEvent event) { 
    if(event.getValueIsAdjusting() == false) {
      list.setCellRenderer(new MyRenderer());
    }
  }
)
...
于 2009-08-16T07:10:24.187 回答
0

这是一个简单的解决方案:

public class VariableHeightListUI extends BasicListUI {

  @Override
  public void paint(Graphics g, JComponent c) {
    updateLayoutState();
    super.paint(g, c); 
  }
}

当然你需要编写自己的实现ListCellRenderer,并且根据列表元素的不同选择状态,你可以设置不同的返回组件的prefer高度。

只有一个问题需要解决:当您第一次选择 List 的一个元素时,没有正确绘制。但在那之后,一切正常。

希望这可以帮到你。

于 2015-05-20T08:38:26.117 回答