控件功能分析
圆形控件 能识别顺时针、逆时针滑动的手势,并能识别滑动速度。
系统提供的相关事件
OnManipulationStarted | 滑动开始 | 手按下 |
OnManipulationDelta | 滑动中 | 手按住并移动 |
OnManipulationCompleted | 滑动完成 | 手放开 |
这3个事件是实现滑动的必要事件,因为EventArgs提供了手的XY坐标已经移动速度,
不过遗憾要识别顺时针需要自己实现。
识别顺时针滑动
识别了顺时针,反之就能识别逆时针,但如何识别顺时针滑动呢,
其实这个问题困扰了我不少时间,首先看看默认的坐标轴结构图:
用红色刷子表示手势,貌似没有找到突破口,光从X和Y的变化没有什么参考的地方。
必须要换一下思路,将坐标轴移动到圆心,方向不变(Y向下和数学的有点区别)
同样用红色刷子表示手势,光从X和Y的变化貌似也没有什么参考的地方,但是在不同区间却又一定的规律。
我们来看看顺时针滑动的X和Y的变化规律
区间1 | X 增大 | Y 减少 |
区间2 | X 增大 | Y 增大 |
区间3 | X 减少 | Y 减少 |
区间4 | X 减少 | Y 增大 |
恩,这样就基本可以识别方向了,但是写起来有点麻烦,有没有更好的方法呢。
这时我丢了N年的几何突然捡回了,我一直纠结X和Y,不是还有角度这个概念么,
一直坐冷板凳的Math出场了里面有个Atan函数可以求出反三角函数。
角度公式如下:var angle = Math.Atan(x / y) * 180.0 / Math.PI;
接下来通过区间就可以确定角度的方向,这些大问题解决了。突然感慨数学的重要。
当然还有一些细节问题可以参考代码的实现
控件代码实现
XAML:
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:VirtualKeyboard.Controls"
mc:Ignorable="d"
x:Class="VirtualKeyboard.Controls.LoopControl"
d:DesignWidth="340" d:DesignHeight="340">
<Canvas>
<Ellipse x:Name="bigEllipse" Height="340" Width="340" Canvas.Left="0" Canvas.Top="0" StrokeThickness="4" Stroke="White" Fill="#FF7A7A7A" />
<local:RoundButton x:Name="centerButton" Height="150" Width="150" Canvas.Left="95" Canvas.Top="95" ImageSource="/VirtualKeyboard.Controls;component/Icons/word.ok.png" Background="#FF1D1D1D"/>
</Canvas>
</UserControl>
为了方便中间加入了一个圆形的OK按钮,这个按钮的实现将在后面的篇幅中实现,如果有兴趣练习可以直接去掉local:RoundButton
C#
public partial class LoopControl
{
#region Const
const double BigRadius = 200.0;
const double SmailRadius = 70.0;
private readonly TimeSpan deltaSpan = new TimeSpan(0, 0, 0, 0, 200);
#endregion
#region Private
private DateTime startTime;
private Angle startAngle;
#endregion
public event RoutedEventHandler CenterClick
{
add { centerButton.Click += value; }
remove { centerButton.Click -= value; }
}
public event EventHandler<LoopGlideEventArg> LoopGlide;
public void OnLoopGlide(LoopGlideEventArg e)
{
EventHandler<LoopGlideEventArg> handler = LoopGlide;
if (handler != null) handler(this, e);
}
public LoopControl()
{
// 为初始化变量所必需
InitializeComponent();
}
protected override void OnManipulationStarted(ManipulationStartedEventArgs e)
{
startTime = DateTime.Now;
startAngle = ToAngle(e.ManipulationOrigin);
base.OnManipulationStarted(e);
}
protected override void OnManipulationDelta(ManipulationDeltaEventArgs e)
{
if (IsInCenter(e.ManipulationOrigin)) return;
var now = DateTime.Now;
if (now - startTime > deltaSpan)
{
GetDirection(e.ManipulationOrigin);
startTime = now;
startAngle = ToAngle(e.ManipulationOrigin);
}
base.OnManipulationDelta(e);
}
private void GetDirection(Point point)
{
var angle = ToAngle(point);
var angleDistance = startAngle - angle;
OnLoopGlide(new LoopGlideEventArg(angleDistance.Distance));
}
/// <summary>
/// 求角度
/// </summary>
/// <param name="point"></param>
/// <returns></returns>
private Angle ToAngle(Point point)
{
var xPoint = ToXPoint(point);
var x = xPoint.X;
var y = xPoint.Y;
var angle = Math.Atan(x / y) * 180.0 / Math.PI;
if (!(y < 0.0))
{
angle += 180.0;
}
else if (x > 0.0)
{
angle += 360.0;
}
return new Angle(angle);
}
/// <summary>
/// 变化坐标
/// </summary>
/// <param name="point"></param>
/// <returns></returns>
private Point ToXPoint(Point point)
{
var x = point.X - BigRadius;
var y = point.Y - BigRadius;
return new Point(x, y);
}
private bool IsInCenter(Point point)
{
var xPoint = ToXPoint(point);
var radius = Math.Sqrt(xPoint.X * xPoint.X + xPoint.Y * xPoint.Y);
return radius < SmailRadius;
}
#region Private Struct
/// <summary>
/// 角度
/// </summary>
private struct Angle : IEquatable<Angle>
{
private readonly double value;
private const double Epsilon = 0.1;
public double Value
{
get { return value; }
}
public Angle(double value)
{
if (value > 360.0 || value < 0)
{
throw new ArgumentOutOfRangeException("value", "value must between 0 and 360");
}
this.value = value;
}
public override string ToString()
{
return string.Format("{0:N2}", value);
}
public static AngleDistance operator -(Angle angle1, Angle angle2)
{
double angleDistance = angle1.value - angle2.value;
if (angleDistance > 180.0)
{
angleDistance -= 360;
}
else if (angleDistance < -180.0)
{
angleDistance += 360;
}
return new AngleDistance(angleDistance);
}
public override bool Equals(object obj)
{
if (obj is Angle)
{
return Equals((Angle)obj);
}
return false;
}
public override int GetHashCode()
{
return 360 ^ value.GetHashCode();
}
public bool Equals(Angle other)
{
return Math.Abs(value - other.value) < Epsilon;
}
}
/// <summary>
/// 夹角
/// </summary>
private struct AngleDistance
{
private readonly double angleDistance;
public AngleDistance(double angleDistance)
{
this.angleDistance = angleDistance;
}
public double Distance
{
get { return angleDistance; }
}
public override string ToString()
{
return string.Format("{0:N2}", angleDistance);
}
/// <summary>
/// 顺时针
/// </summary>
public bool Clockwise
{
get { return angleDistance > 0; }
}
}
#endregion
}