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?
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):
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:
Next, let's just draw the border: change the Paint
style
paint.setStyle(Paint.Style.STROKE);
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);
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")
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);
}