I would like to change an existing drawable state list (default and pressed) colours programmatically.
This is my custom drawable I would like to edit:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<ripple android:color="#1AFFFFFF">
<item>
<shape android:shape="rectangle">
<solid android:color="#color/colorPrimary" />
<corners android:radius="5dp" />
</shape>
</item>
</ripple>
</item>
<item>
<shape android:shape="rectangle">
<solid android:color="#color/colorPrimary" />
<corners android:radius="5dp" />
</shape>
</item>
</selector>
I've tried How can I change colors in my StateListDrawable? but I am getting an error:
val customButtonTest: Drawable
get() {
val stateListDrawable = ContextCompat.getDrawable(context, R.drawable.custom_button) as StateListDrawable
val drawableContainerState = stateListDrawable.constantState as DrawableContainer.DrawableContainerState
val children = drawableContainerState.children
val selectedItem = children[0] as GradientDrawable
selectedItem.setColor(Color.parseColor("#FD00DF"))
return stateListDrawable.current
}
**ClassCastException: android.graphics.drawable.RippleDrawable cannot be cast to android.graphics.drawable.GradientDrawable**
As I am intend to use it for a BandingAdapter, how can I change both state colours and return it as a drawable?
Thanks
** Edited ---
val customButtonTest: Drawable
get() {
val stateListDrawable =
ContextCompat.getDrawable(context, R.drawable.custom_button) as StateListDrawable
val drawableContainerState =
stateListDrawable.constantState as DrawableContainer.DrawableContainerState
val children = drawableContainerState.children
val pressedState = children[0] as RippleDrawable
val defaultState = children[1] as GradientDrawable
pressedState.setColor(ColorStateList.valueOf(Color.parseColor("#1AFFFFFF")))
defaultState.setColor(Color.parseColor("#4267b2"))
drawableContainerState.addChild(pressedState)
drawableContainerState.addChild(defaultState)
return drawableContainerState.newDrawable()
}
How to change an item solid colour inside the ripple container? As
pressedState.setColor(ColorStateList.valueOf(Color.parseColor("#1AFFFFFF")
changes ripple colour itself.
If someone needs to access ripple and its child, here you go:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<ripple android:color="#1A1C1C1C">
<item>
<shape android:shape="rectangle">
<solid android:color="#color/colorPrimary" />
<corners android:radius="5dp" />
</shape>
</item>
</ripple>
</item>
<item>
<shape android:shape="rectangle">
<solid android:color="#color/colorPrimary" />
<corners android:radius="5dp" />
</shape>
</item>
</selector>
val customButtonTest: Drawable
get() {
val stateListDrawable = ContextCompat.getDrawable(context, R.drawable.custom_button) as StateListDrawable
val drawableContainerState = stateListDrawable.constantState as DrawableContainer.DrawableContainerState
val children = drawableContainerState.children
val pressedState = children[0] as RippleDrawable
val defaultState = children[1] as GradientDrawable
val layerDrawable = pressedState as LayerDrawable
val rippleChildDrawable = layerDrawable.getDrawable(0) as GradientDrawable
rippleChildDrawable.setColor(primaryColor)
defaultState.setColor(primaryColor)
return drawableContainerState.newDrawable()
}
To reach <items> inside <ripple> you need to cast RippleDrawable to LayerDrawable
Related
I want to set the android:id = "#android:id/background" programmatically.
I try to adapting drawable xml code to java code programmatically.
I wrote other options to code, but couldn't write the android:id = "#android:id/background" to code.
here is my code. please let me know the way.
xml
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="#android:id/background">
<shape android:shape="rectangle">
<corners android:radius="10dp"/>
<solid android:color="#color/colorOrange"/>
<stroke android:color="#fff" android:width="1dp"/>
</shape>
</item>
<item android:id="#android:id/progress">
<scale
android:scaleHeight="10%"
android:scaleWidth="100%" >
<shape android:shape="rectangle">
<corners android:radius="10dp"/>
<solid android:color="#color/colorGreen"/>
<stroke android:color="#color/colorGreen"
android:width="4dp"/>
</shape>
</scale>
</item>
</layer-list>
code
val layer1 = GradientDrawable()
layer1.cornerRadius = 10f
layer1.setStroke(1, Color.WHITE)
layer1.setColor(Color.YELLOW)
val scaleDrawable = GradientDrawable()
scaleDrawable.cornerRadius = 10f
scaleDrawable.setStroke(1, Color.WHITE)
scaleDrawable.setColor(Color.BLUE)
val layer2 = ScaleDrawable(scaleDrawable, Gravity.BOTTOM,1f,0.1f)
val drawableList = arrayOf(layer1, layer2)
val layerList : LayerDrawable = LayerDrawable(drawableList)
this.progressDrawable = layerList
result screen
You can use #layerList.setId and set the id by index . Check the code below . I have made few changes in it to match it to xml-drawable .
val layer1 = GradientDrawable()
layer1.cornerRadius = 10f
layer1.setStroke(1, Color.WHITE)
layer1.setColor(Color.YELLOW)
val scaleDrawable = GradientDrawable()
scaleDrawable.cornerRadius = 10f
scaleDrawable.setStroke(1, Color.WHITE)
scaleDrawable.setColor(Color.BLUE)
val layer2 = ScaleDrawable(scaleDrawable, Gravity.START,1f,0.1f)
val drawableList = arrayOf(layer1, layer2)
val layerList = LayerDrawable(drawableList)
layerList.setId(0,android.R.id.background)
layerList.setId(1,android.R.id.progress)
seekBar.progressDrawable = layerList
So I've implemented a custom button for our app, the implementation works perfect on Android 6 and up but I run into an issue on Android 5, where the background color doesn't get applied until I press the button once. Then it looks as it should.
Buttons not correct
Buttons correct
The implementation looks like this:
class MpButton #JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = R.style.ButtonStyle) : AppCompatButton(context, attrs, defStyleAttr) {
private val toScale = 0.9f
private val fromScale = 1f
init {
var style = 0
var allCaps = false
attrs?.let {
val a = context.obtainStyledAttributes(it, R.styleable.MpButton)
style = a.getInt(R.styleable.MpButton_MpButtonColor, 0)
allCaps = a.getBoolean(R.styleable.MpButton_MpButtonAllcaps, false)
a.recycle()
}
when (style) {
WHITE -> {
this.setBackgroundResource(R.drawable.mp_button_white)
this.setTextColor(ContextCompat.getColor(context, R.color.white))
}
RED -> {
this.setBackgroundResource(R.drawable.mp_button_red)
this.setTextColor(ContextCompat.getColor(context, R.color.white))
}
BLACK -> {
this.setBackgroundResource(R.drawable.mp_button_black)
this.setTextColor(ContextCompat.getColor(context, R.color.mp_black))
}
BLUE -> {
this.setBackgroundResource(R.drawable.mp_button_blue)
this.setTextColor(ContextCompat.getColor(context, R.color.white))
}
YELLOW -> {
this.setBackgroundResource(R.drawable.mp_button_yellow)
this.setTextColor(ContextCompat.getColor(context, R.color.white))
}
else -> {
this.setBackgroundResource(R.drawable.mp_button_green)
this.setTextColor(ContextCompat.getColor(context, R.color.white))
}
}
this.gravity = Gravity.CENTER
val padding = Utils.convertDpToPixel(context, 5)
this.setPadding(padding, padding, padding, padding)
this.isAllCaps = allCaps
}
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
when(event.action) {
MotionEvent.ACTION_DOWN -> {
this.animate().scaleX(toScale).scaleY(toScale).setDuration(100).start()
}
MotionEvent.ACTION_UP -> {
this.animate().scaleX(fromScale).scaleY(fromScale).setDuration(100).start()
}
}
return super.dispatchTouchEvent(event)
}
companion object {
const val WHITE = 1
const val RED = 2
const val BLACK = 3
const val BLUE = 4
const val YELLOW = 5
}
}
For info Ill also add the XML for one of the colors:
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="#color/mp_black">
<item>
<selector
android:exitFadeDuration="#android:integer/config_mediumAnimTime"
android:enterFadeDuration="#android:integer/config_shortAnimTime">
<item android:state_enabled="true" android:state_pressed="false">
<shape android:shape="rectangle">
<corners android:radius="30dp"/>
<solid android:color="#color/jungle_green" />
</shape>
</item>
<item android:state_enabled="true" android:state_pressed="true">
<shape android:shape="rectangle">
<corners android:radius="30dp"/>
<solid android:color="#color/forest" />
</shape>
</item>
<item android:state_enabled="false">
<shape android:shape="rectangle">
<corners android:radius="30dp"/>
<solid android:color="#color/mp_gray_5" />
</shape>
</item>
</selector>
</item>
</ripple>
As a last ill copy the part of the XML of the view aswell to give as much info as possible:
<se.motesplatsen.app.ui.controls.MpButton
android:id="#+id/btnStartLogin"
android:layout_width="220dp"
android:layout_height="44dp"
android:text="#string/DEFAULT_LOGIN"
android:layout_marginBottom="10dp"
android:layout_marginTop="20dp"
app:MpButtonAllcaps="true"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="#id/btnStartBecomeMember"/>
<se.motesplatsen.app.ui.controls.MpButton
android:id="#+id/btnStartBecomeMember"
android:layout_width="220dp"
android:layout_height="44dp"
android:layout_marginTop="25dp"
android:text="#string/BECOME_MEMBER_BUTTON"
app:MpButtonColor="1"
app:MpButtonAllcaps="true"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintTop_toBottomOf="#id/tvStartDesc"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toTopOf="#id/btnStartLogin"/>
UPDATE & SOLUTION!:
Finally got this to work! Solution was to remove
android:exitFadeDuration="#android:integer/config_mediumAnimTime"
android:enterFadeDuration="#android:integer/config_shortAnimTime"
Im guessing it somehow interferes with the ripple animation on certain Android versions. So for anyone else that have my issue, check so you dont have Ripple togheter with fadeduration in the selector :)!
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape >
<solid android:color="#android:color/white"/>
<size android:height="1dp"/>
</shape>
</item>
<item android:bottom="2dip" android:top="2dip" android:left="2dip" android:right="2dip">
<shape>
<solid android:color="#android:color/Transparent"/>
</shape>
</item>
this is exactly look like your correct button
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
<corners
android:radius="5dp"
/>
<solid
android:color="#color/Transparent"
/>
just use this as background you are using on pressed state in your file
In my app, I have the selector below.
Is possible to create a similar selector, by color instead of by image-drawable, but at runtime?
<?xml version="1.0" encoding="UTF-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_window_focused="false"
android:drawable="#drawable/ic_button_orange_normal" />
<item
android:state_pressed="true"
android:drawable="#drawable/ic_button_orange_pressed" />
<item
android:state_focused="true"
android:drawable="#drawable/ic_button_orange_focused" />
<item
android:drawable="#drawable/ic_button_orange_normal" />
</selector>
I have tried the function below to create the selector in runtime, but I need to create a round button, not square.
Is this possible?
private StateListDrawable makeSelector(int color)
{
StateListDrawable res = new StateListDrawable();
res.setExitFadeDuration(400);
res.setAlpha(45);
ShapeDrawable d = new ShapeDrawable();
res.addState(new int[] { android.R.attr.state_pressed }, new ColorDrawable(ColorUtilities.decrease(color, 0x003030)));
res.addState(new int[] {}, new ColorDrawable(Color.TRANSPARENT));
return res;
}
Edit again... I have found the way to create round selectors...
private static ShapeDrawable getRoundShapeDrawable(int color)
{
ShapeDrawable shape_drawable = new ShapeDrawable(new OvalShape());
shape_drawable.getPaint().setColor(color);
return shape_drawable;
}
public static StateListDrawable getRoundShapeSelector(int normal_color, int pressed_color)
{
ShapeDrawable normal_shape_drawable = getRoundShapeDrawable(normal_color), pressed_shape_drawable = getRoundShapeDrawable(pressed_color);
StateListDrawable state_list_drawable = new StateListDrawable();
state_list_drawable.addState(new int[] { android.R.attr.state_pressed }, pressed_shape_drawable);
state_list_drawable.addState(new int[] { android.R.attr.state_focused }, pressed_shape_drawable);
state_list_drawable.addState(new int[] { }, normal_shape_drawable);
return state_list_drawable;
}
You can use "StateListDrawable"
StateListDrawable
and add state in it.
Let me know if you face problem in implementing this.
If you want to create a round button. Then you can use layer-list then add this drawable xml as a Layer.
Eg. How to create round button.
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:bottom="0dp"
android:left="0dp"
android:right="0dp"
android:top="0dp">
<shape android:shape="rectangle" >
<corners android:radius="40dp" />
<solid android:color="#fff" />
</shape>
</item>
<item
android:bottom="1dp"
android:left="1dp"
android:right="1dp"
android:top="1dp">
<shape android:shape="rectangle" >
<corners android:radius="40dp" />
<solid android:color="#d3d3d3" />
</shape>
</item>
<item
android:bottom="1dp"
android:left="2dp"
android:right="1dp"
android:top="2dp">
<shape android:shape="rectangle" >
<corners android:radius="40dp" />
<solid android:color="#EBEBEB" />
</shape>
</item>
<item
android:id="#+id/wl_btn_bg_shape"
android:bottom="6dp"
android:left="6dp"
android:right="6dp"
android:top="6dp">
<shape android:shape="rectangle" >
<corners android:radius="40dp" />
<solid android:color="#00caa8" />
</shape>
</item>
</layer-list>
create and save about code in drawable as wl_btn_bg.xml
Now you need to add this xml as view background
LayerDrawable layers = (LayerDrawable) context.getResources().getDrawable(R.drawable.wl_btn_bg);
view.setBackground(layers);
and if you want to change the color of selector then you need to add following lines in layers
LayerDrawable layers = (LayerDrawable) context.getResources().getDrawable(R.drawable.wl_btn_bg);
GradientDrawable shape = (GradientDrawable) (layers.findDrawableByLayerId(R.id.wl_btn_bg_shape));
shape.setColor(Color.parseColor("#ff6655"));
Done....
Here is the layer-list, named index.xml:
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:id="#+id/ll_23_1015_0" android:top="0dp" android:left="0dp" android:bottom="0dp" android:right="0dp">
<shape android:shape="rectangle">
<size android:width="1205dp" android:height="1795dp"/>
<solid android:color="#FFF"/>
</shape>
</item>
<item android:id="#+id/ll_23_1015_1" android:top="75dp" android:left="50dp" android:bottom="1366dp" android:right="919dp">
<bitmap android:src="#drawable/pic_23" />
</item>
<item android:id="#+id/ll_23_1015_2" android:top="504dp" android:left="50dp" android:bottom="937dp" android:right="919dp">
<bitmap android:src="#drawable/pic_23" />
</item>
<item android:id="#+id/ll_23_1015_3" android:top="933dp" android:left="50dp" android:bottom="508dp" android:right="919dp">
<bitmap android:src="#drawable/pic_23" />
</item>
<item android:id="#+id/ll_23_1015_4" android:top="1362dp" android:left="50dp" android:bottom="79dp" android:right="919dp">
<bitmap android:src="#drawable/pic_23" />
</item>
</layer-list>
and this is my code:
imgVShare = (ImageView) findViewById(R.id.imgVShare);
String path = "/storage/sdcard0/temp_photo.jpg";
Drawable finalPic = Drawable.createFromPath(path);
LayerDrawable myDrawable= (LayerDrawable)getResources().getDrawable(R.drawable.index);
for(int i=0; i<5; i++)
{
String ID = "ll_23_1015_" + (i+1);
int resID = getResources().getIdentifier(ID, "id", getPackageName());
Drawable layer = myDrawable.findDrawableByLayerId(resID);
layer = finalPic;
myDrawable.setDrawableByLayerId(resID, layer);
}
imgVShare.setImageDrawable(myDrawable);
All I want to do is to replace all bitmaps inside layer-list with "temp_photo.jpg" that is on internal storage. This method just replace the last bitmap and clear the rest.....please help....!
I make a circle button with states and I want to add shadow to the button like the default buttons how I can do this? Here is my code:
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<shape android:shape="oval">
<solid android:color="#color/yellow_pressed" />
<stroke android:width="1dp" android:color="#fff" />
</shape>
</item>
<item android:state_focused="true">
<shape android:shape="oval">
<solid android:color="#color/yellow_pressed" />
<stroke android:width="1dp" android:color="#fff" />
</shape>
</item>
<item>
<shape android:shape="oval">
<solid android:color="#color/yellow_default" />
<stroke android:width="1dp" android:color="#fff" />
</shape>
</item>
</selector>
Starting from Lollipop, you can use:
public static void setOvalElevationToView(final View view) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP)
view.setOutlineProvider(new ViewOutlineProvider() {
#TargetApi(VERSION_CODES.LOLLIPOP)
#Override
public void getOutline(View view, Outline outline) {
final int size = view.getWidth();
outline.setOval(0, 0, size, size);
}
});
}
You might also need to disable clipping on the parent of the view, so that the shadows won't be clipped