87

I need to change the stroke color from the app. The user is able to change the background color so I need to also let them change the stroke (outline) of the button. As its is already set in the drawable (sample below) I have not found a way to change this. Seems like all of the other questions like this just said to use the XML file.... but that doesnt let me make it dynamic. Thank you for any help!

I need to change the stroke color to a user defined color. Nothing to do with the state.

<?xml version="1.0" encoding="UTF-8"?> 
<shape xmlns:android="http://schemas.android.com/apk/res/android"> 

    <solid android:color="#ffffffff"/>    
      <stroke
                android:width="3dp"
                android:color="@color/Dim_Gray" />  <<<<--- This is what I need to change


    <padding android:left="10dp"
             android:top="10dp"
             android:right="10dp"
             android:bottom="10dp"
             /> 

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

</shape>
Mark Worsnop
  • 4,407
  • 15
  • 54
  • 79

7 Answers7

251

1. If you have drawable file for a "view" like this

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

<corners android:radius="5dp" />

<solid android:color="@android:color/white" />

<stroke
    android:width="3px"
    android:color="@color/blue" />

</shape>

Then you can change
a. Stroke color :

GradientDrawable drawable = (GradientDrawable)view.getBackground();
drawable.mutate(); // only change this instance of the xml, not all components using this xml
drawable.setStroke(3, Color.RED); // set stroke width and stroke color 


b. Solid color :

GradientDrawable drawable = (GradientDrawable)view.getBackground();
drawable.setColor(Color.RED); // set solid color

2. If you have drawable file for a "view" like this

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_checked="true" android:id="@+id/buttonSelected">
        <shape>
            <solid android:color="@color/blue" />
            <stroke android:width="1px" android:color="@color/blue" />
        </shape>
    </item>
    <item android:state_checked="false" android:id="@+id/buttonNotSelected">
        <shape android:shape="rectangle">
            <solid android:color="@color/white" />
            <stroke android:width="1px" android:color="@color/blue" />
        </shape>
    </item>
</selector>

Then you can change the individual item attributes by taking separate drawable objects by there positions.

StateListDrawable drawable = (StateListDrawable)view.getBackground();
DrawableContainerState dcs = (DrawableContainerState)drawable.getConstantState();
Drawable[] drawableItems = dcs.getChildren();
GradientDrawable gradientDrawableChecked = (GradientDrawable)drawableItems[0]; // item 1 
GradientDrawable gradientDrawableUnChecked = (GradientDrawable)drawableItems[1]; // item 2

now to change stroke or solid color :

//solid color
gradientDrawableChecked.setColor(Color.BLUE);
gradientDrawableUnChecked.setColor(Color.RED);

//stroke
gradientDrawableChecked.setStroke(1, Color.RED);
gradientDrawableUnChecked.setStroke(1, Color.BLUE);
Bruno
  • 3,872
  • 4
  • 20
  • 37
Rana Ranvijay Singh
  • 6,055
  • 3
  • 38
  • 54
  • 1
    Great overview of how working with selectors and shapes programatically. – Mercury May 04 '17 at 20:03
  • Perfect answer. Working like a charm. – Marlon Sep 11 '17 at 13:03
  • is it possible to use this on a vector drawable? – Red M Sep 15 '18 at 21:41
  • Haven't really tried that, I think there are objects to handle specific parameter changes in a tag like or . Not really sure if same will apply for vector images. – Rana Ranvijay Singh Sep 16 '18 at 05:55
  • 1
    But problem with this approach is, when you change the stroke color programatically, so its changed xml drawable too, which shouldn't be the case. Is it possible to change stroke color for selected view without changing actual xml drawable? – Ritesh Adulkar May 02 '19 at 09:35
  • 1
    @RiteshAdulkar The `.setStroke` documentation says _Note: changing this property will affect all instances of a drawable loaded from a resource. It is recommended to invoke `mutate()` before changing this property._ – Big_Chair May 12 '19 at 10:27
  • @Big_Chair ya i got ur point, but is there any hack or alternate way to do the same without changing original state of drawable? – Ritesh Adulkar May 13 '19 at 07:26
  • @RiteshAdulkar to make the changes specific to this drawable, you have to use `GradientDrawable drawable = (GradientDrawable)view.getBackground().mutate()` . This creates a copy of the drawable rather than sharing the same instance of drawable. – Hissaan Ali Feb 03 '20 at 13:53
  • what about the corners tag? how can we update it programmatically? – epool May 21 '20 at 01:26
  • perfectamente :D – C. Alen Nov 19 '20 at 13:30
  • Anyone else had a casting issue? java.lang.ClassCastException: android.graphics.drawable.ColorDrawable cannot be cast to android.graphics.drawable.GradientDrawable – Aidan Lee Apr 05 '23 at 16:09
34

I needed a way to change the stroke color of any GradientDrawable without knowing the width of the stroke. My goal was to do this using Drawable.setTint. I had to add a transparent solid element to my shape xml to get it to work:

<!-- stroke_background.xml -->
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <stroke
        android:width="4dp"
        android:color="@android:color/white" />
    <!-- Need transparent solid to get tint applied to only the stroke -->
    <solid android:color="@android:color/transparent"/>
</shape>
// StrokeView.kt
setBackgroundResource(R.drawable.stroke_background)
// Set the color of your stroke
drawable.setTint(Color.BLUE)

In your case, since you have both a solid and stroke, then you'll need to use a layer-list, where the stroke is added AFTER the solid (so that it's drawn on top). This will let you set different colors on the solid and the stroke without knowing in advance what the stroke width is:

<!-- stroke_solid_background.xml -->
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/solid_background" />
    <item android:drawable="@drawable/stroke_background" />
</layer-list>
// StrokeSolidView.kt
setBackgroundResource(R.drawable.stroke_solid_background)
(drawable as LayerDrawable).apply {
    // Set the color of your solid
    getDrawable(0).setTint(Color.GREEN)
    // Set the color of your stroke
    getDrawable(1).setTint(Color.BLUE)
}
iamreptar
  • 1,461
  • 16
  • 29
  • 3
    Best answer for me, provided API Level 21+. Follows KISS principle. The others are over complicated. – Fran Marzoa Sep 13 '19 at 09:09
  • 2
    Thanks for the comment about the drawable needing a transparent color. I've been routinely tinting views for this purpose but found it strange that it was tinting the whole view in this case. That was the key. – lbarbosa Sep 30 '19 at 16:57
  • 3
    Thank you for the clean solution! Setting background tint via `view.background.setTint(tintColor)` and adding a transparent `` to drawable helped me. – Yamashiro Rion Nov 30 '20 at 04:33
  • Works well `DrawableCompat.setTint()` for custom color resources. – AL. Mar 30 '23 at 10:35
5

Please look at the LayerDrawable because it created from your XML and used at runtime.

Here is a Demo Example:

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <shape android:shape="rectangle" >
            <solid android:color="@android:color/transparent" />
        </shape>
    </item>
    <item android:id="@+id/itemId" android:top="-4dp" android:right="-4dp" android:left="-4dp">
        <shape>
            <solid android:color="@android:color/transparent"/>
            <stroke
                android:width="1.5dp"
                android:color="#06C1D7" >
            </stroke>
            <padding
                android:bottom="-2dp"/>

        </shape>
    </item>
</layer-list>

You can modify it at runtime like:

 LayerDrawable layerDrawable = (LayerDrawable) getResources()
                .getDrawable(R.drawable.only_one_bottom_line);
        GradientDrawable gradientDrawable = (GradientDrawable) layerDrawable.findDrawableByLayerId(R.id.itemId);
        int px = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1.5f, getResources().getDisplayMetrics());

        gradientDrawable.setStroke(px, getResources().getColor(R.color.theme_color));
        tv_select_city_name.setBackground(layerDrawable);
Rahul
  • 3,293
  • 2
  • 31
  • 43
  • I've tried it but findDrawableByLayerId() returned null. Not sure why. I only managed it by calling getDrawable(int index). – ka3ak Aug 17 '19 at 16:54
0

I answered a similar question in Change shape border color at runtime

Its like the same solution proposed by f20k but in my case the drawable was a GradientDrawable instead of a ShapeDrawable.

see if it works...

Community
  • 1
  • 1
rodalves
  • 201
  • 2
  • 3
0

Try using StateLists (as opposed to ColorStateList). Take a look: http://developer.android.com/guide/topics/resources/drawable-resource.html#StateList

You can also create a ShapeDrawable (or a RoundRectShape in your example) programmatically, and then call the button's setBackgroundDrawable

Lior
  • 7,845
  • 2
  • 34
  • 34
  • Sorry I didnt explain it good enough. I need to change the stroke color to a user defined color. nothing to do with the state. – Mark Worsnop Jan 23 '11 at 14:28
0

Create background (file in res/drawable) with transparent (#00000000) solid:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">

    <corners android:radius="4dp" />

    <stroke
        android:width="1dp"
        android:color="#FF0000" />

    <solid android:color="#00000000" />

</shape>

set this file as background:

<View   
    android:layout_width="match_parent"
    android:layout_height="40dp"
    android:background="@drawable/bg_enter_number" />

and now, using android:backgroundTint you can change color of your border.


For example:

<View   
    android:layout_width="match_parent"
    android:layout_height="40dp"
    android:background="@drawable/bg_enter_number"
    android:backgroundTint="#FF00FF" />

#FF00FF

or:

<View   
    android:layout_width="match_parent"
    android:layout_height="40dp"
    android:background="@drawable/bg_enter_number"
    android:backgroundTint="#00FF00" />

#00FF00

Boken
  • 4,825
  • 10
  • 32
  • 42
-1

Perhaps they are referring to Color State Lists which allows you to change the color based on whether the button was pressed/focused/enabled/etc

f20k
  • 3,106
  • 3
  • 23
  • 32
  • Sorry I didnt explain it good enough. I need to change the stroke color to a user defined color. nothing to do with the state. – Mark Worsnop Jan 23 '11 at 14:29
  • Try using shapeDrawable (http://developer.android.com/reference/android/graphics/drawable/ShapeDrawable.html) where the Paint has Style=Stroke, StrokeWidth=, Color= – f20k Jan 23 '11 at 16:02