Бөлісу құралы:


Собственные представления в C#

Собственные представления из iOS, Android и UWP можно напрямую ссылаться на Xamarin.Forms страницы, созданные с помощью C#. В этой статье показано, как добавить собственные представления в Xamarin.Forms макет, созданный с помощью C#, и как переопределить макет пользовательских представлений для исправления использования API измерения.

Обзор

Любой Xamarin.Forms элемент управления, позволяющий Content задать или имеющий Children коллекцию, может добавлять представления для конкретной платформы. Например, iOS UILabel можно добавить непосредственно в ContentView.Content свойство или в коллекцию StackLayout.Children . Однако обратите внимание, что эта функция требует использования определений #if в Xamarin.Forms решениях общего проекта и недоступна в Xamarin.Forms решениях библиотек .NET Standard.

На следующих снимках экрана показаны представления для конкретной платформы, добавленные в следующее Xamarin.FormsStackLayout:

StackLayout, содержащий представления, зависящие от платформы

Возможность добавления представлений для конкретной Xamarin.Forms платформы в макет включена двумя методами расширения на каждой платформе:

  • Add — добавляет представление для конкретной платформы в Children коллекцию макета.
  • ToView — принимает представление для конкретной платформы и упаковывает его как свойство Xamarin.FormsViewContent элемента управления.

Использование этих методов в общем проекте требует импорта соответствующего пространства имен для конкретной Xamarin.FormsXamarin.Forms платформы:

  • iOS — Xamarin.Forms.Platform.iOS
  • Android — Xamarin.Forms.Platform.Android
  • Универсальная платформа Windows (UWP) — Xamarin.Forms.Platform.UWP

Добавление представлений для конкретной платформы на каждой платформе

В следующих разделах показано, как добавить представления для конкретной платформы в макет на каждой Xamarin.Forms платформе.

iOS

В следующем примере кода показано, как добавить в a UILabelStackLayout и a ContentView:

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

В этом примере предполагается, что stackLayoutcontentView ранее созданные экземпляры были созданы в XAML или C#.

Android

В следующем примере кода показано, как добавить в a TextViewStackLayout и a ContentView:

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

В этом примере предполагается, что stackLayoutcontentView ранее созданные экземпляры были созданы в XAML или C#.

Универсальная платформа Windows

В следующем примере кода показано, как добавить в a TextBlockStackLayout и a ContentView:

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

В этом примере предполагается, что stackLayoutcontentView ранее созданные экземпляры были созданы в XAML или C#.

Переопределение измерений платформы для пользовательских представлений

Пользовательские представления на каждой платформе часто правильно реализуют измерение для сценария макета, для которого они были разработаны. Например, пользовательское представление может быть разработано только для того, чтобы занять только половину доступной ширины устройства. Однако после совместного использования с другими пользователями пользовательское представление может потребоваться для получения полной доступной ширины устройства. Поэтому при повторном использовании в макете может потребоваться переопределить реализацию измерения пользовательских представлений Xamarin.Forms . По этой причине Add методы расширения предоставляют ToView переопределения, позволяющие указывать делегаты измерения, которые могут переопределить макет пользовательского Xamarin.Forms представления при добавлении в макет.

В следующих разделах показано, как переопределить макет пользовательских представлений, чтобы исправить использование API измерения.

iOS

В следующем примере кода показан CustomControl класс, от которого наследуется: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);
  }
}

Экземпляр этого представления добавляется в код StackLayout, как показано в следующем примере кода:

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

Однако, поскольку CustomControl.SizeThatFits переопределение всегда возвращает высоту 150, представление будет отображаться с пустым пробелом выше и ниже, как показано на следующем снимке экрана:

IOS CustomControl с реализацией Bad SizeThatFits

Решение этой проблемы заключается в предоставлении GetDesiredSizeDelegate реализации, как показано в следующем примере кода:

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

Этот метод использует ширину, предоставляемую CustomControl.SizeThatFits методом, но заменяет высоту 150 высотой 70. CustomControl При добавлении StackLayoutFixSize экземпляра в этот метод можно указать как GetDesiredSizeDelegate исправление плохого измерения, предоставленного CustomControl классом:

stackLayout.Children.Add (customControl, FixSize);

Это приводит к правильному отображению пользовательского представления без пустого пространства над ним и ниже, как показано на следующем снимке экрана:

IOS CustomControl с GetDesiredSize Override

Android

В следующем примере кода показан CustomControl класс, от которого наследуется: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);
  }
}

Экземпляр этого представления добавляется в код StackLayout, как показано в следующем примере кода:

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

Однако, поскольку CustomControl.OnMeasure переопределение всегда возвращает половину запрошенной ширины, представление будет отображаться только в половине доступной ширины устройства, как показано на следующем снимке экрана:

Android CustomControl с реализацией Bad OnMeasure

Решение этой проблемы заключается в предоставлении GetDesiredSizeDelegate реализации, как показано в следующем примере кода:

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

Этот метод использует ширину, предоставляемую методом CustomControl.OnMeasure , но умножает ее на два. CustomControl При добавлении StackLayoutFixSize экземпляра в этот метод можно указать как GetDesiredSizeDelegate исправление плохого измерения, предоставленного CustomControl классом:

stackLayout.Children.Add (customControl, FixSize);

Это приводит к правильному отображению пользовательского представления, занимая ширину устройства, как показано на следующем снимке экрана:

Android CustomControl с пользовательским делегатом GetDesiredSize

Универсальная платформа Windows

В следующем примере кода показан CustomControl класс, от которого наследуется: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);
  }
}

Экземпляр этого представления добавляется в код StackLayout, как показано в следующем примере кода:

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

Однако, поскольку CustomControl.ArrangeOverride переопределение всегда возвращает половину запрошенной ширины, представление будет обрезано до половины доступной ширины устройства, как показано на следующем снимке экрана:

Пользовательская функция UWP с реализацией Bad ArrangeOverride

Решение этой проблемы заключается в предоставлении ArrangeOverrideDelegate реализации при добавлении представления в StackLayoutпредставление, как показано в следующем примере кода:

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

Этот метод использует ширину, предоставляемую методом CustomControl.ArrangeOverride , но умножает ее на два. Это приводит к правильному отображению пользовательского представления, занимая ширину устройства, как показано на следующем снимке экрана:

CustomControl UWP с делегатом ArrangeOverride

Итоги

В этой статье объясняется, как добавить собственные представления в Xamarin.Forms макет, созданный с помощью C#, и как переопределить макет пользовательских представлений, чтобы исправить использование API измерения.