Since Material Design’s implementation in the Android OS, some controls that ship with either the new styles, or with the App Compat packages place some under-the-cover restrictions on what you can do with the control by default. In this example, we will look at updating the App Compat Button Shadows and Elevation that ship with the control.
According to Material Design’s standards, “raised buttons” (versus flat buttons and floating action buttons) should have a resting elevation of 2dp, and an pressed/hover elevation of 8dp.
This principle is also implemented in the App Compat Button
. However, if you try to update the Elevation
of your Button
, you’ll notice that it won’t stay that way on the redraw, but will go right back to the 4dp it is by default.
supportButton.Elevation = 9; // set it directly ViewCompat.SetElevation(supportButton, 9); // set using app compat method ... Console.WriteLine(supportButton.Elevation); // will return 4...
So why is this? And how is Android creating the pressed animation automatically to increase the elevation? It certainly isn’t any code we’ve written. The answer is in the StateListAnimator
property of the Button
. The StateListAnimator
is responsible for setting properties of the Button
during certain states such as Enabled
, Disabled
, Focused
, Pressed
, etc. and is what is overriding the manual set of Button.Elevation
.
You can override this in a few different ways to claim back full control. First, if you want to handle your different different states manually in your code, you can set the StateListAnimator
to a new instance, or null, then set the Elevation
to what you want.
In Code
supportButton.StateListAnimator = new StateListAnimator(); ViewCompat.SetElevation(supportButton, 9); ... Console.WriteLine(supportButton.Elevation); // 9!
The most reusable way to do this is to subclass Button
and set the StateListAnimator
in the constructor:
CustomElevatingButton.cs
public class CustomElevatingButton : Android.Support.V7.Widget.AppCompatButton { public CustomElevatingButton(Context context): base(context) { StateListAnimator = new StateListAnimator(); } }
Using Styles
Alternatively, you can set it using styles for your Button
:
styles.xml
<?xml version="1.0" encoding="UTF-8"?> <resources> <style name="AppTheme" parent="AppTheme.Base"> </style> <style name="AppTheme.Base" parent="Theme.AppCompat.Light.NoActionBar"> <item name="android:buttonStyle">@style/NoShadowButton</item> </style> <style name="NoShadowButton" parent="android:style/Widget.Button"> <item name="android:stateListAnimator">@null</item> </style> </resources>
You can also do it per-button:
styles.xml
<?xml version="1.0" encoding="UTF-8"?> <resources> <style name="AppTheme" parent="AppTheme.Base"> </style> <style name="AppTheme.Base" parent="Theme.AppCompat.Light.NoActionBar"> ... </style> <style name="NoShadowButton" parent="android:style/Widget.Button"> <item name="android:stateListAnimator">@null</item> </style> </resources>
some_layout.axml
... <Button style="@style/NoShadowButton" ... /> ...
In Xamarin.Forms
We can do the same thing in Xamarin.Forms with either a custom renderer or a custom Effect
. In this example, we will create a universal Xamarin.Forms.Button
custom renderer to set an explicit height:
ElevatedButtonRenderer
public class ElevatedButtonRenderer : Xamarin.Forms.Platform.Android.AppCompat.ButtonRenderer { public override void OnElementChanged(ElementChangedEventArgs<Button> e) { StateListAnimator = null; // clear the state list animator Elevation = 9; // set the elevation } }
Creating Your Own StateListAnimator
Of course, instead of clearing the StateListAnimator
and handling your elevation manually, you could create your own to handle the states and animations however you want. Google has documentation included in the discussion about animations here. Here’s an example of creating and applying your own:
anim/reverse_state_list_animator.xml
<!-- animate the elevation property of a view when pressed --> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_pressed="true"> <set> <objectAnimator android:propertyName="elevation" android:duration="@android:integer/config_shortAnimTime" android:valueTo="0dp" android:valueType="floatType"/> <!-- you could have other objectAnimator elements here for "x" and "y", or other properties --> </set> </item> <item android:state_enabled="true" android:state_pressed="false" android:state_focused="true"> <set> <objectAnimator android:propertyName="elevation" android:duration="100" android:valueTo="2dp" android:valueType="floatType"/> </set> </item> </selector>
This animation will do the reverse of the Material Design Standard, and will take the Button
elevation from 2dp to 0dp when pressed.
Now we just need to apply this animation resource to our Button
style either universally or on a specific button:
<?xml version="1.0" encoding="UTF-8"?> <resources> <style name="AppTheme" parent="AppTheme.Base"> </style> <style name="AppTheme.Base" parent="Theme.AppCompat.Light.NoActionBar"> <item name="android:buttonStyle">@style/NoShadowButton</item> </style> <style name="NoShadowButton" parent="android:style/Widget.Button"> <item name="android:stateListAnimator">@anim/reverse_state_list_animator</item> </style> </resources>
Now pressing any button within the AppTheme
will reverse the elevation property and go more “into” the view rather than elevating.
If you like what you see, don’t forget to follow me on twitter @Suave_Pirate, check out my GitHub, and subscribe to my blog to learn more mobile developer tips and tricks!
Interested in sponsoring developer content? Message @Suave_Pirate on twitter for details.
Very awesome !!!!
LikeLike