First I will explain what I am trying to achieve. I am trying to achieve cropping functionality. For that, I am drawing one rectangle on canvas and then trying to draw heart inside rectangle. So If user minimize/maximize rectangle heart will also minimize/maximize.
Here is my code.
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mIsInitialized) {
setMatrix();
Matrix localMatrix1 = new Matrix();
localMatrix1.postConcat(this.mMatrix);
Bitmap bm = getBitmap();
if (bm != null) {
canvas.drawBitmap(bm, localMatrix1, mPaintBitmap);
// draw edit frame
drawEditFrame(canvas);
}
}
}
private void drawEditFrame(Canvas canvas) {
mPaintTransparent.setFilterBitmap(true);
mPaintTransparent.setColor(mOverlayColor);
mPaintTransparent.setStyle(Paint.Style.FILL);
Path path = new Path();
path.addRect(mImageRect.left, mImageRect.top, mImageRect.right, mImageRect.bottom,
Path.Direction.CW);
drawHeart(mFrameRect.centerX(), mFrameRect.centerY(), path);
canvas.drawPath(path, mPaintTransparent);
}
private Path drawHeart(float width, float height, Path path) {
// Starting point
path.moveTo(width / 2, height / 5);
// Upper left path
path.cubicTo(5 * width / 14, 0,
0, height / 15,
width / 28, 2 * height / 5);
// Lower left path
path.cubicTo(width / 14, 2 * height / 3,
3 * width / 7, 5 * height / 6,
width / 2, height);
// Lower right path
path.cubicTo(4 * width / 7, 5 * height / 6,
13 * width / 14, 2 * height / 3,
27 * width / 28, 2 * height / 5);
// Upper right path
path.cubicTo(width, height / 15,
9 * width / 14, 0,
width / 2, height / 5);
return path;
}
private void setMatrix() {
mMatrix.reset();
mMatrix.setTranslate(mCenter.x - mImgWidth * 0.5f, mCenter.y - mImgHeight * 0.5f);
mMatrix.postScale(mScale, mScale, mCenter.x, mCenter.y);
mMatrix.postRotate(mAngle, mCenter.x, mCenter.y);
}
But heart is sticking at top left corner and not moving or minimizing/maximizing as per rectangle.
Here is current image for reference.
Please provide me hint or any reference.
EDIT I am able to draw circle inside rectangle which minimize/maximize as per rectangle size and move as per rectangle move. But I am not able to do same with custom heart. Here is code for circle.
path.addCircle((mFrameRect.left + mFrameRect.right) / 2,
(mFrameRect.top + mFrameRect.bottom) / 2,
(mFrameRect.right - mFrameRect.left) / 2, Path.Direction.CCW);
Here is image in which I am drawing circle in rectangle which I want to achieve same with heart too.
The implementation be like you should retake the whole bitmap size(height, width) before resizing then max/min the size of that bitmap.
Related
I'm trying to use the PorterDuff library to trim a canvas circle and rectangle to form a quarter of a square in my custom view for my app, I managed to get it to work but not fully because it trims the square out but keeps the rest of the circle in, I'm using the SRC_IN mode which seems like the right mode to use from looking at the android documentation for it but it's not working as expected, this is a snippet of the onDraw method in my custom view class:
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int height = getHeight();
int width = getWidth();
canvas.drawCircle(width / 2, height / 2, (width + height) / 10, paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
Rect rectangle = new Rect(0, 0, width / 2, height / 2);
paint.setColor(Color.RED);
canvas.drawRect(rectangle, paint);
}
I'm drawing the circle in the center of the screen and then drawing the square in the top left of the screen and the PorterDuff mode should basically get the intersected part between the shapes but it just trims non-intersected square part out but doesn't do the same for the circle.
This is what it looks like:
I can't tell what i'm doing wrong here, hopefully someone can point it out.
the issue is with the source, the rectangle is the source in this context, draw the rectangle first then use PorterDuff.Mode.SRC_IN to cut the circle based on it.
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int height = getHeight();
int width = getWidth();
Rect rectangle = new Rect(0, 0, width / 2, height / 2);
paint.setColor(Color.RED);
canvas.drawRect(rectangle, paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawCircle(width / 2, height / 2, (width + height) / 10, paint);
}
I have a button which is drawn using canvas onDraw method. The button itself may will change size depending on device, but I want the text I'm drawing inside to remain consistent, even with different screen size or font size user may have set in settings. That is why I'm doing it in pixels.
#Override
public void onDraw(Canvas canvas) {
// 1. draw background
paint.setStyle(Paint.Style.FILL);
paint.setColor(colorBg);
canvas.drawRect(padding, padding, w-padding, h-padding, paint);
// 2. draw border
paint.setStyle(Paint.Style.STROKE);
paint.setColor(colorBorder);
canvas.drawRect(padding, padding, w-padding, h-padding, paint);
// 3. draw text
int fontSizeTextNew = canvas.getHeight() / 100 * 19;
int fontSizePaddingNew = canvas.getHeight() / 100 * 19;
textPaint.setTextSize(fontSizeTextNew);
textPaint.setShader(blackShader);
textPaint.setTypeface(Typeface.DEFAULT);
textPaint.getTextBounds(
prefTitle, // text
0, // start
prefTitle.length(), // end
rectangle // bounds
);
canvas.drawText(
prefTitle,
canvas.getWidth()/2,
Math.abs(rectangle.height()) + padding + fontSizePaddingNew,
textPaint
);
// 4. draw icon
int fontSizeIconNew = canvas.getHeight() / 100 * 55;
String str = prefIcon;
textPaint.setTypeface(awesomeFont);
textPaint.setTextSize(fontSizeIconNew);
textPaint.setShader(yellowShader);
// center for the text
// https://stackoverflow.com/questions/11120392/android-center-text-on-canvas
int yPos = (int) ((canvas.getHeight() / 2) - ((textPaint.descent() + textPaint.ascent()) / 2)) ;
canvas.drawText(
str,
canvas.getWidth()/2,
Math.abs(yPos) + fontSizeIconPadding,
textPaint
);
}
This 19% looks nice right now but it will look different on variouse screens. What is your approach to this problem? how you would solvethis problem?
Button1 This is how it may look
I wish to set a custom shape (different radius for each corner of rectangle) to my frame layout, so that views in the frame layout will clip to the shape's bounds.
ViewOutlineProvider provider = new ViewOutlineProvider() {
#Override
public void getOutline(View view, Outline outline) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
configurePath(getWidth(), getHeight());
outline.setConvexPath(borderPath);
}
}
};
setOutlineProvider(provider);
setClipToOutline(true);
And the configurePath() looks like this:
private void configurePath (int width, int height) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
return;
}
borderPath.rewind();
float minSize = Math.min(width, height);
float maxRadiusWidth = 2 * Math.max(Math.max(topLeftRadius, topRightRadius),
Math.max(bottomLeftRadius, bottomRightRadius));
if (minSize < maxRadiusWidth) {
borderPath.addRect(0, 0, width, height, Path.Direction.CCW);
return;
}
// Top left circle
oval.set(0, 0, 2 * topLeftRadius, 2 * topLeftRadius);
borderPath.moveTo(0, topLeftRadius);
borderPath.arcTo(oval, 180, -90);
borderPath.rLineTo(width - topLeftRadius - topRightRadius, 0);
// Top right circle
oval.set(width - 2 * topRightRadius, 0, width, 2 * topRightRadius);
borderPath.arcTo(oval, 90, -90);
borderPath.rLineTo(0, height - topRightRadius - bottomRightRadius);
// Bottom right circle
oval.set(width - 2 * bottomRightRadius, height - 2 * bottomRightRadius, width, height);
borderPath.arcTo(oval, 0, -90);
borderPath.rLineTo(-width + bottomRightRadius + bottomLeftRadius, 0);
// Bottom left circle
oval.set(0, height - 2 * bottomLeftRadius, 2 * bottomLeftRadius, height);
borderPath.arcTo(oval, -90, -90);
borderPath.rLineTo(0, -height + bottomLeftRadius + topLeftRadius);
}
When I run it, I got java.lang.IllegalArgumentException: path must be convex and I could not get into native_isConvex() and see how it decides if a path is convex.
So what is a convex path? Why the path in congfigurePath() is not convex?
How can I create a custom convex path? Thank you.
I figured it out by myself. The path is not convex because I was not drawing the path correctly. The correct path that achieve the multiple corner radius effect I wanted should be:
// Top left circle
oval.set(0, 0, 2 * topLeftRadius, 2 * topLeftRadius);
borderPath.moveTo(0, topLeftRadius);
borderPath.arcTo(oval, -180, 90);
borderPath.rLineTo(width - topLeftRadius - topRightRadius, 0);
// Top right circle
oval.set(width - 2 * topRightRadius, 0, width, 2 * topRightRadius);
borderPath.arcTo(oval, -90, 90);
borderPath.rLineTo(0, height - topRightRadius - bottomRightRadius);
// Bottom right circle
oval.set(width - 2 * bottomRightRadius, height - 2 * bottomRightRadius, width, height);
borderPath.arcTo(oval, 0, 90);
borderPath.rLineTo(-width + bottomRightRadius + bottomLeftRadius, 0);
// Bottom left circle
oval.set(0, height - 2 * bottomLeftRadius, 2 * bottomLeftRadius, height);
borderPath.arcTo(oval, 90, 90);
borderPath.rLineTo(0, -height + bottomLeftRadius + topLeftRadius);
Update: Although the path is correct now, it is still not convex path, seems like customized path will not be treated as convex path.
Nice that you got it working, but there are built in tools for this.
Easiest is in a drawable xml:
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners
android:bottomLeftRadius="8dp"
android:topLeftRadius="8dp"
android:topRightRadius="8dp"
android:bottomRightRadius="0dp" />
<solid android:color="#fff" />
</shape>
Programatically you can use the RoundRectShape class. That's also what will be inflated from above xml.
In the curstructor you pass outer corner radius, line thickness and inner corner radius. You can also look at the source to see how they create a path (tip: path.addRoundRect()).
I have view in which, I want to display a heart that is filled based on percentage shown in middle of heart.
I tried canvas, was able to draw heart but could not partial fill, used cliprect,etc but could not achieve this, please see Image
Now if its 30 %, then only 30% area from bottom should be filled with redcolor rest should be white, also the color outside of heart is blue which needs to be same way always as shown in image.
int percentage = 85;
int w = 100;
int h = 100;
Bitmap bitmap = Bitmap.createBitmap(w,h, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
Paint paint = new Paint();
Path path = new Path();
// Fill the canvas with background color
canvas.drawColor(Color.BLUE);
paint.setShader(null);
float width = 100.00f;
float height =100.00f;
// Starting point
path.moveTo(width / 2, height / 5);
// Upper left path
path.cubicTo(5 * width / 14, 0,
0, height / 15,
width / 28, 2 * height / 5);
// Lower left path
path.cubicTo(width / 14, 2 * height / 3,
3 * width / 7, 5 * height / 6,
width / 2, height);
// Lower right path
path.cubicTo(4 * width / 7, 5 * height / 6,
13 * width / 14, 2 * height / 3,
27 * width / 28, 2 * height / 5);
// Upper right path
path.cubicTo(width, height / 15,
9 * width / 14, 0,
width / 2, height / 5);
paint.setColor(Color.RED);
paint.setStyle(Paint.Style.FILL);
// draw text percentage
Paint textPaint = new Paint();
textPaint.setColor(Color.BLACK);
textPaint.setTextAlign(Paint.Align.CENTER);
textPaint.setTextSize(12);
int xPos = (canvas.getWidth() / 2);
int yPos = (int) ((canvas.getHeight() / 2) - ((textPaint.descent() + textPaint.ascent()) / 2)) ;
//((textPaint.descent() + textPaint.ascent()) / 2) is the distance from the baseline to the center.
canvas.drawText(percentage+" % ", xPos, yPos, textPaint);
int heightOfClip = 100 - percentage;
//clip
canvas.clipRect(0,0,100,heightOfClip, Region.Op.XOR);
// draw heart and color it
canvas.drawPath(path, paint);
// draw text percentage
canvas.drawText(percentage+"%", xPos, yPos, textPaint);
mSampleIv.setImageBitmap(bitmap);
I want to draw texts on the sides of a circle and another one below it. I edited the code in this answer but the problem is that the circle and arc are taking the whole space of the rect, which makes no space for the text to be drawn.
Here is my code
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// getHeight() is not reliable, use getMeasuredHeight() on first run:
// Note: mRect will also be null after a configuration change,
// so in this case the new measured height and width values will be used:
if (mRect == null) {
// take the minimum of width and height here to be on he safe side:
centerX = getMeasuredWidth() / 2;
centerY = getMeasuredHeight() / 2;
radius = Math.min(centerX, centerY);
// mRect will define the drawing space for drawArc()
// We have to take into account the STROKE_WIDTH with drawArc() as well as drawCircle():
// circles as well as arcs are drawn 50% outside of the bounds defined by the radius (radius for arcs is calculated from the rectangle mRect).
// So if mRect is too large, the lines will not fit into the View
int startTop = STROKE_WIDTH / 2;
int startLeft = startTop;
int endBottom = 2 * radius - startTop;
int endRight = endBottom;
mRect = new RectF(startTop, startLeft, endRight, endBottom);
}
// subtract half the stroke width from radius so the blue circle fits inside the View
canvas.drawCircle(centerX, centerY, radius - STROKE_WIDTH / 2, mBasePaint);
// Or draw arc from degree 192 to degree 90 like this ( 258 = (360 - 192) + 90:
// canvas.drawArc(mRect, 192, 258, false, mBasePaint);
// draw an arc from 90 degrees to 192 degrees (102 = 192 - 90)
// Note that these degrees are not like mathematical degrees:
// they are mirrored along the y-axis and so incremented clockwise (zero degrees is always on the right hand side of the x-axis)
canvas.drawArc(mRect, 270, mTemp * 6, false, mDegreesPaint); // Each degree in the temp scale = 6 degrees on circle
canvas.drawArc(mRect, 270 + mSeparator * 6, 3, false, mSeparatorPaint); // The separator size = 3 degrees
// subtract stroke width from radius so the white circle does not cover the blue circle/ arc
canvas.drawCircle(centerX, centerY, radius - STROKE_WIDTH, mCenterPaint);
drawCenter(canvas, mTextPaint, mTemp + "°");
canvas.drawText("Temp 1", mRect.centerX(), radius * 2, mTextPaint);
}
This code produces what I need as an arc and circles, but I'm not able to draw degrees on the circle sides and a text below it
Any help??