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;

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