Android - How to draw an arc based gradient

21.6k views Asked by At

I am trying to create an arc (variable number of degrees) that gradually goes from one color to another. From example from blue to red:

enter image description here

This is my code:

SweepGradient shader = new SweepGradient(center.x, center.y, resources.getColor(R.color.startColor),resources.getColor(R.color.endColor));
Paint paint = new Paint()
paint.setStrokeWidth(1);
paint.setStrokeCap(Paint.Cap.FILL);
paint.setStyle(Paint.Style.FILL);
paint.setShader(shader);
canvas.drawArc(rectF, startAngle, sweepAngle, true, paint);

But the result is the entire arc is painted with the same color.

Edit:
After more experimenting, I found out that the color spread is determined by the angle of the arc. If I draw an arc with a small angle, only the first color is displayed. The larger the angle, more colors are drawn. If the angle is small it seems that there is no gradient.
Here is an example. I am drawing 4 arcs - 90, 180, 270 and 360:

RectF rect1 = new RectF(50, 50, 150, 150);
Paint paint1 = new Paint();
paint1.setStrokeWidth(1);
paint1.setStrokeCap(Paint.Cap.SQUARE);
paint1.setStyle(Paint.Style.FILL);

SweepGradient gradient1 = new SweepGradient(100, 100,
        Color.RED, Color.BLUE);
paint1.setShader(gradient1);

canvas.drawArc(rect1, 0, 90, true, paint1);

RectF rect2 = new RectF(200, 50, 300, 150);
Paint paint2 = new Paint();
paint2.setStrokeWidth(1);
paint2.setStrokeCap(Paint.Cap.SQUARE);
paint2.setStyle(Paint.Style.FILL);

SweepGradient gradient2 = new SweepGradient(250, 100,
        Color.RED, Color.BLUE);
paint2.setShader(gradient2);

canvas.drawArc(rect2, 0, 180, true, paint2);

RectF rect3 = new RectF(50, 200, 150, 300);
Paint paint3 = new Paint();
paint3.setStrokeWidth(1);
paint3.setStrokeCap(Paint.Cap.SQUARE);
paint3.setStyle(Paint.Style.FILL);

SweepGradient gradient3 = new SweepGradient(100, 250,
        Color.RED, Color.BLUE);
paint3.setShader(gradient3);

canvas.drawArc(rect3, 0, 270, true, paint3);

RectF rect4 = new RectF(200, 200, 300, 300);
Paint paint4 = new Paint();
paint4.setStrokeWidth(1);
paint4.setStrokeCap(Paint.Cap.SQUARE);
paint4.setStyle(Paint.Style.FILL);

SweepGradient gradient4 = new SweepGradient(250, 250,
        Color.RED, Color.BLUE);
paint4.setShader(gradient4);

canvas.drawArc(rect4, 0, 360, true, paint4);

And here is the result:

enter image description here

This is surprising because I'd expect the RED to be at the start of the arc, the BLUE at the end and enything between to be spread evenly regardless of angle.
I tried to space the colors manually using the positions parameter but the results were the same:

int[] colors = {Color.RED, Color.BLUE};
float[] positions = {0,1};
SweepGradient gradient = new SweepGradient(100, 100, colors , positions);

Any idea how to solve this?

4

There are 4 answers

2
Yoav On BEST ANSWER

The solution for this is to set the position of BLUE. This is done like so:

int[] colors = {Color.RED, Color.BLUE};
float[] positions = {0,1};
SweepGradient gradient = new SweepGradient(100, 100, colors , positions);

The problem here is that when setting the position of BLUE to '1' this does not mean that it will be positioned at the end of the drawn arc, but instead at the end of the circle of which the arc is part of. To solve this, BLUE position should take into account the number of degrees in the arc. So if I'm drawing an arc with X degrees, position will be set like so:

float[] positions = {0,Xf/360f};

So if X is 90, the gradient will place BLUE at 0.25 of the circle:

enter image description here

0
Linh On

Using SweepGradient, you can pass null for positions (in case you want default gradient) and you can also rotate the gradient by setLocalMatrix

override fun onDraw(canvas: Canvas?) {
    super.onDraw(canvas)
    val centerX = width.toFloat() / 2
    val centerY = height.toFloat() / 2

    val paint = Paint()
    paint.isAntiAlias = true

    val colors = intArrayOf(Color.RED, Color.BLUE)
    val gradient = SweepGradient(centerX, centerY, colors, null) // null position
    val matrix = Matrix()
    matrix.postRotate(270f, centerX, centerY) // rotate
    gradient.setLocalMatrix(matrix)
    paint.shader = gradient

    val radius = 400f
    val oval = RectF(centerX - radius,centerY - radius,centerX + radius,centerY + radius)
    canvas?.drawArc(oval, 0f, 360f, false, paint)
}
0
zmj110 On

five years later, but the right way is make 0.749f with 0.750f of the separate line, simple code is like:

    val colors = intArrayOf(0xffff0000.toInt(), 0xff0000ff.toInt(), 0xffff0000.toInt(), 0xffff0000.toInt())
    val positions = floatArrayOf(0.0f, 0.749f, 0.750f, 1.0f)
0
Tom anMoney On

To add to Yoav's solution.

On my Galaxy Nexus 4.2 stock Android, the solution given doesn't work.

If the array doesn't contain a 0.0f and 1.0f, it appears to be ignored.

My final solution was:

int[] colors = {Color.RED, Color.BLUE, Color.RED}; 
float[] positions = {0, Xf/360f, 1};