ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

JavaFX界面美化:FontAwesomeFX图标与JFoenix Material控件集成实战

JavaFX界面美化:FontAwesomeFX图标与JFoenix Material控件集成实战 话说这个系列写到第十五篇前面一直在折腾JavaFX原生控件、布局和CSS样式到这期终于要进入一个让界面质感明显提升的阶段了。标题里这两个库——FontAwesomeFX和JFoenix很多做JavaFX桌面端的开发者应该都听过名字但不一定认真上手过。简单说FontAwesomeFX解决的是“图标从哪来、怎么用”的问题JFoenix解决的是“控件能不能再现代一点”的问题两个搭配起来JavaFX默认那套朴素的界面能直接换个气质。这篇文章不打算写成API文档搬运而是按照我在实际项目中集成的顺序把依赖引入、工程配置、代码写法和坑位排查都过一遍。无论你是刚把JavaFX在IDEA里跑起来的新手还是已经写了不少不规则界面、想加图标和Material风格控件的开发者这篇都能直接照着操作。1. 先把这两个库搞清楚谁解决图标谁解决控件1.1 FontAwesomeFX到底做了什么FontAwesomeFX本质上是一个字体图标方案它的核心不是Image图片而是“字体”。开发者把矢量图标打包进字体文件JavaFX再把文字渲染成图形。这样做的最大好处是图标可以无限缩放不失真颜色可以直接用CSS或代码改比切一张张PNG、SVG图省太多事。它在工程里提供了几个不同字体库的Java封装日常最常用的有三个FontAwesome图标集经典的web图标包含搜索、用户、设置、删除、编辑这些高频操作图标。Material Design Icons图标集谷歌Material设计语法的图标集风格更简洁。Weather Icons图标集天气类图标虽然不是主需求但做个性化界面时很能救场。实际项目里我主要用的是FontAwesome和Material Design Icons这两个。尤其在做工具栏、侧边栏菜单、按钮图标时有它们就不需要再单独准备任何图片资源。1.2 JFoenix给控件换了一身Material Design皮肤如果说FontAwesomeFX是给界面“加图标”那JFoenix就是给JavaFX“换控件”。它基于JavaFX原生控件做了一套符合Material Design规范的实现比如带水波纹反馈的JFXButton、带浮动提示的JFXTextField、滑动开关JFXSwitch、还有JFXDialog、JFXSnackbar这类高级容器。有人可能会问纯CSS不是也能美化JavaFX按钮吗为什么要引入一个第三方控件库这里有一个关键区别原生控件加上CSS之后静态外观可以做到比较好看但交互动效尤其是Material Design里标志性的水波纹涟漪效果用纯CSS很难模拟。JFoenix通过自定义Skin类把动画写在了控件内部开发者直接用现成的特效控件就好不需要自己造轮子。另外JFoenix的输入校验、日期选择、树形表格等控件也做了很多增强。比如JFXTextField自带校验提示样式、JFXDatePicker的弹出日历样式完全符合Material风格这些用原生控件要写不少代码才能实现。2. 集成前的准备工作依赖与工程配置直接影响成败2.1 Maven依赖这样引入先说我比较推荐的Maven方式。项目里只要在pom.xml里添加如下依赖dependency groupIdde.jensd/groupId artifactIdfontawesomefx-fontawesome/artifactId version4.7.0-9.1.2/version /dependency dependency groupIdde.jensd/groupId artifactIdfontawesomefx-materialdesignfont/artifactId version1.7.22-9.1.2/version /dependency dependency groupIdcom.jfoenix/groupId artifactIdjfoenix/artifactId version9.0.10/version /dependency这里要特别提一下版本选择。FontAwesomeFX最早的集成包是de.jensd:fontawesomefx这一个包里面把所有图标集都合在一起但那个是针对Java 8设计的。后来作者把项目拆分成了fontawesomefx-fontawesome、fontawesomefx-materialdesignfont、fontawesomefx-weathericons等独立模块并且出了适配Java 9以上模块化系统的版本也就是上面坐标里的9.1.2后缀。如果项目还在用JDK 8可以用老的fontawesomefx:8.15这种整体包编码方式完全一样。但新项目我建议直接按上面拆分包走依赖更清晰Java 11、Java 17的兼容性也更稳。JFoenix这边目前最新版本是9.0.10适配Java 9以上的模块化环境。如果还在用Java 8用8.0.10更安全。2.2 不走Maven的项目怎么加jar包有些项目是传统Java工程没有用Maven或Gradle管理只靠IDEA的Artifacts打包。这种情况就把对应版本的jar包下载下来放到项目的lib目录然后在IDEA里做两件事在“Project Structure - Libraries”点加号把lib目录下的jar包加入。确保打包Artifacts时把jar包输出到最终jar的同级目录并且Class Path里能定位到它们。这个方案能跑但后面做模块化运行和打包时比较容易踩坑。因为FontAwesomeFX和JFoenix都涉及大量的反射、字体加载操作非Maven工程里只要classpath路径有问题最容易出现的症状就是代码编译通过、程序启动也不报错但界面上的图标全部变成方框或问号。2.3 module-info.java与IDEA运行参数配置JDK 11之后JavaFX已经不在JDK里自带咱们的项目要么用Maven插件自动下载JavaFX依赖要么手动配置SDK。不管哪种方式只要工程里有module-info.java就得在模块描述文件里显式声明用到了哪些模块module com.example.demo { requires javafx.controls; requires javafx.fxml; requires de.jensd.fx.glyphs.commons; requires de.jensd.fx.glyphs.fontawesome; requires de.jensd.fx.glyphs.materialdesignicons; requires com.jfoenix; exports com.example.demo; }这里模块名和Maven坐标里的artifactId不一样很多人在这一步卡住。fontawesomefx-fontawesome对应的模块名是de.jensd.fx.glyphs.fontawesomefontawesomefx-materialdesignfont对应的模块名是de.jensd.fx.glyphs.materialdesignicons而公共基础包fontawesomefx-commons对应de.jensd.fx.glyphs.commons。如果是IDEA里通过Run Configuration直接跑主类分模块和非模块两种情况。非模块化工程需要在VM options里指定JavaFX SDK--module-path /path/to/javafx-sdk-17/lib --add-modules javafx.controls,javafx.fxml模块化工程在配置了module-info.java之后IDEA会自动检测JavaFX模块但FontAwesomeFX和JFoenix如果报“package not visible”之类的错误多半是少了上面那几个requires声明。还有一种常见做法是直接给VM options加上--add-modules de.jensd.fx.glyphs.commons,de.jensd.fx.glyphs.fontawesome,de.jensd.fx.glyphs.materialdesignicons,com.jfoenix这条配置在非模块化工程里能绕过很多module-info的报错实测下来比较省事。3. FontAwesomeFX实战从图标到按钮的真实用法3.1 三行代码实现带图标的按钮依赖配好后代码接入其实很轻量。举个例子做一个带搜索图标的按钮import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon; import de.jensd.fx.glyphs.fontawesome.FontAwesomeIconView; import javafx.scene.control.Button; FontAwesomeIconView searchIcon new FontAwesomeIconView(FontAwesomeIcon.SEARCH); searchIcon.setGlyphSize(16); Button searchButton new Button(搜索, searchIcon);这里FontAwesomeIconView是FontAwesomeFX提供的视图类它本质上是一个Text节点所以按钮设置Graphic时可以直接传进去。setGlyphSize控制图标大小单位是像素级和字号概念相似。Material Design Icons图标集用法完全一样只不过类名变成MaterialDesignIconView枚举类变成MaterialDesignIconimport de.jensd.fx.glyphs.materialdesignicons.MaterialDesignIcon; import de.jensd.fx.glyphs.materialdesignicons.MaterialDesignIconView; MaterialDesignIconView userIcon new MaterialDesignIconView(MaterialDesignIcon.ACCOUNT); userIcon.setGlyphSize(20);3.2 图标大小、颜色与对齐的那些细节实际用起来有几个参数我觉得值得好好说一下因为它们直接决定界面细节的精致程度。首先是图标颜色。FontAwesomeIconView继承自Text所以可以直接调用setFill方法searchIcon.setFill(Color.WHITE);也可以用CSS来控制给图标view设置一个样式类然后在样式表里写-fx-fill属性.search-icon { -fx-fill: #ffffff; }其次是图标大小。除了setGlyphSize还有setGlyphStyle可以组合字体样式但日常场景用setGlyphSize就够了。需要注意的是setGlyphSize里的数字是图标所在字号的点阵大小不是最终渲染宽高像素值实际控件的尺寸会跟随这个值按比例缩放所以设置成16和设置成18之间差别肉眼可见。还有一个比较容易忽略的点带文字的按钮文字和图标的间距控制。默认情况下new Button(搜索, searchIcon)里图标会紧贴文字视觉上有点挤。可以在图标view外面包一个空的Region或者直接给按钮的graphicTextGap属性赋值searchButton.setGraphicTextGap(8);graphicTextGap在原生ButtonBase里就有JFoenix的JFXButton也继承了它。我给项目做统一工具类时通常是批量把按钮的graphicTextGap设为6到10像素整体间距会更舒服。3.3 进阶用GlyphsFactory给图标加上叠加特效如果觉得单个图标太单薄想要图标叠底、描边、阴影这类效果FontAwesomeFX提供了GlyphsFactory工具。简单说GlyphsFactory可以把一个图标渲染成Image对象再转换成ImageView进行更自由的样式控制。同时它还支持用StackPaneIconWrapper把图标和字符合并成一个节点适合做那种带角标的图标效果比如消息图标右上角带一个数字提示。我实际在桌面端项目里给侧边栏菜单做过这样的效果菜单项左边的图标用一种颜色图标右下角叠一个小红点表示“有更新”。用StackPaneIconWrapper实现起来很方便import de.jensd.fx.glyphs.GlyphsFactory; import de.jensd.fx.glyphs.fontawesome.FontAwesomeIconView; import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon; FontAwesomeIconView envelopeIcon new FontAwesomeIconView(FontAwesomeIcon.ENVELOPE); FontAwesomeIconView dotIcon new FontAwesomeIconView(FontAwesomeIcon.CIRCLE); dotIcon.setGlyphSize(8); dotIcon.setFill(Color.RED); StackPane stackPane new StackPane(envelopeIcon, dotIcon); StackPane.setAlignment(envelopeIcon, Pos.CENTER); StackPane.setAlignment(dotIcon, Pos.BOTTOM_RIGHT);这种方式做出来的角标图标是矢量的放大缩小都不糊比截一张带角标的PNG图灵活得多。4. JFoenix控件接入与风格统一4.1 常用控件替换对照表JFoenix的接入思路不是推翻原有代码而是把原生控件替换成对应的JFX版本。它提供了几乎全套的Material风格控件日常开发最常用的是下面几个功能场景JavaFX原生控件JFoenix替换控件按钮ButtonJFXButton单行输入框TextFieldJFXTextField密码输入PasswordFieldJFXPasswordField下拉选择ComboBoxJFXComboBox日期选择DatePickerJFXDatePicker开关CheckBox / ToggleButtonJFXCheckBox / JFXToggleButton对话框Alert / DialogJFXDialog / JFXAlert提示消息无内置JFXSnackbar树形表格TreeTableViewJFXTreeTableView替换时有个细节要记住JFXButton、JFXTextField这些类都继承自原生控件类所以原有事件方法、属性方法都可以继续用。比如JFXTextField就是TextField的子类setText、getText、textProperty()这些方法完全不冲突。但JFXComboBox和原生ComboBox的差异稍微大一点它的数据源需要通过setItems传入一个FXCollections.observableArrayList这个用法和原生的ComboBox是一致的只是视觉样式完全不同。4.2 JFXButton和JFXTextField的典型配置JFXButton的一大亮点是水波纹点击效果。它不是默认就有的效果而是需要设置样式类和按钮类型。import com.jfoenix.controls.JFXButton; import javafx.scene.paint.Color; JFXButton loginButton new JFXButton(登录); loginButton.getStyleClass().add(button-raised); loginButton.setRipplerFill(Color.web(#FFF)); loginButton.setButtonType(JFXButton.ButtonType.RAISED); loginButton.setPrefHeight(40); loginButton.setPrefWidth(120); loginButton.setStyle(-fx-background-color: #3F51B5; -fx-text-fill: white; -fx-font-size: 14px;);这里ButtonType.RAISED会让按钮带有投影效果比较适合主要操作按钮。如果希望按钮扁平化用ButtonType.FLAT就好。setRipplerFill控制水波纹颜色浅色背景上建议用深色波纹深色背景上用白色波纹对比度更明显。JFXTextField的浮动提示效果很实用在输入框上显示一个label输入文字后label会上浮到边框上。标准用法import com.jfoenix.controls.JFXTextField; JFXTextField usernameField new JFXTextField(); usernameField.setLabelFloat(true); usernameField.setPromptText(请输入用户名); usernameField.setPrefWidth(300); usernameField.setFocusColor(Color.web(#3F51B5)); usernameField.setUnFocusColor(Color.web(#9E9E9E));setLabelFloat(true)是核心配置开了之后提示文字才有浮动效果。setFocusColor和setUnFocusColor分别控制聚焦和失焦时边框与label的颜色。4.3 组合实战做一个小型搜索工具栏把两个库结合起来才是集成它们的最终目的。我拿一个常用的“顶部搜索工具栏”做示例看看真实项目里是怎么写的。需求是顶部一排工具栏左边一个带用户图标的标题中间一个搜索框右边一个带刷新图标的按钮和一个带设置图标的按钮。代码大概长这样import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXTextField; import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon; import de.jensd.fx.glyphs.fontawesome.FontAwesomeIconView; import javafx.geometry.Pos; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.scene.paint.Color; HBox toolBar new HBox(12); toolBar.setAlignment(Pos.CENTER_LEFT); toolBar.setPadding(new Insets(12, 16, 12, 16)); toolBar.setStyle(-fx-background-color: #FFFFFF; -fx-effect: dropshadow(gaussian, rgba(0,0,0,0.12), 8, 0, 0, 2);); // 标题图标 FontAwesomeIconView userIcon new FontAwesomeIconView(FontAwesomeIcon.USER_CIRCLE); userIcon.setGlyphSize(24); userIcon.setFill(Color.web(#3F51B5)); // 搜索输入框 JFXTextField searchField new JFXTextField(); searchField.setLabelFloat(true); searchField.setPromptText(搜索用户、订单、商品); searchField.setFocusColor(Color.web(#3F51B5)); searchField.setUnFocusColor(Color.web(#BDBDBD)); HBox.setHgrow(searchField, Priority.ALWAYS); // 带图标的刷新按钮 FontAwesomeIconView refreshIcon new FontAwesomeIconView(FontAwesomeIcon.REFRESH); refreshIcon.setGlyphSize(16); JFXButton refreshButton new JFXButton(刷新, refreshIcon); refreshButton.setButtonType(JFXButton.ButtonType.FLAT); refreshButton.setRipplerFill(Color.web(#3F51B5)); // 带图标的设置按钮 FontAwesomeIconView settingIcon new FontAwesomeIconView(FontAwesomeIcon.COG); settingIcon.setGlyphSize(16); JFXButton settingButton new JFXButton(设置, settingIcon); settingButton.setButtonType(JFXButton.ButtonType.FLAT); settingButton.setRipplerFill(Color.web(#3F51B5)); toolBar.getChildren().addAll(userIcon, searchField, refreshButton, settingButton);这个例子里我做了几个关键设计HBox.setHgrow(searchField, Priority.ALWAYS)让搜索框自动占满剩余宽度窗口大小变化时控件布局自适应。刷新和设置按钮用FLAT模式视觉上不抢主操作按钮的权重。图标用setGlyphSize统一成16像素保证工具栏所有图标视觉大小一致。整个toolBar整体加了一个浅色阴影模拟Material Design里的Elevation层级。如果还想再精致一点可以把用户图标放在一个圆形裁剪的区域里像头像一样显示或者加上一段JFXSnackbar点击反馈让按钮点击后弹出轻提示。这些后续根据项目需要扩展就好。5. 常见问题与排查技巧实录5.1 图标变成方框或问号这是FontAwesomeFX最典型的报错现场。代码明明没报错按钮上也设置了图标但运行后图标位置显示一个空心矩形或问号。这种情况百分之九十是字体文件没有加载到。FontAwesomeFX的原理是让JavaFX去读取jar包里的TTF字体文件如果字体文件没有被ClassLoader发现渲染自然失败。排查步骤按顺序来确认工程里真的引入了对应jar包检查idea的External Libraries里有没有。检查所用图标枚举和实际引入的图标集是否匹配。比如引入的是fontawesomefx-materialdesignfont却用了FontAwesomeIcon这个枚举那一定显示不出来反过来也一样。检查module-info.java里的requires声明是否完整漏了de.jensd.fx.glyphs.commons的话图标view类可能能编译但底层字体资源加载会失败。确认打包后的jar里有没有.ttf文件。如果是自定义打包流程很容易漏掉jar中的字体资源解压后看看/fonts/目录下有没有fontawesome-webfont.ttf之类的文件。最后一种情况如果是通过IDEA直接Run运行的很少出现单图标不显示的问题更多是出现在打包可执行jar或exe之后。解决方案是在打包配置里确保fontawesomefx相关jar没有被过滤掉。5.2 JDK版本不同导致的module-info报错这个问题的询问热度一直很高很多人在IDEA里配置JavaFX时踩到。核心原因是JDK 9及以后引入了模块系统FontAwesomeFX和JFoenix如果要用在带module-info.java的工程里必须显式声明对它们模块的依赖。但国内很多JavaFX教程还是基于JDK 8写的教程里根本没提模块概念导致新手跟着教程配完代码一运行就报Package de.jensd.fx.glyphs.fontawesome is not visible解决办法有两个思路。一是规范地补全requires像前面代码那样把需要用到的一一列出来。二是如果想快速绕过可以把项目的module-info.java删掉让项目退回classpath模式同时用VM options指定JavaFX模块。我个人的建议是如果项目用了Maven而且不太复杂直接保留module-info.java把依赖关系写清楚后面打包原生化、做自定义运行时镜像都会省很多事。如果只是临时跑个Demo删了省心。还有一个JFoenix特有的坑在JDK 17高版本下JFoenix内部一些反射代码会触达Java内部API运行时会打印IllegalAccessError或InaccessibleObjectException。解决办法是在VM options里加上--add-opens java.base/java.lang.reflectALL-UNNAMED --add-opens java.base/java.utilALL-UNNAMED实测JDK 17下加上这两个参数之后JFoenix的很多特效控件才能正常工作不然轻则功能异常重则直接启动失败。5.3 JFoenix样式覆盖失效JFoenix控件默认套用了一套内置样式以自己的样式类名作为命名空间。比如JFXButton在使用CSS时对应的样式类名是.jfx-button而不是原生Button的.button。很多人想当然地写.button { -fx-background-color: red; }结果发现按钮颜色完全没变。原因就是JFoenix的默认样式类覆盖了原生样式类直接用-fx-background-color未必生效。正确的做法是.jfx-button { -fx-background-color: red; }但这里还有一个隐坑如果同时使用了JFoenix的ButtonType.RAISED按钮背景色能不能覆盖成功还取决于是否加过-fx-background-radius、-fx-background-insets这些属性。建议在CSS里处理JFXButton时尽量把背景色、圆角、内边距一起设置不然容易出现只有边框变色、内部还是默认底色的情况。JFXTextField的样式覆盖相对复杂一些它内部包含多层结构直接用-fx-background-color大概率不生效。需要覆盖的是它内部的.text-field和JFXTextField的line颜色这个一般通过代码里的setFocusColor、setUnFocusColor处理更方便不用碰CSS。5.4 版本兼容速查表做JavaFX桌面项目这几年我越来越有一个体会JavaFX最折磨人的往往不是写界面逻辑而是各种库的版本组合。这里整理一个实测过的兼容参考表使用场景推荐组合说明JDK 8 JavaFX 8fontawesomefx 8.15 jfoenix 8.0.10老程序稳定运行方案JDK 11 JavaFX 11fontawesomefx-fontawesome 4.7.0-9.1.2 jfoenix 9.0.10目前最稳组合JDK 17 JavaFX 17同上 两个--add-opens参数能跑但需要注意反射警告Maven 模块化需要配好module-info里的requires按前面配置即可如果JDK版本升到21及以上JFoenix尚未完全适配部分控件可能会出现渲染异常建议先评估后再引入。最后再说几句这个系列一路写到这里JavaFX的界面已经从能用变成了好看。FontAwesomeFX和JFoenix的集成思路其实并不复杂一个解决图标资源一个解决控件皮肤但真正让项目受益的地方在于它们能一起把界面的统一性拉高一个档次。没有这两个库的时候很多桌面项目里各个页面按钮风格五花八门有了Material Design规范约束之后操作区、工具栏、表单控件的观感自然统一起来。我实际用下来的一个小建议是图标别贪多一个页面里按重要程度保留三到五个图标就足够。JFoenix的控件也不要全盘替换替换那些用户高频交互的控件就好比如按钮、输入框、下拉框这样代码改动范围内收得住视觉提升却非常明显。这一篇的集成工作做完下一期可以试试用JFoenix的JFXDecorator做自定义窗口标题栏把程序边框也完全掌控住。做桌面端的人应该懂这一步做完整个应用的气场就彻底不一样了。
返回列表