最近完成了一个Windows Store上面的手势操作的页面。在这里总结了一下经验和心得,希望能和大家一起分享和讨论一下。
首先,要纠正一个误区,在Windows Store里面,手势和鼠标的操作事件是不区分的。比如简单的手指滑动事件,正常的写法还是在PointerPressed, PointerMoved和PointerReleased里面去处理。大家可能认为,手势的事件应该在,ManipulationStarted和ManipulationDelta等事件里面去处理。但是如果我们在Manipulation事件里面处理了,Pointer的事件依然会被触发,而且我们也并不能通过这样的方式区分是鼠标,还是手势触发了这个事件。
文档上是这样说的,Tapped,Pointer和Manipulation,是三个级别的输入相应事件。Tapped 最高,Pointer是中间,Manipulation是底层。如果高级别的事件被触发,底层的事件也会被触发,反之则不然。所以,如果能在高层处理的事件,尽量在高层处理,否则底层会写的很复杂。比如手指滑动的事件,应该在Pointer层面上解决,他比Manipulation的层次高。
Windows Store 列举了一些常用的手势。
Tap |
一个手指的点击 |
Press and hold |
一个手指点击并长按 |
Slide |
一个或多个手指向同一方向滑动 |
Swipe |
一个或多个手指向同一方向短距离滑动 |
Pinch |
两个以上手指聚拢 |
Rotate |
两个以上手指旋转 |
Stretch |
两个以上手指分散 |
Tapped 的事件是最高级的,但是要在UIElement里面声明才能用相应的事件。比如 Tapped, DoubleTapped,RightTapped, 和Holding 对应的就是 IsTapEnabled,IsDoubleTapEnabled, IsRightTapEnabled, 和 IsHoldingEnabled。
Pointer的层面就可以处理类似于Slide或者Swipe之类的手势,但是这样的手势没有对应的事件,我们只能在Pointer里面处理,Pointer里面可以提供多点触摸的支持。我们可以通过
PointerRoutedEventArgs. GetCurrentPoint 函数获取PointerPoint,这个函数的坐标系选取可以根据传入的Element来确定。在这个类里面,我们可以用PointerId去区分不同手指的操作。PointerDevice可以帮我们区别鼠标和手势。
另外,在SelectionStates的VisualStateGroup里面,可以处理SelectedSwiping事件,这是在ListViewItem的template里面的,请参考http://msdn.microsoft.com/en-us/library/windows/apps/jj709921.aspx
Manipulation是最低层,他可以处理旋转,缩放,还有速度,这里我就不详细说明了,大家可以参考
和这个例程
http://code.msdn.microsoft.com/windowsapps/Input-XAML-user-input-e61c8054
Tips 1:
我这次做的是播放器的页面,只是处理手指移动的事件,但在做的过程中,我发现区别鼠标和手势还是很麻烦的一件事情。我们可以通过Windows.Devices.Input.PointerDevice.GetPointerDevices API 去获得这个设备有哪些输入设备,但是我们不能确定用户当前使用的是鼠标或者是触摸的设备。
我的做法就是在PointerPressed事件里面判断当前使用的设备,并且设置一个标示符,在PointerMoved 事件里面通过标示符来区分手势和鼠标的操作。手势可以在播放器里面直接控制进度和音量,但是鼠标的移动事件并不需要这样。
另外,要区别手势的点击和滑动更是一个头痛的问题,因为无论是点击,还是滑动,都要激发PointerPressed, PointerMoved和PointerReleased,这三个事件。我曾经很傻很天真的认为,手势操作我只需要处理PointerMoved事件就好了。但是这种情况有时会有Pointer这个结构体为空的状况,我也不清楚为什么。后来的解决方案是,在PointerPressed事件里面建一个10个Pointer的List,在PointerMoved里面对这个List添加Poninter,如果小于10,退出,这个时候就是点击事件,超过10个就是滑动事件。
Tip 2:
关于延时隐藏的问题,一开始我是用动画做的,但是后来发现,动画做完之后那个属性被锁定了。再去操作就很难,后来就改用ThreadPoolTimer,起一个UI线程来修改的。我之前的同事是用DispatcherTimer 去做的,我觉得这个做法需要timer.start和timer.stop函数去辅助,有些繁琐。
Tip 3:
Image 控件有个bug 就是用代码修改Image.Source 会失效,尤其是快速的切换这个Source。之前我做过一个用很多图片切换,形成一个动画的效果。这个实现的思路类似于双缓冲,通过设置Visibility来切换前后的Image,把后面的Image的Source修改,然后呈现到前端,再把前端的Image切换到后面换Source,这样的做法效果还不错。但是如果图片没有那么多,我们直接在一个Grid里面放所有的Image 然后修改Visibility好了。