Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Draw bubble programmatically

I would like to have a bubble with a precentage value in my app, I can't use 9 patches as i want it to be customizable and its background color should be changeble. It should look something like this enter image description here

How can I do it? This bubble will have views inflated inside of it, like this percentage or some larger layouts. Also depending on the layout(phone or tablet) it might have one side larger than the other (arrow not at the center) so that's another reason i prefer doing it programmatically

like image 304
DoubleP90 Avatar asked Dec 27 '13 19:12

DoubleP90


1 Answers

Obviously it's never a good idea to have code in your app that you don't understand, so I won't just write out a bunch of equations in java code for you. If however you follow and understand the maths below, then it will be a relatively simple matter for you to use the described equations in your code and draw the arc.


To achieve a rounded tip on the pointer you will need to modify updatePointerPath().
At the moment it just uses rLineTo() to draw three lines forming a triangle.
There is another method in the android Path class called arcTo() which takes the form:

    arcTo(RectF oval, float startAngle, float sweepAngle)

You can use this method to draw your arc at the tip of the pointer, but you need to work out a few things first.

You can already calculate the coordinates of the three corners of the pointer triangle. This is what updatePointerPath() does already. Now take a look at the diagram below. To use arcTo(), you will need to calculate the following:

  1. The coordinates of point T which is where your arc will start.
  2. The coordinates of the top left and bottom right corners of the bounding RectF
  3. Your starting angle (Alpha)
  4. Your sweep angle (2 * Phi)

Diagram 2

The start angle Alpha can easily be found with basic trig as shown in the diagram below.

Diagram 1

Note: It will be best if you stick to using Radians instead of Degrees for all the angles since this is what all the trig functions in the android 'Math' class require.
With this in mind:

  • There are 2Pi Radians in a circle
  • The three angles in a triangle add up to Pi Radians
  • A right angle is Pi/2 Radians

So adding the three angles in the triangle formed by points C, T and P you get:

Alpha + Phi + Pi/2 = Pi

Therefore

Phi = Pi/2 - Alpha

So we have now calculated Alpha and Phi.

Next, d is the distance between the point P and the bottom of the bounding box.
You can get it by calculating the distance from point C to point P, and then subtracting the radius r.
Now:

sin(Alpha) = r / (distance from C to P)

Therefore:

distance from C to P = r / sin(Alpha)

And so given that the distance d is the distance from point C to point P minus the radius r, we get:

d = (r / sin(Alpha)) - r

That gives you all the info you need to calculate the coordinates of the top left and bottom right corners of the bounding RectF.

Now all that's left is to work out the coordinates of point T.

First work out the distance from P to T.
Given that:

tan(Alpha) = r / (distance from P to T)

We get:

distance from P to T = r / tan(Alpha)

Finally, adding one more point to the diagram....

Diagram 3

We can see that:

sin(Alpha) = (distance from P to A) / (distance from P to T)

So:

distance from P to A = (distance from P to T) * sin(Alpha)

Similarly:

cos(Alpha) = (distance from T to A) / (distance from P to T)

So:

distance from T to A = (distance from P to T) * cos(Alpha)

With this info you can now calculate the coordinates of point T !!

If you understood all that, then the coding from here is easy. If you're unsure of anything, then just ask.


The following gives an idea how the updated updatePointerPath() might look.

private void updatePointerPath() {
    float xDistance;
    float yDistance;
    mPointer = new Path();
    mPointer.setFillType(Path.FillType.EVEN_ODD);

    // Set the starting point (top left corner of the pointer)
    mPointer.moveTo(pointerHorizontalStart(), mBoxHeight);

    // Define the lines

    // First draw a line to the top right corner
    xDistance= mPointerWidth;
    yDistance= 0;
    mPointer.rLineTo(xDistance, yDistance);

    // Next draw a line down to point T
    xDistance= (mPointerWidth / 2) - distancePtoA;
    yDistance= mPointerHeight - distanceTtoA;
    mPointer.rLineTo(-xDistance, yDistance); //Note: Negative sign because we are moving back to the left

    // Next draw the arc
    // Note: The RectF used in arcTo() is defined in absolute screen coordinates
    float boundingBoxLeft = pointerHorizontalStart() + (mPointerWidth / 2) - (2 * radius);
    float boundingBoxTop = mBoxHeight - distanceD - (2 * radius);
    float boundingBoxRight = boundingBoxLeft + (2 * radius);
    float boundingBoxBottom = boundingBoxTop + (2 * radius);

    RectF boundingBox = new RectF(boundingBoxLeft, boundingBoxTop, boundingBoxRight, boundingBoxBottom);

    // Note: While the methods in the android Math class like sin() and asin() all use Radians,
    // for some reason it was decided that arcTo() in the Path class would use Degrees,
    // so we have to convert the angles
    float startAngleInDegrees = angleAlpha * (180 / Math.PI);
    float sweepAngleInDegrees = 2 * anglePhi * (180 / Math.PI);

    mPointer.arcTo(boundingBox, startAngleInDegrees, sweepAngleInDegrees);

    // Finally draw the line from the end of the arc back up to the top left corner
    // Note: The distances are the same as from the top right corner to point T,
    // just the direction is different.
    mPointer.rLineTo(-xDistance, -yDistance); // Note: Negative in front of yDistance because we are moving up

    // Close off the path  
    mPointer.close();
}
like image 149
Sound Conception Avatar answered Nov 02 '22 03:11

Sound Conception