• 移动开发

    iPhone开发之:iPhone上的触摸和手势

    by on 2011年07月29日

拥有iPhone的人一定感觉很欣喜,在这个设备上有那么多好玩的东西可以折腾,特别是那些应用。但是对Web开发者来讲,却是渴 望在iPhone上做出更优秀的Web App。这意味着我们需要对鼠标事件进行一些改变。现在我们可以办到了,尽管这些API起初会有些粗糙,可一旦你掌握了要领,你就可以在你的Web App上做一些很疯狂的事情。

我先来说说iPhone的控制台,因为我发现它在测试时是非常重要的。在Settings-Safari-Developer下,你可以把它打开或 关闭。它提供了一些简单的信息,包括错误、警告等。

当我阅读这份[Apple Developer Connection page]文档之后,虽然它写的很生动和完整,但仍 然给我留下了一些细节上的困惑。并且,如果你不是ADC(Apple开发者中心)的会员,看这个文档可能会让你更迷糊。

清理思路

Apple给API加了两个新想法:触摸(touch)和手势(gesture)。触摸用来记录你有多少手指放在屏幕上、它们都在哪里、它们在做什 么操作以及它们的轨迹。手势来检测你在用两个手指做什么,是否在捏(pinching)、推(pushing)、或是在旋转(rotating)。

触摸

当你把一根手指头放在屏幕上时,触摸(touch)事件便被触发了。每当一根新的手指放在屏幕上,就会触发一个新的touchstart事件。而当 手指抬起离开屏幕,touchend事件便被触发了。如果在手指接触屏幕后,你又挪动了这根手指,那么触发的是touchmove事件。

有以下touch事件:

  • touchstart: 手指放在屏幕上时触发
  • touchend: 手指离开屏幕时触发
  • touchmove: 手指在屏幕上移动时触发
  • touchcancel: 系统可以取消touch事件,但我不确定它如何才能触发。我想可能是当你拖放时,接到一个类似短信或者其它事件时,才会触发。但我没有测试成功过。
node.ontouchstart = function(evt){
 console.log(evt.pageX + "/" + evt.pageY);
 // 哦不! 值都是空的,肯定有BUG
}

我的第一个错误,发生在监听这些事件并试着得到这些事件的坐标(pageX, pageY等)时。在再一次看了ADC的文档后,我了解了这三个事件依附于对象。但我不能十分确定,所以我去测试、记录、试验。

它帮助我解决了Apple开发者们一直觉得棘手的问题。在使用鼠标时,你实际上只产生一个点:通过鼠标指针。当你用手时,你可以用两个手指,当你击 打(tap)屏幕右侧时,另一只手指可以放在屏幕左侧。

我们的事件对象有一个列表,这个列表包含了每个接触屏幕的手指所产生的信息。它还包含了另两个列表,一个只包含来自相同节点的手指信息,另一个只包 含与当前事件相关的手指信息。每一个touch事件都会激活这些列表。

下面就是这个列表:

  • touches: 每个接触屏幕的手指所产生的信息
  • targetTouches: 和touches类似,但它过滤出只来自相同节点的手指信息
  • changedTouches: 只包含与当前事件相关的每个手指信息(见下文)

要更好地理解这些列表,让我们来快速浏览一些例子吧:

  • 当我把一根手指放在屏幕上,3个列表都是相同的信息。它会在changedTouches中,因为手指放上去就会触发这个事件。
  • 当我放上第二根手指,touches会变为两项,每一根手指都有一项。只当第二根手指放在第一根手指所在节点上时,targetTouches才 会变成两 个项。changedTouches的信息和第二根手指有关,因为它触发了事件。
  • 当我恰巧在同一时刻放上两根手指时,changedTouches才可能会有两个项目,每根手指一个。
  • 如果我移动手指,唯一会改变的列表是changedTouches并且会包含多个移动手指的信息(最少有一个)。
  • 当我抬起一根手指,它会从touches、targetTouches中移除,同时它会出现在changedTouches中,因为它又触发了这 个事件。
  • 移除最后一根手指,会清空touches和targetTouches,changedTouches会包含最后一根手指的信息。

有了这个列表,我可以对用户的操作保持密切的监控。想象一下,用JavaScript创造(另)一个Super Mario的克隆版本。我可以告诉用户,他(她)的拇指在一个什么方向上,同时也可以监控用户的跳跃或发射子弹。

正如我之前所说,这些列表包含了手指触摸屏幕时的信息。这些对象都非常相似,就像你通常看到的那样,在一个事件对象转到一个事件处理器时,一组有限 的属性可以用在这些对象上。下面是这些对象上属性的完整列表:

  • clientX: touch事件相对于视图(viewport)的X轴坐标(不包括滚动位移)
  • clientY: touch事件相对于视图(viewport)的Y轴坐标(不包括滚动位移)
  • screenX: 相对于屏幕
  • screenY: 相对于屏幕
  • pageX: 相对于整个页面(包括滚动)
  • pageY: 相对于整个页面(包括滚动)
  • target: touch事件所发生的节点
  • identifier: 一个id号,为每一个touch事件提供唯一标识

对于桌面Web设计而言,在一个常规的mousemove事件中,进入节点的target属性通常是鼠标当前悬停的属性。但是在所有的iPhone Touch事件中,target则是参照于源节点。

开发iPhone Web App的烦恼之一就是,即使你为你的App设置好了视图区域(viewport),拖动手指时仍会让页面移动。幸运的是,touchmove的事件对象提 供了[preventDefault()]函数(一个标准的DOM事件函数),通过这个方法,在你用手指拖动时,可以 让页面保持绝对静止。

用Touch API来实现拖放

我们不必像担心mousemove那样费心跟踪down/up事件,因为touchmove仅在touchstart后才会触发。

node.ontouchmove = function(e){
 if(e.touches.length == 1){ // 仅处理一根手指
 var touch = e.touches[0]; // 获取#1号手指的信息
 var node = touch.target; // 寻找拖放开始时的节点
 node.style.position = "absolute";
 node.style.left = touch.pageX + "px";
 node.style.top = touch.pageY + "px";
 }
}

手势

这个比Touch API要容易得多。手势(gestures)事件在两根手指任何时候触摸屏幕时都会触发。如果任何一根手指落在你绑定了手势处理 (gesturestart、gesturechange、gestureend)的节点上时,你都会接收到对应的事件。

scale和rotation是这个事件对象的两个关键点。因为,用户在进行捏(pinch)或推(push)的手势时,scale可以提供给你放 大或缩小的倍数。而rotation会提供给你用户在用手指旋转时所产生的角度。

用Gestures API实现缩放和旋转

我们将使用Webkit的transform属性来旋转对象节点。

var width = 100, height = 200, rotation = ;

node.ongesturechange = function(e){
 var node = e.target;
 // 缩放和旋转都是相对值,
 // 所以要等手势结束时再更改我们的变量
 node.style.width = (width * e.scale) + "px";
 node.style.height = (height * e.scale) + "px";
 node.style.webkitTransform = "rotate(" + ((rotation + e.rotation) % 360) + "deg)";
}

node.ongestureend = function(e){
 // 更新这些变量,以备后用
 width *= e.scale;
 height *= e.scale;
 rotation = (rotation + e.rotation) % 360;
}

冲突

一些读者可能已经注意到,gesture只是touch事件的一种看起来更漂亮的展现形式。完全正确,如果处理不当的话,你会发现一些古怪的行为。 记得在页面中记录当前所发生的信息,当它们出现冲突时,你也许会让这些事件实现”双赢“。

行动

我把这些事件组合在一起,做了个简单的Demo:

这是一个简单的应用,展示了这些API的令人难以置信的灵活性和能力。这是一个简单的灰色方块,可以给它着色、改变边框样式,可以拖放、缩放和旋 转。

在iPhone上打开http://tinyurl.com/sp-iphone, 并尝试以下操作:

  • 把一根手指放到有颜色的方块上,把另一根手指放在有边框的方块上
  • 用两个有颜色的方块或两个有边框的方块做相同的事
  • 用一根手指在页面上拖放方块
  • 缩放、旋转方块
  • 一根手指拖放方块,同时另一根手指缩放旋转它;移开一根手指,另一根手指继续拖放它

我还可以做些什么?

通过Apple目前提供给我们的这些API,我们能利用它们来实现哪些更NB的应用?我不清楚。我只知道Apple给了我们一个很深思熟虑的 API。

这个新的API,我们可以通过mousedown和mouseup事件来很容易的模仿和学习。mousemove却是一个糟糕的东东。首先,我们只 能在手指接触后(相当于mousedown)才可以获得touch事件,而我们不管鼠标是否按下,都可以获得mousemove。此外,防止页面来回跳动 并不是我们可以自动控制的。如果给document加一个handler,用户将完全无法滚动页面了!

这也带给我们常规拖放方式的思考。尽管拖放在鼠标键按下的情况下(touchmove的工作方式)只和mousemove有关系,我们仍没有任何方 法来决定拖动结束的时候用户的手指在什么节点上(由于target参照源节点)。如果打算使用一个拖放系统,它就必须知道已注册的拖动目标在页面上的位置 和尺寸。


注:本文译自http://www.sitepen.com/blog/2008/07/10/touching-and-gesturing-on-the-iphone/, 原作者为nroberts。