Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SweepGradient Android - How to set start angle for Gradient

I am having a hard time understanding how to use a SweepGradient to show my gradient color from a specific angle on Canvas.

For example: If I have an arc from 1 - 3pm, I would like to provide a Gradient as a Color. The Gradient should start from 2pm.

I have the following code which shows the Color without any gradient applied to it.

 SweepGradient sweepGradient = new
                                SweepGradient(provideRectF().width() / 2, provideRectF().height() / 2,
                                arcColors, new float[]{
                                0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f, 1.0f
                        });

                        Matrix matrix = new Matrix();
                        matrix.postRotate(currentAngle, provideRectF().width() / 2, provideRectF().height() / 2);
                        sweepGradient.setLocalMatrix(matrix);

How can I make my Color show the gradient from 2pm (in terms of angle) for the given arc?

like image 368
user2511882 Avatar asked Nov 25 '25 05:11

user2511882


1 Answers

This answer is based on the excellent post on SweepGradient by Attila Tanyi.

To get a better understanding of SweepGradient, let's first paint the whole screen with a SweepGradient which is centered in the middle of the screen and shows sections corresponding to a clock face (please note that the area between 1pm and 2pm is a solid color since this is part of your setup):

fullscreen sweep gradient

The positions:

// positions for a clock face
// note: we need an opening position 0.0f as well as a closing position 1.0f, both of which belong to 3 pm
float[] positions = {0.0f,
            1/12f, 2/12f, 3/12f, 
            4/12f, 5/12f, 6/12f,
            7/12f, 8/12f, 9/12f,
            10/12f, 11/12f, 1.0f};

The colors:

int yellow = 0xFFFFFF88;
int blue = 0xFF0088FF;
int white = 0xFFFFFFFF;
int black = 0xFF000000;

// provide as many color values as there are positions
// we want to paint a "normal color" from 1pm to 2pm and then a gradient from 2pm to 3 pm
int[] colors = { black, // the first value is for 3 pm, the sweep starts here
        yellow, // 4
        white,  // 5
        black,  // 6
        white,  // 7
        yellow, // 8
        blue,   // 9
        black,  // 10
        white,  // 11
        black,  // 12
        blue,   // 1 constant color from 1pm to 2pm
        blue,   // 2
        white // the last value also is at 3 pm, the sweep ends here
};

Initialising the Path and the Paint:

private Path circle = new Path();
private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);

// ...
paint.setStyle(Paint.Style.FILL_AND_STROKE);
paint.setStrokeWidth(30);

I experimented with a custom View and configured the gradient in onLayout(). Since the post is about drawing an arc, I tried a full circle next:

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    super.onLayout(changed, left, top, right, bottom);
    // use a square rectangle which does not touch the screen borders:
    float squareMaxWidth = Math.min(right - left, bottom - top) - 20;
    RectF circleRect = new RectF(left + 20, top + 20, left + squareMaxWidth, top + squareMaxWidth);
    // draw a full circle
    circle.addArc(circleRect, 180, 360);
    // calculate its center
    float centerH = (circleRect.right + circleRect.left)*0.5f;
    float centerV = (circleRect.bottom + circleRect.top)*0.5f;
 
    sweepGradient = new SweepGradient(centerH,centerV, colors, positions);
    paint.setShader(sweepGradient);
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.drawPath(circle, paint);
}

The result:

full circle with sweep gradient

Next, let's just draw the border: change the Paint style

paint.setStyle(Paint.Style.STROKE);

enter image description here

And finally, just draw an arc from 1 pm to 3 pm. Remember that the angle for 3 pm is zero and that one section on the clock face corresponds to 30 degrees. So in onLayout() we have

circle.addArc(circleRect, 300, 60);

arc from 1 pm to 3 pm

Another example: if you want to draw an arc from 4:15 pm to 4:45 pm with a gradient starting at 4:33 pm, you get the following picture (colored arc drawn over a full SweepGradient circle with alternating black and white at each "hour")

arc from 4:15 pm to 4:45 pm with a gradient starting at 4:33 pm

The colors:

int[] colors = {      
        primaryColor,
        primaryColor,
        accentColor };

To calculate the positions one needs to do a little math:

60 minutes = one hour ~ 1/12f
3 minutes = one hour / 20 ~ 1/240f
15 minutes = 5 * 3 minutes ~ 5 / 240f = 1 / 48f
45 minutes = 3 * 15 minutes ~ 1 / 16f
33 minutes = 11 * 3 minutes ~ 11 / 240f

float[] positions = {
        (1/12f + 1/48f),   // 4:15 pm
        (1/12f + 11/240f), // 4:33 pm
        (1/12f + 1/16f)    // 4:45 pm
     };

Similarly, one can calculate the values for the start angle and sweep angle:

one hour ~ 30 degrees
one minute ~ 0.5 degrees

// start at 4:15 pm:
float startAngle = (30 +  15*0.5f);
// sweep for 1/2 hour:
float sweepAngle = 15f;
circle.addArc(circleRect, startAngle, sweepAngle);

For the use case where the clockface is defined by an arbitrary rectangle, one may want to calculate the angles depending on the coordinates for the hours:

// coordinates of hours on the frame of a rectangular clockface
private PointF[] coordinates = new PointF[12];

// angles of hours for a rectangular clockface
private float[] angles = new float[13];

private void init() {
    redPaint.setARGB(255, 255, 0,0);
    redPaint.setStrokeWidth(6);

    backgroundPaint.setARGB(255, 24, 24, 24);
    backgroundPaint.setStrokeWidth(10);
    backgroundPaint.setStyle(Paint.Style.STROKE);

    paint.setStyle(Paint.Style.FILL);
}

Calculate the coordinates and the corresponding angles for the SwipeGradient in onLayout()

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    super.onLayout(changed, left, top, right, bottom);
    all.set(left, top, right, bottom);
    centerX = (right + left)*0.5f;
    centerY = (bottom + top)*0.5f;

    calculateCoordinates();
    calculateAngles();

    sweepGradient = new SweepGradient(centerX,centerY, colors1, angles);
    paint.setShader(sweepGradient);
}

private void calculateAngles() {
    for(int i = 0; i < coordinates.length; i++) {
        PointF point = coordinates[i];
        double length = Math.sqrt(point.x * point.x + point.y * point.y);

        double pX = point.x/ length;
        double pY = point.y/ length;

        double rawAngle = Math.atan2(pX, pY)/  (2 * Math.PI);
        // rawAngle is a value between -0.5 and 0.5 where 0.5 corresponds to 6 o'clock and 0.0 to 12 o'clock
        // We need to make 0.0 correspond to 3 o'clock:
        double angle = rawAngle + 0.75;
        if (angle >= 1) angle--;
        
        angles[i] = (float) angle;
    }
    angles[12] = 1.0f;
}

/**
 * Start at 3 o'clock and proceed clockwise
 */
private void calculateCoordinates() {
    float halfWidth =(all.right - all.left) * 0.5f;
    float halfHeight =(all.bottom - all.top) * 0.5f;

    coordinates[0] = new PointF(halfWidth, 0);

    coordinates[1] = new PointF(halfWidth, -0.5f * halfHeight );

    coordinates[2] = new PointF(0.5f * halfWidth, - halfHeight);
    coordinates[3] = new PointF(0, - halfHeight);
    coordinates[4] = new PointF(- 0.5f * halfWidth, - halfHeight);

    coordinates[5] = new PointF(- halfWidth, -0.5f * halfHeight);
    coordinates[6] = new PointF(- halfWidth, 0);
    coordinates[7] = new PointF(- halfWidth, 0.5f * halfHeight);

    coordinates[8] = new PointF(-0.5f * halfWidth, halfHeight);
    coordinates[9] = new PointF(0, halfHeight);
    coordinates[10] = new PointF(0.5f * halfWidth, halfHeight);

    coordinates[11] = new PointF(halfWidth, 0.5f * halfHeight);
}

In onDraw(), draw the clockface covering the whole View, and for each hour, draw a red line connecting the hour to the center:

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    canvas.drawRect(all, paint);

    for (PointF point : coordinates) {
        drawLine(point, canvas);
    }
    canvas.drawRect(all, backgroundPaint);
}

private void drawLine(PointF point, Canvas canvas) {
    canvas.drawLine(centerX, centerY, centerX + point.x, centerY + point.y, redPaint);
}
rectangular clock 300dp x 200dp rectangular clock 300dp x 500dp
black-and-white SwipeGradient 300dp x 200dp plus red lines connecting hours to center black-and-white SwipeGradient 300dp x 500dp plus red lines connecting hours to center
like image 65
Bö macht Blau Avatar answered Nov 27 '25 20:11

Bö macht Blau



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!