After adding color to an android button, it loses its ripple effect that makes the user feel like there is a responsive click. How do I fix this? I\'ve searched through many
You can add the ripple effect & background color with an additionnal ripple drawable:
your layout :
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/button_connect"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dip"
android:fontFamily="sans-serif"
android:text="Connect"
android:background="@drawable/ripple"
android:textColor="#FFFFFF"
android:textSize="18sp" />
</LinearLayout>
ripple.xml (this is where you can add background color in addition to the ripple effect) :
<?xml version="1.0" encoding="utf-8"?>
<!-- in drawable folder-->
<ripple
xmlns:android="http://schemas.android.com/apk/res/android"
android:color="?android:colorControlHighlight">
<item android:id="@android:id/mask">
<shape android:shape="rectangle">
<solid android:color="?android:colorAccent" />
</shape>
</item>
<item>
<shape android:shape="rectangle">
<!-- put your background color here-->
<solid android:color="@color/default_color" />
</shape>
</item>
</ripple>
Actually, you can use <layer-list>
of drawables to combine ripple effect with any other drawable. This is a universal solution also for pre-lolipop: I've tested it in many configurations.
The only problem is that pre-lolipop crashes when ?selectableItemBackground
appears inside <layer-list>
, so we have to create LayerDrawable programmatically.
A very fast simple solution looks like:
Specify for your View
android:background="?selectableItemBackground"
Then anywhere in the code create mySpecialDrawable and do the trick:
Drawable[] layers = {mySpecialDrawable, getBackground()};
setBackgroundDrawable(new LayerDrawable(layers).mutate());
Please note that .mutate()
for LayeredDrawable is essential here!
A more complex solution could be useful when you already have your custom View and prefer rather extend its functionality and compatibility than add extra empty FrameLayout as a parent.
Inside attrs.xml put:
<resources>
<declare-styleable name="MyView">
<attr name="selectableBackground" format="reference"/>
<attr name="backgroundDrawable" format="reference"/>
</declare-styleable>
</resources>
then inside your View-descendant class:
private Drawable selectableBackground;
private Drawable backgroundDrawable;
public MyView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
try {
TypedArray attributeArray;
attributeArray = context.obtainStyledAttributes(attrs, R.styleable.MyView);
int id = attributeArray.getResourceId(R.styleable.MyView_selectableBackground, -1);
if (id != -1) {
selectableBackground = ResourcesCompat.getDrawable(getResources(), id, context.getTheme());
}
id = attributeArray.getResourceId(R.styleable.MyView_backgroundDrawable, -1);
if (id != -1) {
backgroundDrawable = ResourcesCompat.getDrawable(getResources(), id, context.getTheme());
}
constructBackground();
attributeArray.recycle();
} catch (Exception e) {
Log.e(this.toString(), "Attributes initialization error", e);
throw e;
}
}
void setSelectableBackground(Drawable drawable) {
selectableBackground = drawable;
constructBackground();
}
void setDrawable(Drawable drawable) {
backgroundDrawable = drawable;
constructBackground();
}
private void constructBackground() {
if (selectableBackground != null) {
if (backgroundDrawable != null) {
Drawable[] layers = {backgroundDrawable, selectableBackground};
setBackgroundDrawable(new LayerDrawable(layers).mutate()); // Both, using layers
} else setBackgroundDrawable(selectableBackground); // Selectable only
} else setBackgroundDrawable(backgroundDrawable); // Background only or null
}
I prefer this approach because it has no issues like android:foreground
attribute which is 23+ or extra overhead of enclosing clickable views inside FrameLayout.
You should use styles
<style parent="Theme.AppCompat.Light" name="RaisedButtonGreen">
<item name="colorButtonNormal">@color/green</item>
<item name="colorControlHighlight">@color/greenLight</item>
<item name="android:textColor">@android:color/white</item>
</style>
this is the optimal solution
<Button
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Button with ripple for style"
android:theme="@style/RaisedButtonGreen"/>
A very simple and straight forward way of doing this is to set ?attr/selectableItemBackground
to android:foreground
attribute of your button. Following xml is perfectly valid and works
<Button
android:id="@+id/btn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/white"
android:foreground="?attr/selectableItemBackground"/>
Just use :
android:backgroundTint="#4CAF50"
Instead of:
android:background="#4CAF50"
Don't forget to change your Button
to android.support.v7.widget.AppCompatButton
Don't change the background of Button. Change the theme.
<style name="ButtonGray">
<item name="colorButtonNormal">@color/gray</item>
</style>
and in your xml file
<Button
android:id="@+id/accept_button"
android:layout_height="wrap_content"
android:layout_width="0dp"
android:layout_weight="1"
android:text="@string/button_accept_group"
android:theme="@style/ButtonGray"/>
Or you can add it in your main app theme
<style name="AppTheme"
parent="Theme.AppCompat.Light.NoActionBar">
<item name="colorButtonNormal">@color/primary_color</item>
</style>
And don't need change button background.
If you want totally custom background you need create your selector. And you can set there ripple effect.