模拟时钟
用于图形用户界面的经典示例程序之一是模拟时钟。 BoxView再一次为时钟之手进行救援。 必须根据当前时间的小时,分钟和秒旋转这些BoxView元素。
让我们首先使用名为AnalogClockViewModel的类来处理旋转数学,该类包含在Xamarin.FormsBook.Toolkit库中:
namespace Xamarin.FormsBook.Toolkit
{
public class AnalogClockViewModel : ViewModelBase
{
double hourAngle, minuteAngle, secondAngle;
public AnalogClockViewModel()
{
UpdateLoop();
}
async void UpdateLoop()
{
while (true)
{
DateTime dateTime = DateTime.Now;
HourAngle = 30 * (dateTime.Hour % 12) + 0.5 * dateTime.Minute;
MinuteAngle = 6 * dateTime.Minute + 0.1 * dateTime.Second;
SecondAngle = 6 * dateTime.Second + 0.006 * dateTime.Millisecond;
await Task.Delay(16);
}
}
public double HourAngle
{
private set { SetProperty(ref hourAngle, value); }
get { return hourAngle; }
}
public double MinuteAngle
{
private set { SetProperty(ref minuteAngle, value); }
get { return minuteAngle; }
}
public double SecondAngle
{
private set { SetProperty(ref secondAngle, value); }
get { return secondAngle; }
}
}
}
三个属性中的每一个在Task.Delay调用的循环中每秒更新60次。 当然,时针旋转角度不仅基于小时,而且基于DateTime值的Minute部分可用的小时部分。 类似地,分针的角度基于分钟和秒属性,秒针基于秒和毫秒属性。
ViewModel的这三个属性可以绑定到模拟时钟三个指针的Rotation属性。
如你所知,一些时钟具有平滑的滑动秒针,而其他时钟的秒针则以不连续的刻度移动。 AnalogClockViewModel类似乎强加了一个平滑的秒针,但如果你想要离散的刻度,你可以为此目的提供一个值转换器:
namespace Xamarin.FormsBook.Toolkit
{
public class SecondTickConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
return 6.0 * (int)((double)value / 6);
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
return (double)value;
}
}
}
如果你不知道它应该做什么,这个类的名称甚至是微小的代码都可能是模糊的:Convert方法转换double类型的角度,范围从0到360度,小数部分转换为离散角度值0 ,6,12,18,24等。 这些角度对应于秒针的离散位置。
MinimalBoxViewClock程序在其XAML文件中实例化三个BoxView元素,并将Rotation属性绑定到AnalogClockViewModel的三个属性:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:toolkit=
"clr-namespace:Xamarin.FormsBook.Toolkit;assembly=Xamarin.FormsBook.Toolkit"
x:Class="MinimalBoxViewClock.MinimalBoxViewClockPage">
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness"
iOS="0, 20, 0, 0" />
</ContentPage.Padding>
<ContentPage.Resources>
<ResourceDictionary>
<toolkit:SecondTickConverter x:Key="secondTick" />
</ResourceDictionary>
</ContentPage.Resources>
<AbsoluteLayout BackgroundColor="White"
SizeChanged="OnAbsoluteLayoutSizeChanged">
<AbsoluteLayout.BindingContext>
<toolkit:AnalogClockViewModel />
</AbsoluteLayout.BindingContext>
<BoxView x:Name="hourHand"
Color="Black"
Rotation="{Binding HourAngle}" />
<BoxView x:Name="minuteHand"
Color="Black"
Rotation="{Binding MinuteAngle}" />
<BoxView x:Name="secondHand"
Color="Black"
Rotation="{Binding SecondAngle, Converter={StaticResource secondTick}" />
</AbsoluteLayout>
</ContentPage>
代码隐藏文件根据AbsoluteLayout的大小设置这些BoxView时钟指针的大小,并设置位置,以便所有指针在12:00位置从时钟中心指向:
public partial class MinimalBoxViewClockPage : ContentPage
{
public MinimalBoxViewClockPage()
{
InitializeComponent();
}
void OnAbsoluteLayoutSizeChanged(object sender, EventArgs args)
{
AbsoluteLayout absoluteLayout = (AbsoluteLayout)sender;
// Calculate a center and radius for the clock.
Point center = new Point(absoluteLayout.Width / 2, absoluteLayout.Height / 2);
double radius = Math.Min(absoluteLayout.Width, absoluteLayout.Height) / 2;
// Position all hands pointing up from center.
AbsoluteLayout.SetLayoutBounds(hourHand,
new Rectangle(center.X - radius * 0.05,
center.Y - radius * 0.6,
radius * 0.10, radius * 0.6));
AbsoluteLayout.SetLayoutBounds(minuteHand,
new Rectangle(center.X - radius * 0.025,
center.Y - radius * 0.7,
radius * 0.05, radius * 0.7));
AbsoluteLayout.SetLayoutBounds(secondHand,
new Rectangle(center.X - radius * 0.01,
center.Y - radius * 0.9,
radius * 0.02, radius * 0.9));
// Set the anchor to bottom center of BoxView.
hourHand.AnchorY = 1;
minuteHand.AnchorY = 1;
secondHand.AnchorY = 1;
}
}
例如,时针的长度为时钟半径的0.60,时钟半径的宽度为0.10。 这意味着时针左上角的水平位置必须设置为时钟中心左侧宽度的一半(半径的0.05倍)。 时针的垂直位置是指针在时钟中心上方的高度。 AnchorY的设置确保所有旋转都相对于每个时钟指针的中心底部:
当然,这个程序叫做MinimalBoxViewClock是有原因的。 它周围没有方便的刻度标记,因此有点难以辨别实际时间。 此外,钟针应该更恰当地重叠钟面的中心,使得它们至少看起来连接到旋转的销或管上。