I would like to try and start a new series of articles focused on giving you small recipes you can use in your applications to achieve very specific visual effects. For this first installment, I will show you how to draw a bitmap with rounded corners. Many people have asked me how to achieve this effect and I often see developers use a much more complicated solution than is necessary.
I wrote a simple application to illustrate this effect. You can download an APK for your Android device and download the source code. This is what the application looks like:
To generate the rounded images I simply wrote a custom Drawable
that draws a rounded rectangle using Canvas.drawRoundRect()
. The trick is to use a Paint
with a BitmapShader
to fill the rounded rectangle with a texture instead of a simple color. Here is what the code looks like:
BitmapShader shader; shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); Paint paint = new Paint(); paint.setAntiAlias(true); paint.setShader(shader); RectF rect = new RectF(0.0f, 0.0f, width, height); // rect contains the bounds of the shape // radius is the radius in pixels of the rounded corners // paint contains the shader that will texture the shape canvas.drawRoundRect(rect, radius, radius, paint);
The sample application goes a little further and fakes a vignette effect by combining the BitmapShader
with a RadialGradient
.
You can do the same with a layout but you need to disable the hardware acceleration for this view (a bug ?) :
public class LinearLayoutCorner extends LinearLayout {
/** Used locally to tag Logs */
@SuppressWarnings(“unused”)
private static final String TAG = LinearLayoutCorner.class.getSimpleName();
private final Path mPath = new Path();
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public LinearLayoutCorner(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
public LinearLayoutCorner(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public LinearLayoutCorner(Context context) {
super(context);
init();
}
@SuppressLint(“NewApi”)
private void init() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
// we have to remove the hardware acceleration if we want the clip
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
setBackgroundResource(R.drawable.bg_classement);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mPath.reset();
float round = getResources().getDimension(R.dimen.home_page_corner_angle);
mPath.addRoundRect(new RectF(getPaddingLeft(), getPaddingTop(), w – getPaddingRight(), h – getPaddingBottom()), round, round, Direction.CW);
}
@Override
protected void dispatchDraw(Canvas canvas) {
canvas.clipPath(mPath);
super.dispatchDraw(canvas);
}
}
This solution is more expensive because it relies on path clipping; drawing a rounded rectangles directly is going to be more efficient. It also doesn’t provide nice results because path clipping does not support antialiasing and you get jagged edges. Note that the hardware accelerated renderer does not support path clipping at the moment but when it does, it will also be more expensive than just drawing the rounded rect.
But it’s the only solution to make a round corner on listview for clipping the textview too, right ?
If I want something like that for example : http://i.stack.imgur.com/jq4ez.jpg (The first “I” of “Item 1” must be clipped when I’m scrolling)
A much cheaper way is to draw a 9patch on top of the ListView.
Something like that : http://i.stack.imgur.com/TyMNG.png ? Unfortunately the result is not the same …
No, a 9patch you draw *over* the ListView. That 9patch contains the outer area of the rounded rect, not the rounded rect itself.
Can’t set a StreamDrawable as a view background before 4.1, any workaround beside path clipping ?
Hi, nice tip.
But the .apk you provide us won’t install on a real device (package error)
Hello, i have an question. Why don’t use xml to define corners and use them on background “drawable” ?
In list adapter, use an imageview with background’s corner … ?
Simon – It’s working just fine on my device.
Nice photos, as always, Romain.
How do I display the images in the correct proportions? I see in your code that you’re using LayoutParams, but it doesn’t seem to work.
Great stuff, would it be possible with a 9patch for the mask instead?
Thanks for the post. This will be useful.
I noticed though that scrolling on the apk you provided isn’t very smooth. Any ideas on why that is and how to improve on it?
[Tried to post this on Google+ but couldn’t and for some reason you’re not on my circles anymore and can’t re-add]
@Simon: my APK is built for API level 17. Build from source if you don’t have such a device.
@Fritte7: No, it won’t work. The image won’t be clipped by a rounded rect, there will just be a rounded rect behind it.
@Marco: The code sample I provide calculates the aspect ratio.
@Daniel: The sample uses large images but that shouldn’t matter for the GPU. Are you running the sample in software?
Doesnt seems to be stretching on Nexus 10. Need to find some time to sit down and study the code. looks nice though. https://plus.google.com/app/basic/stream/z13tdr1rusn0jtu5g04cdrxpguypyjyays00k?cbp=17x3d12x4q65w&spath=/app/basic/stream&sparm=force%3D1%26partnerid%3Dt1%26source%3Dapppromo&force=1&partnerid=t1
As far as I’m aware, it should be running with HW acceleration.
I’m running stock Android and have not selected any of the developer options save for activating the adb.
To be clear, the scrolling isn’t exactly jerky, but it’s clearly struggling and not as smooth as, say, scrolling the contact list in the People app.
Could we use PorterDuffXfermode to create a Path of rounded-rectangle and use a DST_IN to paint the Path? What would be the performance difference between this approach and using a BitmapShader to draw a rounded-rectangle?
You can but it takes two drawing passes instead of one. It also doesn’t work on opaque backgrounds.
Nice. Can I request a drop shadow as recipe 2?
Re: Tim- I’d like to see that too.
@Tim: I’ll keep that in mind but these articles take a lot of the little spare time I have so I’m more likely to write about stuff I want :) Note that I try to choose topics that I know matter to a lot of developers.
Really appreciate this new series
view.setBackground method throws an error .. How do i make this work in a pre Jelly Bean device .. ?
I used setbackgrounddrawable and it worked .. Thanks for this great recipe .. ;)
Romain Guy , I’ve posted some issues I’ve found on your sample here:
http://stackoverflow.com/questions/14109187/android-using-a-rounded-corners-drawable#comment19523400_14109187
Please spare some time reading them …
Dear Romain Guy,
thanks a lot for this tutorial and for your source code.
I tried to use your Drawable for an ImageView, but this does only work if the ImageView has a fixed layout_width and layout_height. Using wrap_content results in showing nothing at all.
Do you know a way to fix this? I think the sizes of your drawable are clear (bitmap width and height minus margin).
Best regards,
Marco Schmitz
Well, I solved this problem :)
1) in the constructor store the bitmap in a local variable (e.g. private Bitmap bmp;)
2) override 2 more methods:
@Override
public int getIntrinsicWidth() {
return bmp.getWidth();
}
@Override
public int getIntrinsicHeight() {
return bmp.getHeight();
}
Best regards,
Marco
PS: I took a closer look at ImageView sources. There was something with intrisic width an height according to drawables…
I am struggling to center align the image, im using:
Matrix m = new Matrix();
mBitmapShader.getLocalMatrix(m);
m.setTranslate(-bitmap.getWidth()/2, -bitmap.getHeight()/2);
mBitmapShader.setLocalMatrix(m);
mPaint.setShader(mBitmapShader);
But it doesnt center the bit map.. any better ways of doing this?
How could this be adapted to work with a TransitionDrawable? I’m using a “fade-in” image cache. The final image is not always rounded. I need all of the bitmaps in the drawable to be rounded.
How’s the memory efficiency of an implementation? I know in the past I’ve strayed away from drawing a rounded bitmap because it would essentially keep 2 bitmaps in memory for every bitmap I was trying to display.
From quickly glancing over your implementation, I see a drawable and bitmap that would be in memory for each imageview. Seems inefficient, especially if you were applying this to a large gridview of bitmaps.
What are your thoughts on this?
Ben, this implementation does not use 2 bitmaps in memory. That’s the whole point. The drawable simply draws a rounded rectangle and uses only a few bytes of memory. The only memory usage in this solution is that of the bitmap which you need anyway.
Exactly! This solution didn’t use the two bitmaps. Solutions I’ve run across in the past did! That’s why I was curious on the amount of memory usage added by using this drawable.
I’m going to do some performance testing on applying this at runtime to a large amount of images in a Grid View.
Thanks again.
Cool, but this drawable will lose ability to scale if set to imageview as imageDrawable. Is there any way to keep the “ScaleType” in imageview? Thanks.
Jason, the ScaleType of an ImageView will work with any Drawable.
But after modification form your sample application (add ImageView in stream_item layout, set StreamDrawable to ImageView instead of RelativeLayout’s background). I can’t get any of the scale type work. Just wonder that is there more works to do to support ScaleType? Thanks.
Is that because we are not drawing the whole bitmap on canvas? We only draw “region” of bitmap to canvas through the paint object. Which means we have to scale the bitmap by ourself in the customized drawable. It goes much more complex than I thought
@Chris:
For centering:
mBitmapRect.set(0, 0, bitmap.getWidth(), bitmap.getHeight());
mShaderMatrix.setRectToRect(mBitmapRect, mRect, Matrix.ScaleToFit.FILL);
mBitmapShader.setLocalMatrix(mShaderMatrix);
What is the license on this code? I’d like to make some modifications and open source a library if you’re open to it.
Hi all,
I just extended this to a RoundedImageView (as opposed to drawable) that has full support for different ScaleTypes, in addition to rudimentary support for TransitionDrawables:
https://github.com/makeramen/RoundedImageView
@Vince nice nice nice… I just tryied to do the same. The point is i’m using AQuery + my RoundedImageView, so inside I can scale the bitmap, and after that create the corners… this is using so much memory so… I’m gonna take a look to your solution! Thx!
Thanks a lot.
This worked perfectly for me. Just that the image was not scaled within the imageview – may be because an image-matrix was set to ImageView with a dynamic scale. Scale was calculated using intrinsic-width and intrinsic-height. So, just used this – http://stackoverflow.com/questions/14109187/using-a-rounded-corners-drawable#comment19523400_14109187 – and it worked.
:)
Romain, thanks for the tutorial. I was trying what you mentioned regarding rounded corners for a list view. In a relative layout, added a imageView which was a square 9 patch and rounded corners with center part transparent. It worked well.
However, since the list is long shouldnt it go right uptil the bottom of the list, instead of just the visible section. Can we achieve that without path clipping, is there a way for me to draw the image which would include the translation and scroll amount of a list view?
If wrap_content is used in layout, the getIntrinsicWidth and getIntrinsicHeight must be override for StreamDrawable.
@Marco Schmitz