Android: OvalShape Drawable clipped at edges - android

I'm trying to create a OvalShape Drawable with border. To give it an elevated effect, I'm adding shadows around the border.
Relevant code:
class Badge extends ShapeDrawable{
void Badge(){
borderPaint = new Paint();
borderPaint.setColor(borderColor);
borderPaint.setStyle(Paint.Style.STROKE);
borderPaint.setStrokeWidth(borderThickness);
borderPaint.setAntiAlias(true);
borderPaint.setShadowLayer(4.0f, 0.0f, 2.0f, Color.BLACK);
}
#Override
public void draw(Canvas canvas) {
super.draw(canvas);
Rect r = getBounds();
// draw border if needed
if (borderThickness > 0) {
drawBorder(canvas);
}
int count = canvas.save();
canvas.translate(r.left, r.top);
// draw text inside badge
int width = this.width < 0 ? r.width() : this.width;
int height = this.height < 0 ? r.height() : this.height;
int fontSize = this.fontSize < 0 ? (Math.min(width, height) / 2) : this.fontSize;
textPaint.setTextSize(fontSize);
Rect textBounds = new Rect();
textPaint.getTextBounds(text, 0, text.length(), textBounds);
canvas.drawText(text, width / 2, height / 2 - textBounds.exactCenterY(), textPaint);
canvas.restoreToCount(count);
}
private void drawBorder(Canvas canvas) {
RectF rect = new RectF(getBounds());
rect.inset(borderThickness / 2, borderThickness / 2);
canvas.drawOval(rect, borderPaint);
}
}
However, the border drawn is clipped at the edges as you can see in the image. Also as the size of the drawable decreases, the edges gets clipped more.
What modification should I do in the code to achieve a perfect circle?

Related

Sharp edge with custom drawable clip path

I am trying to draw a circular drawable which consists of horizontal divisions of different color.
But the circular path that i clip from the drawable creates a circle which has broken circumference.
Here is the snippet for onDraw() from Drawable
#Override
public void draw(#NonNull Canvas canvas) {
// get drawable dimensions
Rect bounds = getBounds();
int width = bounds.right - bounds.left;
int height = bounds.bottom - bounds.top;
mPath.reset();
mPath.addCircle(width/2, height/2, width/2, Path.Direction.CW);
canvas.clipPath(mPath);
int barHeight = height / themeColors.length;
mRectF.left = 0;
mRectF.top = 0;
mRectF.right = width;
mRectF.bottom = height;
//Draw Rect. consisting of multiple rects acc. to height
for (int i = 0; i < themeColors.length; i++) {
mPaint.setColor(themeColors[i]);
canvas.drawRect(0, i*barHeight, width, (i+1)*barHeight , mPaint);
}
//Draw stroke border
if(mStrokeWidth!=0)
canvas.drawCircle(width/2, height/2, width/2, mStrokePaint);
}
How do i go about developing the drawable such that the edges are smooth ?
Can this be done with just xml drawable ?
EDIT:
I tried using PorterDuff modes with paint, but they did not work as given.

android canvas draw text in the triangle

In this Image I want text to totally be in the triangle with CYAN color.
I have created my own ImageView:
public class BookImageView extends android.support.v7.widget.AppCompatImageView {
private static final Float DISCOUNT_SIDE_SIZE = 0.33333F;
private Bitmap bitmap;
private Paint drawPaint = new Paint();
private Paint trianglePaint = new Paint();
{
trianglePaint.setColor(Constants.DISCOUNT_COLOR);
trianglePaint.setStyle(Paint.Style.FILL);
trianglePaint.setShadowLayer(10.0f, 10.0f, 10.0f, Color.parseColor("#7f000000"));
trianglePaint.setAntiAlias(true);
drawPaint.setColor(Color.BLACK);
drawPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD));
drawPaint.setShadowLayer(1f, 0f, 1f, Color.BLACK);
}
// Constractors ...
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (bitmap != null) {
Bitmap tempBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.RGB_565);
Canvas tempCanvas = new Canvas(tempBitmap);
tempCanvas.drawBitmap(bitmap, 0, 0, null);
Path path = new Path();
path.setFillType(Path.FillType.EVEN_ODD);
float size = bitmap.getWidth() * DISCOUNT_SIDE_SIZE;
path.lineTo(size, 0);
path.lineTo(0, size);
path.lineTo(0, 0);
path.close();
tempCanvas.drawPath(path, trianglePaint);
float scale = getResources().getDisplayMetrics().density;
drawPaint.setTextSize((int) (14 * scale));
Rect textBounds = new Rect();
drawPaint.getTextBounds("50%", 0, "50%".length(), textBounds);
int x = (int) (size / 2) - textBounds.width() / 2;
int y = (int) (size / 2) - textBounds.height() / 2;
tempCanvas.save();
tempCanvas.rotate(-45, x, y);
tempCanvas.drawText("50%", x, y, drawPaint);
tempCanvas.restore();
setImageDrawable(new BitmapDrawable(getContext().getResources(), tempBitmap));
}
}
#Override
public void setImageBitmap(Bitmap bitmap) {
this.bitmap = bitmap;
invalidate();
}
}
what can I do to solve this problem ?
You can try something like this
1) Measure the width of your text
Use measureText
2) From the point you are drawing calculate the width remaining to draw
3) Now depending on the use case you can curtail the length of text or scale the text as needed
int textWidthRequired = (int) drawPaint.measureText(textToDraw);
int widthRemainingToDraw = totalWidth/2 - textDrawX;
if(textWidthRequired > widthRemainingToDraw){
//handling
}
// draw text
tempCanvas.drawText(textToDraw,textDrawX, textDrawY, drawPaint);
Depending on how high up you want the text to be, you can use properties of similar triangles to first determine the maximum width of the text. In your case, size = the triangle's base, and size = the triangle's height/altitude. Let's define two variables:
Correction: The altitude won't be equal to the base. You'd need to calculate the altitude in order to use the below solution.
float triangleBase = size; // triangle base
float triangleAltitude = size; // Calculate this.
Let's say we want the text to be halfway up the center of the triangle:
float textYHeight = triangleHeight/2;
We figure out the width of the triangle at this point by using the following formula since the sides of similar triangles are proportional:
baseOfTriangleA/baseOfTriangleB = altitudeOfTriangleA/altitudeOfTriangleB;
float triangleWidthAtTextYLocation = (textYHeight * triangleBase)/triangleAltitude;
Now that we know what the width of the triangle is at this location, we can just iterate through different text scales until the text width is less than the value triangleWidthAtTextYlocation.
float scale = getResources().getDisplayMetrics().density;
int scaleFactor = 0;
drawPaint.setTextSize((int) (scaleFactor * scale));
Rect textBounds = new Rect();
drawPaint.getTextBounds("50%", 0, "50%".length(), textBounds);
while(textBounds.length < triangleWidthAtTextYLocation){
// Re-measure the text until it exceeds the width
scaleFactor++;
drawPaint.setTextSize((int) (scaleFactor * scale));
drawPaint.getTextBounds("50%", 0, "50%".length(), textBounds);
}
// Once we know the scaleFactor that puts it over the width of the triangle
// at that location, we reduce it by 1 to be just under that width:
scaleFactor = Maths.abs(scaleFactor - 1);
// final text size:
drawPaint.setTextSize((int) (scaleFactor * scale));

Animation not fluent Android Wear

I'm starting with Android Wear and I want to make a circle animation, making grow.
I know how to do it, I think, but it's doing it very very slow, hope you can help me
I have this class variable
Paint mAnimation;
intialized on the method OnCreate
mAnimation = new Paint(Paint.ANTI_ALIAS_FLAG);
mAnimation.setStyle(Paint.Style.FILL);
mAnimation.setColor(Color.GREEN);
and on the OnDraw method I have
#Override
public void onDraw(Canvas canvas, Rect bounds) {
int width = bounds.width();
int height = bounds.height();
float centerX = width / 2f;
float centerY = height / 2f;
// Draw the background.
canvas.drawRect(0, 0, bounds.width(), bounds.height(), mBackgroundPaint);
canvas.drawCircle(0, centerY, radiusPlus, mAnimation);
radiusPlus += 20;
}
The animation is "correct", but is very slow, like if was paused.
Thanks!!
EDIT
I found a example and now I finally found why. I didn't invalidate the view at the end of the OnDraw. Now It's working fine. Thanks.
#Override
public void onDraw(Canvas canvas, Rect bounds) {
int width = bounds.width();
int height = bounds.height();
float centerX = width / 2f;
float centerY = height / 2f;
// Draw the background.
canvas.drawRect(0, 0, bounds.width(), bounds.height(), mBackgroundPaint);
canvas.drawCircle(0, centerY, radiusPlus, mAnimation);
radiusPlus += 5;
if (isVisible() && !isInAmbientMode()) {
invalidate();
}
}

Circle drawn on canvas doesn't match the screen

I want to draw circle in center of screen, but I'm getting something like this:
I'm using this code to draw this circle.
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Display display = getWindowManager().getDefaultDisplay();
int width = display.getWidth();
int height = display.getHeight();
Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_4444);
Canvas c = new Canvas(bmp);
RectF rect = new RectF(0,0,width,width);
drawCircle(rect, c, width, height);
ImageView img = (ImageView) findViewById(R.id.imageView1);
img.setImageBitmap(bmp);
img.setScaleType(ScaleType.FIT_CENTER);
}
private void drawCircle(RectF rect, Canvas c, int width, int height) {
Paint paint = new Paint();
paint.setARGB(255, 255 , 10, 21);
paint.setStrokeWidth(10);
paint.setAntiAlias(true);
paint.setStrokeCap(Paint.Cap.BUTT);
paint.setStyle(Paint.Style.STROKE);
int radius;
if(width < height)
radius = width/2;
else
radius = height/2;
c.drawCircle(width/2, height/2, radius, paint);
}
I don't understand why it's cut at sides even though I use size of screen to draw it, so it should perfectly fit it.
You didn't account for the thickness of the line (strokeWidth). You drew a circle assuming it had 0 thickness, so the "actual" circle IS touching the edges of the screen, but since you used a thick paintbrush, some of the paint leaked past the edge.
you should decrease the thickness/2.
private void drawCircle(RectF rect, Canvas c, int width, int height) {
Paint paint = new Paint();
paint.setARGB(255, 255 , 10, 21);
paint.setStrokeWidth(10);
paint.setAntiAlias(true);
paint.setStrokeCap(Paint.Cap.BUTT);
paint.setStyle(Paint.Style.STROKE);
int radius;
if(width < height)
radius = width/2;
else
radius = height/2;
//this is the new line:
radius-= 5;
c.drawCircle(width/2, height/2, radius, paint);
}
Account for the StrokeWidth in the radius:
// Substract stroke width.
radius -= paint.getStrokeWidth() / 2;
c.drawCircle(width/2, height/2, radius, paint);

How to draw Arc between two points on the Canvas?

I have two points in the canvas, now I'm able to draw a line between those points like this below image by using
This code canvas.drawLine(p1.x, p1.y, p2.x, p2.y, paint);
I want to draw the arc between two points like below image.
How can I draw like this.
Finally I got the solution from this code:
float radius = 20;
final RectF oval = new RectF();
oval.set(point1.x - radius, point1.y - radius, point1.x + radius, point1.y+ radius);
Path myPath = new Path();
myPath.arcTo(oval, startAngle, -(float) sweepAngle, true);
To calculate startAngle, use this code:
int startAngle = (int) (180 / Math.PI * Math.atan2(point.y - point1.y, point.x - point1.x));
Here, point1 means where you want to start drawing the Arc. sweepAngle means the angle between two lines. We have to calculate that by using two points like the blue points in my Question image.
Do something like this:
//Initialized paint on a class level object.
Paint p = new Paint();
p.setColor(Color.BLACK);
//Calculate the rect / bounds of oval
RectF rectF = new RectF(50, 20, 100, 80);
#Override
protected void onDraw(Canvas canvas) {
//Do the drawing in onDraw() method of View.
canvas.drawArc (rectF, 90, 45, false, p);
}
first we need to visual how the coordinates are in terms of start and sweep angels then it will become more clear.
so if you wanted just the right top piece of the circle, we could do something like this:
val rect = RectF(0f, 0f, 500f, 300f)
val paint = Paint()
paint.apply {
strokeWidth = 5f
setStyle(Paint.Style.STROKE)
color = COLOR.BLUE
}
path.addArc(rect, 270f, 90f)
..
this starts at 270 (per the diagram above and 'sweeps` 90 degrees forward. you then have this shape:
let's create one more so you get the hang of it. this time let's use a negative value: we want to create a semi half moon (arc) starting from the right side:
path.addArc(rect, 0f, -180f)
here we started at 0 and 'sweeped` -180 degrees.
and the results are:
I was trying to do something a little different and it's all about calculating sweep and start angles.
I wanted to show an arc that represents progress on a circle that goes from top to bottom.
So I had progress value from 0...100 and I want to show an arc that start from top to bottom to fill the circle when the progress is 100.
To calculate the sweepAngle I use:
int sweepAngle = (int) (360 * (getProgress() / 100.f));
Next is to calculate the startAngle
int startAngle = 270 - sweepAngle / 2;
Start Angle is calculated this way because:
It's always going to start from the left side, starting from the top to bottom. So starting angle at the top equals 270 (Note that it goes clockwise and 0 = 3 o'clock, so 12 o'clock equals 270 degrees)
Next I want to calculate how far I'm going to get away from my starting point (270) and to do that I only calculate half of the sweep angle because only half of the arc will be on the left side and the other half on the right side.
So considering I have progress of 25%
sweepAngle = 90 degrees (90 degrees is quarter of a circle)
start angle = 225 (45 degrees away from 270)
If you want the progress to go from other sides (Left to right, right to left etc..) you will only need to replace 270 with the starting the angle.
I may be late to answer but I got more information.
After Android Lollipop there are two ways to address this problem
public void drawArc(RectF oval, float startAngle, float sweepAngle,
boolean useCenter, Paint paint)
public void drawArc(float left, float top, float right, float bottom,
float startAngle, float sweepAngle, boolean useCenter, Paint paint)
Usage:
RectF rectF = new RectF(left, top, right, bottom);
// method 1
canvas.drawArc (rectF, 90, 45, true, paints[0]);
// method 2
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
canvas.drawArc (left, top, right, bottom, 0, 45, true, paints[1]);
}
Sweep angle is nothing more than angle of Sector which is drawn clockwise eg. for below code
private void drawArcs(Canvas canvas) {
RectF rectF = new RectF(left, top, right, bottom);
// white arc
canvas.drawArc (rectF, 90, 45, true, paints[0]);
// Green arc
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
canvas.drawArc (left, top, right, bottom, 0, 45, true, paints[1]);
}
// Red stroked arc
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
canvas.drawArc (left, top, right, bottom, 180, 45, true, paints[2]);
}
}
Result will look like this
Same can be achieved with the help of defining Paths and then iterating over them in onDraw method as illustrated in this snippet:
public class ArcDrawable extends Drawable {
private int left, right, top, bottom;
private Paint[] paints = new Paint[3];
private HashMap<Path, Paint> pathMap = new HashMap();
public ArcDrawable() {
// white paint
Paint whitePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
whitePaint.setColor(Color.WHITE);
paints[0]= whitePaint;
// green paint
Paint greenPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
greenPaint.setColor(Color.GREEN);
paints[1]= greenPaint;
// red paint
Paint redPaint =new Paint(Paint.ANTI_ALIAS_FLAG);
redPaint.setColor(Color.RED);
redPaint.setStyle(Paint.Style.STROKE);
paints[2]= redPaint;
}
#Override
public void draw(Canvas canvas) {
//----------USE PATHS----------
// Define and use custom Path
for (Map.Entry<Path, Paint> entry : pathMap.entrySet()) {
// Draw Path on respective Paint style
canvas.drawPath(entry.getKey(), entry.getValue());
}
// -------OR use conventional Style---------
//drawArcs(canvas);
}
//Same result
private void drawArcs(Canvas canvas) {
RectF rectF = new RectF(left, top, right, bottom);
// method 1
canvas.drawArc (rectF, 90, 45, true, paints[0]);
// method 2
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
canvas.drawArc (left, top, right, bottom, 0, 45, true, paints[1]);
}
// method two with stroke
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
canvas.drawArc (left, top, right, bottom, 180, 45, true, paints[2]);
}
}
#Override
protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds);
int width = bounds.width();
int height = bounds.height();
left = bounds.left;
right = bounds.right;
top = bounds.top;
bottom = bounds.bottom;
final int size = Math.min(width, height);
final int centerX = bounds.left + (width / 2);
final int centerY = bounds.top + (height / 2);
pathMap.clear();
//update pathmap using new bounds
recreatePathMap(size, centerX, centerY);
invalidateSelf();
}
private Path recreatePathMap(int size, int centerX, int centerY) {
RectF rectF = new RectF(left, top, right, bottom);
// first arc
Path arcPath = new Path();
arcPath.moveTo(centerX,centerY);
arcPath.arcTo (rectF, 90, 45);
arcPath.close();
// add to draw Map
pathMap.put(arcPath, paints[0]);
//second arc
arcPath = new Path();
arcPath.moveTo(centerX,centerY);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
arcPath.arcTo (rectF, 0, 45);
}
arcPath.close();
// add to draw Map
pathMap.put(arcPath, paints[1]);
// third arc
arcPath = new Path();
arcPath.moveTo(centerX,centerY);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
arcPath.arcTo (rectF, 180, 45);
}
arcPath.close();
// add to draw Map
pathMap.put(arcPath, paints[2]);
return arcPath;
}
#Override
public void setAlpha(int alpha) {
}
#Override
public void setColorFilter(#Nullable ColorFilter colorFilter) {
}
#Override
public int getOpacity() {
return 0;
}
}
Complete source code:
https://github.com/hiteshsahu/Arc-Drawable
a sample for draw arc.
public static Bitmap clipRoundedCorner(Bitmap bitmap, float r, boolean tr, boolean tl, boolean bl, boolean br)
{
int W = bitmap.getWidth();
int H = bitmap.getHeight();
if (r < 0)
r = 0;
int smallLeg = W;
if(H < W )
smallLeg = H;
if (r > smallLeg)
r = smallLeg / 2;
float lineStop = r/2;
Path path = new Path();
path.moveTo(0,0);
if(tr)
{
path.moveTo(0, lineStop);
path.arcTo(new RectF(0,0, r,r), 180, 90, false);
}
path.lineTo(W-lineStop, 0);
if(tl)
path.arcTo(new RectF(W-r,0, W,r), 270, 90, false);
else
path.lineTo(W, 0);
path.lineTo(W, H-lineStop);
if(bl)
path.arcTo(new RectF(W-r,H-r, W,H), 0, 90, false);
else
path.lineTo(W, H);
path.lineTo(lineStop, H);
if(br)
path.arcTo(new RectF(0,H-r, r,H), 90, 90, false);
else
path.lineTo(0,H);
if(tr)
path.lineTo(0,lineStop);
else
path.lineTo(0,0);
Bitmap output = Bitmap.createBitmap(W, H, Config.ARGB_8888);
Canvas canvas = new Canvas(output);
final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(Color.BLACK);
canvas.drawPath(path, paint);
paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
canvas.drawBitmap(bitmap, 0, 0, paint);
return output;
}
A simple solution was suggested here by Langkiller. This draws a cubic line from the start point via the control point to the end point.
Path path = new Path();
float startX = 0;
float startY = 2;
float controlX = 2;
float controlY = 4;
float endX = 4
float endY = 2
conePath.cubicTo(startX, startY, controlX, controlY,endX, endY);
Paint paint = new Paint();
paint.setARGB(200, 62, 90, 177);
paint.setStyle(Paint.Style.FILL);
canvas.drawPath(path, paint)

Categories

Resources