180

How do I create a ListView with rounded corners in Android?

Mycoola
  • 1,135
  • 1
  • 8
  • 29
Legend
  • 113,822
  • 119
  • 272
  • 400

11 Answers11

378

Here is one way of doing it (Thanks to Android Documentation though!):

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: listView.setBackgroundResource(R.drawable.customshape);

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

android:background="@drawable/customshape"

Hope someone finds it useful...

Danny
  • 1,050
  • 3
  • 11
  • 26
Legend
  • 113,822
  • 119
  • 272
  • 400
  • 2
    Thanks for the great tip. Just FYI, copy-pasting gave me a runtime exception saying, "XmlPullParserException: Binary XML file line #4 tag requires 'angle' attribute to be a multiple of 45".. Easily remedied by changing the angle to 270. – allclaws Jan 25 '10 at 16:25
  • Thanks for the fix... But I don't know why that could be happening.. Did you find any specific reason? – Legend Jan 26 '10 at 17:21
  • Thanks for the tip! Just one problem though - I have created a roundedrectangle.xml but eclipse code completion was not available in that file. It works just fine on the main.xml layout file. I kinda rely on code completion to learn and experiment. Any ideas why it's not working in my new file? Thanks, D. – codedog Jun 06 '10 at 10:21
  • android:angle="250" is causing application to crash, changed to 45 and worked fine. – Youssef Sep 01 '10 at 18:02
  • @youssef: I am not sure why it is happening. It worked fine for me in 5 different instances. Do you have the log from adb by any chance? – Legend Sep 01 '10 at 18:23
  • 1
    same as allclaws, angle should be multiple of 45 : "XmlPullParserException: Binary XML file line #4 tag requires 'angle' attribute to be a multiple of 45" – Youssef Sep 02 '10 at 09:27
  • 29
    It doesn't work well with selection-highlighting though: When top or bottom item is selected, it's colored background is rectangular and drawn on top of the round-cornered background. – Kris Van Bael Jul 27 '11 at 13:30
  • would be nice if people would provide a preview in such cases :) – seb Oct 15 '12 at 17:22
  • Hi, do you know why I can't use different gradients? Thanxs. – Guillermo Oramas R. Nov 16 '12 at 16:57
  • Thanks for the code. It works great. But how will I make the selection also curved? – Sachin Thampan Nov 13 '13 at 05:28
  • and how to make sure it's still in round corner even top/bottom item is clicked? – suitianshi Feb 10 '14 at 10:08
  • Is there any possibility to set different color for each rows and have rounded corners? – Lukas Sagner Sep 06 '15 at 18:20
  • Yes, there is. Just set xml with rounded corners to item layout and then in adapter call: GradientDrawable drawable = (GradientDrawable)rowView.getBackground(); drawable.setColor(Color.parseColor(myObject.getBackgroundColor())); rowView.setBackground(drawable); – Lukas Sagner Sep 07 '15 at 20:16
57

Although that did work, it took out the entire background colour as well. I was looking for a way to do just the border and just replace that XML layout code with this one and I was good to go!

<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <stroke android:width="4dp" android:color="#FF00FF00" />
    <padding android:left="7dp" android:top="7dp"
            android:right="7dp" android:bottom="7dp" />
    <corners android:radius="4dp" />
</shape> 
Kevin Parker
  • 16,975
  • 20
  • 76
  • 105
12

@kris-van-bael

For those having issues with selection highlight for the top and bottom row where the background rectangle shows up on selection you need to set the selector for your listview to transparent color.

listView.setSelector(R.color.transparent);

In color.xml just add the following -

<color name="transparent">#00000000</color>
alvins
  • 226
  • 3
  • 5
  • 5
    it didn't work for me. I added the following line, however, and it got rid of it: `android:cacheColorHint="@android:color/transparent"` – cesar Jan 10 '12 at 03:37
  • 1
    This definitely fixed the selection issue for me - thanks! You can also use android.R.color.transparent for the Selector color instead of creating your own. – greg7gkb May 23 '12 at 04:20
  • 3
    Instead of doing it programatically, add this to your ListView in the XML layout to hide selection color: android:listSelector="#00000000" – Elad Nava Sep 14 '12 at 18:21
  • @alvins Is there any way to make Layout selectable for highlighting like list view item? – Bharat Dodeja Jun 26 '13 at 07:57
  • what should i do if i want to use an opaque color for listSelector? – suitianshi Feb 10 '14 at 10:11
4

Update

The solution these days is to use a CardView with support for rounded corners built in.


Original answer*

Another way I found was to mask out your layout by drawing an image over the top of the layout. It might help you. Check out Android XML rounded clipped corners

Richard Le Mesurier
  • 29,432
  • 22
  • 140
  • 255
3

The other answers are very useful, thanks to the authors!

But I could not see how to customise the rectangle when highlighting an item upon selection rather than disabling the highlighting @alvins @bharat dojeha.

The following works for me to create a rounded list view item container with no outline and a lighter grey when selected of the same shape:

Your xml needs to contain a selector such as e.g. ( in res/drawable/customshape.xml):

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:state_pressed="true" >
    <shape xmlns:android="http://schemas.android.com/apk/res/android" >
        <stroke android:width="8dp" android:color="@android:color/transparent" />
        <padding android:left="14dp" android:top="14dp"
                android:right="14dp" android:bottom="14dp" />
        <corners android:radius="10dp" />
        <gradient 
             android:startColor="@android:color/background_light"
             android:endColor="@android:color/transparent" 
             android:angle="225"/> 
    </shape>
</item>
<item android:state_pressed="false">
    <shape xmlns:android="http://schemas.android.com/apk/res/android" >
        <stroke android:width="8dp" android:color="@android:color/transparent" />
        <padding android:left="14dp" android:top="14dp"
                android:right="14dp" android:bottom="14dp" />
        <corners android:radius="10dp" />
        <gradient 
             android:startColor="@android:color/darker_gray"
             android:endColor="@android:color/transparent" 
             android:angle="225"/> 
    </shape>        
</item>

Then you need to implement a list adapter and override the getView method to set the custom selector as background

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        //snip
        convertView.setBackgroundResource(R.drawable.customshape);
        //snip
    }

and need to also 'hide' the default selector rectangle e.g in onCreate (I also hide my thin grey divider line between the items):

listView.setSelector(android.R.color.transparent);
listview.setDivider(null);

This approach solves a general solution for drawables, not just ListViewItem with various selection states.

dr_g
  • 1,179
  • 7
  • 10
2

Yet another solution to selection highlight problems with first, and last items in the list:

Add padding to the top and bottom of your list background equal to or greater than the radius. This ensures the selection highlighting doesn't overlap with your corner curves.

This is the easiest solution when you need non-transparent selection highlighting.

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
    <solid android:color="@color/listbg" />
    <stroke
        android:width="2dip"
        android:color="#D5D5D5" />
    <corners android:radius="10dip" />

    <!-- Make sure bottom and top padding match corner radius -->
    <padding
        android:bottom="10dip"
        android:left="2dip"
        android:right="2dip"
        android:top="10dip" />
</shape>
William Morrison
  • 10,953
  • 2
  • 31
  • 48
1

actually, i think the best solution is described on this link:

http://blog.synyx.de/2011/11/android-listview-with-rounded-corners/

in short, it uses a different background for the top, middle and bottom items, so that the top and bottom ones would be rounded.

android developer
  • 114,585
  • 152
  • 739
  • 1,270
1

This was incredibly handy to me. I would like to suggest another workaround to perfectly highlight the rounded corners if you are using your own CustomAdapter.

Defining XML Files

First of all, go inside your drawable folder and create 4 different shapes:

  • shape_top

    <gradient
        android:startColor="#ffffff"
        android:endColor="#ffffff"
        android:angle="270"/>
    <corners
        android:topLeftRadius="10dp"
        android:topRightRadius="10dp"/>
    

  • shape_normal

    <gradient
        android:startColor="#ffffff"
        android:endColor="#ffffff"
        android:angle="270"/>
    <corners
        android:topLeftRadius="10dp"
        android:topRightRadius="10dp"/>
    

  • shape_bottom

    <gradient
        android:startColor="#ffffff"
        android:endColor="#ffffff"
        android:angle="270"/>
    <corners
        android:bottomRightRadius="10dp"
        android:bottomRightRadius="10dp"/>
    

  • shape_rounded

    <gradient
        android:startColor="#ffffff"
        android:endColor="#ffffff"
        android:angle="270"/>
    <corners
        android:topLeftRadius="10dp"
        android:topRightRadius="10dp"
        android:bottomRightRadius="10dp"
        android:bottomRightRadius="10dp"/>
    

Now, create a different row layout for each shape, i.e. for shape_top :

  • You can also do this programatically changing the background.

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginLeft="20dp"
        android:layout_marginRight="10dp"
        android:fontFamily="sans-serif-light"
        android:text="TextView"
        android:textSize="22dp" />
    
    <TextView
        android:id="@+id/txtValue1"
        android:layout_width="match_parent"
        android:layout_height="48dp"
        android:textSize="22dp"
        android:layout_gravity="right|center"
        android:gravity="center|right"
        android:layout_marginLeft="20dp"
        android:layout_marginRight="35dp"
        android:text="Fix"
        android:scaleType="fitEnd" />
    

And define a selector for each shaped-list, i.e. for shape_top:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- Selected Item -->

    <item android:state_selected="true"
        android:drawable="@drawable/shape_top" />
    <item android:state_activated="true"
        android:drawable="@drawable/shape_top" />

    <!-- Default Item -->
    <item android:state_selected="false"
        android:drawable="@android:color/transparent" />
</selector>

Change your CustomAdapter

Finally, define the layout options inside your CustomAdapter:

if(position==0)
{
 convertView = mInflater.inflate(R.layout.list_layout_top, null);
}
else
{
 convertView = mInflater.inflate(R.layout.list_layout_normal, null);
}

if(position==getCount()-1)
{
convertView = mInflater.inflate(R.layout.list_layout_bottom, null);
}

if(getCount()==1)
{
convertView = mInflater.inflate(R.layout.list_layout_unique, null);
}

And that's done!

Machado
  • 14,105
  • 13
  • 56
  • 97
1

to make border u have to make another xml file with property of solid and corners in the drawable folder and calls it in background

pawan kumar
  • 81
  • 1
  • 1
0

I'm using a custom view that I layout on top of the other ones and that just draws the 4 small corners in the same color as the background. This works whatever the view contents are and does not allocate much memory.

public class RoundedCornersView extends View {
    private float mRadius;
    private int mColor = Color.WHITE;
    private Paint mPaint;
    private Path mPath;

    public RoundedCornersView(Context context) {
        super(context);
        init();
    }

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

        TypedArray a = context.getTheme().obtainStyledAttributes(
                attrs,
                R.styleable.RoundedCornersView,
                0, 0);

        try {
            setRadius(a.getDimension(R.styleable.RoundedCornersView_radius, 0));
            setColor(a.getColor(R.styleable.RoundedCornersView_cornersColor, Color.WHITE));
        } finally {
            a.recycle();
        }
    }

    private void init() {
        setColor(mColor);
        setRadius(mRadius);
    }

    private void setColor(int color) {
        mColor = color;
        mPaint = new Paint();
        mPaint.setColor(mColor);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setAntiAlias(true);

        invalidate();
    }

    private void setRadius(float radius) {
        mRadius = radius;
        RectF r = new RectF(0, 0, 2 * mRadius, 2 * mRadius);
        mPath = new Path();
        mPath.moveTo(0,0);
        mPath.lineTo(0, mRadius);
        mPath.arcTo(r, 180, 90);
        mPath.lineTo(0,0);
        invalidate();
    }

    @Override
    protected void onDraw(Canvas canvas) {

        /*Paint paint = new Paint();
        paint.setColor(Color.RED);
        canvas.drawRect(0, 0, mRadius, mRadius, paint);*/

        int w = getWidth();
        int h = getHeight();
        canvas.drawPath(mPath, mPaint);
        canvas.save();
        canvas.translate(w, 0);
        canvas.rotate(90);
        canvas.drawPath(mPath, mPaint);
        canvas.restore();
        canvas.save();
        canvas.translate(w, h);
        canvas.rotate(180);
        canvas.drawPath(mPath, mPaint);
        canvas.restore();
        canvas.translate(0, h);
        canvas.rotate(270);
        canvas.drawPath(mPath, mPaint);
    }
}
mbonnin
  • 6,893
  • 3
  • 39
  • 55
0

There are different ways to achieve it. The latest approach is using CardView for each ListItem component. Here are some steps.

  1. Create a layout resource file; let's name it "listitem.xml
  2. Copy and paste the under enclosed Listitem.xml layout body into it.
  3. Create RowItem class for each listitem data; later you will instantiate this to assign values for each list item. Check Code below, RowItem.class.
  4. Create a custom ListAdapter; let's name it ListAdapter.class, and inflate this (#1) list item layout for each list item (Check the second code snippet for this one)
  5. Set this adapter (#3) the way you set default adapters inside an activity the listview belongs to. maybe the only difference would be you first have to instantiate RowItem class with values and add RowItem object to your adapter then notify your adapter that the data is changed.
**listitem.xml**
<?xml version="1.0" encoding="utf-8"?>
    <LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">
        <GridLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"

            android:alignmentMode="alignMargins"
            android:columnCount="1"
            android:columnOrderPreserved="false"
            android:rowCount="1">
            <androidx.cardview.widget.CardView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_rowWeight="1"
                android:layout_columnWeight="1"
                android:layout_margin="6dp"
                app:cardCornerRadius="8dp"
                app:cardElevation="6dp">
                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:orientation="horizontal">
                    <ImageView
                        android:id="@+id/sampleiconimageID"
                        android:layout_width="60dp"
                        android:layout_height="60dp"
                        android:padding="5dp"/>
                    <LinearLayout android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:orientation="vertical">
                        <TextView
                            android:id="@+id/titleoflistview"
                            android:layout_width="wrap_content"
                            android:layout_height="wrap_content"
                            android:text="Main Heading"
                            android:textStyle="bold" />
                        <TextView
                            android:id="@+id/samplesubtitle"
                            android:layout_width="wrap_content"
                            android:layout_height="wrap_content"
                            android:text="Sub Heading"
                           />
                        </LinearLayout>
                </LinearLayout>
            </androidx.cardview.widget.CardView>
        </GridLayout>
    </LinearLayout>

RowItem.Class

public class RowItem {
    private String heading;
    private String subHeading;
    private int smallImageName;
    private String datetime;
    private int count;

    public void setHeading( String theHeading ) {
        this.heading = theHeading;
    }

    public String getHeading() {
        return this.heading;
    }
    public void setSubHeading( String theSubHeading ) {

        this.subHeading = theSubHeading;

    }

    public String getSubHeading( ) {

        return this.subHeading;

    }

    public void setSmallImageName(int smallName) {

        this.smallImageName = smallName;

    }

    public int getSmallImageName() {

        return this.smallImageName;

    }

    public void setDate(String datetime) {
        this.datetime = datetime;
    }

    public String getDate() {
        return this.datetime;
    }

    public void setCount(int count) {
        this.count = count;
    }

    public int getCount() {
        return this.count;
    }

}

Sample ListAdapter

public class ListAdapter extends BaseAdapter {
    private ArrayList<RowItem> singleRow;
    private LayoutInflater thisInflater;

    public ListAdapter(Context context, ArrayList<RowItem> aRow){

        this.singleRow = aRow;
        thisInflater = ( LayoutInflater.from(context) );
    }
    @Override
    public int getCount() {
        return singleRow.size();    }

    @Override
    public Object getItem(int position) {
        return singleRow.get( position );    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    public View getView(int position, View view, ViewGroup parent) {

        if (view == null) {
            view = thisInflater.inflate( R.layout.mylist2, parent, false );
            //set listview objects here
            //example

            TextView titleText = (TextView) view.findViewById(R.id.titleoflistview);
           
            RowItem currentRow = (RowItem) getItem(position);
            
            titleText.setText( currentRow.getHeading() );
          
        }

        return view;

//        LayoutInflater inflater=.getLayoutInflater();
//        View rowView=inflater.inflate(R.layout.mylist, null,true);
//

//        titleText.setText(maintitle[position]);
//        subtitleText.setText(subtitle[position]);

//        return null;

    };
}
NatiCog
  • 49
  • 3