Drawing a rounded hollow thumb over arc Drawing a rounded hollow thumb over arc android android

Drawing a rounded hollow thumb over arc


I would use masks for your first two problems.

1. Create a smooth gradient

The very first step would be drawing two rectangles with a linear gradient. The firstrectangle contains the colors blue and green while the second rectangle contains greenand red as seen in the following picture. I marked the line where both rectangles touch each otherblack to clarify that they are infact two different rectangles.

first step

This can be achieved using the following code (excerpt):

// Both color gradientsprivate Shader shader1 = new LinearGradient(0, 400, 0, 500, Color.rgb(59, 242, 174), Color.rgb(101, 172, 242), Shader.TileMode.CLAMP);private Shader shader2 = new LinearGradient(0, 400, 0, 500, Color.rgb(59, 242, 174), Color.rgb(255, 31, 101), Shader.TileMode.CLAMP);private Paint paint = new Paint();// ...@Overrideprotected void onDraw(Canvas canvas) {    float width = 800;    float height = 800;    float radius = width / 3;    // Arc Image    Bitmap.Config conf = Bitmap.Config.ARGB_8888; // See other config types    Bitmap mImage = Bitmap.createBitmap(800, 800, conf); // This creates a mutable bitmap    Canvas imageCanvas = new Canvas(mImage);    // Draw both rectangles    paint.setShader(shader1);    imageCanvas.drawRect(0, 0, 400, 800, paint);    paint.setShader(shader2);    imageCanvas.drawRect(400, 0, 800, 800, paint);    // /Arc Image    // Draw the rectangle image    canvas.save();    canvas.drawBitmap(mImage, 0, 0, null);    canvas.restore();}

As your goal is having a colored arc with rounded caps, we next need to define the area ofboth rectangles that should be visible to the user. This means that most of both rectangleswill be masked away and thus not visible. Instead the only thing to remain is the arc area.

The result should look like this:

second step

In order to achieve the needed behavior we define a mask that only reveals the arc area withinthe rectangles. For this we make heavy use of the setXfermode method of Paint. As argumentwe use different instances of a PorterDuffXfermode.

private Paint maskPaint;private Paint imagePaint;// ...// To be called within all constructorsprivate void init() {    // I encourage you to research what this does in detail for a better understanding    maskPaint = new Paint();    maskPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));    imagePaint = new Paint();    imagePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OVER));}@Overrideprotected void onDraw(Canvas canvas) {    // @step1    // Mask    Bitmap mMask = Bitmap.createBitmap(800, 800, conf);    Canvas maskCanvas = new Canvas(mMask);    paint.setColor(Color.WHITE);    paint.setShader(null);    paint.setStrokeWidth(70);    paint.setStyle(Paint.Style.STROKE);    paint.setStrokeCap(Paint.Cap.ROUND);    paint.setAntiAlias(true);    final RectF oval = new RectF();    center_x = 400;    center_y = 400;    oval.set(center_x - radius,            center_y - radius,            center_x + radius,            center_y + radius);    maskCanvas.drawArc(oval, 135, 270, false, paint);    // /Mask    canvas.save();    // This is new compared to step 1    canvas.drawBitmap(mMask, 0, 0, maskPaint);    canvas.drawBitmap(mImage, 0, 0, imagePaint); // Notice the imagePaint instead of null    canvas.restore();}

2. Create the overlay white thumb

This solves your first problem. The second one can be achieved using masks again, though thistime we want to achieve something different. Before, we wanted to show only a specific area (the arc)of the background image (being the two rectangles). This time we want to do the opposite:We define a background image (the thumb) and mask away its inner content, so that onlythe stroke seems to remain. Applied to the arc image the thumb overlays the colored arc witha transparent content area.

So the first step would be drawing the thumb. We use an arc for this with the same radius asthe background arc but different angles, resulting in a much smaller arc. But becaus thethumb should "surround" the background arc, its stroke width has to be bigger than thebackground arc.

@Overrideprotected void onDraw(Canvas canvas) {    // @step1    // @step2    // Thumb Image    mImage = Bitmap.createBitmap(800, 800, conf);    imageCanvas = new Canvas(mImage);    paint.setColor(Color.WHITE);    paint.setStrokeWidth(120);    final RectF oval2 = new RectF();    center_x = 400;    center_y = 400;    oval2.set(center_x - radius,            center_y - radius,            center_x + radius,            center_y + radius);    imageCanvas.drawArc(oval2, 270, 45, false, paint);    // /Thumb Image    canvas.save();    canvas.drawBitmap(RotateBitmap(mImage, 90f), 0, 0, null);    canvas.restore();}public static Bitmap RotateBitmap(Bitmap source, float angle){    Matrix matrix = new Matrix();    matrix.postRotate(angle);    return Bitmap.createBitmap(source, 0, 0, source.getWidth(), source.getHeight(), matrix, true);}

The result of the code is shown below.

third step

So now that we have a thumb that is overlaying the background arc, we need to define the maskthat removes the inner part of the thumb, so that the background arc becomes visible again.

To achieve this we basically use the same parameters as before to create another arc, butthis time the stroke width has to be identical to the width used for the background arc asthis marks the area we want to remove inside the thumb.

Using the following code, the resulting image is shown in picture 4.

fourth step

@Overrideprotected void onDraw(Canvas canvas) {    // @step1    // @step2    // Thumb Image    // ...    // /Thumb Image    // Thumb Mask    mMask = Bitmap.createBitmap(800, 800, conf);    maskCanvas = new Canvas(mMask);    paint.setColor(Color.WHITE);    paint.setStrokeWidth(70);    paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));    final RectF oval3 = new RectF();    center_x = 400;    center_y = 400;    oval3.set(center_x - radius,            center_y - radius,            center_x + radius,            center_y + radius);    maskCanvas.drawBitmap(mImage, 0, 0, null);    maskCanvas.drawArc(oval3, 270, 45, false, paint);    // /Thumb Mask    canvas.save();    canvas.drawBitmap(RotateBitmap(mMask, 90f), 0, 0, null); // Notice mImage changed to mMask    canvas.restore();}

3. Animate the white thumb

The last part of your question would be animating the movement of the arc. I have no solidsolution for this, but maybe can guide you in a useful direction. I would try the following:

First define the thumb as a ImageView that is part of your whole arc graph. When changingthe selected values of your graph, you rotate the thumb image around the center of the backgroundarc. Because we want to animate the movement, just setting the rotation of the thumb image wouldnot be adequate. Instead we use a RotateAnimation kind of like so:

final RotateAnimation animRotate = new RotateAnimation(0.0f, -90.0f, // You have to replace these values with your calculated angles        RotateAnimation.RELATIVE_TO_SELF, // This may be a tricky part. You probably have to change this to RELATIVE_TO_PARENT        0.5f, // x pivot        RotateAnimation.RELATIVE_TO_SELF,        0.5f); // y pivotanimRotate.setDuration(1500);animRotate.setFillAfter(true);animSet.addAnimation(animRotate);thumbView.startAnimation(animSet);

This is far from final I guess, but it very well may aid you in your search for the neededsolution. It is very important that your pivot values have to refer to the center of yourbackground arc as this is the point your thumb image should rotate around.

I have tested my (full) code with API Level 16 and 22, 23, so I hope that this answer at leastgives you new ideas on how to solve your problems.

Please note that allocation operations within the onDraw method are a bad idea and shouldbe avoided. For simplicity I failed to follow this advise. Also the code is to be used asa guide in the right direction and not to be simply copy & pasted, because it makes heavyuse of magic numbers and generally does not follow good coding standards.


  1. I would change a bit of the way you draw your view, by looking on the original design, instead of drawing 3 caps I would draw just 1 line, that way the SweepGradient will work.

  2. This migth be a bit tricky, you have 2 options:

    • create a Path with 4 arcs
    • draw 2 arcs- one is the big white (filled with white so you still want to use Paint.Style.STROKE) and another on top of that make it fill transparent, you can achieve it with PorterDuff xfermode, it probably take you couple of tries until you get that without clearing the green circle too.
  3. I imagine you want to animate thumb position, so just use simple Animation that invalidate the view and draw the thumb view position accordingly.

Hopes this helps


Create a gradient than follow a path is not so simple.So I can suggest you to use some libraries than already did it.

Include the library:

dependencies {    ...    compile 'com.github.paroca72:sc-gauges:3.0.7'}

Create the gauge in XML:

<com.sccomponents.gauges.library.ScArcGauge            android:id="@+id/gauge"            android:layout_width="300dp"            android:layout_height="wrap_content"            android:layout_gravity="center_horizontal" />

Your code:

ScArcGauge gauge = this.findViewById(R.id.gauge);gauge.setAngleSweep(270);gauge.setAngleStart(135);gauge.setHighValue(90);int lineWidth = 50;ScCopier baseLine = gauge.getBase();baseLine.setWidths(lineWidth);baseLine.setColors(Color.parseColor("#dddddd"));baseLine.getPainter().setStrokeCap(Paint.Cap.ROUND);ScCopier progressLine = gauge.getProgress();progressLine.setWidths(lineWidth);progressLine.setColors(     Color.parseColor("#65AAF2"),     Color.parseColor("#3EF2AD"),     Color.parseColor("#FF2465"));progressLine.getPainter().setStrokeCap(Paint.Cap.ROUND);

Your result:

Result

You can find something more complex on this site: ScComponents