Собственные представления в 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
:
Возможность добавления представлений для конкретной Xamarin.Forms платформы в макет включена двумя методами расширения на каждой платформе:
Add
— добавляет представление для конкретной платформы вChildren
коллекцию макета.ToView
— принимает представление для конкретной платформы и упаковывает его как свойство Xamarin.FormsView
Content
элемента управления.
Использование этих методов в общем проекте требует импорта соответствующего пространства имен для конкретной Xamarin.Forms Xamarin.Forms платформы:
- iOS — Xamarin.Forms.Platform.iOS
- Android — Xamarin.Forms.Platform.Android
- Универсальная платформа Windows (UWP) — Xamarin.Forms.Platform.UWP
Добавление представлений для конкретной платформы на каждой платформе
В следующих разделах показано, как добавить представления для конкретной платформы в макет на каждой Xamarin.Forms платформе.
iOS
В следующем примере кода показано, как добавить в a UILabel
StackLayout
и a ContentView
:
var uiLabel = new UILabel {
MinimumFontSize = 14f,
Lines = 0,
LineBreakMode = UILineBreakMode.WordWrap,
Text = originalText,
};
stackLayout.Children.Add (uiLabel);
contentView.Content = uiLabel.ToView();
В этом примере предполагается, что stackLayout
contentView
ранее созданные экземпляры были созданы в XAML или C#.
Android
В следующем примере кода показано, как добавить в a TextView
StackLayout
и a ContentView
:
var textView = new TextView (MainActivity.Instance) { Text = originalText, TextSize = 14 };
stackLayout.Children.Add (textView);
contentView.Content = textView.ToView();
В этом примере предполагается, что stackLayout
contentView
ранее созданные экземпляры были созданы в XAML или C#.
Универсальная платформа Windows
В следующем примере кода показано, как добавить в a TextBlock
StackLayout
и 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();
В этом примере предполагается, что stackLayout
contentView
ранее созданные экземпляры были созданы в 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, представление будет отображаться с пустым пробелом выше и ниже, как показано на следующем снимке экрана:
Решение этой проблемы заключается в предоставлении 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
При добавлении StackLayout
FixSize
экземпляра в этот метод можно указать как GetDesiredSizeDelegate
исправление плохого измерения, предоставленного CustomControl
классом:
stackLayout.Children.Add (customControl, FixSize);
Это приводит к правильному отображению пользовательского представления без пустого пространства над ним и ниже, как показано на следующем снимке экрана:
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
переопределение всегда возвращает половину запрошенной ширины, представление будет отображаться только в половине доступной ширины устройства, как показано на следующем снимке экрана:
Решение этой проблемы заключается в предоставлении 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
При добавлении StackLayout
FixSize
экземпляра в этот метод можно указать как GetDesiredSizeDelegate
исправление плохого измерения, предоставленного CustomControl
классом:
stackLayout.Children.Add (customControl, FixSize);
Это приводит к правильному отображению пользовательского представления, занимая ширину устройства, как показано на следующем снимке экрана:
Универсальная платформа 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
переопределение всегда возвращает половину запрошенной ширины, представление будет обрезано до половины доступной ширины устройства, как показано на следующем снимке экрана:
Решение этой проблемы заключается в предоставлении 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
, но умножает ее на два. Это приводит к правильному отображению пользовательского представления, занимая ширину устройства, как показано на следующем снимке экрана:
Итоги
В этой статье объясняется, как добавить собственные представления в Xamarin.Forms макет, созданный с помощью C#, и как переопределить макет пользовательских представлений, чтобы исправить использование API измерения.