Compartilhar via


Exibições nativas em C#

As exibições nativas do iOS, Android e UWP podem ser referenciadas diretamente a partir de Xamarin.Forms páginas criadas usando C#. Este artigo demonstra como adicionar modos de exibição nativos a um Xamarin.Forms layout criado usando C# e como substituir o layout de modos de exibição personalizados para corrigir o uso da API de medição.

Visão geral

Qualquer Xamarin.Forms controle que permita Content ser definido, ou que tenha uma Children coleção, pode adicionar exibições específicas da plataforma. Por exemplo, um iOS UILabel pode ser adicionado diretamente à ContentView.Content propriedade ou à StackLayout.Children coleção. No entanto, observe que essa funcionalidade requer o uso de definições em soluções de #if projeto compartilhado e não está disponível em Xamarin.Forms soluções de biblioteca do Xamarin.Forms .NET Standard.

As capturas de tela a seguir demonstram que exibições específicas da plataforma foram adicionadas a um Xamarin.FormsStackLayout:

StackLayout contendo exibições específicas da plataforma

A capacidade de adicionar exibições específicas da plataforma a um Xamarin.Forms layout é habilitada por dois métodos de extensão em cada plataforma:

  • Add – adiciona uma visualização específica da Children plataforma à coleção de um layout.
  • ToView – pega uma exibição específica da plataforma e a encapsula como uma Xamarin.FormsView que pode ser definida como a Content propriedade de um controle.

O uso desses métodos em um Xamarin.Forms projeto compartilhado requer a importação do namespace específico Xamarin.Forms da plataforma apropriado:

  • iOS – Xamarin.Forms. Plataforma.iOS
  • Android – Xamarin.Forms. Plataforma.Android
  • Plataforma Universal do Windows (UWP) – Xamarin.Forms. Plataforma.UWP

Adicionando visualizações específicas da plataforma em cada plataforma

As seções a seguir demonstram como adicionar exibições específicas da plataforma a um Xamarin.Forms layout em cada plataforma.

iOS

O exemplo de código a seguir demonstra como adicionar um UILabel a StackLayout e um ContentView:

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

O exemplo pressupõe que as stackLayout instâncias e contentView foram criadas anteriormente em XAML ou C#.

Android

O exemplo de código a seguir demonstra como adicionar um TextView a StackLayout e um ContentView:

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

O exemplo pressupõe que as stackLayout instâncias e contentView foram criadas anteriormente em XAML ou C#.

Plataforma Universal do Windows

O exemplo de código a seguir demonstra como adicionar um TextBlock a StackLayout e um ContentView:

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

O exemplo pressupõe que as stackLayout instâncias e contentView foram criadas anteriormente em XAML ou C#.

Substituindo medidas de plataforma para exibições personalizadas

As exibições personalizadas em cada plataforma geralmente só implementam corretamente a medição para o cenário de layout para o qual foram projetadas. Por exemplo, um modo de exibição personalizado pode ter sido projetado para ocupar apenas metade da largura disponível do dispositivo. No entanto, depois de ser compartilhada com outros usuários, a exibição personalizada pode ser necessária para ocupar toda a largura disponível do dispositivo. Portanto, pode ser necessário substituir uma implementação de medição de exibições personalizadas ao ser reutilizada em um Xamarin.Forms layout. Por esse motivo, os Add métodos e ToView extensão fornecem substituições que permitem que os delegados de medição sejam especificados, o que pode substituir o layout de exibição personalizado quando ele é adicionado a um Xamarin.Forms layout.

As seções a seguir demonstram como substituir o layout de exibições personalizadas para corrigir o uso da API de medição.

iOS

O exemplo de código a seguir mostra a CustomControl classe, que herda 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);
  }
}

Uma instância desse modo de exibição é adicionada a um StackLayout, conforme demonstrado no exemplo de código a seguir:

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);

No entanto, como a CustomControl.SizeThatFits substituição sempre retorna uma altura de 150, a exibição será exibida com espaço vazio acima e abaixo dela, conforme mostrado na captura de tela a seguir:

iOS CustomControl com tamanho ruim que se encaixa na implementação

Uma solução para esse problema é fornecer uma GetDesiredSizeDelegate implementação, conforme demonstrado no exemplo de código a seguir:

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));
}

Esse método usa a largura fornecida pelo CustomControl.SizeThatFits método, mas substitui a altura de 150 por uma altura de 70. Quando a CustomControl instância é adicionada StackLayoutao , o FixSize método pode ser especificado como o GetDesiredSizeDelegate para corrigir a medida incorreta fornecida pela CustomControl classe:

stackLayout.Children.Add (customControl, FixSize);

Isso resulta na exibição personalizada sendo exibida corretamente, sem espaço vazio acima e abaixo dela, conforme mostrado na captura de tela a seguir:

iOS CustomControl com GetDesiredSize Override

Android

O exemplo de código a seguir mostra a CustomControl classe, que herda 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);
  }
}

Uma instância desse modo de exibição é adicionada a um StackLayout, conforme demonstrado no exemplo de código a seguir:

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);

No entanto, como a CustomControl.OnMeasure substituição sempre retorna metade da largura solicitada, a exibição será exibida ocupando apenas metade da largura disponível do dispositivo, conforme mostrado na captura de tela a seguir:

Android CustomControl com implementação Bad OnMeasure

Uma solução para esse problema é fornecer uma GetDesiredSizeDelegate implementação, conforme demonstrado no exemplo de código a seguir:

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));
}

Esse método usa a largura fornecida pelo CustomControl.OnMeasure método, mas a multiplica por dois. Quando a CustomControl instância é adicionada StackLayoutao , o FixSize método pode ser especificado como o GetDesiredSizeDelegate para corrigir a medida incorreta fornecida pela CustomControl classe:

stackLayout.Children.Add (customControl, FixSize);

Isso resulta na exibição personalizada sendo exibida corretamente, ocupando a largura do dispositivo, conforme mostrado na captura de tela a seguir:

Android CustomControl com Delegado GetDesiredSize personalizado

Plataforma Universal do Windows

O exemplo de código a seguir mostra a CustomControl classe, que herda 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);
  }
}

Uma instância desse modo de exibição é adicionada a um StackLayout, conforme demonstrado no exemplo de código a seguir:

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

No entanto, como a CustomControl.ArrangeOverride substituição sempre retorna metade da largura solicitada, a exibição será cortada para metade da largura disponível do dispositivo, conforme mostrado na captura de tela a seguir:

UWP CustomControl com implementação incorreta de ArrangeOverride

Uma solução para esse problema é fornecer uma ArrangeOverrideDelegate implementação, ao adicionar o modo de exibição para o StackLayout, conforme demonstrado no exemplo de código a seguir:

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;
});

Esse método usa a largura fornecida pelo CustomControl.ArrangeOverride método, mas a multiplica por dois. Isso resulta na exibição personalizada sendo exibida corretamente, ocupando a largura do dispositivo, conforme mostrado na captura de tela a seguir:

UWP CustomControl com ArrangeOverride Delegate

Resumo

Este artigo explicou como adicionar modos de exibição nativos a um Xamarin.Forms layout criado usando C# e como substituir o layout de modos de exibição personalizados para corrigir o uso da API de medição.