Android, setting background color of button loses ripple effect
Asked Answered
G

7

39

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 solutions but I couldn't find a definite one that wasn't ambiguous.

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                xmlns:tools="http://schemas.android.com/tools"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                tools:context=".ClockInOutFragment">


    <AnalogClock
        android:id="@+id/clock"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentEnd="true"
        android:layout_alignParentRight="true"
        android:layout_alignParentTop="true"
        android:layout_toRightOf="@+id/date_and_time"/>


    <RelativeLayout
        android:id="@+id/date_and_time"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="10dp">

        <TextView
            android:id="@+id/time_digits"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="12:10"
            android:textSize="45sp"/>

        <TextView
            android:id="@+id/am_pm"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignBaseline="@+id/time_digits"
            android:layout_toRightOf="@+id/time_digits"
            android:paddingLeft="3dp"
            android:text="PM"
            android:textSize="20sp"/>

        <TextView
            android:id="@+id/date"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@+id/time_digits"
            android:text="Mon, July 11"
            android:textSize="22sp"/>
    </RelativeLayout>

    <!--Clock in and out buttons-->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:orientation="horizontal">

        <Button
            android:id="@+id/textView3"
            android:layout_width="0dp"
            android:layout_height="56dp"
            android:layout_weight="1"
            android:background="#4CAF50"
            android:gravity="center"
            android:text="Clock In"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:textColor="#FFFFFF"/>

        <!--Divider between the clock in and out button-->
        <View
            android:layout_width="1dp"
            android:layout_height="match_parent"
            android:background="#B6B6B6"/>

        <Button
            android:id="@+id/textView4"
            android:layout_width="0dp"
            android:layout_height="56dp"
            android:layout_weight="1"
            android:background="#FF5252"
            android:gravity="center"
            android:text="Clock Out"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:textColor="#FFFFFF"/>
    </LinearLayout>


</RelativeLayout>
Gay answered 12/7, 2016 at 11:10 Comment(0)
P
54

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>
Peaceable answered 12/7, 2016 at 11:14 Comment(3)
how android differentiates which one is the mask and which one is the default color?Integer
By id. For example - android:id="@android:id/mask"Cutie
And this works for changing menu background with retaining ripple effect alsoPersas
M
45

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"/>
Moyna answered 15/12, 2017 at 9:37 Comment(4)
Nice. However, backward compatibility is not supported under version 23.Irrigate
How to change the color of the effect?Phonology
This doesn't work for curved buttons using style="@style/Widget.AppCompat.Button.Colored" The result is the shadow has sharp corners, not curved corners.Hadfield
Works good with Widget.AppCompat.Button.BorderlessSyllepsis
G
16

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.

Geraint answered 12/7, 2016 at 11:16 Comment(0)
W
11

There was no answer for MaterialButton, so i will put it here.

For MaterialButton (com.google.android.material.button.MaterialButton), use 'backgroundTint' and 'rippleColor'.

<com.google.android.material.button.MaterialButton
    android:id="@+id/button_sign_in"
    android:layout_width="0dp"
    android:layout_height="55dp"
    app:backgroundTint="@android:color/white"
    app:rippleColor="?attr/colorControlHighlight"

?attr/colorControlHighlight is the default ripple color, you can change this value.

Weekend answered 8/3, 2020 at 15:5 Comment(0)
O
8

Just use :

android:backgroundTint="#4CAF50"

Instead of:

android:background="#4CAF50"

Don't forget to change your Button to android.support.v7.widget.AppCompatButton

Overcapitalize answered 29/6, 2017 at 15:40 Comment(1)
This doesn't work with androidx.appcompat.widget.AppCompatButtonSyllepsis
S
1

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.

Sprinkle answered 25/5, 2019 at 22:17 Comment(0)
A
-1

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"/>
Aldose answered 26/10, 2018 at 4:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.