javafx treeview菜单千层渲染算法

通过接口自动构建TreeView树形菜单

进行javafx开发时候,渲染树形菜单代码比较繁琐,本文将通过查询到的sql结果集,自动装配TreeView。其中装配算法通过java的引用重定向,递归回调等实现,,代码算法逻辑难道较大,小白慎入。且该算法可以升级到一切父子结构菜单,通过返回json串,只需要一次调用,即可渲染10层,100层菜单树。算法效率很高。复杂度仅仅为O(N)(N是所有节点个数)。

下例展示5层菜单的无延迟极快渲染

sql建表,预装数据


CREATE TABLE IF NOT EXISTS `menu0` (
  `id` int(11) DEFAULT NULL,
  `name` varchar(50) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='0级菜单';
INSERT INTO `menu0` (`id`, `name`) VALUES
	(1, '世界');

CREATE TABLE IF NOT EXISTS `menu1` (
  `id` int(11) DEFAULT NULL,
  `name` varchar(50) DEFAULT NULL,
  `parent_id` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='一级菜单';
INSERT INTO `menu1` (`id`, `name`, `parent_id`) VALUES
	(1, '亚洲', 1),
	(2, '美洲', 1),
	(3, '欧洲', 1);


CREATE TABLE IF NOT EXISTS `menu2` (
  `id` int(11) DEFAULT NULL,
  `name` varchar(50) DEFAULT NULL,
  `parent_id` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='二级菜单';
INSERT INTO `menu2` (`id`, `name`, `parent_id`) VALUES
	(1, '中国', 1),
	(2, '韩国', 1),
	(4, '美国', 2),
	(3, '日本', 1),
	(5, '加拿大', 2),
	(6, '墨西哥', 2),
	(7, '巴拿马', 2),
	(8, '英国', 3),
	(9, '法国', 3);

CREATE TABLE IF NOT EXISTS `menu3` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(50) DEFAULT NULL,
  `parent_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8 COMMENT='三级菜单';
INSERT INTO `menu3` (`id`, `name`, `parent_id`) VALUES
	(1, '澳门', 1),
	(2, '香港', 1),
	(3, '首尔', 2),
	(4, '釜山', 2),
	(5, '纽约', 4),
	(6, '华盛顿', 4),
	(7, '安徽', 1),
	(8, '长崎', 3),
	(9, '东京', 3),
	(10, '多伦多', 5),
	(11, '阿瓜斯卡连特斯', 6),
	(12, '阿瓜斯卡连特斯1', 6),
	(13, '巴黎', 9),
	(14, '伦敦', 8),
	(15, '伯明翰', 8);
	
CREATE TABLE IF NOT EXISTS `menu4` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(50) DEFAULT NULL,
  `parent_id` int(11) DEFAULT NULL,
  `parent_name` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=29 DEFAULT CHARSET=utf8 COMMENT='四级菜单';
INSERT INTO `menu4` (`id`, `name`, `parent_id`, `parent_name`) VALUES
	(1, '澳门半岛', 1, '澳门'),
	(2, '九龙', 2, '香港'),
	(3, '江南区', 3, '首尔'),
	(4, '西临沙上区', 4, '釜山'),
	(5, '布朗克斯', 5, '纽约'),
	(6, '奥林匹亚市', 6, '华盛顿'),
	(7, '安庆', 7, '安徽'),
	(8, '半岛', 8, '长崎'),
	(9, '千代田区 ', 9, '东京'),
	(10, '士嘉堡', 10, '多伦多'),
	(11, 'a区', 11, '阿瓜斯卡连特斯'),
	(12, 'a1区', 12, '阿瓜斯卡连特斯1'),
	(13, '第1区', 13, '巴黎'),
	(14, '伦敦城', 14, '伦敦'),
	(15, 'a', 15, '伯明翰'),
	(16, '氹仔岛', 1, '澳门'),
	(17, '江东区', 3, '首尔'),
	(19, '布朗克斯', 5, '纽约'),
	(20, '布鲁克林', 5, '纽约'),
	(21, '斯波坎', 6, '华盛顿'),
	(22, '合肥', 7, '安徽'),
	(23, '蚌埠', 7, '安徽'),
	(24, '港区', 9, '东京'),
	(25, '中央区', 9, '东京'),
	(26, '第2区 ', 13, '巴黎'),
	(27, '第3区 ', 13, '巴黎'),
	(28, '西伦敦', 14, '伦敦');


查询结果集

select
		cast(t0.id as char) f_f_1_menuId,
		t0.name f_f_1_menuName,

		cast(t1.id as char) f_f_2_menuId,
		t1.name f_f_2_menuName,

		cast(t2.id as char) f_f_3_menuId,
		t2.name f_f_3_menuName,

		cast(t3.id as char) f_f_4_menuId,
		t3.name f_f_4_menuName,
		
		cast(t4.id as char) f_f_5_menuId,
		t4.name f_f_5_menuName

		from  menu0 t0 
		left join menu1 t1 on t0.id=t1.parent_id

		left join menu2 t2 on t1.id=t2.parent_id

		left join menu3 t3 on t2.id=t3.parent_id
		left join menu4 t4 on t3.id=t4.parent_id

如下sql结果为:
在这里插入图片描述

main函数渲染结果

public class Main extends Application {
	private MysqlUtils mysqlUtils = new MysqlUtils();
	private MenuNode menuNode = new MenuNode();
	@Override
	public void start(Stage primaryStage) {
		try {
			Pane p = new Pane();
			p.setPrefHeight(500);
			p.setPrefWidth(400);
			Scene scene = new Scene(p,800,800);
			
			scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
			
			
			//@1 sql获取菜单集合,只需要查询菜单列表,调用一个接口,就自动获取treeview,你不需要去写任何sql以外的代码。
			String sql ="select\r\n" + 
					"		cast(t0.id as char) f_f_1_menuId,\r\n" + 
					"		t0.name f_f_1_menuName,\r\n" + 
					"\r\n" + 
					"		cast(t1.id as char) f_f_2_menuId,\r\n" + 
					"		t1.name f_f_2_menuName,\r\n" + 
					"\r\n" + 
					"		cast(t2.id as char) f_f_3_menuId,\r\n" + 
					"		t2.name f_f_3_menuName,\r\n" + 
					"\r\n" + 
					"		cast(t3.id as char) f_f_4_menuId,\r\n" + 
					"		t3.name f_f_4_menuName,\r\n" + 
					"		\r\n" + 
					"		cast(t4.id as char) f_f_5_menuId,\r\n" + 
					"		t4.name f_f_5_menuName\r\n" + 
					"\r\n" + 
					"		from  menu0 t0 \r\n" + 
					"		left join menu1 t1 on t0.id=t1.parent_id\r\n" + 
					"\r\n" + 
					"		left join menu2 t2 on t1.id=t2.parent_id\r\n" + 
					"\r\n" + 
					"		left join menu3 t3 on t2.id=t3.parent_id\r\n" + 
					"		left join menu4 t4 on t3.id=t4.parent_id";
			List<Map<String, Object>> muneList = mysqlUtils.getList(sql);
			
			//@2 调用公共接口,一键渲染得到treeview对象;
			TreeView<String> tree = menuNode.getMenuTreeview(muneList);
			tree.setPrefHeight(800);
			tree.setPrefWidth(800);
			
			//@3 写入scene
			p.getChildren().add(tree);
			
			
			primaryStage.setScene(scene);
			primaryStage.show();
		} catch(Exception e) {
			e.printStackTrace();
		}
	}
	
	public static void main(String[] args) {
		launch(args);
	}
}

运行代码得到结果如下:
在这里插入图片描述
在这里插入图片描述

核心算法源码

public static MenuNode getMenu(List<Map<String, Object>> muneList,MenuNode rootMenuNode, String... arg) throws Exception {
		MenuNode rootMenu = rootMenuNode;
		String menuStartWith = menuFieldRegDefalt;
		
		if(arg.length > 0) {//针对arg有参数,
			menuStartWith = arg[0];
			if(!RegexpTool.isMatch(menuStartWith, menuFieldRegLeft)) {
				throw new Exception(String.format("arg[0] pattern is not '%s'", menuFieldRegLeft) );
			}
		}
		
		/**
		 * 1、获取菜单层次
		 */
		
		int maxLevel = 0;//菜单最大层数
		boolean f_1th_map = true;//第一次遍历muneList标记
		for(Map<String, Object> map : muneList) {//遍历数据行(f1_*,f1_*,f2_*,f2_*格式的数据)(后改成f_f_1_*)
			
			if(f_1th_map) {//第一次遍历记录maxLevel
				
				for(Entry<String, Object> entry : map.entrySet()) {
					if(!RegexpTool.isMatch(entry.getKey(), menuFieldRegLeft + ".*")) {
						continue;
					}else {
						if(!RegexpTool.isMatch(entry.getKey(), menuFieldReg)) {
							throw new Exception(String.format("muneList field about mune %s pattern is not '%s'", entry.getKey(), menuFieldReg));
						}
					}
					String levelStr = RegexpTool.getOneByReg(entry.getKey(), "(?<="+menuStartWith+")\\d+");
					int level = Integer.parseInt(levelStr == null ? "0" : levelStr);//获取当前菜单层级。

					if(maxLevel < level) {
						maxLevel = level;
					}
				}
				f_1th_map = false;
			}
			
			int i = 0;
			List<MenuNode> enuNodeList_1 = new ArrayList<>();
			MenuNode menuNode = null;
			String parentId = "";
			while(maxLevel > i) {//依次处理 f1,f2,f3,f4...........,该行各个层级的数据,放入rootMenu 对应的位置。
				if(i == 0) {
					enuNodeList_1 = rootMenu.getMenuSub();
				}else {
					enuNodeList_1 = menuNode.getMenuSub();
				}
				
				/**
				 * 获取map值。
				 */
				String parentID = (String) map.get(String.format("%s%d_%s", menuStartWith,i+1,"parentID"));
				String menuId = (String) map.get(String.format("%s%d_%s", menuStartWith,i+1,"menuId"));
				String menuName = (String) map.get(String.format("%s%d_%s", menuStartWith,i+1,"menuName"));
				String orderById = (String) map.get(String.format("%s%d_%s", menuStartWith,i+1,"orderById"));
				String remark = (String) map.get(String.format("%s%d_%s", menuStartWith,i+1,"remark"));
				String icon = (String) map.get(String.format("%s%d_%s", menuStartWith,i+1,"icon"));
				String style = (String) map.get(String.format("%s%d_%s", menuStartWith,i+1,"style"));
				String show = (String) map.get(String.format("%s%d_%s", menuStartWith,i+1,"show"));
				String path = (String) map.get(String.format("%s%d_%s", menuStartWith,i+1,"path"));
				boolean expanded = (boolean) (map.get(String.format("%s%d_%s", menuStartWith,i+1,"expanded")) == null ? false :map.get(String.format("%s%d_%s", menuStartWith,i+1,"expanded")));
				if(menuId == null) {
					break;
				}
				if(enuNodeList_1.size() == 0) {
					menuNode = new MenuNode(parentID == null ? "-1" : parentID, menuId == null ? "0" : menuId, menuName == null ? "0" : menuName, orderById == null ? "" : orderById,
							remark == null ? "" : remark, icon == null ? "" : icon, style == null ? "" : style, StringUtils.isBlank(show) ? "1" : show, expanded, path);
					enuNodeList_1.add(menuNode);
				}else {
					//如果size>0,且相等于当前,则menuNode重新指向;如果size=0且不相等,则新建。
					boolean f_sizem0e = false;//flag
					for(MenuNode m : enuNodeList_1) {
						if(m.getMenuId().equals(menuId)) {
							menuNode = m;
							f_sizem0e = true;
							break;
						}
					}
					
					if(!f_sizem0e) {
						menuNode = new MenuNode(parentID == null ? "-1" : parentID, menuId == null ? "0" : menuId, menuName == null ? "0" : menuName, orderById == null ? "" : orderById,
								remark == null ? "" : remark, icon == null ? "" : icon, style == null ? "" : style, StringUtils.isBlank(show) ? "1" : show, expanded, path);
						enuNodeList_1.add(menuNode);
					}
					
				}
				
				enuNodeList_1 = menuNode.getMenuSub();//重新指向下一个menuNode
				i++;
			}
		}
		return rootMenu;
	}

总结

通过一个sql查询结果集,调用该源码公共接口,直接渲染模型。该算法,解决了遍历的空间复杂度,时间复杂度问题,合理利用java的对象引用概念,递归模式,灵活指向父子对象,完成百层,千层菜单渲染。而且你可以升级我的源码,将可以通过sql直接得到前端web界面需要的父子结果菜单json串,从而快捷渲染树形菜单。且比你的传统获取菜单方式快捷,方便。而且关于该源码算法,也是研读了base64算法源码,得来的灵感。当然该算法对java引用的理解要求高。我相信你如果喜欢java,通过java实现复杂算法逻辑,实现复杂的业务逻辑,你可以下载该源码,执行完sql建表语句后,可以在jdk环境下直接运行。欢迎大家一起交流,你会发现java很美妙。最近也改造了不少javafx相关源码,你会发现这是一件有趣的事情。且在这个过程中你会体会到对象这个概念在java中是如何诠释地淋漓尽致。万物皆对象。且我的博客以后会发很多类似文章。
博主希望实现公司项目地复杂算法逻辑,对于java性能优化,sql优化有较多实践。欢迎各位,不吝赐教。其实我也是一个菜鸟,很多都不懂,只是喜欢java 对象,它很优雅。
算法java项目:

欢迎阅读源码,交流。正在搭建中网站:albagu.com;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章