Draw a semi ring - JavaFX

2020-02-07 01:16发布

问题:

I would like to know how to draw a semi circle in JavaFX. I tried to use Shape and QuadCurve but I couldn't make a perfect semicircle.

Here is a picture of what I'm trying to draw :

回答1:

The picture you linked is actually a semi-ring. You can get it in JavaFX by drawing nested 2 arcs and some lines. But my preferred way is to use the Path.

public class SemiDemo extends Application {

    @Override
    public void start(Stage primaryStage) {

        Group root = new Group();
        root.getChildren().add(drawSemiRing(120, 120, 100, 50, Color.LIGHTGREEN, Color.DARKGREEN));
        root.getChildren().add(drawSemiRing(350, 350, 200, 30, Color.LIGHTSKYBLUE, Color.DARKBLUE));

        Scene scene = new Scene(root, 300, 250);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private Path drawSemiRing(double centerX, double centerY, double radius, double innerRadius, Color bgColor, Color strkColor) {
        Path path = new Path();
        path.setFill(bgColor);
        path.setStroke(strkColor);
        path.setFillRule(FillRule.EVEN_ODD);

        MoveTo moveTo = new MoveTo();
        moveTo.setX(centerX + innerRadius);
        moveTo.setY(centerY);

        ArcTo arcToInner = new ArcTo();
        arcToInner.setX(centerX - innerRadius);
        arcToInner.setY(centerY);
        arcToInner.setRadiusX(innerRadius);
        arcToInner.setRadiusY(innerRadius);

        MoveTo moveTo2 = new MoveTo();
        moveTo2.setX(centerX + innerRadius);
        moveTo2.setY(centerY);

        HLineTo hLineToRightLeg = new HLineTo();
        hLineToRightLeg.setX(centerX + radius);

        ArcTo arcTo = new ArcTo();
        arcTo.setX(centerX - radius);
        arcTo.setY(centerY);
        arcTo.setRadiusX(radius);
        arcTo.setRadiusY(radius);

        HLineTo hLineToLeftLeg = new HLineTo();
        hLineToLeftLeg.setX(centerX - innerRadius);

        path.getElements().add(moveTo);
        path.getElements().add(arcToInner);
        path.getElements().add(moveTo2);
        path.getElements().add(hLineToRightLeg);
        path.getElements().add(arcTo);
        path.getElements().add(hLineToLeftLeg);

        return path;
    }

    public static void main(String[] args) {
        launch(args);
    }
}

Refer to Shape API of JavaFX for more info about the shapes used in the code.
Screenshot:



回答2:

Suggestions:

  • If you don't need a full outlining path, you can just use an Arc.
  • If you don't need the arc filled and just want to trace the outline path of the arc, then set the fill of the arc to null.
  • If you want the outline path of the arc thick, then set the stroke parameters on the arc.
  • If you need the a thick arc which is also outlined, then it is best to define a full arc as in Uluk's answer.

Sample code:

import javafx.application.Application;
import javafx.scene.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.*;
import javafx.stage.Stage;

public class SemiCircleSample extends Application {
  @Override public void start(Stage stage) {
    Arc arc = new Arc(50, 50, 25, 25, 0, 180);
    arc.setType(ArcType.OPEN);
    arc.setStrokeWidth(10);
    arc.setStroke(Color.CORAL);
    arc.setStrokeType(StrokeType.INSIDE);
    arc.setFill(null);

    stage.setScene(new Scene(new Group(arc), 100, 80));
    stage.show();
  }

  public static void main(String[] args) { launch(args); }
}


回答3:

As an experiment, I tried to do the same thing on a Canvas. This is what I came up with, making use of a RadialGradient and the function GraphicsContext.fillArc:

/**
 *
 * @param x Coordinate x of the centre of the arc
 * @param y Coordinate y of the centre of the arc
 * @param outer Outer radius of the arc
 * @param innerPercentage Inner radius of the arc, from 0 to 1 (as percentage)
 * @param arcStartAngle Start angle of the arc, in degrees
 * @param arcExtent Extent of the arc, in degrees
 */
private void drawSemiCircle(float x, float y, float outer, float innerPercentage, float arcStartAngle, float arcExtent) {
    RadialGradient rg = new RadialGradient(
            0,
            0,
            x,
            y,
            outer,
            false,
            CycleMethod.NO_CYCLE,
            new Stop((innerPercentage + (.0 * innerPercentage)), Color.TRANSPARENT),
            new Stop((innerPercentage + (.1 * innerPercentage)), Color.RED),
            new Stop((innerPercentage + (.6 * innerPercentage)), Color.YELLOW),
            new Stop((innerPercentage + (1 * innerPercentage)), Color.GREEN)
    );
    gc.setFill(rg);
    gc.fillArc(
            x - outer,
            y - outer,
            outer * 2,
            outer * 2,
            arcStartAngle,
            arcExtent,
            ArcType.ROUND
    );
}

Key points here are the arc type as ArcType.ROUND and the use of Color.TRANSPARENT as the first color.

Then it can be used something along the line:

drawSemiCircle(100, 100, 100, .5f, -45, 270);

It's not a perfect solution but it worked for me.



回答4:

Path.arcTo() the parameter SweepAngle refers to the rotation degree, if sweepAngle is positive the arc is clockwise, if sweepAngle is negative the arc is counterclockwise.

This code is used in my production environment, it draws a semi-circle ring using a bitmap image, the path goes clockwise on the outer radius, and counter-clockwise on the inner radius:

drawpercent = 0.85; //this draws a semi ring to 85% you can change it using your code.
DegreesStart = -90;
DegreesRotation = 180;

radiusPathRectF = new android.graphics.RectF((float)CentreX - (float)Radius, (float)CentreY - (float)Radius,  (float)CentreX + (float)Radius, (float)CentreY + (float)Radius);
innerradiusPathRectF = new android.graphics.RectF((float)CentreX - (float)InnerRadius, (float)CentreY - (float)InnerRadius, (float)CentreX + (float)InnerRadius, (float)CentreY + (float)InnerRadius);

Path p = new Path(); //TODO put this outside your draw() function,  you should never have a "new" keyword inside a fast loop.

                degrees = (360 + (DegreesStart)) % 360;
                radians = (360 - degrees + 90) * Math.PI / 180.0;
                //radians = Math.toRadians(DegreesStart);
                int XstartOuter = (int)Math.round((Math.cos(radians) * Radius + CentreX));
                int YstartOuter = (int)Math.round((Math.sin(-radians)* Radius + CentreY));
                int XstartInner = (int)Math.round((Math.cos(radians) * InnerRadius + CentreX));
                int YstartInner = (int)Math.round((Math.sin(-radians) * InnerRadius + CentreY));

                degrees = (360 + (DegreesStart + drawpercent * DegreesRotation)) % 360;
                //radians = degrees * Math.PI / 180.0;
                radians = (360 - degrees + 90) * Math.PI / 180.0;
                //radians = Math.toRadians(DegreesStart + drawpercent * DegreesRotation);
                int XendOuter = (int)Math.round((Math.cos(radians) * Radius + CentreX));
                int YendOuter = (int)Math.round((Math.sin(-radians) * Radius + CentreY));
                int XendInner = (int)Math.round((Math.cos(radians) * InnerRadius + CentreX));
                int YendInner = (int)Math.round((Math.sin(-radians) * InnerRadius + CentreY));

                //draw a path outlining the semi-circle ring.
                p.moveTo(XstartInner, YstartInner);
                p.lineTo(XstartOuter, YstartOuter);
                p.arcTo(radiusPathRectF, (float)DegreesStart - (float)90, (float)drawpercent * (float)DegreesRotation);
                p.lineTo(XendInner, YendInner);
                p.arcTo(innerradiusPathRectF, (float)degrees - (float)90, -1 * (float)drawpercent * (float)DegreesRotation);
                p.close();

                g.clipPath(p);

                g.drawBitmap(bitmapCircularBarImage, bitmapRect0, bitmapRectXY, paint);