Material effect on button with background color

2020-01-22 13:59发布

问题:

I am using Android v21 support library.

I have created a button with custom background color. The Material design effects like ripple, reveal are gone (except the elevation on click) when I use the back ground color.

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

The following is a normal button and the effects are working just fine.

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

回答1:

When you use android:background, you are replacing much of the styling and look and feel of a button with a blank color.

Update: As of the version 23.0.0 release of AppCompat, there is a new Widget.AppCompat.Button.Colored style which uses your theme's colorButtonNormal for the disabled color and colorAccent for the enabled color.

This allows you apply it to your button directly via

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

If you need a custom colorButtonNormal or colorAccent, you can use a ThemeOverlay as explained in this pro-tip and android:theme on the button.

Previous Answer

You can use a drawable in your v21 directory for your background such as:

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

This will ensure your background color is ?attr/colorPrimary and has the default ripple animation using the default ?attr/colorControlHighlight (which you can also set in your theme if you'd like).

Note: you'll have to create a custom selector for less than 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>

Assuming you have some colors you'd like for the default, pressed, and focused state. Personally, I took a screenshot of a ripple midway through being selected and pulled the primary/focused state out of that.



回答2:

There is another simple solution to provide custom background for "Flat" buttons while keeping their "Material" effects.

  1. Place your button in ViewGroup with desired background set there
  2. set selectableItemBackground from current theme as background for your button (API >=11)

i.e. :

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

Can be used for Flat buttons, it works on API >=11, and you will get ripple effect on >=21 devices, keeping regular buttons on pre-21 till AppCompat is updated to support ripple there as well.

You can also use selectableItemBackgroundBorderless for >=21 buttons only.



回答3:

Here is a simple and backward compatible way to deliver ripple effect to raised buttons with the custom background.

Your layout should look like this

<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@drawable/my_custom_background"
    android:foreground="?android:attr/selectableItemBackground"/>


回答4:

I ran into this problem today actually playing with the v22 library.

Assuming that you're using styles you can set the colorButtonNormal property and the buttons will use that color by default.

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

Outside of that you could still make a style for the button then use that per button if you needed an assortment of colors (haven't tested, just speculating).

Remember to add android: before the item names in your v21 style.



回答5:

With AppCompat (22.1.1+) you can add a style like this:

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

And use it by just applying the style:

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

Programmatically changing the color, I found that the only way to update the color (on API 15 or 16) was to use the 'background tint list' instead. And it doesn't remove the nice radial animation on API 21 devices:

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

Because button.setBackground(...) and button.getBackground().mutate().setColorFilter(...) do not change the button color on API 15 and 16 like they do on API 21.



回答6:

The @ianhanniballake's answer is absolutely correct and simple. But it took me few days to understand. For someone who don't understand his answer, here is more detail implementation

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

===Or===

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


回答7:

I used the backgroundTint and foreground:

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


回答8:

I came to this post looking for a way to have a background color of my ListView Item, yet keep the ripple.

I simply added my color as a background and the selectableItemBackground as a foreground:

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

and it works like a charm. I guess the same technique could be used for buttons as well. Good luck :)



回答9:

Use backgroundTint instead of background



回答10:

If you are interested in ImageButton you can try this simple thing :

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


回答11:

v22.1 of appcompat-v7 introduced some new possibilities. Now it's possible to assign a specific theme only to one view.

Deprecated use of app:theme for styling Toolbar. You can now use android:theme for toolbars on all API level 7 and higher devices and android:theme support for all widgets on API level 11 and higher devices.

So instead of setting the desired color in a global theme, we create a new one and assign it only to the Button

Example:

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

And use this style as theme of your Button

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


回答12:

If you're ok with using a third party library, check out traex/RippleEffect. It allows you to add a Ripple effect to ANY view with just a few lines of code. You just need to wrap, in your xml layout file, the element you want to have a ripple effect with a com.andexert.library.RippleView container.

As an added bonus it requires Min SDK 9 so you can have design consistency across OS versions.

Here's an example taken from the libraries' GitHub repo:

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

You can change the ripple colour by adding this attribute the the RippleView element: app:rv_color="@color/my_fancy_ripple_color



回答13:

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

Use

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

and the color will acept on pre-lolipop



回答14:

There are two approaches explained in the great tutorial be Alex Lockwood: http://www.androiddesignpatterns.com/2016/08/coloring-buttons-with-themeoverlays-background-tints.html:

Approach #1: Modifying the button’s background color w/ a 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"/>

Approach #2: Setting the AppCompatButton’s background tint

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


回答15:

Programmatically applying the colors:

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


回答16:

I tried this:

android:backgroundTint:"@color/mycolor"

instead of changing background property. This does not remove the material effect.