Swing/TreeNodeExpandCollapseAnimations の変更点
- 追加された行はこの色です。
- 削除された行はこの色です。
- Swing/TreeNodeExpandCollapseAnimations へ行く。
- Swing/TreeNodeExpandCollapseAnimations の差分を削除
--- category: swing folder: TreeNodeExpandCollapseAnimations title: JTreeにノード展開、折り畳みアニメーションを実装する tags: [JTree, Animation, TreeWillExpandListener] author: aterai pubdate: 2024-07-22T02:27:36+09:00 description: JTreeのノード展開、折り畳みでその子ノードの高さを増減するアニメーションを実行します。 image: https://drive.google.com/uc?id=1hHsf6k4Zt-UnrvuI-GIZWmu8NDCd6Yii hreflang: href: https://java-swing-tips.blogspot.com/2024/07/animates-effect-of-expanding-and.html lang: en --- * 概要 [#summary] `JTree`のノード展開、折り畳みでその子ノードの高さを増減するアニメーションを実行します。 #download(https://drive.google.com/uc?id=1hHsf6k4Zt-UnrvuI-GIZWmu8NDCd6Yii) * サンプルコード [#sourcecode] #code(link){{ 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); } } } }); 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; } } }} * 解説 [#explanation] - `JTree#setRowHeight(-1)`を設定して各表示行の高さを固定サイズではなくセルレンダラーの推奨サイズの高さを使用するよう設定 -- [[JTreeのノードを名前で検索して表示のフィルタリングを行う>Swing/TreeNodeFilter]] - セルレンダラーの推奨サイズを`DefaultMutableTreeNode`のユーザーオブジェクトで指定した高さを指定することで展開・折り畳み中のアニメーションを実行 -- ユーザーオブジェクトの設定は`DefaultMutableTreeNode#setUserObject(obj)`ではなく`TreeModel#valueForPathChanged(path, obj)`を使用しないと`TreeModelListener`などにノード更新イベントが伝わらない --- [[JTreeのノード追加、削除>Swing/AddNode]] -- `JLabel#setPreferredSize(null)`を実行してからノードの文字列を設定しないと推奨サイズの幅が正しく取得できない -- `JTree`のノードサイズキャッシュが影響している? - `JTree`に`TreeWillExpandListener`を追加してノードの展開・折り畳み前にその子ノードの高さを増減するアニメーションを実行 -- `TreeWillExpandListener#treeWillExpand(...)`で展開前に子ノードすべての高さを縮小してから、`Timer`をスタートして段階的に元の高さに戻すことでアニメーションを表現 -- `TreeWillExpandListener#treeWillCollapse(...)`で子ノードの高さが元の高さの場合は、一旦`throw new ExpandVetoException(...)`で折り畳みを中止してから`Timer`をスタートして段階的に高さを縮小するアニメーションを実行し、一定の高さまで縮小されたら`JTree#collapsePath(TreePath)`で再度アニメーションなしの折り畳みを実行 #code{{ 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(); } }} ---- - %%`Windows 10` + `Java 1.8.0_422`で作成した`example.jar`で`+`アイコンをクリックしてノード展開を実行するとアニメーションが開始されない%% -- テスト中の古いソースコードで作成した`example.jar`が原因だった -- `+`アイコンをクリックしてノード展開したかどうかではなく、ノードが選択状態かどうかをチェックするコードが残っていた * 参考リンク [#reference] - [[JTreeのノードを名前で検索して表示のフィルタリングを行う>Swing/TreeNodeFilter]] - [[JTreeで親ノードが展開されたときに子ノードの選択状態を変更する>Swing/TreeSelectionPaths]] - [[JTableで行の追加、削除アニメーション>Swing/SlideTableRows]] - [[JTreeのノードを折り畳み不可に設定する>Swing/TreeNodeCollapseVeto]] - [[JTreeのノード追加、削除>Swing/AddNode]] * コメント [#comment] #comment #comment