Windows Phone 8.1应用中有很多关于跟用户触控交互的效果,比如用手指将图片放大或者缩小以及将图片旋转之类
的操作等等。那么这都如何实现的呢。
以及看到基本每一个UIElement时,将其对应的属性列表看到一长串的事件,整个人都凌乱了。
以上这么多圈住的划横线的都是点击手势和指针事件,那怎么区分其中的不同呢?
触控输入:触摸输入 (XAML)
指针:指针 (XAML)
触控输入:属于UIElement事件
首先认识三个不同抽象级别的 UIElement 事件
1.高级事件:用于响应简单的交互,如Tap事件
2.指针事件提供较低级别的详细信息,如指针动作以及区分按压和释放手势,如PointerPressed事件
3.操作事件提供较低级别的详细信息,如手势速度和延迟以及多点触摸数据,如ManipulationStarting事件
其次呢,现如今除了PC,可以触控的PC,平板电脑,平板以及手机等设备,随着这些设备,我们依然可以用传统的鼠
标,当然也可以用手指了,触控笔也应运而生了。暂且分别可以标识为Mouse,Pen,Touch。
如果为每一个触控输入建立3个事件(按下,保持,放开)的话,那就要建立9个事件了,为此,微软特定统一了API,
将指针输入统一为Pointer,这样既简化了代码,又不容易出错。
最后呢,指针输入只针对点对点的,指针事件仅限于一个手指的交互,如点击和滑动,而手势则偏向于丰富多彩的操
作,如点击,长按,轻扫,滑动,收缩,旋转,拉伸。
好了,知道区分手势和指针,就知道具体在什么场合用了。那具体怎么用呢?
高级事件:Tap等事件就不介绍了,比较简单交互,在这边大家不能误解了,说是简单,是因为封装的好,要完成一
个Tap事件其实经历了很多事件,拆开来还是得讲Manipulation事件和指针Pointer事件。
在下面Pointer部分会提及到
Tap的API是通过Manipulation API封装而来的,所以不要看上去就一个Tap事件,其实里面早已经触发了很多事件,
比如ManipulationStarting事件以及指针的好多事件
手势,一切的成功要建立在设置了ManipulationMode这个属性的基础上,要不然一切都发生不了。当然
ManipulationMode属性具体的值可以根据你具体的需求而定,如果怕麻烦,直接设定为All即可
触控触发的事件顺序:(结合Tap事件的话请看下面Pointer部分)
因为太烦了,所以以下用 A 代表Manipulation
->AStarting(触控操作开始前引发)
->AStarted(触控操作开始之后引发)
->ADelta(触控中引起的变化都会引发此事件,也就是说可以多次引发)
(ADelta里面有两个重要的属性:Delta和Cumulative,同时ADelta事件的Complete()方法可以终止惯性运动)
(Delta属性是指当前发生ADelta事件时的所有数据,Cumulative是指从触控操作开始一来发生的所有数据的更改)
->AStarting(触控终止,元素开始惯性运动时引发)
->ADelta(上面说过此事件可多次引发,所以再写一遍)
->ACompleted(触控完成,惯性完成,引发此操作)
好了,贴伪代码了:
下面中用到的CompositeTransform变换在我之前一篇博客中有提到,博客地址:动画与变换
XAML:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock x:Name="textblock" HorizontalAlignment="Center" Text="结果" FontSize="35"/>
<!--ManipulationMode必须指定,当然可以按需指定,如果嫌麻烦,直接指定为All即可-->
<Image x:Name="image" Grid.Row="1" Source="DemoPic.png" Stretch="UniformToFill" Width="200" Height="200"
ManipulationMode="All"
ManipulationStarting="image_ManipulationStarting"
ManipulationStarted="image_ManipulationStarted"
ManipulationDelta="image_ManipulationDelta"
ManipulationCompleted="image_ManipulationCompleted">
</Image>
<!--ManipulationMode必须指定,当然可以按需指定,如果嫌麻烦,直接指定为All即可-->
<Image x:Name="image2" Grid.Row="2" Source="DemoPic2.png" Stretch="UniformToFill" Width="200" Height="200"
RenderTransformOrigin="0.5,0.5"
ManipulationMode="All"
ManipulationStarting="image2_ManipulationStarting"
ManipulationStarted="image2_ManipulationStarted"
ManipulationDelta="image2_ManipulationDelta"
ManipulationCompleted="image2_ManipulationCompleted">
<Image.RenderTransform>
<!--组合变换,但是变换之间是独立的-->
<CompositeTransform x:Name="compositeTransform"/>
</Image.RenderTransform>
</Image>
</Grid>
.cs:
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
this.NavigationCacheMode = NavigationCacheMode.Required;
}
/// <summary>
/// 在此页将要在 Frame 中显示时进行调用。
/// </summary>
/// <param name="e">描述如何访问此页的事件数据。
/// 此参数通常用于配置页。</param>
protected override void OnNavigatedTo(NavigationEventArgs e)
{
// TODO: 准备此处显示的页面。
// TODO: 如果您的应用程序包含多个页面,请确保
// 通过注册以下事件来处理硬件“后退”按钮:
// Windows.Phone.UI.Input.HardwareButtons.BackPressed 事件。
// 如果使用由某些模板提供的 NavigationHelper,
// 则系统会为您处理该事件。
}
private void image_ManipulationStarting(object sender, ManipulationStartingRoutedEventArgs e)
{
textblock.Text = "ManipulationStarting";
}
private void image_ManipulationStarted(object sender, ManipulationStartedRoutedEventArgs e)
{
textblock.Text = "ManipulationStarted";
}
private void image_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{
textblock.Text = "ManipulationDelta";
}
private void image_ManipulationCompleted(object sender, ManipulationCompletedRoutedEventArgs e)
{
textblock.Text = "ManipulationCompleted";
}
private void image2_ManipulationStarting(object sender, ManipulationStartingRoutedEventArgs e)
{
textblock.Text = "ManipulationStarting";
}
private void image2_ManipulationStarted(object sender, ManipulationStartedRoutedEventArgs e)
{
textblock.Text = "ManipulationStarted";
}
private void image2_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{
//这边采用的是在XAML页面中已经给UIElement定义好了RenderTransform变换模式的方法
//当然你也可以不采用这种方法,直接在.cs中动态指定也是可以的,思想都是一样的。只不过XAML把需要在.cs中申明的做好了而已,这也是XAML封装的方便性
//比如如下,当然下面的不能在ManipulationDelta申明,要在函数体外申明
//CompositeTransform cpTransform = null;
//image2.ManipulationMode = ManipulationModes.TranslateX | ManipulationModes.TranslateY | ManipulationModes.ScaleX | ManipulationModes.ScaleY | ManipulationModes.Rotate;
//cpTransform = new CompositeTransform();
//cpTransform.TranslateX = cpTransform.TranslateY = 0;
//cpTransform.Rotation = 0;
//........
//image2.RenderTransform = cpTransform;
textblock.Text = "ManipulationDelta";
//可以自由拖动图片,说白了就是对位置坐标的不断加减以使图片到拖动指定的位置上,注意这边是加法
compositeTransform.TranslateX += e.Delta.Translation.X;
compositeTransform.TranslateY += e.Delta.Translation.Y;
//自由伸缩图片,这边缩放当然是等比例缩放,当然你也可以自己设置。注意这边是乘法,因为是放大倍数
//甚至你可以不写Y轴上的缩放,看是什么效果
compositeTransform.ScaleX *= e.Delta.Scale;
compositeTransform.ScaleY *= e.Delta.Scale;
//设置缩放最大最小比例,当图片放大时,如果放大倍数小于3,则放大到指定的程度;如果放大倍数大于3,则只放大到3倍,不能再放大了
//缩小图片,同理
compositeTransform.ScaleX = Math.Min(compositeTransform.ScaleX,3.0);
compositeTransform.ScaleY = Math.Min(compositeTransform.ScaleY,3.0);
compositeTransform.ScaleX = Math.Max(compositeTransform.ScaleX,0.5);
compositeTransform.ScaleY = Math.Max(compositeTransform.ScaleY,0.5);
//旋转图片,注意这边是加法
compositeTransform.Rotation += e.Delta.Rotation * 180 / Math.PI;
}
private void image2_ManipulationCompleted(object sender, ManipulationCompletedRoutedEventArgs e)
{
textblock.Text = "ManipulationCompleted";
}
}
这边就随便截了图,包含图片位置变化,缩放,旋转:
手势操作之后:
手势之后便是介绍指针了:
其实还是弄清楚执行的顺序即可(当然其中应该有Tap的一些相关事件和Manipulation相关事件)
PointerEntered -> PointerPressed -> PointerMoved(可多次) -> PointerReleased -> PointerExited
当然就Tab事件所触发了多少事件的话,写全了应该如下:
PointerEntered -> PointerPressed -> ManipulationStarting -> PointerMoved(可多次)
-> PointerReleased -> Tapped->PointerExited
具体所代表的意思可以查看上面给出的链接
直接给出利用Pointer实现画板功能的伪代码:
XAML:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock x:Name="textblock" FontSize="35" Text="结果" HorizontalAlignment="Center"/>
<Canvas Grid.Row="1" x:Name="canvas" Width="350" Height="350" HorizontalAlignment="Center" VerticalAlignment="Center" Background="Coral"
PointerEntered="ellipse_PointerEntered"
PointerPressed="ellipse_PointerPressed"
PointerMoved="ellipse_PointerMoved"
PointerReleased="ellipse_PointerReleased"
PointerExited="ellipse_PointerExited"/>
</Grid>
.cs:
//uint表示32位无符号的整数
Dictionary<uint, PointerPoint> points = new Dictionary<uint, PointerPoint>();
private void ellipse_PointerEntered(object sender, PointerRoutedEventArgs e)
{
textblock.Text = "PointerEntered";
}
private void ellipse_PointerPressed(object sender, PointerRoutedEventArgs e)
{
//判断是否与设备接触
if(e.Pointer.IsInContact)
{
points.Add(e.Pointer.PointerId, e.GetCurrentPoint(canvas));
textblock.Text = "PointerPressed";
}
}
private void ellipse_PointerMoved(object sender, PointerRoutedEventArgs e)
{
if(e.Pointer.IsInContact)
{
var point = e.GetCurrentPoint(canvas);
if(points.ContainsKey(e.Pointer.PointerId))
{
var prev = points[e.Pointer.PointerId];
Line line = new Line();
line.X1 = prev.Position.X;
line.Y1 = prev.Position.Y;
line.X2 = point.Position.X;
line.Y2 = point.Position.Y;
line.Stroke = new SolidColorBrush(Colors.Blue);
line.StrokeThickness = 10.0;
line.StrokeStartLineCap = line.StrokeEndLineCap = PenLineCap.Round;
canvas.Children.Add(line);
}
points[e.Pointer.PointerId] = point;
textblock.Text = "PointerMoved";
}
}
private void ellipse_PointerReleased(object sender, PointerRoutedEventArgs e)
{
if (points.ContainsKey(e.Pointer.PointerId))
{
points.Remove(e.Pointer.PointerId);
textblock.Text = "PointerReleased";
}
}
private void ellipse_PointerExited(object sender, PointerRoutedEventArgs e)
{
if (points.ContainsKey(e.Pointer.PointerId))
{
points.Remove(e.Pointer.PointerId);
textblock.Text = "PointerExited";
}
}
截图:注意当Pointer脱离所作用的元素时,触发PointerExited事件,如最后一张图所见
推荐链接:我看了之后又很大的帮助和收获,也希望帮到大家
zhangjunjian127的专栏:WP7 ——触控操作Manipulation