Skip to content

Instantly share code, notes, and snippets.

@DarkIrata
Created April 26, 2023 12:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save DarkIrata/3918afe7af8ad4731a2747863ac3c15a to your computer and use it in GitHub Desktop.
Save DarkIrata/3918afe7af8ad4731a2747863ac3c15a to your computer and use it in GitHub Desktop.
Android Rippleeffect and Clickable StackLayout
// Code
// Example for ImageButton Background Rippleeffect
Microsoft.Maui.Handlers.ImageButtonHandler.Mapper.AppendToMapping(nameof(IView.Background), (handler, _) =>
{ // Todo: Needs Update after Theme Change so it uses the different Color
#if ANDROID
var contextResourceColor = AndroidX.Core.Content.ContextCompat.GetColor(handler.MauiContext.Context, Resource.Color.ripple_material_light);
var rippleColor = new Android.Graphics.Color(contextResourceColor);
var rippleDrawable = RippleDrawableHelper.GetRippleDrawableByStates(rippleColor);
handler.PlatformView.SetBackground(rippleDrawable);
#endif
});
// Code
using Android.Content;
using Android.Content.Res;
using Android.Graphics.Drawables;
using Microsoft.Maui.Platform;
using AndroidGraphics = Android.Graphics;
// Add in Maui Project to Platforms/Android
using AndroidResource = Android.Resource;
using AndroidUtil = Android.Util;
namespace IPUP.Maui.Android.Effects
{
public static class RippleDrawableHelper
{
private static int[][] GetStates(int[] rawStates)
{
if (rawStates == null || rawStates.Length == 0)
{
throw new ArgumentException("Possible States cant be empty");
}
return rawStates.Select(s => new int[] { s }).ToArray();
}
private static int[] GetColors(AndroidGraphics.Color[] rawColors)
{
if (rawColors == null || rawColors.Length == 0)
{
throw new ArgumentException("Colors cant be empty");
}
return rawColors.Select(c => (int)c).ToArray();
}
public static RippleDrawable GetRippleDrawableByStates(AndroidGraphics.Color pressedColor)
=> GetRippleDrawableByStates(new Dictionary<int, AndroidGraphics.Color> {
{ AndroidResource.Attribute.StatePressed, pressedColor },
{ AndroidResource.Attribute.StateFocused, Color.FromArgb("#00000000").ToPlatform() },
});
public static RippleDrawable GetRippleDrawableByStates(Dictionary<int, AndroidGraphics.Color> colorToStateMapping)
{
var states = GetStates(colorToStateMapping.Keys.ToArray());
var colors = GetColors(colorToStateMapping.Values.ToArray());
var colorStateList = new ColorStateList(states, colors);
return new RippleDrawable(colorStateList, null, null);
}
public static Drawable? GetRippleDrawableFromAttribute(Context context, int attribute = AndroidResource.Attribute.SelectableItemBackground)
{
AndroidGraphics.Drawables.Drawable? drawable = null;
if (context != null)
{
using (var typedValue = new AndroidUtil.TypedValue())
{
context.Theme!.ResolveAttribute(attribute, typedValue, true);
var resourceId = typedValue.ResourceId;
drawable = context.Theme!.GetDrawable(resourceId);
}
}
return drawable;
}
}
}
// Effect is only implemented for Android
using System.Windows.Input;
using Microsoft.Maui.Platform;
#if ANDROID
using IPUP.Maui.Android.Effects;
using AndroidDrawable = Android.Graphics.Drawables.Drawable;
#endif
namespace IPUP.Maui.Controls
{
public class StackLayoutButton : StackLayout, IDisposable
{
private IElementHandler? handler;
private bool clickSubscribed = false;
public static BindableProperty NativeAnimationProperty = BindableProperty.CreateAttached(
propertyName: nameof(NativeAnimation),
returnType: typeof(bool),
declaringType: typeof(StackLayoutButton),
defaultValue: false,
defaultBindingMode: BindingMode.OneWay,
propertyChanged: NativeAnimationPropertyChanged);
public static BindableProperty TappedCommandProperty = BindableProperty.CreateAttached(
propertyName: nameof(TappedCommand),
returnType: typeof(ICommand),
declaringType: typeof(StackLayoutButton),
defaultValue: null,
defaultBindingMode: BindingMode.OneWay,
propertyChanged: TappedCommandPropertyChanged);
public static BindableProperty TappedCommandParameterProperty = BindableProperty.CreateAttached(
propertyName: nameof(TappedCommandParameter),
returnType: typeof(object),
declaringType: typeof(StackLayoutButton),
defaultValue: null,
defaultBindingMode: BindingMode.OneWay);
public bool NativeAnimation
{
get => (bool)this.GetValue(NativeAnimationProperty);
set => this.SetValue(NativeAnimationProperty, value);
}
public ICommand TappedCommand
{
get => (ICommand)this.GetValue(TappedCommandProperty);
set => this.SetValue(TappedCommandProperty, value);
}
public object TappedCommandParameter
{
get => this.GetValue(TappedCommandParameterProperty);
set => this.SetValue(TappedCommandParameterProperty, value);
}
public StackLayoutButton()
{
this.Unloaded += this.StackLayoutButton_Unloaded;
}
private void StackLayoutButton_Unloaded(object? sender, EventArgs e)
{
this.Dispose();
}
protected override void OnHandlerChanging(HandlerChangingEventArgs args)
{
base.OnHandlerChanging(args);
this.handler = args.NewHandler;
}
protected override void OnHandlerChanged()
{
base.OnHandlerChanged();
this.SetBackgroundToNativeAnimation();
this.HandleClickEventSubscribtion();
}
private void HandleClickEventSubscribtion(bool forceUnsubscribe = false)
{
#if ANDROID
if (this.handler != null && this.handler!.PlatformView is LayoutViewGroup viewGroup)
{
viewGroup.Clickable = this.TappedCommand?.CanExecute(this.TappedCommandParameter) ?? true;
if ((this.clickSubscribed && this.TappedCommand == null) || forceUnsubscribe)
{
viewGroup.Click -= this.ViewGroup_Click;
this.clickSubscribed = false;
}
if (!this.clickSubscribed && this.TappedCommand != null && !forceUnsubscribe)
{
viewGroup.Click += this.ViewGroup_Click;
this.clickSubscribed = true;
}
}
#endif
}
private void ViewGroup_Click(object? sender, EventArgs e)
{
if (this.TappedCommand?.CanExecute(this.TappedCommandParameter) ?? true)
{
this.TappedCommand?.Execute(this.TappedCommandParameter);
}
}
private void SetBackgroundToNativeAnimation()
{
#if ANDROID
if (this.handler != null && this.NativeAnimation && this.handler!.PlatformView is LayoutViewGroup viewGroup)
{
AndroidDrawable? drawable = RippleDrawableHelper.GetRippleDrawableFromAttribute(this.handler.MauiContext.Context);
viewGroup.Clickable = true;
viewGroup.Focusable = true;
viewGroup.Background = drawable;
}
#endif
}
private static void NativeAnimationPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
if (bindable is StackLayoutButton button)
{
button.SetBackgroundToNativeAnimation();
}
}
private static void TappedCommandPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
if (bindable is StackLayoutButton button)
{
button.TappedCommand.CanExecuteChanged -= button.TappedCommand_CanExecuteChanged;
button.HandleClickEventSubscribtion();
button.TappedCommand.CanExecuteChanged += button.TappedCommand_CanExecuteChanged;
}
}
private void TappedCommand_CanExecuteChanged(object? sender, EventArgs e)
{
this.HandleClickEventSubscribtion();
}
public void Dispose()
{
this.Unloaded -= this.StackLayoutButton_Unloaded;
this.TappedCommand.CanExecuteChanged -= this.TappedCommand_CanExecuteChanged;
this.HandleClickEventSubscribtion(true);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment