JTreeにノード展開、折り畳みアニメーションを実装する
Total: 172
, Today: 2
, Yesterday: 2
Posted by aterai at
Last-modified:
概要
JTree
のノード展開、折り畳みでその子ノードの高さを増減するアニメーションを実行します。
Screenshot
![Swing/TreeNodeExpandCollapseAnimations screenshot](https://ateraimemo.com/swing/treenodeexpandcollapseanimations/screenshot.png)
Advertisement
サンプルコード
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;
}
}
View in GitHub: Java, Kotlin解説
JTree#setRowHeight(-1)
を設定して各表示行の高さを固定サイズではなくセルレンダラーの推奨サイズの高さを使用するよう設定- セルレンダラーの推奨サイズを
DefaultMutableTreeNode
のユーザーオブジェクトで指定した高さを指定することで展開・折り畳み中のアニメーションを実行- ユーザーオブジェクトの設定は
DefaultMutableTreeNode#setUserObject(obj)
ではなくTreeModel#valueForPathChanged(path, obj)
を使用しないとTreeModelListener
などにノード更新イベントが伝わらない JLabel#setPreferredSize(null)
を実行してからノードの文字列を設定しないと推奨サイズの幅が正しく取得できないJTree
のノードサイズキャッシュが影響している?
- ユーザーオブジェクトの設定は
JTree
にTreeWillExpandListener
を追加してノードの展開・折り畳み前にその子ノードの高さを増減するアニメーションを実行TreeWillExpandListener#treeWillExpand(...)
で展開前に子ノードすべての高さを縮小してから、Timer
をスタートして段階的に元の高さに戻すことでアニメーションを表現TreeWillExpandListener#treeWillCollapse(...)
で子ノードの高さが元の高さの場合は、一旦throw new ExpandVetoException(...)
で折り畳みを中止してからTimer
をスタートして段階的に高さを縮小するアニメーションを実行し、一定の高さまで縮小されたらJTree#collapsePath(TreePath)
で再度アニメーションなしの折り畳みを実行
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();
}
参考リンク
- JTreeのノードを名前で検索して表示のフィルタリングを行う
- JTreeで親ノードが展開されたときに子ノードの選択状態を変更する
- JTableで行の追加、削除アニメーション
- JTreeのノードを折り畳み不可に設定する
- JTreeのノード追加、削除