java - 在 JavaFX 中实现 Fluent Design 的高亮显示效果
问题描述
我想实现 JavaFX 的显示高亮效果,可以在 Windows 10 的各个部分看到,特别是设置和计算器应用程序。
效果似乎由两部分组成,一个边框高光(见这里)和一个背景高光(见这里,尽管由于压缩确实看起来更好)。
我的第一直觉是看看这是否可以在某种像素着色器中完成,但在谷歌搜索之后,JavaFX 似乎确实为类似的东西提供了公共 API?
是否可以在不借助画布并手动绘制整个 UI 的情况下创建这种效果?
解决方案
首先我想说我不知道 Windows 是如何实现这种风格的。但我的一个想法是有多个层次:
黑色背景。
一个带有从白色到透明的径向渐变的圆圈,随鼠标移动。
具有黑色背景的区域和在选项节点所在的任何位置都有孔的形状。
具有分层背景的选项节点。
当鼠标没有悬停时:
- 没有插图的透明背景。
- 黑色背景,带有轻微的插图。
当鼠标悬停时:
- 没有插图的低不透明度白色背景。
- 黑色背景,带有轻微的插图。
- 以鼠标为中心的白色到透明径向渐变背景。
不幸的是,这意味着许多样式必须在代码中完成,即使我更愿意将大部分样式放在 CSS 中。这是我快速模拟的一个概念验证。它功能不全,但显示您想要的外观是可能的。
OptionsPane.java
import javafx.beans.InvalidationListener;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Bounds;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.VPos;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.TilePane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.paint.RadialGradient;
import javafx.scene.paint.Stop;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Shape;
import javafx.scene.text.Font;
public class OptionsPane extends Region {
public static class Option {
private final String title;
private final String subtitle;
private final Node graphic;
public Option(String title, String subtitle, Node graphic) {
this.title = title;
this.subtitle = subtitle;
this.graphic = graphic;
}
public String getTitle() {
return title;
}
public String getSubtitle() {
return subtitle;
}
public Node getGraphic() {
return graphic;
}
}
private final ObservableList<Option> options = FXCollections.observableArrayList();
private final TilePane topTiles = new TilePane();
private final Region midCover = new Region();
private final Circle underGlow = new Circle();
public OptionsPane() {
setBackground(new Background(new BackgroundFill(Color.BLACK, null, null)));
underGlow.setManaged(false);
underGlow.setRadius(75);
underGlow.visibleProperty().bind(hoverProperty());
underGlow.setFill(
new RadialGradient(
0, 0,
0.5, 0.5,
1.0,
true,
null,
new Stop(0.0, Color.WHITE),
new Stop(0.35, Color.TRANSPARENT)));
addEventFilter(
MouseEvent.MOUSE_MOVED,
e -> {
underGlow.setCenterX(e.getX());
underGlow.setCenterY(e.getY());
});
midCover.setBackground(new Background(new BackgroundFill(Color.BLACK, null, null)));
topTiles.setMinSize(0, 0);
topTiles.setVgap(20);
topTiles.setHgap(20);
topTiles.setPadding(new Insets(20));
topTiles.setPrefTileWidth(250);
topTiles.setPrefTileHeight(100);
topTiles.setPrefColumns(3);
options.addListener(
(InvalidationListener)
obs -> {
topTiles.getChildren().clear();
options.forEach(opt -> topTiles.getChildren().add(createOptionRegion(opt)));
});
getChildren().addAll(underGlow, midCover, topTiles);
}
public final ObservableList<Option> getOptions() {
return options;
}
@Override
protected void layoutChildren() {
double x = getInsets().getLeft();
double y = getInsets().getTop();
double w = getWidth() - getInsets().getRight() - x;
double h = getHeight() - getInsets().getBottom() - y;
layoutInArea(midCover, x, y, w, h, -1, HPos.CENTER, VPos.CENTER);
layoutInArea(topTiles, x, y, w, h, -1, HPos.CENTER, VPos.CENTER);
Shape coverShape = new Rectangle(x, y, w, h);
for (Node optionNode : topTiles.getChildren()) {
Bounds b = optionNode.getBoundsInParent();
Rectangle rect = new Rectangle(b.getMinX(), b.getMinY(), b.getWidth(), b.getHeight());
coverShape = Shape.subtract(coverShape, rect);
}
midCover.setShape(coverShape);
}
private Region createOptionRegion(Option option) {
Label titleLabel = new Label(option.getTitle());
titleLabel.setTextFill(Color.WHITE);
titleLabel.setFont(Font.font("System", 13));
Label subtitleLabel = new Label(option.getSubtitle());
subtitleLabel.setTextFill(Color.DARKGRAY);
subtitleLabel.setFont(Font.font("System", 10));
VBox textBox = new VBox(5, titleLabel, subtitleLabel);
HBox.setHgrow(textBox, Priority.ALWAYS);
HBox container = new HBox(10, textBox);
container.setPadding(new Insets(10));
if (option.getGraphic() != null) {
container.getChildren().add(0, option.getGraphic());
}
setNonHoverBackground(container);
container
.hoverProperty()
.addListener(
(obs, ov, nv) -> {
if (!nv) {
setNonHoverBackground(container);
}
});
container.setOnMouseMoved(e -> setHoverBackground(container, e.getX(), e.getY()));
return container;
}
private void setNonHoverBackground(Region region) {
BackgroundFill fill1 = new BackgroundFill(Color.TRANSPARENT, null, null);
BackgroundFill fill2 = new BackgroundFill(Color.BLACK, null, new Insets(2));
region.setBackground(new Background(fill1, fill2));
}
private void setHoverBackground(Region region, double x, double y) {
RadialGradient gradient =
new RadialGradient(
0, 0,
x, y,
400,
false,
null,
new Stop(0.0, new Color(1, 1, 1, 0.2)),
new Stop(0.35, Color.TRANSPARENT));
BackgroundFill fill1 = new BackgroundFill(new Color(1, 1, 1, 0.3), null, null);
BackgroundFill fill2 = new BackgroundFill(Color.BLACK, null, new Insets(2));
BackgroundFill fill3 = new BackgroundFill(gradient, null, null);
region.setBackground(new Background(fill1, fill2, fill3));
}
}
主.java
import java.util.ArrayList;
import java.util.List;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class Main extends Application {
@Override
public void start(Stage primaryStage) {
OptionsPane pane = new OptionsPane();
List<OptionsPane.Option> options = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Rectangle graphic = new Rectangle(20, 20, Color.DARKGRAY);
options.add(
new OptionsPane.Option("Option Title #" + (i + 1), "Description #" + (i + 1), graphic));
}
pane.getOptions().addAll(options);
primaryStage.setScene(new Scene(pane));
primaryStage.show();
}
}
这就是它的样子:
这并不完全相同,但您可以自己试验并根据需要进行更改。
推荐阅读
- flutter - Flutter TextField/TextFormField 不会在 setState() 上更新其值
- c# - 反射 Raycast2D 时出现 StackOverflowException
- flutter - 如何定义函数的动态参数
- autocomplete - 谷歌表格中的自动填充词
- c++ - 要从 Delphi 调用的 C++ dll 函数 - 数组参数
- intellij-idea - Intellij 的想法。javax.net.ssl.SSLHandshakeException:没有合适的协议(协议被禁用或密码套件不合适)
- php - 网页被封锁!从数据发送 POST 中的 URL 时
- awk - 不要在 awk 中转义变量
- node.js - 如何停止接收有关主题的 kafka-metadata 消息
- android - 数据绑定:如何在一个视图中绑定不同的数据变量