Windows Phone 7 开发探索笔记2——触控操作之Manipulation
在上篇文章中介绍了底层的触控编程接口,本文将讲解Silverlight for Windows Phone中的高级触控编程接口,与之相关的是定义在UIElement中的
ManipulationStarted,ManipulationDelta和ManipulationCompleted事件。
一.Manipulation相关事件
这3个事件并不是单独来处理每个手指的触控信息的,它们将所有手指的平移和缩放操作进行了整合。由于这3个事件都是在UIElement类中定义的,都是基于具体的元素的,而非应用程序级别的事件,所以我们可以为任何UI元素添加对这些事件的处理,比如ListBox,Canvas,Rectangle等等。下面的XAML代码是在一个Rectangle元素中添加了对这3个事件的处理程序:
<Canvas x:Name="canvas" Background="Orange">
<Rectangle x:Name="rectangle" Width="200" Height="200"
Fill="Blue" Stroke="Red" StrokeThickness="5"
ManipulationStarted="rectangle_ManipulationStarted"
ManipulationDelta="rectangle_ManipulationDelta"
ManipulationCompleted="rectangle_ManipulationCompleted">
<Rectangle.RenderTransform>
<TranslateTransform x:Name="translation"/>
</Rectangle.RenderTransform>
</Rectangle>
</Canvas>
</Grid>
从它们的名字就可以看出它们被触发的顺序:先是一个ManipulationStarted事件, 然后是0个或多个ManipulationDelta事件, 最后是一个ManipulationCompleted事件。在它们对应的Code-Behind文件中相应的事件处理程序的定义如下:
void rectangle_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
void rectangle_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
我们在程序中重点使用的就是这3个类型的事件参数,它们的类型虽有不同,但却有几个相同的属性:
- OriginalSource:类型为object,它是定义在RoutedEventArgs类中的,通过它我们可以获取到触发这个事件的原始对象。
- ManipulationContainer:类型为UIElement,可以获取到定义当前这个触控操作坐标的对象(通常与OriginalSource是相同的)。
- ManipulationOrigin:类型为Point。获取操作的起源坐标,即手指触摸到的那一点的坐标,此坐标的数值就是相对于ManipulationContainer对象左上角的。如果有两个或多个手指在操作一个元素,那么ManipulationOrigin 属性会给出多个手指的平均坐标。
- Handled:类型为bool,是用来指示路由事件在路由过程中的事件处理状态的。如果我们不想让当前事件沿着Visual Tree继续传播可以将它设置为true。有关路由事件请参见路由事件。
在接下来的文章中我会通过一个例子介绍所有与Manipulation事件相关的内容。首先来看一下程序的Logic Tree和效果图:
下面的代码演示了这3个事件中相同的属性:
{
Debug.WriteLine("Rectangle ManipulationStarted");
Debug.WriteLine("OriginalSource: " + (e.OriginalSource as FrameworkElement).Name);
Debug.WriteLine("ManipulationContainer: " + (e.ManipulationContainer as FrameworkElement).Name);
Debug.WriteLine("ManipulationOrigin: " + "X: " + e.ManipulationOrigin.X.ToString() + " Y: " + e.ManipulationOrigin.Y.ToString() + "\n");
}
void rectangle_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
{
Debug.WriteLine("Rectangle ManipulationDelta");
Debug.WriteLine("OriginalSource: " + (e.OriginalSource as FrameworkElement).Name);
Debug.WriteLine("ManipulationContainer: " + (e.ManipulationContainer as FrameworkElement).Name);
Debug.WriteLine("ManipulationOrigin: " + "X: " + e.ManipulationOrigin.X.ToString() + " Y: " + e.ManipulationOrigin.Y.ToString() + "\n");
}
void rectangle_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
{
Debug.WriteLine("Rectangle ManipulationCompleted");
Debug.WriteLine("OriginalSource: " + (e.OriginalSource as FrameworkElement).Name);
Debug.WriteLine("ManipulationContainer: " + (e.ManipulationContainer as FrameworkElement).Name);
Debug.WriteLine("ManipulationOrigin: " + "X: " + e.ManipulationOrigin.X.ToString() + " Y: " + e.ManipulationOrigin.Y.ToString() + "\n");
}
下面是输出结果:
和上面说过的顺序一样。此次操作触发了3次ManipulationDelta事件,由于手指(这里是鼠标,因为是在模拟器上操作的)在屏幕上移动了,所以每次的ManipulationOrigin值都是不一样的。同时我们还可以看到OriginalSource和ManipulationContainer是相同的。
二.理解构建在单个元素上的一系列操作
和底层的触控API Touch.FrameReported为每个特定的手指都关联一个特定的Id不同,Manipulation相关的事件是基于UI元素的,所以这些事件不需要这个Id。当多个手指触摸一个元素时它们会被转化为一系列的Manipulation事件,当不同的手指在不同的元素上时则会产生两个系列的Manipulation事件,这两个系列是独立的。当然它们可以通过ManipulationContainer属性来区分。例如:将一个手指放在一个元素上,首先一个ManipulatedStarted事件会被触发,如果手指移动那么就会触发ManipulationDelta事件。保持这个手指不动,将另一个手指放在相同的元素上不会再触发一个新的ManipulatonStarted 事件。但如果此时我将另一个手指放在其他的元素上,则会触发那个元素的ManipulationStarted事件。
如果你想在单个元素上跟踪不同手指的触控信息,那就应该使用Touch.FrameReported事件,关于此事件的内容可以参见我的上一篇文章。
三.详解3种Manipulation事件参数
1.首先来看最简单的ManipulationStartedEventArgs类:除了上面说的4种共有的属性外,它还有一个Complete方法,它是用来告诉系统将ManipulationStarted事件结束掉,这样的话即使你的手指在屏幕上移动ManipulationDelta也不会被触发。
2.接下来是ManipulationDeltaEventArgs类:除了共有属性外,这个类还包含2个ManipulationDelta类型的属性:CumulativeManipulation和DeltaManipulation。ManipulationDelta类包含2个Point类型的属性:Scale和Translation。
Scale和Translation属性帮我们将一个或多个手指在某个元素上的复合动作解析成了元素自身的移动和尺寸变化。Scale表示的是缩放因子,Translation表示的是平移的距离。我们用一个手指操作时就可以改变Translation的值,但如果要改变Scale需要用两个手指操作。当你手指在一个元素上移动时,手指所在的新位置和原来位置的差值就会反映在Translation中。如果是用两个手指进行缩放,原来手指之间的距离与缩放后手指间距离差值会反映在Scale中。有一点需要注意:如果我们没有对元素进行缩放操作,Scale的值是(0, 0),我不知道这是不是Silverlight for Windows Phone的一个Bug,按理说如果没有缩放操作,默认的值应该是(1, 1)。
下面来说CumulativeManipulation属性和DeltaManipulation属性的区别:虽然这两个属性都包含Scale和Translation。但CumulativeManipulation中的值是从ManipulationStarted事件开始到当前事件为止累加得到的(通过属性的名字就可以看出来),而DeltaManipulation中的值是本次ManipulationDelta事件相对于上一次ManipulationDelta或ManipulationStarted事件而言的,是单次的改变。
除了CumulativeManipulation和DeltaManipulation属性,ManipulationDeltaEventArgs类中还有Complete方法,它的作用和ManipulationStarted中Complete方法一样,都是通知系统当前的操作结束,调用此方法后即便手指在元素上移动也只会触发一次ManipulationDelta事件,本系列Manipulation操作中不会再有后续的ManipulationDelta事件被触发(但可以触发ManipulationCompleted事件)。
另外,ManipulationDeltaEventArgs类中还有一个IsInertial属性和Velocities属性,不过这两个属性好像并不能提供有用的信息,在我的测试过程中它们始终是False和(0,0)。
3.最后是ManipulationCompletedEventArgs类型:除了共有属性外,还包含下面3个属性:
- FinalVelocities:类型和ManipulationDeltaEventArgs类中的Velocities一样,都是ManipulationVelocities,通过它可以获取手指离开屏幕时的速度。
- IsInertial:类型为bool,和ManipulationDeltaEventArgs类中的IsInertial一样(IsInertial属性在后面会详细说明)。
- TotalManipulation:类似于ManipulationDeltaEventArgs类的CumulativeManipulation,类型也是ManipulationDelta,它是从ManipulationStarted事件到ManipulationCompleted事件全过程的累加值。
四.完整实例
我在上述程序中的Rectangle元素的RenderTransform属性中添加了一个TranslateTransform变换。并在这个Rectangle元素的ManipulationDelta事件处理程序中对TranslateTransform进行了操作,使得这个矩形可以在屏幕上移动,同时将所能获取到的信息输出到Output窗口,下面是代码:
{
Debug.WriteLine("Rectangle ManipulationStarted\n");
}
void rectangle_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
{
this.translation.X += e.DeltaManipulation.Translation.X;
this.translation.Y += e.DeltaManipulation.Translation.Y;
Debug.WriteLine("Rectangle ManipulationDelta");
Debug.WriteLine("Rectangle Translation X:" + e.DeltaManipulation.Translation.X + " Rectangle Translation Y:" + e.DeltaManipulation.Translation.Y);
Debug.WriteLine("Rectangle Cumulative X:" + e.CumulativeManipulation.Translation.X + " Rectangle Cumulative Y:" + e.CumulativeManipulation.Translation.Y);
Debug.WriteLine("LinearVelocity X:" + e.Velocities.LinearVelocity.X + " LinearVelocity Y:" + e.Velocities.LinearVelocity.Y + " IsInertial:" + e.IsInertial + "\n");
}
void rectangle_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
{
Debug.WriteLine("Rectangle ManipulationCompleted");
Debug.WriteLine("Rectangle Total Translation X:" + e.TotalManipulation.Translation.X + " Rectangle Total Translation Y:" + e.TotalManipulation.Translation.Y);
Debug.WriteLine("FinalVelocities X:" + e.FinalVelocities.LinearVelocity.X + " FinalVelocities Y:" + e.FinalVelocities.LinearVelocity.Y + " IsInertial: " + e.IsInertial + "\n");
}
在上面代码的rectangle_ManipulationDelta方法中,我分别将Translation的X和Y赋给了TranslateTransform变换的X和Y,以保证矩形可以随意移动。下面是模拟器和输出窗口的截图:
注意上图中ManipulationCompleted事件参数中的FinalVelocities数值很大,这是因为我当时移动矩形的速度非常快,如果是很慢的移动它,那么这个数值将变为0,同时IsInertial属性为False。另外,如果在ManipulationDelta的事件处理程序中调用了事件参数的Complete方法,则移动矩形时只能一下一下地移动,因为只会触发一次ManipulationDelta事件,并且ManipulationCompleted事件参数中的FinalVelocities属性会抛出NullReferenceException异常。
五.IsInertial属性
在WPF中,通过设置ManipulationInertiaStartingEventArgs参数,系统会在手指离开屏幕后通过算法来模拟惯性效果,即触发额外的ManipulationDelta事件。而在Silverlight for Windows Phone中并不支持这个特性。在WPF中,ManipulationDelta和ManipulationCompleted事件参数中的IsInertial属性本来是用来说明当前事件是否是在惯性效果发生期间被触发的,而在Silverlight for Windows Phone中,ManipulationDelta事件参数的IsInertial属性总是False。即便在快速移动时,ManipulationCompleted事件中的IsInertial为True,也并没有出现惯性效果,只是移动的过程比较流畅,手指抬起时矩形元素还是立刻就停止了移动。所以,我认为ManipulationDelta和ManipulationCompleted事件参数中的IsInertial属性没有太大的参考价值。如果想在Silverlight for Windows Phone中模拟惯性效果,我们可以借助ManipulationCompleted事件参数中的FinalVelocities属性来实现。
好了,关于Windows Phone 7中的高级触控编程接口就介绍这么多,下一篇文章会介绍与手势相关的触控操作。
六.下载示例代码:
如果大家喜欢我的文章,请点击“推荐”,谢谢!