首页   >   代码编程   >   JAVA开发

Swing界面优化JTree详细图文教程

之前在iteye上发表过一篇关于jtree优化的帖子:swing界面优化进阶五,这几天在整理代码,发现之前写的案例中,有很多的bug和逻辑问题,顺手再次整理了一下,小改动,性能有大的提升。

对比之前的不同之处:

1、鼠标离开时,将滑入的特效还原;

2、将UI渲染的重复代码,抽出来整理成了一个工具类;

3、在UI渲染中,重构了一部分逻辑,去除了不必要的UI渲染;

先来一张效果图:

Swing界面优化JTree详细图文教程

好了,最后的效果并没有多大的差异,直接上代码了。

不打包了,图标之类的,在iteye上已经上传过一次了,这里就不再上传了,直接贴代码,大家可以拷贝直接运行。

一、重写我们自己的Node节点,这里的一个小改动,增加了一个boolean值来记录是否选中,这样就不需要每次getBackground()来判断是否选中了

package com.wolffy.node;

import javax.swing.Icon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.tree.DefaultMutableTreeNode;
import java.awt.Component;
import java.awt.Dimension;

/**
 * Created by SongFei on 2017/11/6.
 */
public class MyTreeNode extends DefaultMutableTreeNode {

    private static final long serialVersionUID = 1007068847268622569L;

    /**
     * 图片
     */
    private Icon icon;

    /**
     * 文字
     */
    private String name;

    /**
     * 签名
     */
    private String sign;

    /**
     * 是否选中
     */
    private boolean select;

    private JPanel groupPanel;
    private JPanel buddyPanel;

    private JLabel iconLabel;
    private JLabel nameLabel;
    private JLabel signLabel;

    public MyTreeNode() {
    }

    /**
     * 初始化分组节点
     *
     * @param name 名称
     */
    public MyTreeNode(Icon icon, String name) {
        this.icon = icon;
        this.name = name;
        // 初始化UI
        initCateGUI();
    }

    /**
     * 初始化好友节点
     *
     * @param icon 头像
     * @param nick 昵称
     * @param sign 签名
     */
    public MyTreeNode(Icon icon, String nick, String sign) {
        this.icon = icon;
        this.name = nick;
        this.sign = sign;
        // 初始化UI
        initNodeGUI();
    }

    /**
     * 自定义分组UI
     */
    private void initCateGUI() {
        groupPanel = new JPanel();
        groupPanel.setLayout(null);
//		groupPanel.setOpaque(false);
        // 这里大家注意,当我们写好UI之后可能会发现他的颜色不太对,
        // 这时候千万不要用上面那句,不然当我们想再次改变其颜色的时候,就生效不了
        // 红绿蓝分别为255的这个颜色趋近于透明,我们可以用它来代替setOpaque
//		groupPanel.setBackground(new Color(255,255,255));
        // 突然发现置成null也可以
        groupPanel.setBackground(null);
        groupPanel.setPreferredSize(new Dimension(300, 25));
//		groupPanel.setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY));

        iconLabel = new JLabel(icon);
        iconLabel.setBounds(6, 5, 20, 16);
        groupPanel.add(iconLabel);

        nameLabel = new JLabel(name);
        nameLabel.setBounds(23, 0, 132, 28);
        groupPanel.add(nameLabel);
    }

    /**
     * 自定义好友UI
     */
    private void initNodeGUI() {
        buddyPanel = new JPanel();
        buddyPanel.setLayout(null);
        buddyPanel.setBackground(null);
        buddyPanel.setPreferredSize(new Dimension(300, 50));
//		buddyPanel.setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY));

        iconLabel = new JLabel(icon);
        iconLabel.setBounds(8, 4, 42, 42);
        buddyPanel.add(iconLabel);

        nameLabel = new JLabel(name);
        nameLabel.setBounds(59, 5, 132, 19);
        buddyPanel.add(nameLabel);

        signLabel = new JLabel(sign);
        signLabel.setBounds(59, 28, 132, 17);
        buddyPanel.add(signLabel);
    }

    /**
     * 将自定义UI返回给渲染器	<br/>
     * 供渲染器调用,返回的必须是一个Component
     *
     * @return
     */
    public Component getGroupView() {
        return groupPanel;
    }

    /**
     * 将自定义UI返回给渲染器	<br/>
     * 供渲染器调用,返回的必须是一个Component
     *
     * @return
     */
    public Component getBuddyView() {
        return buddyPanel;
    }

    public JLabel getIconLabel() {
        return iconLabel;
    }

    public JLabel getNameLabel() {
        return nameLabel;
    }

    public JLabel getSignLabel() {
        return signLabel;
    }

    public Icon getIcon() {
        return icon;
    }

    public void setIcon(Icon icon) {
        this.icon = icon;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSign() {
        return sign;
    }

    public void setSign(String sign) {
        this.sign = sign;
    }

    public boolean isSelect() {
        return select;
    }

    public void setSelect(boolean select) {
        this.select = select;
    }

}

二、继承DefaultTreeCellRenderer,重写里面的getTreeCellRendererComponent()方法(关于renderer,在iteye中的帖子中已经说过了)

package com.wolffy.render;

import com.wolffy.node.MyTreeNode;
import com.wolffy.util.ImgUtils;

import javax.swing.JTree;
import javax.swing.tree.DefaultTreeCellRenderer;
import java.awt.Component;

/**
 * Created by SongFei on 2017/11/6.
 */
public class MyTreeCellRenderer extends DefaultTreeCellRenderer {

    private static final long serialVersionUID = -3617708634867111249L;

    @Override
    public Component getTreeCellRendererComponent(JTree tree, Object value,
                                                  boolean sel, boolean expanded, boolean leaf, int row,
                                                  boolean hasFocus) {
        MyTreeNode node = (MyTreeNode) value;

        // 根节点从0开始,依次往下
        // 一级节点
        if (node.getLevel() == 1) {
            if (expanded) {
                node.getIconLabel().setIcon(ImgUtils.getIcon("arrow_down.png"));
            } else {
                node.getIconLabel().setIcon(ImgUtils.getIcon("arrow_left.png"));
            }
            return node.getGroupView();
        }

        // 二级节点
        if (node.getLevel() == 2) {
            node.getIconLabel().setIcon(ImgUtils.getIcon("qq_icon.png"));
            return node.getBuddyView();
        }

        return this;
    }

}

三、继承BasicTreeUI,重写相关的方法(具体方法在iteye的帖子中也说过了,这里直接贴代码)

package com.wolffy.ui;

import com.wolffy.render.MyTreeCellRenderer;

import javax.swing.JComponent;
import javax.swing.JTree;
import javax.swing.plaf.basic.BasicTreeUI;
import java.awt.Graphics;

/**
 * Created by SongFei on 2017/11/6.
 */
public class MyTreeUI extends BasicTreeUI {

    @Override
    public void installUI(JComponent c) {
        super.installUI(c);
        JTree jTree = (JTree) c;
        jTree.setRootVisible(false);// 这个一定要设置,否则会报错类型转换异常
        jTree.setToggleClickCount(1);// 单击展开
        jTree.setCellRenderer(new MyTreeCellRenderer());
    }

    // 去除JTree的垂直线
    @Override
    protected void paintVerticalLine(Graphics g, JComponent c, int x, int top, int bottom) {
    }

    // 去除JTree的水平线
    @Override
    protected void paintHorizontalLine(Graphics g, JComponent c, int y, int left, int right) {
    }

    // 实现父节点与子节点左对齐
    @Override
    public void setLeftChildIndent(int newAmount) {
    }

    // 实现父节点与子节点右对齐
    @Override
    public void setRightChildIndent(int newAmount) {
    }

}

四、准备工作已经做好了,直接新建一个jtree

package com.wolffy.frame;

import com.wolffy.node.MyTreeNode;
import com.wolffy.ui.MyTreeUI;
import com.wolffy.util.ImgUtils;
import com.wolffy.util.TreeUtils;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTree;
import javax.swing.WindowConstants;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;


/**
 * Created by SongFei on 2017/10/24.
 */
public class JTreeFrame extends JFrame {

    private static final long serialVersionUID = 1683067312766564107L;

    /**
     * 鼠标滑过
     */
    private Color HOVER_COLOR = new Color(200, 200, 200, 100);

    /**
     * 鼠标点击
     */
    private Color SELECT_COLOR = new Color(160, 160, 160, 100);

    private JPanel jPanel;

    private JTree jTree;

    private DefaultMutableTreeNode root;
    private DefaultTreeModel model;

    public JTreeFrame() {
        initGUI();
    }

    private void initGUI() {
        setSize(700, 575);
        //setUndecorated(true);
        setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);

        jPanel = new JPanel();
        getContentPane().add(jPanel, BorderLayout.CENTER);

        root = new DefaultMutableTreeNode();
        model = new DefaultTreeModel(root);

        for (int i = 1; i <= 3; i++) {
            MyTreeNode cate = new MyTreeNode(ImgUtils.getIcon("arrow_left.png"), "我的分组" + i);
            for (int j = 1; j <= 3; j++) {
                MyTreeNode node = new MyTreeNode(ImgUtils.getIcon("avatar.png"), "好友" + i + "-" + j, "人生若只如初见");
                cate.add(node);
            }
            root.add(cate);
        }

        jTree = new JTree(model);
        jTree.setUI(new MyTreeUI());

        jTree.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseExited(MouseEvent e) {
                TreeUtils.restoreUI(root, model, true);
            }

            @Override
            public void mouseClicked(MouseEvent e) {
                TreePath path = jTree.getSelectionPath();
                if (path == null) {
                    return;
                }
                MyTreeNode node = (MyTreeNode) path.getLastPathComponent();
                if (node == null) {
                    return;
                }
                // 除了好友节点,其他节点都没有点击选中功能
                if (node.getLevel() != 2) {
                    return;
                }
                // 已选择了,就不管,避免重复绘制
                if (node.isSelect()) {
                    return;
                }
                TreeUtils.restoreUI(root, model, false);
                TreeUtils.setBackColor(model, node, SELECT_COLOR);
                node.setSelect(true);
            }
        });

        jTree.addMouseMotionListener(new MouseMotionAdapter() {
            @Override
            public void mouseMoved(MouseEvent e) {
                TreePath path = jTree.getPathForLocation(e.getX(), e.getY());
                if (path == null) {
                    return;
                }
                MyTreeNode node = (MyTreeNode) path.getLastPathComponent();
                if (node == null) {
                    return;
                }
                // 已选择了,就不管,避免重复绘制
                if (node.isSelect()) {
                    return;
                }
                TreeUtils.restoreUI(root, model, true);
                TreeUtils.setBackColor(model, node, HOVER_COLOR);
            }
        });

        jPanel.add(jTree);
    }

    public static void main(String[] args) {
        JTreeFrame jTreeFrame = new JTreeFrame();
        jTreeFrame.setVisible(true);
    }

}

上述代码中,添加鼠标事件的时候,大家需要注意一下mouseMoved()方法,此方法在MouseListener和MouseMotionListener都有,但是我们选择了MouseMotionListener中了,因为MouseListener中的mouseMoved()方法,他只在第一次鼠标进入到jtree时生效,当你将鼠标从一个节点移动到另外一个节点时,不会再次触发,而在MouseMotionListener中的mouseMoved()方法,只要你鼠标在移动,他就会不停的触发

在iteye的帖子中,鼠标滑入之后,再次移出,jtree上的滑入特效还存在,当时没在意,这次看了一下QQ的做法,QQ是除了点击选中的那一个之外,其他的特效都恢复原状,而在mouseMoved()和mouseClick()的时候,也会重绘其他节点,这几处的代码有很多重复,所以抽了一个工具类TreeUtils.java

package com.wolffy.util;

import com.wolffy.node.MyTreeNode;

import javax.swing.JPanel;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import java.awt.Color;

/**
 * Created by SongFei on 2017/11/6.
 */
public class TreeUtils {

    /**
     * 设置Node背景颜色
     *
     * @param model 模型,需要用它来刷新jtree
     * @param node  自定义的MyTreeNode节点
     * @param color 颜色
     */
    public static void setBackColor(DefaultTreeModel model, MyTreeNode node, Color color) {
        JPanel panel = null;

        if (node.getLevel() == 1) {
            panel = (JPanel) node.getGroupView();
        }

        if (node.getLevel() == 2) {
            panel = (JPanel) node.getBuddyView();
        }

        // 鼠标move的时候,此方法会被频繁调用,用颜色做一下比对,降低性能开销
        if (panel != null && panel.getBackground() != color) {
            panel.setBackground(color);
            model.reload(node);
        }
    }

    /**
     * 重置jtree的UI,将一些操作效果都还原
     *
     * @param root         根节点
     * @param model        模型
     * @param retainSelect 是否保留已选的Node
     */
    public static void restoreUI(DefaultMutableTreeNode root, DefaultTreeModel model, boolean retainSelect) {
        int count = root.getChildCount();
        if (count <= 0) {
            return;
        }
        // 重置二级节点UI
        for (int i = 0; i < count; i++) {
            MyTreeNode group = (MyTreeNode) root.getChildAt(i);
            restoreGroupUI(model, group);
            // 重置三级节点UI
            for (int j = 0; j < group.getChildCount(); j++) {
                MyTreeNode buddy = (MyTreeNode) group.getChildAt(j);
                // 保留已选择的Node
                if (retainSelect && buddy.isSelect()) {
                    continue;
                }
                restoreBuddyUI(model, buddy);
            }
        }
    }

    /**
     * 重置分组节点的UI
     *
     * @param model 模型
     * @param node  自定义Node
     */
    private static void restoreGroupUI(DefaultTreeModel model, MyTreeNode node) {
        node.getGroupView().setBackground(null);
        node.setSelect(false);
        model.reload(node);
    }

    /**
     * 重置好友节点的UI
     *
     * @param model 模型
     * @param node  自定义Node
     */
    private static void restoreBuddyUI(DefaultTreeModel model, MyTreeNode node) {
        node.getBuddyView().setBackground(null);
        node.setSelect(false);
        model.reload(node);
    }

}

基本上就这些了,上述代码,大家可以直接拷贝运行!

QQ群: 686430774  /  718410762

站长Q: 1347384268

如果文章有帮到你,可以考虑请博主喝杯咖啡!

分享到:

欢迎分享本文,转载请注明出处!

作者:不忘初心

发布时间:2017-11-07

永久地址:https://www.jiweichengzhu.com/article/5ff1be1e8abe463794aa1011b4bcff96

评论