我期待在javafx 2.2或至少在javafx 8中做類似的事情。我瀏覽了Text javadoc和css reference,結果沒有。如何沿貝塞爾曲線書寫文字?
有可能通過在WebView顯示和SVG做到這個效果。但是我的應用程序必須顯示大量帶有這種效果的文本。 WebView是用於繪製具有此效果的文本的過重組件。
我在oracle technology network上問了同樣的問題。
我期待在javafx 2.2或至少在javafx 8中做類似的事情。我瀏覽了Text javadoc和css reference,結果沒有。如何沿貝塞爾曲線書寫文字?
有可能通過在WebView顯示和SVG做到這個效果。但是我的應用程序必須顯示大量帶有這種效果的文本。 WebView是用於繪製具有此效果的文本的過重組件。
我在oracle technology network上問了同樣的問題。
這裏是PathTransition的濫用讓文本沿着Bézier Curve繪製。
該程序可讓您拖動控制點以定義曲線,然後沿該曲線繪製文本。文本中的字符等距離排列,因此如果曲線的總長度與「正常」間距非常接近文本寬度,並且不會對字距調整等進行調整,則效果最佳。
下圖顯示了樣本:
的解決方案是一個快速劈基礎上,答案StackOverflow的問題:CubicCurve JavaFX。我相信可以通過更多的努力,時間和技巧找到更好的解決方案。
因爲程序基於轉換,所以採用它會非常容易,因此文本可以按照曲線動畫化,在溢出時從右向左包裝(例如,您可能會在marquee text或股票代碼中看到)。
任何標準的JavaFX效果,例如發光,陰影等等和字體的改變都可以用來從你的問題中得到來自paintshop pro文本的陰影效果。發光效果在這裏適用是一個很好的效果,因爲它巧妙地柔化旋轉角色周圍的鋸齒狀邊緣。
此解決方案所基於的PathTransition可以將任意形狀作爲路徑的輸入,因此文本可以遵循其他類型的路徑,而不僅僅是三次曲線。
import javafx.animation.*;
import javafx.application.Application;
import javafx.beans.property.DoubleProperty;
import javafx.collections.*;
import javafx.event.*;
import javafx.scene.*;
import javafx.scene.control.ToggleButton;
import javafx.scene.effect.Glow;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.*;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.util.Duration;
/**
* Example of drawing text along a cubic curve.
* Drag the anchors around to change the curve.
*/
public class BezierTextPlotter extends Application {
private static final String CURVED_TEXT = "Bézier Curve";
public static void main(String[] args) throws Exception {
launch(args);
}
@Override
public void start(final Stage stage) throws Exception {
final CubicCurve curve = createStartingCurve();
Line controlLine1 = new BoundLine(curve.controlX1Property(), curve.controlY1Property(), curve.startXProperty(), curve.startYProperty());
Line controlLine2 = new BoundLine(curve.controlX2Property(), curve.controlY2Property(), curve.endXProperty(), curve.endYProperty());
Anchor start = new Anchor(Color.PALEGREEN, curve.startXProperty(), curve.startYProperty());
Anchor control1 = new Anchor(Color.GOLD, curve.controlX1Property(), curve.controlY1Property());
Anchor control2 = new Anchor(Color.GOLDENROD, curve.controlX2Property(), curve.controlY2Property());
Anchor end = new Anchor(Color.TOMATO, curve.endXProperty(), curve.endYProperty());
final Text text = new Text(CURVED_TEXT);
text.setStyle("-fx-font-size: 40px");
text.setEffect(new Glow());
final ObservableList<Text> parts = FXCollections.observableArrayList();
final ObservableList<PathTransition> transitions = FXCollections.observableArrayList();
for (char character : text.textProperty().get().toCharArray()) {
Text part = new Text(character + "");
part.setEffect(text.getEffect());
part.setStyle(text.getStyle());
parts.add(part);
part.setVisible(false);
transitions.add(createPathTransition(curve, part));
}
final ObservableList<Node> controls = FXCollections.observableArrayList();
controls.setAll(controlLine1, controlLine2, curve, start, control1, control2, end);
final ToggleButton plot = new ToggleButton("Plot Text");
plot.setOnAction(new PlotHandler(plot, parts, transitions, controls));
Group content = new Group(controlLine1, controlLine2, curve, start, control1, control2, end, plot);
content.getChildren().addAll(parts);
stage.setTitle("Cubic Curve Manipulation Sample");
stage.setScene(new Scene(content, 400, 400, Color.ALICEBLUE));
stage.show();
}
private PathTransition createPathTransition(CubicCurve curve, Text text) {
final PathTransition transition = new PathTransition(Duration.seconds(10), curve, text);
transition.setAutoReverse(false);
transition.setCycleCount(PathTransition.INDEFINITE);
transition.setOrientation(PathTransition.OrientationType.ORTHOGONAL_TO_TANGENT);
transition.setInterpolator(Interpolator.LINEAR);
return transition;
}
private CubicCurve createStartingCurve() {
CubicCurve curve = new CubicCurve();
curve.setStartX(50);
curve.setStartY(200);
curve.setControlX1(150);
curve.setControlY1(300);
curve.setControlX2(250);
curve.setControlY2(50);
curve.setEndX(350);
curve.setEndY(150);
curve.setStroke(Color.FORESTGREEN);
curve.setStrokeWidth(4);
curve.setStrokeLineCap(StrokeLineCap.ROUND);
curve.setFill(Color.CORNSILK.deriveColor(0, 1.2, 1, 0.6));
return curve;
}
class BoundLine extends Line {
BoundLine(DoubleProperty startX, DoubleProperty startY, DoubleProperty endX, DoubleProperty endY) {
startXProperty().bind(startX);
startYProperty().bind(startY);
endXProperty().bind(endX);
endYProperty().bind(endY);
setStrokeWidth(2);
setStroke(Color.GRAY.deriveColor(0, 1, 1, 0.5));
setStrokeLineCap(StrokeLineCap.BUTT);
getStrokeDashArray().setAll(10.0, 5.0);
}
}
// a draggable anchor displayed around a point.
class Anchor extends Circle {
Anchor(Color color, DoubleProperty x, DoubleProperty y) {
super(x.get(), y.get(), 10);
setFill(color.deriveColor(1, 1, 1, 0.5));
setStroke(color);
setStrokeWidth(2);
setStrokeType(StrokeType.OUTSIDE);
x.bind(centerXProperty());
y.bind(centerYProperty());
enableDrag();
}
// make a node movable by dragging it around with the mouse.
private void enableDrag() {
final Delta dragDelta = new Delta();
setOnMousePressed(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent mouseEvent) {
// record a delta distance for the drag and drop operation.
dragDelta.x = getCenterX() - mouseEvent.getX();
dragDelta.y = getCenterY() - mouseEvent.getY();
getScene().setCursor(Cursor.MOVE);
}
});
setOnMouseReleased(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent mouseEvent) {
getScene().setCursor(Cursor.HAND);
}
});
setOnMouseDragged(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent mouseEvent) {
double newX = mouseEvent.getX() + dragDelta.x;
if (newX > 0 && newX < getScene().getWidth()) {
setCenterX(newX);
}
double newY = mouseEvent.getY() + dragDelta.y;
if (newY > 0 && newY < getScene().getHeight()) {
setCenterY(newY);
}
}
});
setOnMouseEntered(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent mouseEvent) {
if (!mouseEvent.isPrimaryButtonDown()) {
getScene().setCursor(Cursor.HAND);
}
}
});
setOnMouseExited(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent mouseEvent) {
if (!mouseEvent.isPrimaryButtonDown()) {
getScene().setCursor(Cursor.DEFAULT);
}
}
});
}
// records relative x and y co-ordinates.
private class Delta {
double x, y;
}
}
// plots text along a path defined by provided bezier control points.
private static class PlotHandler implements EventHandler<ActionEvent> {
private final ToggleButton plot;
private final ObservableList<Text> parts;
private final ObservableList<PathTransition> transitions;
private final ObservableList<Node> controls;
public PlotHandler(ToggleButton plot, ObservableList<Text> parts, ObservableList<PathTransition> transitions, ObservableList<Node> controls) {
this.plot = plot;
this.parts = parts;
this.transitions = transitions;
this.controls = controls;
}
@Override
public void handle(ActionEvent actionEvent) {
if (plot.isSelected()) {
for (int i = 0; i < parts.size(); i++) {
parts.get(i).setVisible(true);
final Transition transition = transitions.get(i);
transition.stop();
transition.jumpTo(Duration.seconds(10).multiply((i + 0.5) * 1.0/parts.size()));
// just play a single animation frame to display the curved text, then stop
AnimationTimer timer = new AnimationTimer() {
int frameCounter = 0;
@Override
public void handle(long l) {
frameCounter++;
if (frameCounter == 1) {
transition.stop();
stop();
}
}
};
timer.start();
transition.play();
}
plot.setText("Show Controls");
} else {
plot.setText("Plot Text");
}
for (Node control : controls) {
control.setVisible(!plot.isSelected());
}
for (Node part : parts) {
part.setVisible(plot.isSelected());
}
}
}
}
另一種可能的解決方案將是測量每個文本字符,並執行數學不使用PathTransition來內插所述文本的位置和旋轉。但PathTransition已經在那裏,對我來說工作得很好(也許無論如何,文本改進的曲線距離測量可能會挑戰我)。
解答其他問題
你是否認爲這是可以實現通過調整你的代碼javafx.scene.effect.Effect?
號實施的效果將需要顯示沿貝塞爾曲線的文字,這我的回答並沒有提供執行數學(因爲它只是採用現有PathTransition做到這一點)。
此外,JavaFX 2.2中沒有用於實現自定義效果的公共API。
有一個現有的DisplacementMap效果,可能可能用於得到類似的東西。不過,我覺得使用DisplacementMap效果(也許是調整文字佈局的任何效果)可能會扭曲文字。
IMO,沿着貝塞爾曲線書寫文字比效果相關的佈局更多 - 最好是調整字符的佈局和旋轉,而不是使用效果來移動它們。
或者可能有更好的方法將它正確地集成到JFX框架中嗎?
你可以繼承窗格並創建一個自定義PathLayout類似於一個FlowPane,但勾畫出沿着一條路徑,而不是一條直線節點出來。要佈局的節點由每個字符的Text節點組成,類似於我在答案中所做的。但即使如此,由於您要考慮像比例間隔字母,kerning等東西,因此您並不是真正精確地呈現文本。因此,爲了獲得完整的保真度和準確性,您需要實現自己的低級別文本佈局算法。如果是我,我只會去那個努力,如果在這個答案中使用PathTransitions提供的「足夠好」的解決方案證明對你來說質量不夠高。
您可以使用WebView和一些html來顯示svg。這裏有一個例子:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
public class CurvedText extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) throws Exception {
StackPane root = new StackPane();
WebView view = new WebView();
view.getEngine().loadContent("<!DOCTYPE html>\n" +
"<html xmlns=\"http://www.w3.org/1999/xhtml\">\n" +
" <body>\n" +
"<embed width=\"100\" height=\"100\" type=\"image/svg+xml\" src=\"path.svg\">\n" +
" <svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">" +
"<defs>\n" +
" <path id=\"textPath\" d=\"M10 50 C10 0 90 0 90 50\"/>\n" +
"</defs>\n"+
"<text fill=\"red\">\n" +
" <textPath xlink:href=\"#textPath\">Text on a Path</textPath>\n" +
"</text>" +
"</svg>\n" +
"</embed>" +
" </body>\n" +
"</html>");
root.getChildren().add(view);
Scene scene = new Scene(root, 500, 500);
primaryStage.setScene(scene);
primaryStage.show();
}
}
結果:
這不是最佳的解決方案,因爲JavaFX的WebView中的表現有點敏感,當它應該表現得像在我的經驗標籤,但它是開始的東西。
編輯
既然你不想直接使用的WebView,你可以使用的WebView的單個實例來渲染HTML的場景,然後利用它的一個快照產生ImageView的。看到這個例子:
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Worker;
import javafx.scene.Scene;
import javafx.scene.image.ImageView;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.HBox;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
public class CurvedText extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) throws Exception {
final HBox root = new HBox();
final WebView view = new WebView();
view.getEngine().loadContent("<!DOCTYPE html>\n" +
"<html xmlns=\"http://www.w3.org/1999/xhtml\">\n" +
" <body>\n" +
"<embed width=\"100\" height=\"100\" type=\"image/svg+xml\" src=\"path.svg\">\n" +
" <svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">" +
"<defs>\n" +
" <path id=\"textPath\" d=\"M10 50 C10 0 90 0 90 50\"/>\n" +
"</defs>\n"+
"<text fill=\"red\">\n" +
" <textPath xlink:href=\"#textPath\">Text on a Path</textPath>\n" +
"</text>" +
"</svg>\n" +
"</embed>" +
" </body>\n" +
"</html>");
root.getChildren().add(view);
view.getEngine().getLoadWorker().stateProperty().addListener(new ChangeListener<Worker.State>() {
@Override
public void changed(ObservableValue<? extends Worker.State> arg0, Worker.State oldState, Worker.State newState) {
if (newState == Worker.State.SUCCEEDED) {
// workaround for https://javafx-jira.kenai.com/browse/RT-23265
AnimationTimer waitForViewToBeRendered = new AnimationTimer(){
private int frames = 0;
@Override
public void handle(long now) {
if (frames++ > 3){
WritableImage snapshot = view.snapshot(null, null);
ImageView imageView = new ImageView(snapshot);
root.getChildren().add(imageView);
this.stop();
}
}
};
waitForViewToBeRendered.start();
}
}
});
Scene scene = new Scene(root, 500, 500);
primaryStage.setScene(scene);
primaryStage.show();
}
}
我不知道這是否對你有幫助。我必須在unity3d中沿着貝塞爾曲線移動圖形,並使用svg tracepaths(因爲我們有svgs開始)。這就是我們用來建立我們的圖書館; http://www.w3.org/TR/SVG/paths.html – MichelleJS
@MichelleJS謝謝,但SVG在javafx中並不真正支持。 – gontard
也許這可能會幫助你:https://forums.oracle.com/thread/1712335 – Sebastian