How to make a circular drawable with stroke, programmatically?
Asked Answered
P

1

9

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

Pokey answered 10/8, 2017 at 8:14 Comment(2)
stroke_drawable.xml won't be required for the answer I've given below.Frendel
@LalitSinghFauzdar I wanted to have a single drawable to set. Background is used for something else (like pressing effect)Pokey
F
17

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.

Frendel answered 10/8, 2017 at 8:45 Comment(6)
Where's the part that makes it Oval? Why does it have "getBackground" ?Pokey
Can you please show how to do it on Oval shape, and not on a view? Just as the XML shows?Pokey
ImageView can already have a background. I want to create a drawable to set as the content, including what's inside of the circle ("ic_launcher_round" in your case) , just as I've shown on the question. It's the circle, the outline, and the content together. I want to have them all inside one drawable that's not in XML. You've put a part of it in XML. However, I think this can be fixed with LayerDrawable, no?Pokey
I want to have the same content as I've shown on the drawable XML that I've shown, yet programmatically. The drawable should contain all as the XML has. However, I think this can be done using LayerDrawable. I think your solution could work if it had it instead of "src" being set in XML.Pokey
I tried the LayerDrawable I wrote, but it gets stretched. Updated question to hold this solutionPokey
Again, I need to use a single drawable, as a full conversion of what I had in XML.Pokey

© 2022 - 2024 — McMap. All rights reserved.