12

I'm trying to implement a LinearLayout subclass that draws itself with rounded corners. From my research, I set setWillNotDraw(false) and overridden onDraw() to draw a rounded rectangle in the canvas:

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    int sc = canvas.saveLayer(0, 0, getWidth(), getHeight(), drawPaint, Canvas.MATRIX_SAVE_FLAG | Canvas.CLIP_SAVE_FLAG | Canvas.HAS_ALPHA_LAYER_SAVE_FLAG
            | Canvas.FULL_COLOR_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG);
    canvas.drawRoundRect(bounds, mCornerRadius, mCornerRadius, roundPaint);
    canvas.restoreToCount(sc);
}

where:

drawPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
drawPaint.setColor(0xffffffff);
drawPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));

roundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
roundPaint.setColor(0xffffffff);

DST_IN seems the correct option here (according to the APIDemos example), but the area that should be transparent (the rounded one) has instead a black background, and the corners of the children are still visible. This is the result on a Galaxy Nexus with Android 4.2.2:

example

Any hints?

EDIT: Here is what I'd like to achieve, sorry for the crudeness of photoshopping :)

enter image description here

EDIT 2: I added to GitHub an example runnable project: https://github.com/venator85/RoundClippingLayout

Thanks ;)

Venator85
  • 10,245
  • 7
  • 42
  • 57
  • http://stackoverflow.com/a/14534367/646806 – RobinHood Apr 15 '13 at 06:05
  • Already tried, not working, since I'm not working with an ImageView but rather with a layout. I'm not even whether onDraw() rather than dispatchDraw(), or even both, is the correct method to override. – Venator85 Apr 15 '13 at 09:46
  • That algorithm will work. You just have to take the Bitmap returned and create a BitmapDrawable with it. Then set it to the background of your view. – DeeV Apr 16 '13 at 20:54
  • Can you post or link to more code. It would be much easier if you had ready to debug code. – numan salati Apr 17 '13 at 23:46
  • Example runnable project added in the question. Thanks – Venator85 Apr 18 '13 at 07:46
  • You are applying the blend modes directly against the framebuffer. You are seeing black because the window is opaque. One way to do it would be to do your drawing into an intermediate transparent bitmap. – Romain Guy Apr 18 '13 at 19:24
  • Hi Romain, I tried overriding dispatchDraw() only, drawing on a temp bitmap and then drawing that bitmap on the original canvas using the shader technique on your blog. Code available at http://pastebin.com/Jc31s2b5. I correctly see the children clipped, but they are no longer feel interactive, i.e. pressed state for buttons is not shown. What went wrong? Thanks for your suggestion ;) – Venator85 Apr 18 '13 at 22:02
  • @Venator85 did you solve this ? If so, please tell me how. If not, here's an alternative that I've made: http://stackoverflow.com/a/23650467/878126 – android developer May 14 '14 at 18:47

7 Answers7

7

Not quite the same : Romain Guy did a blog post about cropping corners on images using bitmap shaders.. Not sure if you would want to extend the same thing.

http://www.curious-creature.org/2012/12/11/android-recipe-1-image-with-rounded-corners/

Chrispix
  • 17,941
  • 20
  • 62
  • 70
  • I tried with no success. Also, the first comment shows a solution based on clipPath which requires disabling hardware acceleration on the layout, which I want to avoid. – Venator85 Apr 18 '13 at 07:48
  • Any particular reason why you want to avoid disabling hardware acceleration? I think clipping child views will be close to impossible without clipPath. – NPike Apr 19 '13 at 21:48
5

Try this,

Layout:-

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <LinearLayout 
        android:id="@+id/linearLayout"
        android:layout_width="300dp"
        android:gravity="center"
        android:layout_height="300dp"
        android:layout_centerInParent="true"
        android:background="@drawable/rounded_edge">
        <TextView
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:text="foo" />
    </LinearLayout>
</RelativeLayout>

Shape(Drawable):-rounded_edge.xml

<shape 
        xmlns:android="http://schemas.android.com/apk/res/android">
            <solid 
                android:color="@android:color/darker_gray">
            </solid>
            <stroke 
                 android:width="0dp" 
                 android:color="#424242">
            </stroke>
            <corners 
                 android:topLeftRadius="100dip"
                 android:topRightRadius="100dip"
                 android:bottomLeftRadius="100dip"
                 android:bottomRightRadius="100dip">
            </corners>
        </shape>
Sino Raj
  • 6,431
  • 2
  • 22
  • 23
3

I could achieve a LinearLayout with rounded corners like this.

enter image description here

Steps:

1. Create a custom layout

public class RoundedLayout extends LinearLayout {
    private RectF rect;
    private Paint paint;

    public RoundedLayout(Context context) {
        super(context);
        init();
    }
    public RoundedLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        rect = new RectF(0.0f, 0.0f, getWidth(), getHeight());
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setColor(Color.parseColor("#7EB5D6"));
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);           
        canvas.drawRoundRect(rect, 20, 20, paint);
    }
}

2. Use it in the main layout like this

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical"
    android:background="#336699" >

    <com.example.rounded.RoundedLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:padding="30dp"
        android:background="@android:color/transparent" >

        <TextView
            android:id="@+id/textView1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="foo" />

    </com.example.rounded.RoundedLayout>    

</LinearLayout>
appsroxcom
  • 2,821
  • 13
  • 17
  • Have you tried android:clipChildren="true" on the custom layout? – appsroxcom Apr 18 '13 at 07:33
  • As fas as I know, clipChildren="false" allows children views to draw themselves outside the bounds of the container layout. I want to do just the opposite, that is, clipping parts of the children _inside_ the layout, based on a given shape (rounded rect in this case). – Venator85 Apr 18 '13 at 07:44
  • A part of the problem is due to the way Android draws views. Since a child (e.g. Button) is drawn after the parent (e.g. LinearLayout) and you don't have access to the Paint brush used by the child to draw itself so i guess Xfermode can't help much. – appsroxcom Apr 18 '13 at 15:09
  • @Venator85, as a workaround you may use a RelativeLayout to wrap the LinearLayout and position shape (arc) drawables at the four corners of the RelativeLayout. – appsroxcom Apr 20 '13 at 03:01
  • 1
    Where is the round corner? I tested it, but it doesn't have any. – The_Martian Jan 15 '16 at 22:03
3

Try this !! taken from this post

Add the following into a file (say customshape.xml) and then place it in (res/drawable/customshape.xml)

<?xml version="1.0" encoding="UTF-8"?> 
<shape xmlns:android="http://schemas.android.com/apk/res/android" 
 android:shape="rectangle"> 
 <gradient 
     android:startColor="#SomeGradientBeginColor"
     android:endColor="#SomeGradientEndColor" 
     android:angle="270"/> 

<corners 
     android:bottomRightRadius="7dp" 
     android:bottomLeftRadius="7dp" 
     android:topLeftRadius="7dp" 
     android:topRightRadius="7dp"/> 
</shape> 

Once you are done with creating this file, just set the background in one of the following ways:

Through Code:

yourObject.setBackgroundResource(R.drawable.customshape);

Or through XML, just add the following attribute to the container (ex: LinearLayout or to any fields):

android:background="@drawable/customshape"
Community
  • 1
  • 1
Mehdi
  • 974
  • 1
  • 10
  • 24
  • This is awesome solution, 1+ for sharing this. Had to make changes to reflect my needs like the radius was kind of not significant. :) – The_Martian Jan 15 '16 at 23:02
2

How about...

myLayout.setBackgroundResource(R.drawable.my_rounded_drawable);

Then...

my_rounded_drawable.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <shape>
            <solid android:color="#FFFFFFFF" />
            <stroke android:width="1dip" android:color="#FF000000" />
            <corners android:radius="10dp" />
        </shape>
    </item>
</selector>
logray
  • 2,332
  • 1
  • 29
  • 30
  • I've updated the question with a mockup of what I'd like to achieve: clipping children to a rounded rectangle, so your answer doesn't apply. – Venator85 Apr 16 '13 at 20:26
  • @venator85 what you need is Shapes http://development-in-android.blogspot.com.es/2012/05/round-corner-linearlayout-in-android.html?m=1 – yeradis Apr 17 '13 at 21:19
  • @logray thats why you have my vote and a comment in your answer with a link for the author of this question xD – yeradis Apr 22 '13 at 15:14
1

[EDIT: looks like this will be added in L: https://developer.android.com/preview/material/views-shadows.html#clip, which allows you to clip a view to the shape of a rectanglular, circular, or round rectangular drawable.]

I just tried for a really long time to do this myself, and came upon this answer that suggests it is not possible, since the View class is based on Rect class. I just checked the source and from the comments it looks like that's still the case.

Motorola is releasing the Moto 360 (Android Wear watch with round face) later this summer, so maybe there will be updates to the framework that allow for Views with shapes other than rectangles.

Community
  • 1
  • 1
Patricia Li
  • 1,346
  • 10
  • 19
0

Instead of trying to cut the corners off your layout, why not place a Drawable on top of it as a sort of frame that matches the color of your background?

Jschools
  • 2,698
  • 1
  • 17
  • 18
  • Not a general solution and not applicable in my case: the rounding layout is in a ScrollView over a non-uniform background. – Venator85 Apr 18 '13 at 07:07