Search code examples
androidiosxamarinxamarin.formscustom-renderer

Force redraw of Xamarin.Forms View with custom renderer


I have a visual element MyButton with a custom renderer implemented for iOS.

Shared:

namespace RendererTest
{
    public class MyButton: Button
    {
        public Color BoundaryColor { get; set; }
    }

    public static class App
    {
        public static Page GetMainPage()
        {    
            var button = new MyButton { Text = "Click me!", BoundaryColor = Color.Red };
            button.Clicked += (sender, e) => (sender as MyButton).BoundaryColor = Color.Blue;
            return new ContentPage { Content = button };
        }
    }
}

iOS:

[assembly:ExportRenderer(typeof(MyButton), typeof(MyButtonRenderer))]

namespace RendererTest.iOS
{
    public class MyButtonRenderer: ButtonRenderer
    {
        public override void Draw(RectangleF rect)
        {
            using (var context = UIGraphics.GetCurrentContext()) {
                context.SetFillColor(Element.BackgroundColor.ToCGColor());
                context.SetStrokeColor((Element as MyButton).BoundaryColor.ToCGColor());
                context.SetLineWidth(10);
                context.AddPath(CGPath.FromRect(Bounds));
                context.DrawPath(CGPathDrawingMode.FillStroke);
            }
        }
    }
}

When pressing the button, the red boundary should become blue. Apparently the renderer does not notice the changed property. How can I trigger a redraw?

(This example is for iOS. But my question applies to Android as well.)


Solution

  • Two modifications were required:

    1. Call OnPropertyChanged within the setter of the BoundaryColor property:

      public class MyButton: Button
      {
          Color boundaryColor = Color.Red;
      
          public Color BoundaryColor {
              get {
                  return boundaryColor;
              }
              set {
                  boundaryColor = value;
                  OnPropertyChanged();  // <-- here
              }
          }
      }
      
    2. Subscribe to the event within the OnElementChanged method of MyButtonRenderer:

      public class MyButtonRenderer: ButtonRenderer
      {
          protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
          {
              base.OnElementChanged(e);
              Element.PropertyChanged += (s_, e_) => SetNeedsDisplay();  // <-- here
          }
      
          public override void Draw(RectangleF rect)
          {
              // ...
          }
      }
      

    Note: It seems to be important to subscribe within OnElementChanged and not the constructor. Otherwise a System.Reflection.TargetInvocationException is raised.