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
입니다. 속성은 사전순으로 정렬되는 명명된 색의 141개의 정적 읽기 전용 필드를 포함하여 모든 Color
값으로 AliceBlue
YellowGreen
설정할 수 있습니다.
속성은 CornerRadius
형식CornerRadius
입니다. 속성은 단일 double
균일한 모퉁이 반지름 값으로 설정하거나 CornerRadius
왼쪽 위, 오른쪽 위, 왼쪽 아래 및 오른쪽 BoxView
아래에 적용되는 4개의 double
값으로 정의된 구조체로 설정할 수 있습니다.
WidthRequest
레이아웃에 제약이 없는 경우에만 BoxView
및 HeightRequest
속성이 역할을 합니다. 레이아웃 컨테이너가 자식의 크기를 알아야 하는 경우(예: 레이아웃에서 자동 크기 셀 Grid
의 자식인 경우BoxView
)입니다. A BoxView
는 해당 HorizontalOptions
속성과 VerticalOptions
속성이 이외의 값으로 설정된 경우에도 제한되지 않습니다 LayoutOptions.Fill
. BoxView
제한되지 않지만 WidthRequest
속성과 HeightRequest
속성이 설정되지 않은 경우 너비 또는 높이가 기본값인 40 단위 또는 모바일 디바이스에서 약 1/4인치로 설정됩니다.
WidthRequest
레이아웃에서 제한되는 경우 BoxView
및 HeightRequest
속성은 무시됩니다. 이 경우 레이아웃 컨테이너는 자체 크기를 적용합니다BoxView
.
BoxView
는 하나의 차원에서는 제한되고 다른 차원에서는 제한되지 않을 수 있습니다. 예를 들어 세로의 자식인 경우 BoxView
세로 StackLayout
차원은 BoxView
제약이 없으며 가로 차원은 일반적으로 제한됩니다. 그러나 해당 가로 차원에 대한 예외가 있습니다. 해당 속성이 HorizontalOptions
다른 LayoutOptions.Fill
값으로 설정된 경우 BoxView
가로 차원도 제한되지 않습니다. 자체 StackLayout
적으로 제약이 없는 수평 차원을 가질 수도 있습니다. 이 경우 BoxView
가로로 제한되지 않습니다.
샘플은 페이지 가운데에 제약 BoxView
이 없는 1인치 정사각형을 표시합니다.
<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
BoxView
속성 및 HorizontalOptions
속성이 제거되거나 이 속성으로 설정 Fill
BoxView
되면 페이지 크기가 제한되고 페이지가 채워지도록 확장됩니다.
A는 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
구성됩니다.
페이지 맨 위에 있는 세련된 헤더는 자식이 4개 BoxView
요소이고 모두 특정 위치와 Label
크기가 할당된 것으로 구현 AbsoluteLayout
됩니다.
<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
.
값이 다른 Fill
값으로 설정된 텍스트 문자열과 BoxView
StackLayout
해당 HorizontalOptions
문자열을 모두 Label
묶어 텍스트 문자열에 밑줄을 표시할 수 있습니다. 그런 다음 너비는 StackLayout
너비에 따라 Label
제어되며, 너비는 해당 너비를 적용 BoxView
합니다. 명시 BoxView
적 높이만 할당됩니다.
<StackLayout HorizontalOptions="Center">
<Label Text="Underlined Text"
FontSize="24" />
<BoxView HeightRequest="2" />
</StackLayout>
이 기법을 사용하여 긴 텍스트 문자열 또는 단락 내에서 개별 단어에 밑줄을 표시할 수 없습니다.
HTML hr
(가로 규칙) 요소와 유사하게 사용할 BoxView
수도 있습니다. 부모 컨테이너에 의해 너비를 BoxView
결정하도록 하면 됩니다. 이 경우 다음과 같습니다 StackLayout
.
<BoxView HeightRequest="3" />
마지막으로 가로와 가로를 모두 BoxView
Label
묶어 텍스트 단락의 한쪽에 StackLayout
세로 선을 그릴 수 있습니다. 이 경우 높이 BoxView
가 높이와 같으며 높이가 다음의 Label
높이StackLayout
에 의해 제어됩니다.
<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 파일에 설명되어 있습니다. 이 속성은 ItemsSource
정적 NamedColor.All
속성으로 설정됩니다. 즉ListView
, 모든 개별 NamedColor
개체가 ListView
표시됩니다.
<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
지정됩니다. 이 템플릿에는 개체 Color
의 속성에 바인딩된 속성이 NamedColor
Color
포함 BoxView
됩니다.
BoxView 서브클래싱으로 삶의 게임 플레이
삶의 게임은 수학자 존 콘웨이에 의해 발명 된 셀룰러 오토마톤이며 1970년대에 사이언티픽 아메리칸 페이지에서 대중화되었습니다. 좋은 소개는 위키 백과 기사 콘웨이의 삶의 게임에 의해 제공됩니다.
Xamarin.Forms 샘플 프로그램은 .에서 BoxView
파생되는 명명 LifeCell
된 클래스를 정의합니다. 이 클래스는 Game of Life에서 개별 셀의 논리를 캡슐화합니다.
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
에 세 개의 속성을 BoxView
더 추가합니다. 속성은 Col
Row
그리드 내에 셀의 위치를 저장하고 속성은 IsAlive
해당 상태를 나타냅니다. 또한 이 속성은 IsAlive
셀이 Color
활성 BoxView
상태이면 검은색으로, 셀이 활성 상태가 아니면 흰색으로 속성을 설정합니다.
LifeCell
또한 사용자가 셀을 탭하여 셀 상태를 토글할 수 있도록 A를 설치 TapGestureRecognizer
합니다. 클래스는 제스처 인식기에서 자체 Tapped
이벤트로 이벤트를 변환 Tapped
합니다.
GameOfLife 프로그램에는 게임의 많은 논리를 캡슐화하는 클래스와 프로그램의 시각적 개체를 MainPage
처리하는 클래스도 포함되어 LifeGrid
있습니다. 여기에는 게임의 규칙을 설명하는 오버레이가 포함됩니다. 다음은 페이지에서 수백 개의 LifeCell
개체를 보여 주는 실행 중인 프로그램입니다.
디지털 시계 만들기
샘플 프로그램은 210 BoxView
개의 요소를 만들어 구식 5-by-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>
다른 모든 작업은 코드 숨김 파일에서 발생합니다. 점 행렬 표시 논리는 각 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];
···
}
이러한 필드는 6자리 숫자의 점 패턴을 저장하기 위한 요소의 BoxView
3차원 배열로 끝납니다.
생성자는 숫자와 콜론에 대한 모든 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
값의 너비와 높이는 소수 값으로 설정되며, 특히 1의 85%를 가로 점과 세로 점 수로 나눕니다. 위치도 소수 값으로 설정됩니다.
모든 위치와 크기는 총 크기를 AbsoluteLayout
SizeChanged
기준으로 하기 때문에 페이지의 처리기는 다음 중AbsoluteLayout
에서 HeightRequest
만 설정하면 됩니다.
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>
코드 숨김 파일의 생성자는 클록의 둘레 주위에 눈금 표시에 대한 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
요소의 크기 조정 및 위치 지정은 에 대한 AbsoluteLayout
처리기에서 SizeChanged
발생합니다. 호출 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
처리기는 눈금으로 사용되는 60 BoxView
개 요소의 AbsoluteLayout
크기와 위치를 결정합니다. 루프는 for
이러한 BoxView
각 요소의 속성을 설정 Rotation
하여 종료됩니다. 처리기의 LayoutHand
끝에서 메서드는 클록의 SizeChanged
세 손을 크기 조정하고 배치하기 위해 호출됩니다.
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;
}
}
두 번째 손은 약간 다르게 처리됩니다. 애니메이션 감속/가속 함수가 적용되어 무브먼트가 매끄럽지 않고 기계적으로 보입니다. 각 틱에서 초침은 조금 뒤로 당겨서 대상을 오버슈트합니다. 이 약간의 코드는 무브먼트의 리얼리즘에 많은 것을 추가합니다.