Code
JTree tree = new JTree() {
@Override public void updateUI() {
super.updateUI();
setRowHeight(-1);
setCellRenderer(new HeightTreeCellRenderer());
}
};
tree.addTreeWillExpandListener(new TreeWillExpandListener() {
@Override public void treeWillExpand(TreeExpansionEvent e) {
Object o = e.getPath().getLastPathComponent();
if (o instanceof DefaultMutableTreeNode) {
DefaultMutableTreeNode parent = (DefaultMutableTreeNode) o;
List<DefaultMutableTreeNode> list = getTreeNodes(parent);
parent.setUserObject(makeUserObject(parent, END_HEIGHT));
list.forEach(n -> n.setUserObject(makeUserObject(n, START_HEIGHT)));
startExpandTimer(e, list);
}
}
@Override public void treeWillCollapse(TreeExpansionEvent e)
throws ExpandVetoException {
Object c = e.getPath().getLastPathComponent();
if (c instanceof DefaultMutableTreeNode) {
DefaultMutableTreeNode p = (DefaultMutableTreeNode) o;
List<DefaultMutableTreeNode> list = getTreeNodes(p);
boolean b = list
.stream()
.anyMatch(n -> {
Object obj = n.getUserObject();
return obj instanceof SizeNode
&& ((SizeNode) obj).height == END_HEIGHT;
});
if (b) {
startCollapseTimer(e, list);
throw new ExpandVetoException(e);
}
}
}
});
private static void startExpandTimer(
TreeExpansionEvent e, List<DefaultMutableTreeNode> list) {
JTree tree = (JTree) e.getSource();
TreeModel model = tree.getModel();
AtomicInteger height = new AtomicInteger(START_HEIGHT);
new Timer(DELAY, ev -> {
int h = height.getAndIncrement();
if (h <= END_HEIGHT) {
list.forEach(n -> {
Object uo = makeUserObject(n, h);
model.valueForPathChanged(new TreePath(n.getPath()), uo);
});
} else {
((Timer) ev.getSource()).stop();
}
}).start();
}
private static void startCollapseTimer(
TreeExpansionEvent e, List<DefaultMutableTreeNode> list) {
JTree tree = (JTree) e.getSource();
TreePath path = e.getPath();
TreeModel model = tree.getModel();
AtomicInteger height = new AtomicInteger(END_HEIGHT);
new Timer(DELAY, ev -> {
int h = height.getAndDecrement();
if (h >= START_HEIGHT) {
list.forEach(n -> {
Object uo = makeUserObject(n, h);
model.valueForPathChanged(new TreePath(n.getPath()), uo);
});
} else {
((Timer) ev.getSource()).stop();
tree.collapsePath(path);
}
}).start();
}
// ...
class HeightTreeCellRenderer extends DefaultTreeCellRenderer {
@Override public Component getTreeCellRendererComponent(
JTree tree,
Object value,
boolean selected,
boolean expanded,
boolean leaf,
int row,
boolean hasFocus) {
Component c = super.getTreeCellRendererComponent(
tree, value, selected, expanded, leaf, row, hasFocus);
DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
Object uo = node.getUserObject();
if (c instanceof JLabel && uo instanceof SizeNode) {
JLabel l = (JLabel) c;
SizeNode n = (SizeNode) uo;
l.setPreferredSize(null); // reset prev preferred size
l.setText(n.label); // recalculate preferred size
Dimension d = l.getPreferredSize();
d.height = n.height;
l.setPreferredSize(d);
}
return c;
}
}
class SizeNode {
public final String label;
public final int height;
protected SizeNode(String label, int height) {
this.label = label;
this.height = height;
}
@Override public String toString() {
return label;
}
}
References