Google Tag Manager

2024/07/31

Animates the effect of expanding and collapsing JTree nodes

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