@珠海方法+1.
或多或少,我也确实有一个类似的方法来使用路径(包括拐角半径)动态构建边界.我还有另外两种方法,一种是使用inverse clipping
技巧,另一种是使用border segments
技巧.
我在下面的演示中包含了所有这三种方法.您可以 Select 或忽略.但我的主要意图是提供一个方向,如果有人对此感兴趣的话.
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.*;
import javafx.scene.shape.*;
import javafx.stage.Stage;
public class TitledBorderDemo extends Application {
String sampleText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
@Override
public void start(final Stage stage) throws Exception {
VBox root = new VBox(20);
root.setStyle("-fx-background-color:linear-gradient(to bottom, pink, yellow);");
root.setAlignment(Pos.CENTER);
root.setPadding(new Insets(15));
buildWithPath(root);
buildWithClip(root);
buildWithStyle(root);
Scene scene = new Scene(root, 600, 600);
scene.getStylesheets().add(getClass().getResource("titledborder.css").toExternalForm());
stage.setScene(scene);
stage.setTitle("Titled Border Demo");
stage.show();
}
private void buildWithPath(VBox root) {
Label content = new Label();
content.setWrapText(true);
content.setText(sampleText);
TitledBorderWithPath pane = new TitledBorderWithPath();
pane.setTitle("With Path Approach");
pane.setContent(content);
root.getChildren().add(pane);
}
private void buildWithClip(VBox root) {
Label content = new Label();
content.setWrapText(true);
content.setText(sampleText);
TitledBorderWithClip pane = new TitledBorderWithClip();
pane.setTitle("With Clip Approach");
pane.setContent(content);
root.getChildren().add(pane);
}
private void buildWithStyle(VBox root) {
Label content = new Label();
content.setWrapText(true);
content.setText(sampleText);
TitledBorderWithSegment pane = new TitledBorderWithSegment();
pane.setTitle("With Segment Approach");
pane.setContent(content);
root.getChildren().add(pane);
}
/**
* Approach by using border segment.
*/
class TitledBorderWithSegment extends StackPane {
private final Label titleLabel = new Label();
public TitledBorderWithSegment() {
getStyleClass().add("titled-border-segment");
titleLabel.getStyleClass().add("title-label");
getChildren().addAll(titleLabel);
// Position the title label on the top border
titleLabel.translateYProperty().bind(titleLabel.heightProperty().divide(2).add(titleLabel.layoutYProperty()).multiply(-1));
titleLabel.needsLayoutProperty().addListener((obs, old, needsLayout) -> {
if (!needsLayout) {
buildStyle();
}
});
widthProperty().addListener(p -> buildStyle());
heightProperty().addListener(p -> buildStyle());
}
private void buildStyle() {
double buffer = 8;
double r = getBorder().getStrokes().get(0).getRadii().getTopLeftHorizontalRadius();
double arcLength = Math.round(Math.toRadians(90) * r);
double w = getWidth();
double h = getHeight();
double titleX = titleLabel.getLayoutX() + titleLabel.getTranslateX();
double titleLength = titleLabel.getWidth();
double t = (h - (2 * r)) +
arcLength +
(w - (2 * r)) +
arcLength +
(h - (2 * r)) +
arcLength +
(w - titleX - titleLength - r) - buffer;
setStyle("-fx-border-style: segments(" + t + ", " + titleLength + ") line-cap round;");
}
public void setTitle(final String title) {
this.titleLabel.setText(title);
}
public void setContent(Node node) {
getChildren().clear();
getChildren().addAll(titleLabel, node);
}
}
/**
* Approach by using Path.
*/
class TitledBorderWithPath extends StackPane {
private final Path border = new Path();
private final StackPane container = new StackPane();
private final Label titleLabel = new Label();
// Can configure this as a CSS styleable property.
private final double borderRadius = 8;
public TitledBorderWithPath() {
getStyleClass().add("titled-border-path");
getChildren().addAll(border, container);
border.getStyleClass().add("border");
border.setManaged(false);
titleLabel.getStyleClass().add("title-label");
// Position the title label on the top border
titleLabel.translateYProperty().bind(titleLabel.heightProperty().divide(2).add(titleLabel.layoutYProperty()).multiply(-1));
titleLabel.needsLayoutProperty().addListener((obs, old, needsLayout) -> {
if (!needsLayout) {
drawBorder();
}
});
container.getStyleClass().add("container");
container.widthProperty().addListener(p -> drawBorder());
container.heightProperty().addListener(p -> drawBorder());
}
private void drawBorder() {
double w = container.getWidth();
double h = container.getHeight();
double r = borderRadius;
double x = titleLabel.getLayoutX() + titleLabel.getTranslateX();
border.getElements().clear();
border.getElements().addAll(new MoveTo(x, 0),
new LineTo(r, 0),
arc(0, r), // Top left
new LineTo(0, h - r),
arc(r, h), // Bottom left
new LineTo(w - r, h),
arc(w, h - r), // Bottom right
new LineTo(w, r),
arc(w - r, 0), // Top right
new LineTo(x + titleLabel.getWidth(), 0));
}
private ArcTo arc(double x, double y) {
return new ArcTo(borderRadius, borderRadius, 0, x, y, false, false);
}
public void setTitle(final String title) {
this.titleLabel.setText(title);
}
public void setContent(Node node) {
container.getChildren().clear();
container.getChildren().addAll(titleLabel, node);
}
}
/**
* Approach by using clipping.
*/
class TitledBorderWithClip extends StackPane {
private Label titleLabel;
final Rectangle clip = new Rectangle();
final Rectangle inverse = new Rectangle();
final Pane border = new Pane();
final StackPane container = new StackPane();
public TitledBorderWithClip() {
getStyleClass().add("titled-border-clip");
titleLabel = new Label();
titleLabel.getStyleClass().add("title-label");
container.getChildren().add(titleLabel);
container.getStyleClass().add("container");
border.getStyleClass().add("border");
getChildren().addAll(border, container);
border.widthProperty().addListener(p -> setInverseClip(border, clip));
border.heightProperty().addListener(p -> setInverseClip(border, clip));
// Position the title label on the top border
titleLabel.translateYProperty().bind(titleLabel.heightProperty().divide(2).add(titleLabel.layoutYProperty()).multiply(-1));
titleLabel.needsLayoutProperty().addListener((obs, old, needsLayout) -> {
if (!needsLayout) {
setInverseClip(border, clip);
}
});
}
public void setTitle(final String title) {
this.titleLabel.setText(title);
}
public void setContent(Node node) {
container.getChildren().clear();
container.getChildren().addAll(titleLabel, node);
}
private void setInverseClip(final Pane node, final Rectangle clip) {
clip.setWidth(titleLabel.getWidth());
clip.setHeight(titleLabel.getHeight());
clip.setX(titleLabel.getLayoutX() + titleLabel.getTranslateX());
clip.setY(titleLabel.getLayoutY() + titleLabel.getTranslateY());
inverse.setWidth(node.getWidth());
inverse.setHeight(node.getHeight());
node.setClip(Shape.subtract(inverse, clip));
}
}
}
titledborder.css个
.title-label{
-fx-padding: 0px 5px 0px 5px;
-fx-font-weight: bold;
-fx-font-size: 14px;
-fx-translate-x: 5px; /* To adjust the title placement horizontally */
}
/* With Clip Approach */
.titled-border-clip {
-fx-alignment: TOP_LEFT;
}
.titled-border-clip > .container{
-fx-padding:10px 5px 5px 5px;
-fx-alignment: TOP_LEFT;
}
.titled-border-clip > .border{
-fx-border-color:red;
-fx-border-width: 2px;
-fx-border-radius: 5px;
}
/* With Path Approach */
.titled-border-path {
-fx-alignment: TOP_LEFT;
}
.titled-border-path > .container{
-fx-padding:10px 5px 5px 5px;
-fx-alignment: TOP_LEFT;
}
.titled-border-path > .border{
-fx-stroke:red;
-fx-stroke-width:2px;
}
/* With Simple Approach */
.titled-border-simple{
-fx-background-color:inherit;
-fx-border-color:red;
-fx-border-width: 2px;
-fx-border-radius: 5px;
-fx-padding:10px 5px 5px 5px;
-fx-alignment: TOP_LEFT;
}
.titled-border-simple > .title-label{
-fx-background-color:inherit;
-fx-border-color:inherit;
-fx-border-width:inherit;
-fx-border-radius:inherit;
}
/* With Segment Approach */
.titled-border-segment{
-fx-border-color:red;
-fx-border-width: 2px;
-fx-border-radius: 10px;
-fx-padding:10px 5px 5px 5px;
-fx-alignment: TOP_LEFT;
}
For Simple layouts个
好吧,对于doesn't具有动态背景(如渐变、图像)的布局来说,上述方法可能有些矫枉过正.等).对于简单的布局,就像@Slaw在第一条 comments 中提到的那样,在父级和标签上都设置-fx-background-color: inherit;
应该可以做到这一点.
class SimpleTitledBorder extends StackPane {
private final Label titleLabel = new Label();
public SimpleTitledBorder() {
getStyleClass().add("titled-border-simple");
titleLabel.getStyleClass().add("title-label");
getChildren().addAll(titleLabel);
// Position the title label on the top border
titleLabel.translateYProperty().bind(titleLabel.heightProperty().divide(2).add(titleLabel.layoutYProperty()).multiply(-1));
}
public void setTitle(final String title) {
this.titleLabel.setText(title);
}
public void setContent(Node node) {
getChildren().clear();
getChildren().addAll(titleLabel, node);
}
}
Css代码:
/* With Simple Approach */
.title-label{
-fx-padding: 0px 5px 0px 5px;
-fx-font-weight: bold;
-fx-font-size: 14px;
-fx-translate-x: 5px; /* To adjust the title placement horizontally */
}
.titled-border-simple{
-fx-background-color:inherit;
-fx-border-color:red;
-fx-border-width: 2px;
-fx-border-radius: 5px;
-fx-padding:10px 5px 5px 5px;
-fx-alignment: TOP_LEFT;
}
.titled-border-simple > .title-label{
-fx-background-color:inherit;
}
并继承要标记的边框属性,为布局提供了另一种外观:)
.titled-border-simple > .title-label{
-fx-background-color:inherit;
-fx-border-color:inherit;
-fx-border-width:inherit;
-fx-border-radius:inherit;
}