Stéphane Stéphane - 1 month ago 50
Android Question

Oval shape clipped when created programmatically

I've got a clipping problem.

First, I tried to display an oval shape with Xml only. I had the following code:

res/drawable/circle.xml

<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval" >
<size
android:width="240dp"
android:height="240dp" />
<solid
android:color="#FFFFFF" />
<stroke
android:width="2dp"
android:color="#EEEEEE" />
</shape>


*res/layout/test.xml

....
<RelativeLayout
android:id="@+id/circle_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1" >

<ImageView
android:src="@drawable/circle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true" />

</RelativeLayout>
....


It works perfectly, and gave me this: Screenshot

The problem is, for various reason, I must do the same progammatically.

I've got this code:

RelativeLayout layout = (RelativeLayout) findViewById(R.id.circle_layout);

ShapeDrawable drawable = new ShapeDrawable(new OvalShape());
drawable.getPaint().setColor(Color.parseColor("#EEEEEE"));
drawable.getPaint().setStyle(Style.STROKE);
drawable.getPaint().setStrokeWidth(dpToPx(2));
drawable.getPaint().setAntiAlias(true);
drawable.setIntrinsicHeight(dpToPx(240));
drawable.setIntrinsicWidth(dpToPx(240));

ImageView iv = new ImageView(this);
iv.setImageDrawable(drawable);

RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.WRAP_CONTENT,
RelativeLayout.LayoutParams.WRAP_CONTENT);
lp.addRule(RelativeLayout.CENTER_IN_PARENT);
iv.setLayoutParams(lp);

layout.addView(iv);


dpToPx function is:

private float scale = 0;

private float getScale() {
if (scale == 0)
scale = this.getResources().getDisplayMetrics().densityDpi / 160f;
return scale;
}

public float dpToPx(float dp) {
return dp * getScale();
}


... this should gave me the same thing right? Well it gave a slightly larger circle, with top, right, bottom and left edges clipped. Here's a screenshot (same region of the screen than the previous one): Screehshot

Someone has an idea on what and why?

Thank you.

Edit:

if I set the stoke width to a higher value (12dp), I've got this: Screenshot

Answer

I found the solution on this page: http://www.betaful.com/2012/01/programmatic-shapes-in-android/

In Android, when you draw with a stroke, it draws the center of the stroke at the boundaries of the shape you are drawing. As you can see, the stroke is getting cropped by the boundaries of the image. Luckily, you can perform transformations on a canvas. What we want to do is transform our stroke shape to be slightly smaller than the boundary – and there’s a Matrix operation for that!

matrix.setRectToRect(new RectF(0, 0, canvas.getClipBounds().right, canvas.getClipBounds().bottom),
new RectF(strokeWidth/2, strokeWidth/2, canvas.getClipBounds().right - strokeWidth/2,
    canvas.getClipBounds().bottom - strokeWidth/2),
Matrix.ScaleToFit.FILL);

Edit

... Better and easier solution find in the comment section of the link:

As you can see in the Android docs, a shape Drawable in resources is actually mapped to a GradientDrawable, not a ShapeDrawable:

And so, I've got the following code working perfectly:

GradientDrawable drawable = new GradientDrawable();
drawable.setColor(Color.TRANSPARENT);
drawable.setShape(GradientDrawable.OVAL);
drawable.setStroke((int)cET.dpToPx(2), Color.parseColor("#EEEEEE"));
drawable.setSize((int)cET.dpToPx(240), (int)cET.dpToPx(240));