-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Migrating Xamarin.Forms Effects
Effects allow the native controls on each platform to be customized without having to resort to a custom renderer implementation.
With the new extensibility possibilities of .NET MAUI Handlers, the use of Effects will probably decrease. For example, we can customize the Entry control by eliminating the underline with just a few lines:
#if __ANDROID__
Microsoft.Maui.Handlers.EntryHandler.EntryMapper.AppendToMapping("NoUnderline", (h, v) =>
{
h.NativeView.BackgroundTintList = ColorStateList.ValueOf(Colors.Transparent.ToAndroid());
});
#endif
However, effects still exist in .NET MAUI and porting effects from a Xamarin.Forms app is straightforward.
The process for creating an effect in Xamarin.Forms in each platform-specific project is as follows:
- Create a subclass of the
PlatformEffect
class. - Override the
OnAttached
method, and write logic to customize the control. - Override the
OnDetached
method, and write logic to clean up the control customization, if required. - Add a
ResolutionGroupName
attribute to the effect class.- This attribute sets a company-wide namespace for Effects, preventing collisions with other effects with the same name
- Note: this attribute can only be applied once per project.
- Add an
ExportEffect
attribute to the effect class.- This attribute registers the effect with a unique ID that's used by Xamarin.Forms, along with the group name, to locate the effect prior to applying it to a control
- The attribute takes two parameters – the type name of the effect, and a unique string that will be used to locate the effect prior to applying it to a control.
The following example shows an Android effect named FocusEffect
that changes the background color of control, based on whether it has focus:
using System;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ResolutionGroupName("MyCompany")]
[assembly: ExportEffect(typeof(EffectsDemo.Droid.FocusEffect), nameof(EffectsDemo.Droid.FocusEffect))]
namespace EffectsDemo.Droid
{
public class FocusEffect : PlatformEffect
{
Android.Graphics.Color originalBackgroundColor = new Android.Graphics.Color(0, 0, 0, 0);
Android.Graphics.Color backgroundColor;
protected override void OnAttached()
{
try
{
backgroundColor = Android.Graphics.Color.LightGreen;
Control.SetBackgroundColor(backgroundColor);
}
catch (Exception ex)
{
Console.WriteLine("Cannot set property on attached control. Error: ", ex.Message);
}
}
protected override void OnDetached()
{
}
protected override void OnElementPropertyChanged(PropertyChangedEventArgs args)
{
base.OnElementPropertyChanged(args);
if (View == null)
return;
if(args.PropertyName == View.IsFocusedProperty.PropertyName)
{
try
{
if(View.IsFocused)
Control.SetBackgroundColor(originalBackgroundColor);
else
Control.SetBackgroundColor(backgroundColor);
}
catch (Exception ex)
{
Console.WriteLine("Cannot set property on attached control. Error: ", ex.Message);
}
}
}
// ...
}
}
In this example, the OnAttached
method calls the SetBackgroundColor
method to set the background color of the control to LightGreen
, and also stores this color in a field. This functionality is wrapped in a try/catch block in case the control the effect is attached to does not have a SetBackgroundColor
property. No implementation is provided by the OnDetached
method because no cleanup is necessary.
The OnElementPropertyChanged
override responds to BindableProperty
changes on the Xamarin.Forms control. When the IsFocused
property changes, the background color of the control is changed to white if the control has focus, otherwise it's changed to light green. This functionality is wrapped in a try/catch block in case the control the effect is attached to does not have a BackgroundColor
property.
The following XAML code example shows an Entry
control to which the FocusEffect
is attached:
<Entry Text="Effect attached to an Entry" ...>
<Entry.Effects>
<local:FocusEffect />
</Entry.Effects>
...
</Entry>
The FocusEffect
class subclasses the RoutingEffect
class, which represents a platform-independent effect that wraps an inner effect that is usually platform-specific. The FocusEffect
class calls the base class constructor, passing in a parameter consisting of a concatenation of the resolution group name (specified using the ResolutionGroupName
attribute on the effect class), and the unique ID that was specified using the ExportEffect
attribute on the effect class. Therefore, when the Entry
is initialized at runtime, a new instance of the Effects.FocusEffect
is added to the control's Effects
collection.
public class FocusEffect : RoutingEffect
{
public FocusEffect () : base ($"Effects.{nameof(FocusEffect)}")
{
}
}
A Xamarin.Forms effect can be migrated to .NET MAUI by simply changing the namespace from Xamarin.Forms
to Microsoft.Maui.Controls
:
using System;
using System.ComponentModel;
using Microsoft.Maui.Controls;
namespace Effects.Effects
{
public class FocusRoutingEffect : RoutingEffect
{
}
#if WINDOWS
public class FocusPlatformEffect : Microsoft.Maui.Controls.Compatibility.Platform.UWP.PlatformEffect
{
public FocusPlatformEffect() : base()
{
}
protected override void OnAttached()
{
...
}
protected override void OnDetached()
{
}
}
#elif __ANDROID__
public class FocusPlatformEffect : Microsoft.Maui.Controls.Compatibility.Platform.Android.PlatformEffect
{
Android.Graphics.Color originalBackgroundColor = new Android.Graphics.Color(0, 0, 0, 0);
Android.Graphics.Color backgroundColor;
protected override void OnAttached()
{
try
{
backgroundColor = Android.Graphics.Color.LightGreen;
Control.SetBackgroundColor(backgroundColor);
}
catch (Exception ex)
{
Console.WriteLine("Cannot set property on attached control. Error: ", ex.Message);
}
}
protected override void OnDetached()
{
}
// ...
}
#elif __IOS__
public class FocusPlatformEffect : Microsoft.Maui.Controls.Compatibility.Platform.iOS.PlatformEffect
{
// ...
}
#endif
}
The MauiProgram
class contains a CreateMauiApp
method in which the effect must be registered:
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder.UseMauiApp<App>();
builder.ConfigureEffects(effects =>
{
effects.Add<FocusRoutingEffect, FocusPlatformEffect>();
});
return builder.Build();
}
}
The effect is registered with the ConfigureEffects
method.