Draw circle which is filled horizontally in Android - android

To draw circles and pie charts in Android, we can use the AChartEngine Android framework as shown here.
However, how can we draw circles that is partly filled horizontally (or vertically) in Android? I mean circles which are filled, for example, from the bottom to the top according to the percentage specified in Java code.
Here is a preview of what we need:

Try this lib Circle Progress.
Here is a sample from lib's author:
<com.github.lzyzsd.circleprogress.CircleProgress
android:id="#+id/circle_progress"
android:layout_marginLeft="50dp"
android:layout_width="100dp"
android:layout_height="100dp"
custom:circle_progress="20"/>

Extend Drawable and let your draw method look like this:
public void draw(Canvas c) {
float size = ...; // The size of your circle
Paint color = new Paint();
color.setColor(...); // Color of your circle
c.drawCircle(size / 2, size / 2, size / 2, color);
Path p = new Path();
float ratio = ...; // How many of the circle you want to have filled
float offset = size * (float) Math.sqrt(1-((double) (0.5-ratio) * 2)*((double) (0.5-ratio) * 2)) / 2;
float angle = (float) Math.asin((double) (0.5-ratio) * 2);
p.addArc(offset, size * (1-percent), size - offset, size, angle, Math.PI - angle);
p.close();
c.drawPath(p, color);
}

Related

Drawing canvas on framelayout

I have a frame layout with a background image.I am using canvas to draw lines on this framelayout.
The issue is , on different screen sizes , the lines are not appearing at the same place.
I tried multiplying the coordinates by the screen density dpi , but its still not working.
Snippet of my canvas code
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setStyle(Paint.Style.FILL);
bitmap = ((BitmapDrawable) zoom_lay.getBackground()).getBitmap();
mutableBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true);
canvas = new Canvas(mutableBitmap);
canvas.drawColor(Color.TRANSPARENT);
zoom_lay.setBackground(new BitmapDrawable(getResources(), mutableBitmap));
final Path pathPolygon_2 = new Path();
pathPolygon_2.reset(); // only needed when reusing this path for a new build
canvas.drawLine(500,500,600,615,paint);
Thank you
Create your layout with the following params:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/mainView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#mipmap/background"
tools:context=".MainActivity">
Can be any type, FrameLayout is just an example. The important part is to set it's width and height to match parent.
From there you can use your code to get the bitmap and draw on it only thing, let's say you want to draw the line horizontally in the center:
canvas.drawLine(0,muteableBitmap.getHeight()/2,muteableBitmap.getWidth(),muteableBitmap.getHeight()/2,paint);
And say you want a line vertically from the center:
canvas.drawLine(muteable.getWidth()/2,0,muteableBitmap.getWidth()/2,muteableBitmap.getHeight,paint);
I've written this manually so it might not compile due to capital letters and such, but it should work
first you need to get the size of the Bitmap, and then calculate the end points of the line according to your needs. For example, if you want to draw a line in the middle of the Bitmap, the Y coordinate is this Bitmap.getHeight() * 0.5f. I hope I can help you.
Bitmap mutableBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true);
int height = mutableBitmap.getHeight();
int width = mutableBitmap.getWidth();
float startX = width * 0f;
float startY = height * 0.5f;
float stopX = width * 1f;
float stopY = height * 0.5f;
canvas.drawLine(startX, startY, stopX, stopY, paint);

How canvas.drawRect draws a rectangle

I have to create a custom view where I have to draw a rectangle. I'm trying to use the canvas.drawRect method. I wanted to create rectangles somethig like this
the gray colored one is my custom view which is extending the View class.
Inside the onDraw method I'm trying to draw the rectangle.
But actually I'm confused with the parameters of the drawRect method.
As per the documentation
/**
* Draw the specified Rect using the specified paint. The rectangle will
* be filled or framed based on the Style in the paint.
*
* #param left The left side of the rectangle to be drawn
* #param top The top side of the rectangle to be drawn
* #param right The right side of the rectangle to be drawn
* #param bottom The bottom side of the rectangle to be drawn
* #param paint The paint used to draw the rect
*/
What I assume is that left and top forms the x,y coordinates of the starting point, and right is the width and bottom is the height. But it doesn't seem to work that way.
I tried something like this to draw one rectangle but it does not draw anything
paint.setColor(Color.BLUE);
canvas.drawRect(5, canvas.getHeight()/2, 30, 30, paint );
Can anyone please tell how exactly a rectangle is drawn using these values?
It would be very helpful if someone could show the code to draw at least the first rectangle.
My requirement is like, the number of inner rectangles are dynamic, so if I pass some 4 to this View, it should create 4 equal width rectangles horizontally. something like
Thanks in advance!!
But actually I'm confused with the parameters of the drawRect method.
The drawRect method requires only two coordinates to draw a rectangle.
The top left corner and the bottom right corner. So the 4 points form these 2 coordinates
in your canvas. Hope it is clear from the below images
P1 and P2 are points formed by (left,top) and (right,bottom), So the drawn rectangle would be like this.
To draw rectangles dynamically like you have shown in you image , try something like this
int[] colors = new int[]{Color.RED, Color.GREEN, Color.BLUE, Color.YELLOW}; // given some fixed colors
in you onDraw method
#Override
protected void onDraw(Canvas canvas) {
int padding = 5;
float rectangleWidth = (getMeasuredWidth() - padding * 2) / colors.length;
for (int i = 0; i < colors.length; i++) {
paint.setColor(colors[i]);
canvas.drawRect(padding + (rectangleWidth * i),
getMeasuredHeight() / 2,
padding + rectangleWidth * (i + 1),
getMeasuredHeight() - padding, paint
); // 5 px is the padding given to the canvas
}
}
drawRect(float left, float top, float right, float bottom, Paint paint)
It seems in your case the problem is that if right is less than left or bottom is less than top then the rectangle is not drawn. But it seems it happens only in some devices , as #David Medenjak commented
I also recommend you to use the View dimensions and not the Canvas dimensions, that is better to use getWidth() & getHeight() and not Canvas.getWidth() & Canvas.getHeight()
Use this method to draw rectangle at X & Y coordinate with width and height params
fun drawRectangle(left: Int, top: Int, right: Int, bottom: Int, canvas: Canvas, paint: Paint?) {
var right = right
var bottom = bottom
right = left + right // width is the distance from left to right
bottom = top + bottom // height is the distance from top to bottom
canvas.drawRect(left.toFloat(), top.toFloat(), right.toFloat(), bottom.toFloat(), paint!!)
}
Usage
//drawing rectangle
drawRectangle(posX, posY, 60, 40, canvas, paint)

Drawing a rounded hollow thumb over arc

I want to create a rounded graph that will display a range of values from my app. The values can be classified to 3 categories: low, mid, high - that are represented by 3 colors: blue, green and red (respectively).
Above this range, I want to show the actually measured values - in a form of a "thumb" over the relevant range part:
The location of the white thumb over the range arc may change, according to the measured values.
Currently, I'm able to draw the 3-colored range by drawing 3 arcs over the same center, inside the view's onDraw method:
width = (float) getWidth();
height = (float) getHeight();
float radius;
if (width > height) {
radius = height / 3;
} else {
radius = width / 3;
}
paint.setAntiAlias(true);
paint.setStrokeWidth(arcLineWidth);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setStyle(Paint.Style.STROKE);
center_x = width / 2;
center_y = height / 1.6f;
left = center_x - radius;
float top = center_y - radius;
right = center_x + radius;
float bottom = center_y + radius;
oval.set(left, top, right, bottom);
//blue arc
paint.setColor(colorLow);
canvas.drawArc(oval, 135, 55, false, paint);
//red arc
paint.setColor(colorHigh);
canvas.drawArc(oval, 350, 55, false, paint);
//green arc
paint.setColor(colorNormal);
canvas.drawArc(oval, 190, 160, false, paint);
And this is the result arc:
My question is, how do I:
Create a smooth gradient between those 3 colors (I tried using
SweepGradient but it didn't give me the correct result).
Create the overlay white thumb as shown in the picture, so that I'll be able to control where to display it.
Animate this white thumb over my range arc.
Note: the 3-colored range is static - so another solution can be to just take the drawable and paint the white thumb over it (and animate it), so I'm open to hear such a solution as well :)
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 first
rectangle contains the colors blue and green while the second rectangle contains green
and red as seen in the following picture. I marked the line where both rectangles touch each other
black to clarify that they are infact two different rectangles.
This can be achieved using the following code (excerpt):
// Both color gradients
private 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();
// ...
#Override
protected 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 of
both rectangles that should be visible to the user. This means that most of both rectangles
will be masked away and thus not visible. Instead the only thing to remain is the arc area.
The result should look like this:
In order to achieve the needed behavior we define a mask that only reveals the arc area within
the rectangles. For this we make heavy use of the setXfermode method of Paint. As argument
we use different instances of a PorterDuffXfermode.
private Paint maskPaint;
private Paint imagePaint;
// ...
// To be called within all constructors
private 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));
}
#Override
protected 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 this
time 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 only
the stroke seems to remain. Applied to the arc image the thumb overlays the colored arc with
a transparent content area.
So the first step would be drawing the thumb. We use an arc for this with the same radius as
the background arc but different angles, resulting in a much smaller arc. But becaus the
thumb should "surround" the background arc, its stroke width has to be bigger than the
background arc.
#Override
protected 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.
So now that we have a thumb that is overlaying the background arc, we need to define the mask
that 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, but
this time the stroke width has to be identical to the width used for the background arc as
this marks the area we want to remove inside the thumb.
Using the following code, the resulting image is shown in picture 4.
#Override
protected 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 solid
solution 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 changing
the selected values of your graph, you rotate the thumb image around the center of the background
arc. Because we want to animate the movement, just setting the rotation of the thumb image would
not 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 pivot
animRotate.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 needed
solution. It is very important that your pivot values have to refer to the center of your
background 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 least
gives you new ideas on how to solve your problems.
Please note that allocation operations within the onDraw method are a bad idea and should
be avoided. For simplicity I failed to follow this advise. Also the code is to be used as
a guide in the right direction and not to be simply copy & pasted, because it makes heavy
use of magic numbers and generally does not follow good coding standards.
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.
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.
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:
You can find something more complex on this site:
ScComponents

Scale bitmap from center point in Android for zoom in effect

I am trying to scale up a bitmap from its center point in Android to achieve a zoom effect, but without success. The code I have is:
float scaleWidth = ((float) width + (i * 5)) / width;
float scaleHeight = ((float) height + (i * 5)) / height;
Matrix matrix = new Matrix();
matrix.setScale(scaleWidth, scaleHeight, scaleWidth / 2, scaleHeight / 2);
Bitmap rescaledBitmap = Bitmap.createBitmap(src, 0, 0, width, height, matrix, true);
result.add(rescaledBitmap);
I am setting the pivot point by dividing the dimensions by 2, but the effect is just that the image is scaled from 0, 0 as coordinates instead of from the center. What I want is for the image to be a fixed size, but scaled up from its center point (thus cropping the image).
I'm going to offer an alternative solution using a property animator since that is a cleaner solution I think.
SomeLayout.xml (The key here is that the ViewGroup is the same size as the View, so it will clip as you requested (like google maps zoom in))
<FrameLayout
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_gravity="center">
<View
android:id="#+id/zoom"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="#drawable/myCoolImage"
/>
</FrameLayout>
Code: (the 1,2,1 will start at a scale of 1x then 2x then back to 1x, it takes a list of values)
final View view = findViewById(R.id.zoom);
view.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
final ObjectAnimator animator = ObjectAnimator.ofFloat(view, View.SCALE_X, 1, 2, 1)
.ofFloat(view, View.SCALE_Y, 1, 2, 1)
.setDuration(5000);
animator.setInterpolator(new AccelerateDecelerateInterpolator());
animator.start();
}
});
So with this version image if you had a zoom +, and zoom - views with onClickListeners, you could basically simulate the controlled zooming as long as you know what values you want to zoom with.
Also as previously noted the ViewGroup being the same size as the internal view will force the animation to clip to it's parent bounds instead of being completely visible.
References:
Google Android ObjectAnimator
Sure this will come late, but for all looking after me:
double scaleFactor = 0.75; // Set this to the zoom factor
int widthOffset = (int) ((1 - scaleFactor)/2 * bmp.getWidth());
int heightOffset = (int) ((1 - scaleFactor)/2 * bmp.getHeight());
int numWidthPixels = bmp.getWidth() - 2 * widthOffset;
int numHeightPixels = bmp.getHeight() - 2 * heightOffset;
Bitmap rescaledBitmap = Bitmap.createBitmap(bmp, widthOffset, heightOffset, numWidthPixels, numHeightPixels, null, true);
This example will zoom in on the center of a bitmap and with a factor of 25%.
The second and third arguments in createBitmap take the x/y coordinates of the top left corner. You're sending 0,0, so if I understand correctly...
The image is correctly scaled, but the image is not centered, right?
To center it, you need to find the correct (x,y) point for the top left corner. This should be 1/4th the original width/height.
So...
Bitmap rescaledBitmap = Bitmap.createBitmap(src, (width/2), (height/2), width, height, matrix, true);
should work.

How can I scale a character to fit into a Rect for drawing text?

I have an ArrayList of Rects, and characters that I want to draw into the individual Rects, one character per Rect.
I want to stretch the text to fit into a given Rect, filling the Rect, so I can use canvas.drawText() to draw the text.
I'm thinking I'll need some combination of setTextSize() and setTextScaleX(), but I don't know where to start for setting the parameters. How can I get my characters to fit into the Rects?
Hopefully someone has solved this problem before and can point me in the right direction.
I ended up doing this with an approach that uses getTextBounds to determine the size and scale of text to use:
// Adjust text size to fill rect
paint.setTextSize(100);
paint.setTextScaleX(1.0f);
// ask the paint for the bounding rect if it were to draw this text
Rect bounds = new Rect();
paint.getTextBounds(words[i], 0, words[i].length(), bounds);
// get the height that would have been produced
int h = bounds.bottom - bounds.top;
// figure out what textSize setting would create that height of text
float size = (((float)(rect.height())/h)*100f);
// and set it into the paint
paint.setTextSize(size);
// Now set the scale.
// do calculation with scale of 1.0 (no scale)
paint.setTextScaleX(1.0f);
// ask the paint for the bounding rect if it were to draw this text.
paint.getTextBounds(words[i], 0, words[i].length(), bounds);
// determine the width
int w = bounds.right - bounds.left;
// calculate the baseline to use so that the entire text is visible including the descenders
int text_h = bounds.bottom-bounds.top;
int baseline =bounds.bottom+((rect.height()-text_h)/2);
// determine how much to scale the width to fit the view
float xscale = ((float) (rect.width())) / w;
// set the scale for the text paint
paint.setTextScaleX(xscale);
canvas.drawText(words[i], frame.left + rect.left * scaleX, frame.top + rect.bottom * scaleY - baseline, paint);

Categories

Resources