Xamarin.Forms BoxView
BoxView
отрисовывает простой прямоугольник указанной ширины, высоты и цвета. Вы можете использовать BoxView
для декорирования, рупийной графики и взаимодействия с пользователем через сенсорный ввод.
Так как Xamarin.Forms у вас нет встроенной векторной графической системы, BoxView
это помогает компенсировать. Некоторые из примеров программ, описанных в этой статье, используются BoxView
для отрисовки графики. Размер BoxView
может быть похож на линию определенной ширины и толщины, а затем повернуть на любой угол с помощью Rotation
свойства.
Хотя BoxView
можно имитировать простую графику, вам может потребоваться изучить использование SkiaSharp для Xamarin.Forms более сложных требований к графике.
Настройка цвета и размера BoxView
Как правило, вы задали следующие свойства BoxView
:
Color
, чтобы задать его цвет.CornerRadius
для задания радиуса угла.WidthRequest
Для задания ширины в устройствах, независимых отBoxView
устройства.HeightRequest
, чтобы задать высотуBoxView
объекта .
Свойство Color
имеет тип Color
; свойство может быть задано для любого Color
значения, включая 141 статические поля только для чтения именованных цветов, начиная с алфавита AliceBlue
YellowGreen
.
CornerRadius
Свойство имеет типCornerRadius
; свойство может иметь однородное double
значение радиуса угла или CornerRadius
структуру, определенную четырьмя double
значениями, применяемыми к верхнему левому краю, верхнему правому, нижнему левому и нижнему BoxView
правому краю.
HeightRequest
Свойства WidthRequest
играют роль только в том случае, если он BoxView
не ограничен в макете. Это происходит в том случае, если контейнер макета должен знать размер дочернего элемента, например, когда BoxView
он является дочерним элементом ячейки автомасштабирования в макете Grid
. A BoxView
также не ограничивается, если HorizontalOptions
его свойства VerticalOptions
заданы для значений, отличных от LayoutOptions.Fill
значений. BoxView
Если значение не ограничено, но WidthRequest
HeightRequest
свойства не заданы, ширина или высота заданы по умолчанию в 40 единицах или около 1/4 дюйма на мобильных устройствах.
HeightRequest
Свойства WidthRequest
игнорируются, если BoxView
он ограничен в макете, в этом случае контейнер макета накладывает собственный размер.BoxView
BoxView
может быть ограниченным по одному измерению и неограниченным по другому. Например, если BoxView
дочерний элемент вертикали, вертикальное StackLayout
измерение BoxView
не ограничено и его горизонтальное измерение обычно ограничено. Но существуют исключения для этого горизонтального измерения: если BoxView
свойство HorizontalOptions
имеет значение, отличное LayoutOptions.Fill
от этого, горизонтальное измерение также не ограничено. Это также возможно для StackLayout
самого себя иметь неограниченное горизонтальное измерение, в этом случае BoxView
это также будет горизонтально не ограничено.
В примере отображается одно дюймовая квадратная не ограниченная BoxView
в центре страницы:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:BasicBoxView"
x:Class="BasicBoxView.MainPage">
<BoxView Color="CornflowerBlue"
CornerRadius="10"
WidthRequest="160"
HeightRequest="160"
VerticalOptions="Center"
HorizontalOptions="Center" />
</ContentPage>
Ниже приведен результат:
VerticalOptions
HorizontalOptions
Если свойства удаляются из BoxView
тега или задаются в качестве значенияFill
, BoxView
он становится ограниченным размером страницы и расширяется для заполнения страницы.
Может BoxView
также быть дочерним элементом AbsoluteLayout
. В этом случае расположение и размер BoxView
задаются с помощью LayoutBounds
присоединенного привязываемого свойства. Рассматривается AbsoluteLayout
в статье AbsoluteLayout.
Вы увидите примеры всех этих случаев в приведенных ниже примерах программ.
Оформление текста отрисовки
Можно использовать BoxView
для добавления простых украшений на страницах в виде горизонтальных и вертикальных линий. В этом примере показано. Все визуальные элементы программы определяются в файле MainPage.xaml , который содержит несколько Label
элементов BoxView
, приведенных StackLayout
здесь:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:TextDecoration"
x:Class="TextDecoration.MainPage">
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness">
<On Platform="iOS" Value="0, 20, 0, 0" />
</OnPlatform>
</ContentPage.Padding>
<ContentPage.Resources>
<ResourceDictionary>
<Style TargetType="BoxView">
<Setter Property="Color" Value="Black" />
</Style>
</ResourceDictionary>
</ContentPage.Resources>
<ScrollView Margin="15">
<StackLayout>
···
</StackLayout>
</ScrollView>
</ContentPage>
Все следующие разметки являются дочерними элементами StackLayout
. Эта разметка состоит из нескольких типов декоративных BoxView
элементов, используемых с элементом Label
:
Стильный заголовок в верхней части страницы достигается с AbsoluteLayout
дочерними элементами, четырьмя BoxView
элементами и Label
всеми из которых назначены определенные расположения и размеры:
<AbsoluteLayout>
<BoxView AbsoluteLayout.LayoutBounds="0, 10, 200, 5" />
<BoxView AbsoluteLayout.LayoutBounds="0, 20, 200, 5" />
<BoxView AbsoluteLayout.LayoutBounds="10, 0, 5, 65" />
<BoxView AbsoluteLayout.LayoutBounds="20, 0, 5, 65" />
<Label Text="Stylish Header"
FontSize="24"
AbsoluteLayout.LayoutBounds="30, 25, AutoSize, AutoSize"/>
</AbsoluteLayout>
В XAML-файле AbsoluteLayout
за ним следует Label
форматированный текст, описывающий .AbsoluteLayout
Вы можете подчеркнуть текстовую строку, заключив Label
BoxView
и в нее StackLayout
значение HorizontalOptions
, которое имеет значение, отличное от Fill
значения. Ширина затем регулируется шириной StackLayout
Label
, которая затем накладывает на нее BoxView
ширину. Назначается BoxView
только явная высота:
<StackLayout HorizontalOptions="Center">
<Label Text="Underlined Text"
FontSize="24" />
<BoxView HeightRequest="2" />
</StackLayout>
Этот метод нельзя использовать для подчеркивания отдельных слов в длинных текстовых строках или абзаце.
Кроме того, можно использовать BoxView
элемент HTML hr
(горизонтальное правило). Просто позвольте ширине родительского BoxView
контейнера определяться следующим образом:StackLayout
<BoxView HeightRequest="3" />
Наконец, можно нарисовать вертикальную линию на одной стороне абзаца текста, заключив как BoxView
в горизонтальную, так и Label
горизонтальную StackLayout
. В этом случае высота BoxView
объекта совпадает с высотой StackLayout
, которая регулируется высотой Label
:
<StackLayout Orientation="Horizontal">
<BoxView WidthRequest="4"
Margin="0, 0, 10, 0" />
<Label>
···
</Label>
</StackLayout>
Перечисление цветов с помощью BoxView
Удобно BoxView
отображать цвета. Эта программа использует ListView
для перечисления всех общедоступных статических полей структуры, доступных Xamarin.FormsColor
только для чтения:
Пример программы включает класс с именем NamedColor
. Статический конструктор использует отражение для доступа ко всем полям Color
структуры и созданию NamedColor
объекта для каждого из них. Они хранятся в статическом All
свойстве:
public class NamedColor
{
// Instance members.
private NamedColor()
{
}
public string Name { private set; get; }
public string FriendlyName { private set; get; }
public Color Color { private set; get; }
public string RgbDisplay { private set; get; }
// Static members.
static NamedColor()
{
List<NamedColor> all = new List<NamedColor>();
StringBuilder stringBuilder = new StringBuilder();
// Loop through the public static fields of the Color structure.
foreach (FieldInfo fieldInfo in typeof(Color).GetRuntimeFields ())
{
if (fieldInfo.IsPublic &&
fieldInfo.IsStatic &&
fieldInfo.FieldType == typeof (Color))
{
// Convert the name to a friendly name.
string name = fieldInfo.Name;
stringBuilder.Clear();
int index = 0;
foreach (char ch in name)
{
if (index != 0 && Char.IsUpper(ch))
{
stringBuilder.Append(' ');
}
stringBuilder.Append(ch);
index++;
}
// Instantiate a NamedColor object.
Color color = (Color)fieldInfo.GetValue(null);
NamedColor namedColor = new NamedColor
{
Name = name,
FriendlyName = stringBuilder.ToString(),
Color = color,
RgbDisplay = String.Format("{0:X2}-{1:X2}-{2:X2}",
(int)(255 * color.R),
(int)(255 * color.G),
(int)(255 * color.B))
};
// Add it to the collection.
all.Add(namedColor);
}
}
all.TrimExcess();
All = all;
}
public static IList<NamedColor> All { private set; get; }
}
Визуальные элементы программы описаны в XAML-файле. ListView
Свойство ItemsSource
присваивается статическому NamedColor.All
свойству, что означает, что ListView
отображаются все отдельные NamedColor
объекты:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:ListViewColors"
x:Class="ListViewColors.MainPage">
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness">
<On Platform="iOS" Value="10, 20, 10, 0" />
<On Platform="Android, UWP" Value="10, 0" />
</OnPlatform>
</ContentPage.Padding>
<ListView SeparatorVisibility="None"
ItemsSource="{x:Static local:NamedColor.All}">
<ListView.RowHeight>
<OnPlatform x:TypeArguments="x:Int32">
<On Platform="iOS, Android" Value="80" />
<On Platform="UWP" Value="90" />
</OnPlatform>
</ListView.RowHeight>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ContentView Padding="5">
<Frame OutlineColor="Accent"
Padding="10">
<StackLayout Orientation="Horizontal">
<BoxView Color="{Binding Color}"
WidthRequest="50"
HeightRequest="50" />
<StackLayout>
<Label Text="{Binding FriendlyName}"
FontSize="22"
VerticalOptions="StartAndExpand" />
<Label Text="{Binding RgbDisplay, StringFormat='RGB = {0}'}"
FontSize="16"
VerticalOptions="CenterAndExpand" />
</StackLayout>
</StackLayout>
</Frame>
</ContentView>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>
Объекты NamedColor
форматируются ViewCell
объектом, заданным в качестве шаблона данных объекта ListView
. Этот шаблон включает BoxView
свойство, свойство которого Color
привязано к Color
свойству NamedColor
объекта.
Играя в игру жизни подклассом BoxView
Игра жизни - это сотовый автомат, изобретенный математиком Джоном Конуэем и популяризирован на страницах Научного американского в 1970-х годах. Хорошее введение представлено в статье Википедии о игре жизни Конуэя.
Пример Xamarin.Forms программы определяет класс с именем LifeCell
, производным от BoxView
. Этот класс инкапсулирует логику отдельной ячейки в игре жизни:
class LifeCell : BoxView
{
bool isAlive;
public event EventHandler Tapped;
public LifeCell()
{
BackgroundColor = Color.White;
TapGestureRecognizer tapGesture = new TapGestureRecognizer();
tapGesture.Tapped += (sender, args) =>
{
Tapped?.Invoke(this, EventArgs.Empty);
};
GestureRecognizers.Add(tapGesture);
}
public int Col { set; get; }
public int Row { set; get; }
public bool IsAlive
{
set
{
if (isAlive != value)
{
isAlive = value;
BackgroundColor = isAlive ? Color.Black : Color.White;
}
}
get
{
return isAlive;
}
}
}
LifeCell
добавляет три дополнительных свойства в : Col
и Row
свойства BoxView
хранят положение ячейки в сетке, а IsAlive
свойство указывает его состояние. Свойство IsAlive
также задает Color
свойство BoxView
черного цвета, если ячейка жива, и белая, если ячейка не жива.
LifeCell
Также устанавливается, TapGestureRecognizer
чтобы разрешить пользователю переключать состояние ячеек, касаясь их. Класс преобразует Tapped
событие из распознавателя жестов в собственное Tapped
событие.
Программа GameOfLife также включает LifeGrid
класс, который инкапсулирует большую часть логики игры, и MainPage
класс, который обрабатывает визуальные элементы программы. К ним относится наложение, описывающее правила игры. Ниже приведена программа в действии с несколькими сотнями LifeCell
объектов на странице:
Создание цифровых часов
Пример программы создает 210 BoxView
элементов, чтобы имитировать точки старомодного отображения 5-к-7 точечной матрицы. Вы можете прочитать время в книжном или альбомном режиме, но оно больше в альбомном режиме:
XAML-файл создает экземпляры AbsoluteLayout
, используемые для часов:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:DotMatrixClock"
x:Class="DotMatrixClock.MainPage"
Padding="10"
SizeChanged="OnPageSizeChanged">
<AbsoluteLayout x:Name="absoluteLayout"
VerticalOptions="Center" />
</ContentPage>
Все остальное происходит в файле кода программной части. Логика отображения dot-matrix значительно упрощается определением нескольких массивов, описывающих точки, соответствующие каждой из 10 цифр и двоеточию:
public partial class MainPage : ContentPage
{
// Total dots horizontally and vertically.
const int horzDots = 41;
const int vertDots = 7;
// 5 x 7 dot matrix patterns for 0 through 9.
static readonly int[, ,] numberPatterns = new int[10, 7, 5]
{
{
{ 0, 1, 1, 1, 0}, { 1, 0, 0, 0, 1}, { 1, 0, 0, 1, 1}, { 1, 0, 1, 0, 1},
{ 1, 1, 0, 0, 1}, { 1, 0, 0, 0, 1}, { 0, 1, 1, 1, 0}
},
{
{ 0, 0, 1, 0, 0}, { 0, 1, 1, 0, 0}, { 0, 0, 1, 0, 0}, { 0, 0, 1, 0, 0},
{ 0, 0, 1, 0, 0}, { 0, 0, 1, 0, 0}, { 0, 1, 1, 1, 0}
},
{
{ 0, 1, 1, 1, 0}, { 1, 0, 0, 0, 1}, { 0, 0, 0, 0, 1}, { 0, 0, 0, 1, 0},
{ 0, 0, 1, 0, 0}, { 0, 1, 0, 0, 0}, { 1, 1, 1, 1, 1}
},
{
{ 1, 1, 1, 1, 1}, { 0, 0, 0, 1, 0}, { 0, 0, 1, 0, 0}, { 0, 0, 0, 1, 0},
{ 0, 0, 0, 0, 1}, { 1, 0, 0, 0, 1}, { 0, 1, 1, 1, 0}
},
{
{ 0, 0, 0, 1, 0}, { 0, 0, 1, 1, 0}, { 0, 1, 0, 1, 0}, { 1, 0, 0, 1, 0},
{ 1, 1, 1, 1, 1}, { 0, 0, 0, 1, 0}, { 0, 0, 0, 1, 0}
},
{
{ 1, 1, 1, 1, 1}, { 1, 0, 0, 0, 0}, { 1, 1, 1, 1, 0}, { 0, 0, 0, 0, 1},
{ 0, 0, 0, 0, 1}, { 1, 0, 0, 0, 1}, { 0, 1, 1, 1, 0}
},
{
{ 0, 0, 1, 1, 0}, { 0, 1, 0, 0, 0}, { 1, 0, 0, 0, 0}, { 1, 1, 1, 1, 0},
{ 1, 0, 0, 0, 1}, { 1, 0, 0, 0, 1}, { 0, 1, 1, 1, 0}
},
{
{ 1, 1, 1, 1, 1}, { 0, 0, 0, 0, 1}, { 0, 0, 0, 1, 0}, { 0, 0, 1, 0, 0},
{ 0, 1, 0, 0, 0}, { 0, 1, 0, 0, 0}, { 0, 1, 0, 0, 0}
},
{
{ 0, 1, 1, 1, 0}, { 1, 0, 0, 0, 1}, { 1, 0, 0, 0, 1}, { 0, 1, 1, 1, 0},
{ 1, 0, 0, 0, 1}, { 1, 0, 0, 0, 1}, { 0, 1, 1, 1, 0}
},
{
{ 0, 1, 1, 1, 0}, { 1, 0, 0, 0, 1}, { 1, 0, 0, 0, 1}, { 0, 1, 1, 1, 1},
{ 0, 0, 0, 0, 1}, { 0, 0, 0, 1, 0}, { 0, 1, 1, 0, 0}
},
};
// Dot matrix pattern for a colon.
static readonly int[,] colonPattern = new int[7, 2]
{
{ 0, 0 }, { 1, 1 }, { 1, 1 }, { 0, 0 }, { 1, 1 }, { 1, 1 }, { 0, 0 }
};
// BoxView colors for on and off.
static readonly Color colorOn = Color.Red;
static readonly Color colorOff = new Color(0.5, 0.5, 0.5, 0.25);
// Box views for 6 digits, 7 rows, 5 columns.
BoxView[, ,] digitBoxViews = new BoxView[6, 7, 5];
···
}
Эти поля заканчивают трехмерным массивом BoxView
элементов для хранения шаблонов точек для шести цифр.
Конструктор создает все BoxView
элементы для цифр и двоеточия, а также инициализирует Color
свойство BoxView
элементов для двоеточия:
public partial class MainPage : ContentPage
{
···
public MainPage()
{
InitializeComponent();
// BoxView dot dimensions.
double height = 0.85 / vertDots;
double width = 0.85 / horzDots;
// Create and assemble the BoxViews.
double xIncrement = 1.0 / (horzDots - 1);
double yIncrement = 1.0 / (vertDots - 1);
double x = 0;
for (int digit = 0; digit < 6; digit++)
{
for (int col = 0; col < 5; col++)
{
double y = 0;
for (int row = 0; row < 7; row++)
{
// Create the digit BoxView and add to layout.
BoxView boxView = new BoxView();
digitBoxViews[digit, row, col] = boxView;
absoluteLayout.Children.Add(boxView,
new Rectangle(x, y, width, height),
AbsoluteLayoutFlags.All);
y += yIncrement;
}
x += xIncrement;
}
x += xIncrement;
// Colons between the hours, minutes, and seconds.
if (digit == 1 || digit == 3)
{
int colon = digit / 2;
for (int col = 0; col < 2; col++)
{
double y = 0;
for (int row = 0; row < 7; row++)
{
// Create the BoxView and set the color.
BoxView boxView = new BoxView
{
Color = colonPattern[row, col] == 1 ?
colorOn : colorOff
};
absoluteLayout.Children.Add(boxView,
new Rectangle(x, y, width, height),
AbsoluteLayoutFlags.All);
y += yIncrement;
}
x += xIncrement;
}
x += xIncrement;
}
}
// Set the timer and initialize with a manual call.
Device.StartTimer(TimeSpan.FromSeconds(1), OnTimer);
OnTimer();
}
···
}
В этой программе используется функция AbsoluteLayout
относительного позиционирования и изменения размера. Ширина и высота каждого BoxView
значения задаются дробными значениями, в частности, 85% от 1, разделенных на количество горизонтальных и вертикальных точек. Позиции также задаются для дробных значений.
Так как все позиции и размеры относительно общего размера AbsoluteLayout
страницы, SizeChanged
обработчик страницы должен задать только следующее HeightRequest
AbsoluteLayout
:
public partial class MainPage : ContentPage
{
···
void OnPageSizeChanged(object sender, EventArgs args)
{
// No chance a display will have an aspect ratio > 41:7
absoluteLayout.HeightRequest = vertDots * Width / horzDots;
}
···
}
Ширина AbsoluteLayout
страницы устанавливается автоматически, так как она растянута до полной ширины страницы.
Окончательный код в MainPage
классе обрабатывает обратный вызов таймера и цветет точки каждой цифры. Определение многомерных массивов в начале файла программной части помогает сделать эту логику самой простой частью программы:
public partial class MainPage : ContentPage
{
···
bool OnTimer()
{
DateTime dateTime = DateTime.Now;
// Convert 24-hour clock to 12-hour clock.
int hour = (dateTime.Hour + 11) % 12 + 1;
// Set the dot colors for each digit separately.
SetDotMatrix(0, hour / 10);
SetDotMatrix(1, hour % 10);
SetDotMatrix(2, dateTime.Minute / 10);
SetDotMatrix(3, dateTime.Minute % 10);
SetDotMatrix(4, dateTime.Second / 10);
SetDotMatrix(5, dateTime.Second % 10);
return true;
}
void SetDotMatrix(int index, int digit)
{
for (int row = 0; row < 7; row++)
for (int col = 0; col < 5; col++)
{
bool isOn = numberPatterns[digit, row, col] == 1;
Color color = isOn ? colorOn : colorOff;
digitBoxViews[index, row, col].Color = color;
}
}
}
Создание аналоговых часов
Часы с точками матрицы могут показаться очевидным применением BoxView
, но BoxView
элементы также способны реализовать аналоговые часы:
Все визуальные элементы в примере программы являются дочерними элементами AbsoluteLayout
. Эти элементы имеют размер с помощью присоединенного LayoutBounds
свойства и вращаются с помощью Rotation
свойства.
BoxView
Три элемента для рук часов создаются в XAML-файле, но не расположены или не расположены:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:BoxViewClock"
x:Class="BoxViewClock.MainPage">
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness">
<On Platform="iOS" Value="0, 20, 0, 0" />
</OnPlatform>
</ContentPage.Padding>
<AbsoluteLayout x:Name="absoluteLayout"
SizeChanged="OnAbsoluteLayoutSizeChanged">
<BoxView x:Name="hourHand"
Color="Black" />
<BoxView x:Name="minuteHand"
Color="Black" />
<BoxView x:Name="secondHand"
Color="Black" />
</AbsoluteLayout>
</ContentPage>
Конструктор файла code-behind создает экземпляры 60 BoxView
элементов для тиковых меток вокруг окружности часов:
public partial class MainPage : ContentPage
{
···
BoxView[] tickMarks = new BoxView[60];
public MainPage()
{
InitializeComponent();
// Create the tick marks (to be sized and positioned later).
for (int i = 0; i < tickMarks.Length; i++)
{
tickMarks[i] = new BoxView { Color = Color.Black };
absoluteLayout.Children.Add(tickMarks[i]);
}
Device.StartTimer(TimeSpan.FromSeconds(1.0 / 60), OnTimerTick);
}
···
}
Размер и размещение всех BoxView
элементов выполняется в обработчике SizeChanged
AbsoluteLayout
. Небольшая структура, внутренняя для класса, называемая HandParams
, описывает размер каждой из трех рук относительно общего размера часов:
public partial class MainPage : ContentPage
{
// Structure for storing information about the three hands.
struct HandParams
{
public HandParams(double width, double height, double offset) : this()
{
Width = width;
Height = height;
Offset = offset;
}
public double Width { private set; get; } // fraction of radius
public double Height { private set; get; } // ditto
public double Offset { private set; get; } // relative to center pivot
}
static readonly HandParams secondParams = new HandParams(0.02, 1.1, 0.85);
static readonly HandParams minuteParams = new HandParams(0.05, 0.8, 0.9);
static readonly HandParams hourParams = new HandParams(0.125, 0.65, 0.9);
···
}
Обработчик SizeChanged
определяет центр и радиус AbsoluteLayout
, а затем размер и позиционирует 60 BoxView
элементов, используемых в качестве меток. Цикл for
завершается путем задания Rotation
свойства каждого из этих BoxView
элементов. В конце обработчика SizeChanged
LayoutHand
метод вызывается для размера и размещения трех рук часов:
public partial class MainPage : ContentPage
{
···
void OnAbsoluteLayoutSizeChanged(object sender, EventArgs args)
{
// Get the center and radius of the AbsoluteLayout.
Point center = new Point(absoluteLayout.Width / 2, absoluteLayout.Height / 2);
double radius = 0.45 * Math.Min(absoluteLayout.Width, absoluteLayout.Height);
// Position, size, and rotate the 60 tick marks.
for (int index = 0; index < tickMarks.Length; index++)
{
double size = radius / (index % 5 == 0 ? 15 : 30);
double radians = index * 2 * Math.PI / tickMarks.Length;
double x = center.X + radius * Math.Sin(radians) - size / 2;
double y = center.Y - radius * Math.Cos(radians) - size / 2;
AbsoluteLayout.SetLayoutBounds(tickMarks[index], new Rectangle(x, y, size, size));
tickMarks[index].Rotation = 180 * radians / Math.PI;
}
// Position and size the three hands.
LayoutHand(secondHand, secondParams, center, radius);
LayoutHand(minuteHand, minuteParams, center, radius);
LayoutHand(hourHand, hourParams, center, radius);
}
void LayoutHand(BoxView boxView, HandParams handParams, Point center, double radius)
{
double width = handParams.Width * radius;
double height = handParams.Height * radius;
double offset = handParams.Offset;
AbsoluteLayout.SetLayoutBounds(boxView,
new Rectangle(center.X - 0.5 * width,
center.Y - offset * height,
width, height));
// Set the AnchorY property for rotations.
boxView.AnchorY = handParams.Offset;
}
···
}
Размеры LayoutHand
методов и позиции каждой руки, чтобы указать прямо до позиции 12:00. В конце метода AnchorY
свойство устанавливается в положение, соответствующее центру часов. Это означает центр поворота.
Руки поворачиваются в функцию обратного вызова таймера:
public partial class MainPage : ContentPage
{
···
bool OnTimerTick()
{
// Set rotation angles for hour and minute hands.
DateTime dateTime = DateTime.Now;
hourHand.Rotation = 30 * (dateTime.Hour % 12) + 0.5 * dateTime.Minute;
minuteHand.Rotation = 6 * dateTime.Minute + 0.1 * dateTime.Second;
// Do an animation for the second hand.
double t = dateTime.Millisecond / 1000.0;
if (t < 0.5)
{
t = 0.5 * Easing.SpringIn.Ease(t / 0.5);
}
else
{
t = 0.5 * (1 + Easing.SpringOut.Ease((t - 0.5) / 0.5));
}
secondHand.Rotation = 6 * (dateTime.Second + t);
return true;
}
}
Вторая рука обрабатывается немного по-другому: анимация упрощает функцию, чтобы сделать движение кажется механическим, а не гладким. На каждом галочку, вторая рука оттягивает немного, а затем перехозяет свое назначение. Этот немного кода добавляет много к реалистичности движения.