Efeito material no botão com a cor de fundo

246

Estou usando a biblioteca de suporte do Android v21.

Eu criei um botão com a cor de fundo personalizada. Os efeitos de design do material, como ondulação e revelação, desapareceram (exceto a elevação ao clicar) quando uso a cor de fundo do terreno.

 <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"
 />

fundo A seguir, é um botão normal e os efeitos estão funcionando bem.

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

Botão padrão

Sathesh
fonte
9
Isso ocorre porque você substitui o plano de fundo por todos os bons efeitos de material por uma cor sólida. Quanto à cor adequada de um botão, mantendo os efeitos do Material, ninguém no SO conseguiu descobrir isso ainda.
Nathan Walters
2
@SweetWisher A ondulação e outros efeitos não são aplicados no botão quando altero a cor de fundo.
Sathesh
1
@NathanWalters Mas a cor cinza padrão é feia. :-(
Sathesh
6
Acredite, eu sei ... Estaremos em grande dívida com quem descobrir como fazer isso corretamente.
187 Nathan Walters #
1
Resposta impressionante stackoverflow.com/a/32238489/1318946
Pratik Butani

Respostas:

431

Ao usar android:background, você substitui grande parte do estilo e aparência de um botão por uma cor em branco.

Atualização: a partir da versão 23.0.0 do AppCompat , existe um novo Widget.AppCompat.Button.Coloredestilo que usa os temas colorButtonNormalpara a cor desativada e colorAccentpara a cor ativada.

Isso permite que você o aplique diretamente no botão via

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

Se você precisar de um personalizado colorButtonNormalou colorAccent, poderá usar um ThemeOverlaycomo explicado nesta dica profissional e android:themeno botão.

Resposta Anterior

Você pode usar um drawable em seu diretório v21 para o seu plano de fundo, 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>

Isso garantirá que a cor do plano de fundo seja ?attr/colorPrimarye tenha a animação de ondulação padrão usando o padrão ?attr/colorControlHighlight(que você também pode definir no seu tema, se desejar).

Nota: você precisará criar um seletor personalizado para 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>

Supondo que você tenha algumas cores desejadas para o estado padrão, pressionado e focado. Pessoalmente, tirei uma captura de tela de uma onda no meio da seleção e tirei o estado primário / focado disso.

ianhanniballake
fonte
13
Boa resposta! Exatamente o que eu estava procurando, no entanto, precisava limpar e reconstruir antes que o projeto decidisse identificar o drawable no drawable-v21.
user1354603
4
@ianhanniballake Eu me pergunto como combinar esta técnica de trabalho sem perder cantos arredondados personalização naqueles botão
Joaquin Iurchuk
3
@ianhanniballake apenas um comentário, drawable em ripple sob <item android: drawable ... /> não pode ser uma cor transparente. Nesse caso, a ondulação não será mostrada. Levei um tempo para perceber isso.
Ivan Kušt 26/08/15
2
Com appcompat 23.1.1 e API 22, o estado desativado e ativado assumem a cor colorButtonNormal do tema.
Rémy DAVID
93

Existe outra solução simples para fornecer plano de fundo personalizado para os botões "Planas", mantendo os efeitos "Materiais".

  1. Coloque seu botão no ViewGroup com o fundo desejado definido lá
  2. defina selectableItemBackground do tema atual como plano de fundo para o botão (API> = 11)

ou seja:

<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>

Pode ser usado para botões simples , funciona na API> = 11 e você terá efeito de ondulação em dispositivos = 21, mantendo os botões regulares antes dos 21 anos até que o AppCompat seja atualizado para suportar ondulações também.

Você também pode usar selectableItemBackgroundBorderless somente para> = 21 botões.

kiruwka
fonte
Você literalmente pensou fora da caixa :-) Embora essa seja uma maneira de fazer isso, ela pode aumentar o acoplamento de código e usar estilos seria a maneira recomendada.
Sathesh
3
Isso cria um botão plano com fundo colorido, sim. Mas o botão criado parece um quadrado, sem cantos arredondados e efeitos de sombra.
precisa saber é o seguinte
7
@SanketBerde Exatamente. Porque é isso que é o botão "plano".
precisa saber é o seguinte
2
A pergunta original não mencionou nada. Aliás, se você tiver um pai <CardView> em sua solução em vez de <FrameLayout>, o botão resultante será aumentado e terá sombras. Isso funciona mesmo nas versões pré-pirulito.
precisa saber é o seguinte
1
@SanketBerde ótimo saber sobre a CardViewabordagem, obrigado por compartilhar!
Kiruwka
88

Aqui está uma maneira simples e compatível com versões anteriores de fornecer efeito cascata a botões elevados com o plano de fundo personalizado.

Seu layout deve ficar assim

<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@drawable/my_custom_background"
    android:foreground="?android:attr/selectableItemBackground"/>
Bolein95
fonte
9
Esta é a maneira mais fácil e sem complicações de conseguir isso. Boa resposta.
Tyler Pfaff
1
api 21+, mas não está funcionando. Aplicado no layout linear com fundo clicável e verdadeiro - cor diferente, especificada em primeiro plano.
27416 Alex
3
Como Button é um TextView e não um FrameLayout, foregroundnão tem efeito antes de 23. google.com/…
androidguy
2
LEGAIS! Eu nem sabia que "primeiro plano" era uma coisa!
Eric Schlenz
2
Funciona como um encanto..!
264 Kannan
25

Encontrei esse problema hoje, na verdade, brincando com a biblioteca v22.

Supondo que você esteja usando estilos, você pode definir a colorButtonNormalpropriedade e os botões usarão essa cor por padrão.

<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>

Fora isso, você ainda pode criar um estilo para o botão e usá-lo por botão se precisar de uma variedade de cores (não testei, apenas especulei).

Lembre-se de adicionar android:antes dos nomes dos itens no seu estilo v21 .

Robert Rose
fonte
Para obter uma variedade de botões coloridos, consulte a resposta de Robert: stackoverflow.com/a/31825439/1617737
ban-
Com a cor de fundo agora definida, há também um atributo geral que posso usar no bloco de código acima para especificar a cor do texto do botão?
ban-geoengineering
24

Com AppCompat (22.1.1+), você pode adicionar um estilo como este:

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

E use-o apenas aplicando o estilo:

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

Alterando programaticamente a cor, descobri que a única maneira de atualizar a cor (na API 15 ou 16) era usar a 'lista de tonalidades de fundo'. E não remove a bela animação radial nos dispositivos da API 21:

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

Porque button.setBackground(...)e button.getBackground().mutate().setColorFilter(...)não altere a cor do botão na API 15 e 16 como na API 21.

Robert
fonte
1
Muito obrigado, funcionou perfeitamente para mim (mesmo usando Button em layouts XML como AppCompat inflater substitui-lo com AppCompatButton)
Louis CAD
1
Use ColorStateList.valueOf( ... )para construir um simples ColorStateListde apenas uma cor.
Taig
colorAccent e rippleColor não funcionam no estilo
Yar
Obrigado por este post. O que funcionou para mim: ViewCompat.setBackgroundTintList(myButton, ColorStateList.valueOf(myColor); Javadoc para setSupportBackgroundTintListdiz que deve ser assim chamado.
JerabekJakub
22

A resposta do @ianianniballake é absolutamente correta e simples. Mas levei alguns dias para entender. Para alguém que não entende sua resposta, aqui está uma implementação mais detalhada

<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>

=== Ou ===

<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
fonte
5
Sua resposta foi a última peça do quebra-cabeça para mim. Pode-se também escrever colorButtonNormalno tema base @style/AppTheme. Isso fornece uma cor padrão ao AppCompat para colorir os botões adequadamente. E então você pode usar sua resposta para criar um tema individualmente.
Siddharth Pant
1
É android:theme="Theme.MaterialButton"orandroid:theme="@style/Theme.MaterialButton"
osrl 14/11/16
1
@osrl É android:theme="@style/Theme.MaterialButton". Obrigado pela sua correção.
Frank Nguyen
12

Eu usei o backgroundTint e o primeiro 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
fonte
afaik não há necessidade para o atributo de primeiro plano como backgroundTint não substitui o ondulação padrão
Julian Os
7

Eu vim para este post procurando uma maneira de ter uma cor de plano de fundo do meu ListViewitem, mas manter a ondulação.

Simplesmente adicionei minha cor como plano de fundo e o selectableItemBackground como primeiro 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>

E funciona como um encanto. Eu acho que a mesma técnica também pode ser usada para botões. Boa sorte :)

Joakim
fonte
6

Se você está interessado no ImageButton, pode tentar o seguinte:

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

v22.1de appcompat-v7introduziu algumas novas possibilidades. Agora é possível atribuir um tema específico apenas a uma visualização.

Uso preterido do app: tema para estilizar a Barra de Ferramentas. Agora você pode usar o android: theme para barras de ferramentas em todos os dispositivos API nível 7 e superiores e o suporte android: theme para todos os widgets nos dispositivos API level 11 e superiores.

Portanto, em vez de definir a cor desejada em um tema global, criamos um novo e atribuímos apenas ao Button

Exemplo:

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

E use esse estilo como tema do seu 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
fonte
4

Se você estiver bem com o uso de uma biblioteca de terceiros, consulte traex / RippleEffect . Permite adicionar um efeito cascata a QUALQUER visualização com apenas algumas linhas de código. Você só precisa agrupar, em seu arquivo de layout xml, o elemento que deseja que tenha um efeito cascata com um com.andexert.library.RippleViewcontêiner.

Como um bônus adicional, requer o SDK 9 mínimo, para que você possa ter consistência de design nas versões do sistema operacional.

Aqui está um exemplo retirado do repositório GitHub das 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>

Você pode alterar a cor da ondulação adicionando esse atributo ao elemento RippleView: app:rv_color="@color/my_fancy_ripple_color

guy.gc
fonte
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"/>

Usar

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

e a cor será aceita no pré-lolipop

André Bäuml
fonte
2

Há duas abordagens explicadas no ótimo tutorial: Alex Lockwood: http://www.androiddesignpatterns.com/2016/08/coloring-buttons-with-themeoverlays-background-tints.html :

Abordagem 1: Modificando a cor de fundo do botão com um ThemeOverlay

<!-- 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"/>

Abordagem nº 2: definindo a tonalidade de segundo plano do 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
fonte
2

Aplicando programaticamente as cores:

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
fonte
-1

Eu tentei isso:

android:backgroundTint:"@color/mycolor"

em vez de alterar a propriedade em segundo plano. Isso não remove o efeito material.

shiladitya
fonte
Isso não funcionará para dispositivos pré-pirulito (70% de participação de mercado). Na verdade, ele foi suportado, mas o Android Studio ainda mostra erros ao usá-lo sem o prefixo do Android.
Siddharth Pant