Vistas nativas en C#

Se puede hacer referencia a vistas nativas de iOS, Android y UWP directamente desde páginas de Xamarin.Forms creadas mediante C#. En este artículo se muestra cómo agregar vistas nativas a un Xamarin.Forms diseño creado mediante C#, y cómo invalidar el diseño de vistas personalizadas para corregir su uso de la API de medición.

Información general

Cualquier Xamarin.Forms control que permita Content establecerse o que tenga una Children colección, puede agregar vistas específicas de la plataforma. Por ejemplo, una UILabel iOS se puede agregar directamente a la propiedad ContentView.Content o a la colección StackLayout.Children. Sin embargo, tenga en cuenta que esta funcionalidad requiere el uso de #if define en Xamarin.Forms Soluciones de proyecto compartido y no está disponible en Xamarin.Forms soluciones de biblioteca de .NET Standard.

En las capturas de pantalla siguientes se muestran las vistas específicas de la plataforma que se han agregado a un Xamarin.FormsStackLayout:

StackLayout que contiene las vistas específicas de la plataforma

La capacidad de agregar vistas específicas de la plataforma a un diseño de Xamarin.Forms está habilitada por dos métodos de extensión en cada plataforma:

  • Add – agrega una vista específica de la plataforma a la colección Children de un diseño.
  • ToView – toma una vista específica de la plataforma y la ajusta como un Xamarin.FormsView que se puede establecer como la propiedad Content de un control.

El uso de estos métodos en un proyecto compartido de Xamarin.Forms requiere importar el espacio de nombres específico de la plataforma adecuado Xamarin.Forms:

  • iOS: Xamarin.Forms.Platform.iOS
  • Android: Xamarin.Forms.Platform.Android
  • Plataforma universal de Windows (UWP): Xamarin.Forms.Platform.UWP

Agregar vistas específicas de la plataforma en cada plataforma

En las secciones siguientes se muestra cómo agregar vistas específicas de la plataforma a un diseño de Xamarin.Forms en cada plataforma.

iOS

En el ejemplo de código siguiente se muestra cómo agregar un UILabel a un StackLayout y un ContentView:

var uiLabel = new UILabel {
  MinimumFontSize = 14f,
  Lines = 0,
  LineBreakMode = UILineBreakMode.WordWrap,
  Text = originalText,
};
stackLayout.Children.Add (uiLabel);
contentView.Content = uiLabel.ToView();

En el ejemplo se supone que las instancias de stackLayout y contentView se han creado previamente en XAML o C#.

Android

En el ejemplo de código siguiente se muestra cómo agregar un TextView a un StackLayout y un ContentView:

var textView = new TextView (MainActivity.Instance) { Text = originalText, TextSize = 14 };
stackLayout.Children.Add (textView);
contentView.Content = textView.ToView();

En el ejemplo se supone que las instancias de stackLayout y contentView se han creado previamente en XAML o C#.

Plataforma universal de Windows

En el ejemplo de código siguiente se muestra cómo agregar un TextBlock a un StackLayout y un ContentView:

var textBlock = new TextBlock
{
    Text = originalText,
    FontSize = 14,
    FontFamily = new FontFamily("HelveticaNeue"),
    TextWrapping = TextWrapping.Wrap
};
stackLayout.Children.Add(textBlock);
contentView.Content = textBlock.ToView();

En el ejemplo se supone que las instancias de stackLayout y contentView se han creado previamente en XAML o C#.

Invalidación de medidas de plataforma para vistas personalizadas

Las vistas personalizadas en cada plataforma suelen implementar correctamente la medición para el escenario de diseño para el que se diseñaron. Por ejemplo, una vista personalizada puede haberse diseñado para ocupar solo la mitad del ancho disponible del dispositivo. Sin embargo, después de compartirse con otros usuarios, es posible que se requiera la vista personalizada para ocupar el ancho completo disponible del dispositivo. Por lo tanto, puede ser necesario invalidar una implementación de medición de vistas personalizadas al reutilizarse en un diseño de Xamarin.Forms. Por ese motivo, los métodos de extensión Add y ToView proporcionan invalidaciones que permiten especificar delegados de medida, lo que puede invalidar el diseño de vista personalizado cuando se agrega a un diseño de Xamarin.Forms.

En las secciones siguientes se muestra cómo invalidar el diseño de las vistas personalizadas para corregir el uso de la API de medición.

iOS

En el ejemplo de código siguiente se muestra la clase CustomControl, que hereda de UILabel:

public class CustomControl : UILabel
{
  public override string Text {
    get { return base.Text; }
    set { base.Text = value.ToUpper (); }
  }

  public override CGSize SizeThatFits (CGSize size)
  {
    return new CGSize (size.Width, 150);
  }
}

Se agrega una instancia de esta vista a un StackLayout, como se muestra en el ejemplo de código siguiente:

var customControl = new CustomControl {
  MinimumFontSize = 14,
  Lines = 0,
  LineBreakMode = UILineBreakMode.WordWrap,
  Text = "This control has incorrect sizing - there's empty space above and below it."
};
stackLayout.Children.Add (customControl);

Sin embargo, dado que el CustomControl.SizeThatFits invalidación siempre devuelve un alto de 150, la vista se mostrará con espacio vacío encima y debajo, como se muestra en la captura de pantalla siguiente:

CustomControl de iOS con la implementación de Bad SizeThatFits

Una solución a este problema es proporcionar una implementación de GetDesiredSizeDelegate, como se muestra en el ejemplo de código siguiente:

SizeRequest? FixSize (NativeViewWrapperRenderer renderer, double width, double height)
{
  var uiView = renderer.Control;

  if (uiView == null) {
    return null;
  }

  var constraint = new CGSize (width, height);

  // Let the CustomControl determine its size (which will be wrong)
  var badRect = uiView.SizeThatFits (constraint);

  // Use the width and substitute the height
  return new SizeRequest (new Size (badRect.Width, 70));
}

Este método usa el ancho proporcionado por el método CustomControl.SizeThatFits, pero sustituye el alto de 150 por un alto de 70. Cuando se agrega la instancia de CustomControl al StackLayout, el método FixSize se puede especificar como el GetDesiredSizeDelegate para corregir la medida incorrecta proporcionada por la clase CustomControl:

stackLayout.Children.Add (customControl, FixSize);

Esto hace que la vista personalizada se muestre correctamente, sin espacio vacío encima y debajo, como se muestra en la captura de pantalla siguiente:

CustomControl de iOS con la invalidación de GetDesiredSize

Android

En el ejemplo de código siguiente se muestra la clase CustomControl, que hereda de TextView:

public class CustomControl : TextView
{
  public CustomControl (Context context) : base (context)
  {
  }

  protected override void OnMeasure (int widthMeasureSpec, int heightMeasureSpec)
  {
    int width = MeasureSpec.GetSize (widthMeasureSpec);

    // Force the width to half of what's been requested.
    // This is deliberately wrong to demonstrate providing an override to fix it with.
    int widthSpec = MeasureSpec.MakeMeasureSpec (width / 2, MeasureSpec.GetMode (widthMeasureSpec));

    base.OnMeasure (widthSpec, heightMeasureSpec);
  }
}

Se agrega una instancia de esta vista a un StackLayout, como se muestra en el ejemplo de código siguiente:

var customControl = new CustomControl (MainActivity.Instance) {
  Text = "This control has incorrect sizing - it doesn't occupy the available width of the device.",
  TextSize = 14
};
stackLayout.Children.Add (customControl);

Sin embargo, dado que el CustomControl.OnMeasure invalidación siempre devuelve la mitad del ancho solicitado, la vista se mostrará ocupando solo la mitad del ancho disponible del dispositivo, como se muestra en la captura de pantalla siguiente:

Android CustomControl con la implementación de OnMeasure incorrecta

Una solución a este problema es proporcionar una implementación de GetDesiredSizeDelegate, como se muestra en el ejemplo de código siguiente:

SizeRequest? FixSize (NativeViewWrapperRenderer renderer, int widthConstraint, int heightConstraint)
{
  var nativeView = renderer.Control;

  if ((widthConstraint == 0 && heightConstraint == 0) || nativeView == null) {
    return null;
  }

  int width = Android.Views.View.MeasureSpec.GetSize (widthConstraint);
  int widthSpec = Android.Views.View.MeasureSpec.MakeMeasureSpec (
    width * 2, Android.Views.View.MeasureSpec.GetMode (widthConstraint));
  nativeView.Measure (widthSpec, heightConstraint);
  return new SizeRequest (new Size (nativeView.MeasuredWidth, nativeView.MeasuredHeight));
}

Este método usa el ancho proporcionado por el método CustomControl.OnMeasure, pero lo multiplica por dos. Cuando se agrega la instancia de CustomControl al StackLayout, el método FixSize se puede especificar como el GetDesiredSizeDelegate para corregir la medida incorrecta proporcionada por la clase CustomControl:

stackLayout.Children.Add (customControl, FixSize);

Esto da como resultado que la vista personalizada se muestre correctamente, ocupando el ancho del dispositivo, como se muestra en la captura de pantalla siguiente:

CustomControl de Android con el delegado GetDesiredSize personalizado

Plataforma universal de Windows

En el ejemplo de código siguiente se muestra la clase CustomControl, que hereda de Panel:

public class CustomControl : Panel
{
  public static readonly DependencyProperty TextProperty =
    DependencyProperty.Register(
      "Text", typeof(string), typeof(CustomControl), new PropertyMetadata(default(string), OnTextPropertyChanged));

  public string Text
  {
    get { return (string)GetValue(TextProperty); }
    set { SetValue(TextProperty, value.ToUpper()); }
  }

  readonly TextBlock textBlock;

  public CustomControl()
  {
    textBlock = new TextBlock
    {
      MinHeight = 0,
      MaxHeight = double.PositiveInfinity,
      MinWidth = 0,
      MaxWidth = double.PositiveInfinity,
      FontSize = 14,
      TextWrapping = TextWrapping.Wrap,
      VerticalAlignment = VerticalAlignment.Center
    };

    Children.Add(textBlock);
  }

  static void OnTextPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
  {
    ((CustomControl)dependencyObject).textBlock.Text = (string)args.NewValue;
  }

  protected override Size ArrangeOverride(Size finalSize)
  {
      // This is deliberately wrong to demonstrate providing an override to fix it with.
      textBlock.Arrange(new Rect(0, 0, finalSize.Width/2, finalSize.Height));
      return finalSize;
  }

  protected override Size MeasureOverride(Size availableSize)
  {
      textBlock.Measure(availableSize);
      return new Size(textBlock.DesiredSize.Width, textBlock.DesiredSize.Height);
  }
}

Se agrega una instancia de esta vista a un StackLayout, como se muestra en el ejemplo de código siguiente:

var brokenControl = new CustomControl {
  Text = "This control has incorrect sizing - it doesn't occupy the available width of the device."
};
stackLayout.Children.Add(brokenControl);

Sin embargo, dado que el CustomControl.ArrangeOverride invalidación siempre devuelve la mitad del ancho solicitado, la vista se recortará a la mitad del ancho disponible del dispositivo, como se muestra en la captura de pantalla siguiente:

CustomControl de UWP con la implementación de ArrangeOverride incorrecta

Una solución a este problema es proporcionar una implementación de ArrangeOverrideDelegate, al agregar la vista a la StackLayout, como se muestra en el ejemplo de código siguiente:

stackLayout.Children.Add(fixedControl, arrangeOverrideDelegate: (renderer, finalSize) =>
{
    if (finalSize.Width <= 0 || double.IsInfinity(finalSize.Width))
    {
        return null;
    }
    var frameworkElement = renderer.Control;
    frameworkElement.Arrange(new Rect(0, 0, finalSize.Width * 2, finalSize.Height));
    return finalSize;
});

Este método usa el ancho proporcionado por el método CustomControl.ArrangeOverride, pero lo multiplica por dos. Esto da como resultado que la vista personalizada se muestre correctamente, ocupando el ancho del dispositivo, como se muestra en la captura de pantalla siguiente:

CustomControl de UWP con el delegado ArrangeOverride

Resumen

En este artículo se explica cómo agregar vistas nativas a un diseño de Xamarin.Forms creado mediante C#, y cómo invalidar el diseño de vistas personalizadas para corregir su uso de la API de medición.