Efecto material en el botón con color de fondo

246

Estoy usando la biblioteca de soporte de Android v21.

He creado un botón con color de fondo personalizado. Los efectos de diseño de material como ondulación, revelación desaparecen (excepto la elevación al hacer clic) cuando uso el color de fondo.

 <Button
 style="?android:attr/buttonStyleSmall"
 android:background="?attr/colorPrimary"
 android:textColor="@color/white"
 android:textAllCaps="true"
 android:layout_width="fill_parent"
 android:layout_height="wrap_content"
 android:text="Button1"
 />

Antecedentes El siguiente es un botón normal y los efectos funcionan bien.

<Button
 style="?android:attr/buttonStyleSmall"
 android:layout_width="fill_parent"
 android:layout_height="wrap_content"
 android:textAllCaps="true"
 android:text="Button1"
/>

Botón predeterminado

Sathesh
fuente
99
Esto ocurre porque reemplaza el fondo con todos los agradables efectos de Material con un color sólido. En cuanto a cómo colorear correctamente un botón mientras se mantienen los efectos de Material, nadie en SO ha podido resolver eso todavía.
Nathan Walters el
2
@SweetWisher La ondulación y otros efectos no se aplican en el botón cuando cambio el color de fondo.
Sábado
1
@NathanWalters Pero el color gris predeterminado es feo. :-(
Sábado
66
Créeme, lo sé ... Tendremos una gran deuda con quien descubra cómo hacer esto correctamente.
Nathan Walters el
1
Awesome answer stackoverflow.com/a/32238489/1318946
Pratik Butani

Respuestas:

431

Cuando lo usa android:background, está reemplazando gran parte del estilo y la apariencia de un botón con un color en blanco.

Actualización: a partir de la versión 23.0.0 de AppCompat , hay un nuevo Widget.AppCompat.Button.Coloredestilo que utiliza el tema colorButtonNormalpara el color deshabilitado y colorAccentpara el color habilitado.

Esto le permite aplicarlo a su botón directamente a través de

<Button
  ...
  style="@style/Widget.AppCompat.Button.Colored" />

Si necesita una colorButtonNormalo personalizada colorAccent, puede usar una ThemeOverlaycomo se explica en este consejo profesional y android:themeen el botón.

Respuesta anterior

Puede usar un dibujable en su directorio v21 para su fondo como:

<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
        android:color="?attr/colorControlHighlight">
    <item android:drawable="?attr/colorPrimary"/>
</ripple>

Esto asegurará que su color de fondo sea ?attr/colorPrimaryy tenga la animación de ondulación predeterminada usando la predeterminada ?attr/colorControlHighlight(que también puede establecer en su tema si lo desea).

Nota: deberá crear un selector personalizado por menos de v21:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@color/primaryPressed" android:state_pressed="true"/>
    <item android:drawable="@color/primaryFocused" android:state_focused="true"/>
    <item android:drawable="@color/primary"/>
</selector>

Suponiendo que tiene algunos colores que desea para el estado predeterminado, presionado y enfocado. Personalmente, tomé una captura de pantalla de una onda a mitad de camino de ser seleccionado y saqué el estado primario / enfocado de eso.

ianhanniballake
fuente
13
¡Buena respuesta! Exactamente lo que estaba buscando, sin embargo, necesitaba limpiar y reconstruir antes de que el proyecto decidiera identificar el dibujo en drawable-v21.
user1354603
44
@ianhanniballake Me pregunto cómo combinar esta técnica de trabajo sin perder la personalización de las esquinas redondeadas en ese botón
Joaquin Iurchuk
3
@ianhanniballake solo un comentario, dibujable en ondulación debajo de <item android: drawable ... /> no puede ser un color transparente. En ese caso, no se mostrará la ondulación. Me tomó un tiempo darme cuenta de eso.
Ivan Kušt
2
Con appcompat 23.1.1 y API 22, el estado deshabilitado y habilitado toman el color colorButtonNormal del tema.
Rémy DAVID
93

Hay otra solución simple para proporcionar un fondo personalizado para los botones "Planos" mientras se mantienen sus efectos "Materiales".

  1. Coloque su botón en ViewGroup con el fondo deseado establecido allí
  2. establezca selectableItemBackground del tema actual como fondo para su botón (API> = 11)

es decir:

<FrameLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@color/blue">
    <Button
        style="?android:attr/buttonStyleSmall"
        android:background="?android:attr/selectableItemBackground"
        android:textColor="@android:color/white"
        android:textAllCaps="true"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Button1"
        />
</FrameLayout>

Se puede usar para botones planos , funciona en API> = 11, y obtendrá un efecto dominó en> = 21 dispositivos, manteniendo los botones regulares en pre-21 hasta que AppCompat se actualice para admitir la ondulación allí también.

También puede usar selectableItemBackgroundBorderless para> = 21 botones solamente.

kiruwka
fuente
Literalmente pensó de inmediato :-) Aunque esta es una forma de hacerlo, podría aumentar el acoplamiento de código y usar estilos sería la forma recomendada.
Sábado
3
Esto crea un botón plano con fondo de color, sí. Pero el botón creado parece un cuadrado, sin esquinas redondeadas y efectos de sombra.
Sanket Berde
77
@SanketBerde Exactamente. Porque eso es el botón "plano".
kiruwka
2
La pregunta original no mencionaba plana. por cierto, si tiene un padre <CardView> en su solución en lugar de <FrameLayout>, el botón resultante se levantará y tendrá sombras. Esto incluso funciona en versiones pre-lollipop.
Sanket Berde
1
@SanketBerde genial saber sobre el CardViewenfoque, ¡gracias por compartir!
kiruwka
88

Aquí hay una manera simple y compatible con versiones anteriores para proporcionar efectos de onda a los botones elevados con el fondo personalizado.

Su diseño debería verse así

<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@drawable/my_custom_background"
    android:foreground="?android:attr/selectableItemBackground"/>
Bolein95
fuente
99
Esta es la forma más fácil y sencilla de lograr esto. Buena respuesta.
Tyler Pfaff
1
API 21+ pero no funciona. Aplicado en diseño lineal con verdadero clic en fondo, color diferente, primer plano especificado.
Alex
3
Dado que Button es TextView y no FrameLayout, foregroundno tiene efecto antes del 23. google.com/…
androidguy
2
¡BONITO! ¡Ni siquiera sabía que "primer plano" era una cosa!
Eric Schlenz
2
Funciona de maravilla..!
Kannan
25

Me encontré con este problema hoy jugando con la biblioteca v22.

Suponiendo que está usando estilos, puede establecer la colorButtonNormalpropiedad y los botones usarán ese color por defecto.

<style name="AppTheme" parent="BaseTheme">
    <!-- Customize your theme here. -->
    <item name="colorPrimary">@color/primaryColor</item>
    <item name="colorPrimaryDark">@color/primaryColorDark</item>
    <item name="colorAccent">@color/accentColor</item>
    <item name="colorButtonNormal">@color/primaryColor</item>
</style>

Fuera de eso, aún puede crear un estilo para el botón y luego usarlo por botón si necesita una variedad de colores (no lo he probado, solo especulando).

Recuerde agregar android:antes de los nombres de los elementos en su estilo v21 .

Robert Rose
fuente
Para ver una variedad de botones de colores, vea la respuesta de otro Robert: stackoverflow.com/a/31825439/1617737
prohibición de geoingeniería el
Con el color de fondo ahora configurado, ¿hay también un atributo general que pueda usar en el bloque de código anterior para especificar el color del texto del botón?
prohibición de geoingeniería el
24

Con AppCompat (22.1.1+) puede agregar un estilo como este:

<style name="MyGreenButton">
    <item name="colorButtonNormal">#009900</item>
</style>

Y úsalo simplemente aplicando el estilo:

<android.support.v7.widget.AppCompatButton
    style="@style/MyGreenButton"
    android:layout_width="match_width"
    android:layout_height="wrap_content"
    android:text="A Green Button"
    />

Cambiando programáticamente el color, descubrí que la única forma de actualizar el color (en API 15 o 16) era usar la 'lista de tonos de fondo' en su lugar. Y no elimina la agradable animación radial en dispositivos API 21:

ColorStateList colorStateList = new ColorStateList(new int[][] {{0}}, new int[] {0xFF009900}); // 0xAARRGGBB
button.setSupportBackgroundTintList(colorStateList);

Porque button.setBackground(...)y button.getBackground().mutate().setColorFilter(...)no cambie el color del botón en API 15 y 16 como lo hacen en API 21.

Robert
fuente
1
Muchas gracias, funcionó perfectamente para mí (incluso si usa Button en diseños xml cuando el inflador AppCompat lo reemplaza con AppCompatButton)
Louis CAD
1
Úselo ColorStateList.valueOf( ... )para construir un simple ColorStateListde un solo color.
Taig
colorAccent y rippleColor no funcionan en el estilo
Yar
Gracias por este post Lo que funcionó para mí: ViewCompat.setBackgroundTintList(myButton, ColorStateList.valueOf(myColor); Javadoc setSupportBackgroundTintListdice que debería llamarse de esta manera.
JerabekJakub
22

La respuesta de @ianhanniballake es absolutamente correcta y simple. Pero me llevó unos días entenderlo. Para alguien que no entiende su respuesta, aquí hay una implementación más detallada

<Button
        android:id="@+id/btn"
        style="@style/MaterialButton"
        ... />


<style name="MaterialButton" parent="Widget.AppCompat.Button.Colored">
    <item name="android:theme">@style/Theme.MaterialButton</item>
   ...
</style>


<style name="Theme.MaterialButton" parent="YourTheme">
    <item name="colorAccent">@color/yourAccentColor</item>
    <item name="colorButtonNormal">@color/yourButtonNormalColor</item>
</style>

=== O ===

<Button
        android:id="@+id/btn"
        style="@style/Widget.AppCompat.Button.Colored"
        android:theme="@style/Theme.MaterialButton" />

<style name="Theme.MaterialButton" parent="YourTheme">
    <item name="colorAccent">@color/yourAccentColor</item>
    <item name="colorButtonNormal">@color/yourButtonNormalColor</item>
</style>
Frank Nguyen
fuente
55
Tu respuesta fue la última pieza del rompecabezas para mí. También se puede escribir colorButtonNormalen el tema base @style/AppTheme. Esto le da un color predeterminado a AppCompat para colorear los botones en consecuencia. Y luego puede usar su respuesta para tema una vista individualmente.
Siddharth Pant
1
Es android:theme="Theme.MaterialButton"oandroid:theme="@style/Theme.MaterialButton"
osrl
1
@osrl es android:theme="@style/Theme.MaterialButton". Gracias por tu corrección.
Frank Nguyen
12

Usé el backgroundTint y el primer plano:

<Button
    android:layout_width="match_parent"
    android:layout_height="40dp"
    android:backgroundTint="@color/colorAccent"
    android:foreground="?android:attr/selectableItemBackground"
    android:textColor="@android:color/white"
    android:textSize="10sp"/>
SpyZip
fuente
afaik no hay necesidad del atributo de primer plano ya que backgroundTint no anula la onda estándar
Julian Os
7

Vine a esta publicación buscando una manera de tener un color de fondo de mi ListView artículo, pero manteniendo la onda.

Simplemente agregué mi color como fondo y seleccionableItemBackground como primer plano:

<style name="my_list_item">
    <item name="android:background">@color/white</item>
    <item name="android:foreground">?attr/selectableItemBackground</item>
    <item name="android:layout_height">@dimen/my_list_item_height</item>
</style>

Y funciona como un encanto. Supongo que la misma técnica podría usarse para los botones también. Buena suerte :)

Joakim
fuente
6

Si está interesado en ImageButton, puede probar esto:

<ImageButton
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:src="@android:drawable/ic_button"
    android:background="?attr/selectableItemBackgroundBorderless"
/>
Ameer Khalifullah
fuente
5

v22.1de appcompat-v7introdujo algunas nuevas posibilidades. Ahora es posible asignar un tema específico solo a una vista.

Uso obsoleto de la aplicación: tema para el diseño de la barra de herramientas. Ahora puede usar android: theme para barras de herramientas en todos los dispositivos API nivel 7 y superior y android: tema de soporte para todos los widgets en dispositivos API nivel 11 y superior.

Entonces, en lugar de establecer el color deseado en un tema global, creamos uno nuevo y lo asignamos solo a Button

Ejemplo:

<style name="MyColorButton" parent="Theme.AppCompat.Light.DarkActionBar">
        <item name="colorButtonNormal">@color/myColor</item>
</style>

Y usa este estilo como tema de tu Button

<Button
 style="?android:attr/buttonStyleSmall"
 android:layout_width="fill_parent"
 android:layout_height="wrap_content"
 android:text="Button1"
 android:theme="@style/MyColorButton"/>
Bhargav Thanki
fuente
4

Si está de acuerdo con el uso de una biblioteca de terceros, consulte traex / RippleEffect . Le permite agregar un efecto dominó a CUALQUIER vista con solo unas pocas líneas de código. Solo necesita envolver, en su archivo de diseño xml, el elemento que desea que tenga un efecto dominó con un com.andexert.library.RippleViewcontenedor.

Como beneficio adicional, requiere Min SDK 9 para que pueda tener consistencia de diseño en todas las versiones del sistema operativo.

Aquí hay un ejemplo tomado del repositorio de GitHub de las bibliotecas:

<com.andexert.library.RippleView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:rv_centered="true">

    <ImageView
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:src="@android:drawable/ic_menu_edit"
        android:background="@android:color/holo_blue_dark"/> 

</com.andexert.library.RippleView>

Puede cambiar el color de ondulación agregando este atributo al elemento RippleView: app:rv_color="@color/my_fancy_ripple_color

guy.gc
fuente
3
<android.support.v7.widget.AppCompatButton
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:backgroundTint="#fff"
    android:textColor="#000"
    android:text="test"/>

Utilizar

xmlns:app="http://schemas.android.com/apk/res-auto"

y el color se aceptará en pre-lolipop

André Bäuml
fuente
2

Hay dos enfoques explicados en el gran tutorial de Alex Lockwood: http://www.androiddesignpatterns.com/2016/08/coloring-buttons-with-themeoverlays-background-tints.html :

Enfoque n. ° 1: modificación del color de fondo del botón con una superposición de temas

<!-- res/values/themes.xml -->
<style name="RedButtonLightTheme" parent="ThemeOverlay.AppCompat.Light">
    <item name="colorAccent">@color/googred500</item>
</style>

<Button
    style="@style/Widget.AppCompat.Button.Colored"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:theme="@style/RedButtonLightTheme"/>

Enfoque n. ° 2: Configuración del tinte de fondo de AppCompatButton

<!-- res/color/btn_colored_background_tint.xml -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <!-- Disabled state. -->
    <item android:state_enabled="false"
          android:color="?attr/colorButtonNormal"
          android:alpha="?android:attr/disabledAlpha"/>

    <!-- Enabled state. -->
    <item android:color="?attr/colorAccent"/>

</selector>

<android.support.v7.widget.AppCompatButton
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:backgroundTint="@color/btn_colored_background_tint"/>
makovkastar
fuente
2

Aplicando programáticamente los colores:

if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {

    ColorStateList colorStateListRipple = new ColorStateList(
            new int[][] {{0}},
            new int[] {Color.WHITE} // ripple color
            );

    RippleDrawable rippleDrawable = (RippleDrawable) myButton.getBackground();
    rippleDrawable.setColor(colorStateListRipple);
    myButton.setBackground(rippleDrawable); // applying the ripple color
}

ColorStateList colorStateList = new ColorStateList(
        new int[][]{
                new int[]{android.R.attr.state_pressed}, // when pressed
                new int[]{android.R.attr.state_enabled}, // normal state color
                new int[]{} // normal state color
        },
        new int[]{
                Color.CYAN, // when pressed
                Color.RED, // normal state color
                Color.RED // normal state color
        }
);

ViewCompat.setBackgroundTintList(myButton, colorStateList); // applying the state colors
Marlon
fuente
-1

Intenté esto:

android:backgroundTint:"@color/mycolor"

en lugar de cambiar la propiedad de fondo. Esto no elimina el efecto material.

shiladitya
fuente
Esto no funcionará para dispositivos pre-Lollipop (70% de cuota de mercado). En realidad, se ha respaldado, pero Android Studio aún muestra errores al usarlo sin el prefijo de Android.
Siddharth Pant