I've read over 20 questions/answers but I still can't get what I want. I want to cut a circle inside a rectangle as seen below:
Here is my code:
#Override
protected void onDraw(Canvas canvas) {
Paint paint = new Paint();
paint.setStyle(Paint.Style.FILL);
paint.setARGB(180, 0, 0, 0);
canvas.drawRect(0, 0, getWidth(), getHeight(), paint);
Path circularPath = new Path();
circularPath.addCircle(getWidth() / 2, getHeight() / 2, radius, Path.Direction.CCW);
canvas.clipPath(circularPath, Region.Op.REPLACE);
canvas.drawColor(0x00000000);
}
My background (setARGB) displays correctly, however nothing is clipped. I've also tried different Op values other than REPLACE, forced software rasterization (as I've read on some Android versions clipPath doesn't support some of the Ops) by calling setLayerType(LAYER_TYPE_SOFTWARE, null); on constructor, but no avail. How do I achieve the desired effect?
Note: My minimum SDK version is 15, so I don't need to support anything lower than 4.0.
Use clipPath before drawRect.
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int width = this.getWidth();
int height = this.getHeight();
mPaint.setAntiAlias(true);
mPaint.setColor(Color.WHITE);
mPaint.setStyle(Paint.Style.FILL);
canvas.drawPaint(mPaint);
float rectWidth = Utils.dpToPx(100.0f);
Path circularPath = new Path();
circularPath.addCircle(width / 2.0f, rectWidth / 2.0f, rectWidth / 3.0f, Path.Direction.CCW);
canvas.clipPath(circularPath, Region.Op.DIFFERENCE);
mPaint.setColor(Color.BLUE);
canvas.drawRect((width - rectWidth) / 2.0f, 0.0f, ((width - rectWidth) / 2.0f) + rectWidth, rectWidth, mPaint);
}
Try clipping your path in dispatchDraw():
#Override
protected void dispatchDraw(Canvas canvas)
{
canvas.clipPath(mClipPath, mRegion); // previously created path & region
super.dispatchDraw(canvas);
}
Remove the path clipping code from your onDraw method, and that should do it.
Edit:
When creating your path, make sure you do so only after a measure occurred, for example:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mClipPath.reset();
float radius = Math.min((float)getMeasuredWidth() / 2f, (float)getMeasuredHeight() / 2f) + 5;
mClipPath.addCircle((float)getMeasuredWidth() / 2f, (float)getMeasuredHeight() / 2f, radius, Path.Direction.CCW);
}
Related
When I draw path in android, the line seems too thin. So I made a experiment:
draw a round rect using drawPath and using drawRoundRect.
here is my code:
//setPaint
mStrokeWidth = 1;
mPaint.setStrokeWidth(mStrokeWidth);
mPaint.setColor(Color.RED);
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.STROKE);
//draw using drawRoundRect
mStrokeWidth = 1;
#Override
public void draw(Canvas canvas) {
Rect bounds = getBounds();
mRect.set(bounds.left + mStrokeWidth / 2,
bounds.top + mStrokeWidth / 2,
bounds.right - mStrokeWidth / 2,
bounds.bottom - mStrokeWidth / 2
);
canvas.drawRoundRect(mRect, 10, 10, mPaint);
}
//draw using drawPath
mStrokeWidth = 1;
#Override
public void draw(Canvas canvas) {
Rect bounds = getBounds();
mRect.set(bounds.left + mStrokeWidth / 2,
bounds.top + mStrokeWidth / 2,
bounds.right - mStrokeWidth / 2,
bounds.bottom - mStrokeWidth / 2
);
Path path = new Path();
path.addRoundRect(mRect, 10, 10, Path.Direction.CCW);
canvas.drawPath(path, mPaint);
}
here is the result:
the left is using drawRoundRect and the right using drawPath
you can see that the left is thicker than the right. I want to know what caused the phenomenon?
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();
}
}
public static Bitmap drawCircle(int width,int height, int borderWidth) {
Bitmap canvasBitmap = Bitmap.createBitmap( width, height, Bitmap.Config.ARGB_8888);
BitmapShader shader = new BitmapShader(canvasBitmap, TileMode.CLAMP,
TileMode.CLAMP);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setShader(shader);
paint.setShader(null);
paint.setStyle(Paint.Style.STROKE);
paint.setColor(Color.WHITE);
paint.setStrokeWidth(borderWidth);
Canvas canvas = new Canvas(canvasBitmap);
float radius = width > height ? ((float) height) / 2f : ((float) width) / 2f;
canvas.drawCircle(width / 2, height / 2, radius - borderWidth / 2, paint);
return canvasBitmap;
}
Simple this code draws a circle with white border, however I want part of the border to be black and the other part white. 40 % of it black, 60 % of it white
How can this be done?
Try this code
class MyView extends View
{
private Paint paint;
public MyView(Context context, int x, int y)
{
super(context);
paint = new Paint();
// PorterDuffXfermode xfermode = new PorterDuffXfermode(PorterDuff.Mode.CLEAR);
paint.setStyle(Paint.Style.STROKE);
paint.setColor(Color.RED);
paint.setAlpha(255);
// paint.setXfermode(xfermode);
paint.setAntiAlias(true);
// setBackgroundColor(Color.BLACK);
}
#Override
protected void onDraw(Canvas canvas)
{
canvas.drawCircle(100, 100, 50, paint);
}
}
Here a utility method for filling a circle with one color and stroking the circle border with another color.
Use the second method to pass in an existing Paint instance, e.g. to set the anti-alias flag or to prevent memory allocations during onDraw().
public static void fillCircleStrokeBorder(
Canvas c, float cx, float cy, float radius,
int circleColor, float borderWidth, int borderColor) {
fillCircleStrokeBorder(c, cx, cy, radius, circleColor, borderWidth, borderColor, new Paint());
}
public static void fillCircleStrokeBorder(
Canvas c, float cx, float cy, float radius,
int circleColor, float borderWidth, int borderColor, Paint p) {
int saveColor = p.getColor();
p.setColor(circleColor);
Paint.Style saveStyle = p.getStyle();
p.setStyle(Paint.Style.FILL);
c.drawCircle(cx, cy, radius, p);
if (borderWidth > 0) {
p.setColor(borderColor);
p.setStyle(Paint.Style.STROKE);
float saveStrokeWidth = p.getStrokeWidth();
p.setStrokeWidth(borderWidth);
c.drawCircle(cx, cy, radius - (borderWidth / 2), p);
p.setStrokeWidth(saveStrokeWidth);
}
p.setColor(saveColor);
p.setStyle(saveStyle);
}
Just draw your circle at full size with one color, then draw the circles again at the same coordinate, but with a different color and with a smaller radius, and scale down the radius however much you need to, that would be the most trivial way to do it.
In hardware accelerated custom View added in ScrollView or ListView both of the following code snippets produces same result: (please ignore best practises for a sec)
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// centering stuff
float centerX = getWidth() / 2f;
float centerY = getHeight() / 2f;
float size = 80;
float halfSize = size / 2f;
float left = centerX - halfSize;
float top = centerY - halfSize;
RectF oval = new RectF(left, top, left + size, top + size);
Path path = new Path();
path.addArc(oval, 160, 359);
Paint paint = new Paint();
paint.setTextSize(30);
paint.setStyle(Style.STROKE);
canvas.drawTextOnPath("Hello world", path, 0, 0, paint); //<--- line A
canvas.drawCircle(centerX, centerY, 10, paint); //<--- line B
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// centering stuff
float centerX = getWidth() / 2f;
float centerY = getHeight() / 2f;
float size = 80;
float halfSize = size / 2f;
float left = centerX - halfSize;
float top = centerY - halfSize;
RectF oval = new RectF(left, top, left + size, top + size);
Path path = new Path();
path.addArc(oval, 160, 359);
Paint paint = new Paint();
paint.setTextSize(30);
paint.setStyle(Style.STROKE);
canvas.drawCircle(centerX, centerY, 10, paint); //<--- line B
canvas.drawTextOnPath("Hello world", path, 0, 0, paint); //<--- line A
}
Same Result:
But with later code snippet, as soon as you scroll the ScrollView (I have invisible dummy View below so I can scroll) and helloworld touches ActionBar, something very intersting happens and you see something that intelligent humankind used to see in old Windows OS .
I know drawTextOnPath() is not supported in hardware accelration mode, but then why it works if you call it first?
drawTextOnPath() is supported by hardware acceleration after Android 4.1
This is mentioned officially here: https://code.google.com/p/android/issues/detail?id=37925
but the next comment seems to indicate your problem in a way so maybe a bug.
Of course for pre 4.1 just dont make it use HW accel - Set a software layer type on your View by calling View.setLayerType(View.LAYER_TYPE_SOFTWARE, null) and try to get a tradeoff on perf vs errors
I have a line that should get thinner the longer it gets. The problem is, that you can clearly see a jump when it gets a pixel thinner. Is there a way to do subpixel rendering/antialiasing on Android?
canvas.drawRect() takes float values, but it's ignoring those. Here's the code:
#Override
protected void onDraw(Canvas canvas) {
float width = getMeasuredWidth() / (float) getMeasuredHeight() * getMinimumHeight();
float left = (getMeasuredWidth() - width) / 2.0f;
canvas.drawRect(left, 0, left + width, getMeasuredHeight(), paint);
super.onDraw(canvas);
}
The paint object has ANTI_ALIAS_FLAG enabled and contains a solid color.
This is the default line:
This is when it gets longer and thinner. It should have some anti aliasing on the sides, though to make the whole transition seems smoother.
This seems to do a better job:
#Override
protected void onDraw(Canvas canvas) {
float width = getMeasuredWidth() / (float) getMeasuredHeight() * getMinimumHeight();
float left = (getMeasuredWidth() - width) / 2.0f;
paint.setStrokeWidth(width * getResources().getDisplayMetrics().density);
canvas.drawLine(left, 0, left, getMeasuredHeight(), paint);
super.onDraw(canvas);
}