How to make a circular drawable with stroke, programmatically?

android developer picture android developer · Aug 10, 2017 · Viewed 11k times · Source

Background

I'm trying to have a filled circle, with a stroke of certain color and width, and an image inside.

This can easily be done in XML, as such (this is just a sample) :

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <shape android:shape="oval">
            <size
                android:width="120dp" android:height="120dp"/>
            <solid android:color="#ffff0000"/>
            <stroke
                android:width="3dp" android:color="#ff00ff00"/>
        </shape>
    </item>
    <item
        android:width="60dp" android:height="60dp" android:drawable="@android:drawable/btn_star"
        android:gravity="center"/>
</layer-list>

enter image description here

The problem

I need to have certain properties of the above to change programmatically, so I can't use the XML file, but the idea is still the same.

Thing is, I can't find an easy way to put a stroke on an OvalShape drawable, as done in XML. I can't find the function to do it.

What I tried

There are solutions out there here on StackOverflow, but I couldn't find one that works well. Only one I've found is here, but its stroke line is being cut .

I have, however, partially succeeded in one way to solve this, by using an XML just for the stroke itself:

stroke_drawable.xml

<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
    <stroke
        android:width="4dp" android:color="@android:color/white"/>
</shape>

code:

    final int strokeDrawableResId = R.drawable.stroke_drawable;
    Drawable innerDrawable = ResourcesCompat.getDrawable(getResources(), ..., null);
    final Drawable strokeDrawable = ResourcesCompat.getDrawable(getResources(), strokeDrawableResId, null);
    ShapeDrawable biggerCircle = new ShapeDrawable(new OvalShape());
    int size = ...;
    biggerCircle.setIntrinsicHeight(size);
    biggerCircle.setIntrinsicWidth(size);
    biggerCircle.getPaint().setColor(0xffff0000);
    biggerCircle.setBounds(new Rect(0, 0, size, size));
    LayerDrawable layerDrawable = new LayerDrawable(new Drawable[]{biggerCircle, strokeDrawable, innerDrawable});

    ImageView imageView = findViewById(R.id.imageView);
    imageView.setImageDrawable(layerDrawable);

It works, but it's not fully programmatically (stroke is defined in XML).

The question

How to change the code to be fully programmatic ?


EDIT: I tried what was suggested here, yet instead of an additional drawable for a background, since I needed it all in one drawable, I used LayerDrawable:

    Drawable innerDrawable = ResourcesCompat.getDrawable(getResources(), android.R.drawable.btn_star, null);
    int strokeWidth = 5;
    int strokeColor = Color.parseColor("#ff0000ff");
    int fillColor = Color.parseColor("#ffff0000");
    GradientDrawable gD = new GradientDrawable();
    gD.setColor(fillColor);
    gD.setShape(GradientDrawable.OVAL);
    gD.setStroke(strokeWidth, strokeColor);
    LayerDrawable layerDrawable = new LayerDrawable(new Drawable[]{gD, innerDrawable});
    ImageView imageView = findViewById(R.id.imageView);
    imageView.setImageDrawable(layerDrawable);

This works, but for some reason the drawable inside (the star) is being stretched:

enter image description here

Answer

Lalit Fauzdar picture Lalit Fauzdar · Aug 10, 2017

You can do this programmatically as
In YourActivity.XMl, Set-up an ImageView as usual.

<ImageView
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:id="@+id/id1"
            android:src="@mipmap/ic_launcher_round"                                
            android:padding="15dp"/>

And in your MainActivity.java

    ImageView iV = (ImageView) findViewById(R.id.id1);
    int strokeWidth = 5;
    int strokeColor = Color.parseColor("#03dc13");
    int fillColor = Color.parseColor("#ff0000");
    GradientDrawable gD = new GradientDrawable();
    gD.setColor(fillColor);
    gD.setShape(GradientDrawable.OVAL);
    gD.setStroke(strokeWidth, strokeColor);
    iV.setBackground(gD);

setColor here sets the background color and setStroke sets the stroke width and stroke color.
I have created some local variables for color, width etc to make it more easy to understand.
Result
enter image description here
More you increase the padding, more will the size of circle increase.