實際上這個研究是從發現adobe的官方Actionscript3.0開發文檔開始的,在其中有一章是介紹用As3在觸摸設備上進行多點觸摸開發。它的在線版本是:Touch, multitouch and gesture input
先介紹一些我做這個實驗所使用的測試系統環境,我使用的硬件是一個46寸的光學觸摸屏,它有自帶的嵌入式計算機,跟普通的個人電腦沒啥區別,裏面運行的操作系統是Windows 7。雖然與觸摸功能相關的硬件的生產商是中國大陸公司,但硬件驅動則是一家國外的公司寫的,而且開源。
瞭解設備對觸摸功能的支持
首先我們應該討論的是Multitouch類。這個類是用來獲取關於設備對觸摸功能的支持的信息。它是一個全局靜態類,當Flash被加載之後,它被Flash Runtime初始化并賦值給成員變量。這些變量有:inputMode、mapTouchToMouse、maxTouchPoints、supportedGestures、supportsGestureEvents和supportsTouchEvents。其中除了inputMode與mapTouchToMouse之外,都是只讀。
inputMode表示著Flash Runtime對觸摸功能支持到什麽程度,它可能有三個值:MultitouchInputMode.NONE、MultitouchInputMode.TOUCH_POINT和MultitouchInputMode.GESTURE。
官方文檔的解釋:
None No special handling is provided for touch events. Set:Multitouch.inputMode=MultitouchInputMode.NONE and use the MouseEvent class to handle input.
Single touch points All touch input is interpreted, individually, and all touch points can be tracked and handled. Set:Multitouch.inputMode=MultitouchInputMode.TOUCH_POINT and use the TouchEvent class to handle input.
Gesture input The device or operating system interprets input as a complex form of finger movement across the screen. The device or operating system collectively assigns the movement to a single gesture input event. Set: Multitouch.inputMode=MultitouchInputMode.GESTURE and use the TransformGestureEvent, PressAndTapGestureEvent, or GestureEvent classes to handle input.
現在我們來寫一個簡單的程序,來獲取關於設備的觸摸信息:
package {
import flash.display.MovieClip;
import flash.ui.Multitouch;
public class main extends MovieClip
{
public function main()
{
var $output:String = "";
$output += "Multi-touch Mode: ";
$output += Multitouch.inputMode.toString();
$output += "\n";
$output += "Supports Touch Event: ";
$output += Multitouch.supportsTouchEvents.toString();
$output += "\n";
$output += "Supports Gesture Event: ";
$output += Multitouch.supportsGestureEvents.toString();
$output += "\n";
$output += "Maximum number of concurrent touch points: ";
$output += Multitouch.maxTouchPoints.toString();
$output += "\n";
var $gs:Vector.<String> = Multitouch.supportedGestures;
if($gs && $gs.length)
{
$output += "Multi-touch contact types supported: \n";
var $len:int = $gs.length;
for(var $i:int = 0; $i < $len; $i++)
{
$output += ("\t" + $gs[$i] + "\n");
}
}
discoveryTxt.text = $output;
}
}
}
然後放在測試環境上運行,輸出結果為:
捕捉簡單的觸摸事件
現在我們來研究下觸摸事件在Actionscript3.0下的表示。在AS3中,當一個觸摸事件發生后,事件對象將會發出一個TouchEvent類型的對象。
做一個不同的程序,捕捉觸摸事件,并輸出一些與之相關的信息:
package {
import flash.display.MovieClip;
import flash.ui.Multitouch;
import flash.ui.MultitouchInputMode;
import flash.display.Sprite;
import flash.text.TextField;
import flash.events.TouchEvent;
import flash.utils.*;
public class main extends MovieClip{
private var _mySprite:Sprite = new Sprite();
private var _myTextField:TextField = new TextField();
public function main()
{
/* indicate the input mode */
Multitouch.inputMode = MultitouchInputMode.TOUCH_POINT;
/* set up something as target */
this._mySprite.graphics.beginFill(0x336699);
this._mySprite.graphics.drawRect(0,0,100,100);
this._mySprite.graphics.endFill();
this._mySprite.x = 50;
this._mySprite.y = 50;
this.addChild(this._mySprite);
/* set up text field for output */
this._myTextField.width = 850;
this._myTextField.height = 600;
this._myTextField.selectable = false;
this._myTextField.multiline = true;
this._myTextField.x = 150;
/* attach listener */
this._mySprite.addEventListener(TouchEvent.TOUCH_TAP, this.tapHandler);
}
private function tapHandler($e:TouchEvent):void
{
var $output:String = "";
$output += ( "The Event Class Name: " + getQualifiedClassName($e) + "\n" );
$output += ( "The horizontal coordinate in global Stage coordinates: " + $e.stageX.toString() + "\n" );
$output += ( "The vertical coordinate in global Stage coordinates: " + $e.stageY.toString() + "\n" );
$output += ( "The id number assigned to the touch point: " + $e.touchPointID.toString() + "\n" );
$output += ( "The horizontal coordinate relative to the containing sprite: " + $e.localX.toString() + "\n" );
$output += ( "The vertical coordinate relative to the containing sprite: " + $e.localY.toString() + "\n" );
if($e.relatedObject == null)
{
$output += ( "Object that is related to the event: is null \n" );
}
else
{
$output += ( "Object that is related to the event: is not null \n" );
}
this._myTextField.text = $output;
this.addChild(this._myTextField);
}
}
}
輸出的結果為:
從現在開始,我將不再把變量的值輸出到TextField里,因為這樣做效率不高,我將使用一個專業點的調試工具:Alcon,不過很遺憾,剛剛發現它的官方網站消失了。我只好找回我本地的舊備份:Alcon v3.1.4.1854(其實我記得我另外還有個新一點的版本,可是不記得放在哪裡了),如何安裝我就不贅述了,因為以前寫過這個話題。
捕捉多點觸摸以及複雜的手勢事件
現在才終於進入到我們本來關心的話題,在一個支持多點觸摸的設備上捕捉複雜的動作。
首先在多點觸摸的設備上,同時可能有多個點發生事件,於是,Flash運行環境在TouchEvent中加了一個屬性:touchPointID,它用來區分不同的點,這個屬性在捕捉并追蹤一個持續一段時間的複雜動作時會很有用。
不同於鼠標事件,觸摸還有一個概念很重要,就是階段(phase),從TouchEvent的文檔中即可以看出它的類型有:TOUCH_BEGIN、TOUCH_END和TOUCH_MOVE。一個觸摸事件在時間上會被劃分成不同階段,而每個階段目標都會發出一個相應事件。
在Actionscript3.0里,手勢事件是用類GestureEvent和TransformGestureEvent。現在我們來實現一下捕捉放大動作事件。
package {
import flash.display.MovieClip;
import flash.display.Sprite;
import flash.display.Loader;
import flash.net.URLRequest;
import flash.ui.Multitouch;
import flash.ui.MultitouchInputMode;
import flash.events.GesturePhase;
import flash.events.Event;
import flash.events.GestureEvent;
import flash.events.TransformGestureEvent;
import com.hexagonstar.util.debug.Debug;
public class main extends MovieClip{
private var _loader:Loader = new Loader();
private var _mySprite:Sprite = new Sprite();
public function main()
{
Multitouch.inputMode = MultitouchInputMode.GESTURE;
this._mySprite.addEventListener(TransformGestureEvent.GESTURE_ZOOM, this.gestureZoomHandler);
this._loader.contentLoaderInfo.addEventListener(Event.COMPLETE, imgLoaded);
var $req:URLRequest = new URLRequest("polina-nefidova-deer-woman.jpg");
this._loader.load($req);
}
private function imgLoaded($e:Event):void
{
this._loader.x = - this._loader.width / 2;
this._loader.y = - this._loader.height / 2;
this._mySprite.addChild(this._loader);
this._mySprite.scaleX = 0.25;
this._mySprite.scaleY = 0.25;
this._mySprite.x = 500;
this._mySprite.y = 300;
this.addChild(this._mySprite);
}
private function gestureZoomHandler($e:TransformGestureEvent):void
{
if ($e.phase==GesturePhase.BEGIN)
{
Debug.trace("Phase: Begin");
}
if ($e.phase==GesturePhase.UPDATE)
{
this._mySprite.scaleX += 0.01;
this._mySprite.scaleY += 0.01;
Debug.trace("Phase: Update");
Debug.trace("scaleX: " + $e.scaleX + " ,scaleY: " + $e.scaleY);
Debug.trace("localX: " + $e.localX + " ,localY: " + $e.localY);
Debug.trace("offsetX: " + $e.offsetX + " ,offsetY: " + $e.offsetY);
}
if ($e.phase==GesturePhase.END)
{
Debug.trace("Phase: End");
}
}
}
}
它的運行結果為:
Alcon輸出的內容是:
[I] Phase: Begin
[I] Phase: Update
[I] scaleX: 3.2285714149475098 ,scaleY: 3.2285714149475098
[I] localX: 416 ,localY: 312
[I] offsetX: 0 ,offsetY: 0
[I] Phase: Update
[I] scaleX: 1 ,scaleY: 1
[I] localX: 415.402392578125 ,localY: 307.679052734375
[I] offsetX: 0 ,offsetY: 0
[I] Phase: Update
[I] scaleX: 1 ,scaleY: 1
[I] localX: 414.842578125 ,localY: 307.3987060546875
[I] offsetX: 0 ,offsetY: 0
[I] Phase: Update
[I] scaleX: 1 ,scaleY: 1
[I] localX: 414.307958984375 ,localY: 307.169189453125
[I] offsetX: 0 ,offsetY: 0
[I] Phase: Update
[I] scaleX: 1 ,scaleY: 1
[I] localX: 413.821337890625 ,localY: 303.438671875
[I] offsetX: 0 ,offsetY: 0
[I] Phase: Update
[I] scaleX: 1.017699122428894 ,scaleY: 1.017699122428894
[I] localX: 413.368359375 ,localY: 296.6919189453125
[I] offsetX: 0 ,offsetY: 0
[I] Phase: Update
[I] scaleX: 1 ,scaleY: 1
[I] localX: 409.693505859375 ,localY: 296.782080078125
[I] offsetX: 0 ,offsetY: 0
[I] Phase: Update
[I] scaleX: 1 ,scaleY: 1
[I] localX: 409.3638671875 ,localY: 290.6238037109375
[I] offsetX: 0 ,offsetY: 0
[I] Phase: Update
[I] scaleX: 1 ,scaleY: 1
[I] localX: 409.10439453125 ,localY: 290.9212646484375
[I] offsetX: 0 ,offsetY: 0
[I] Phase: Update
[I] scaleX: 1 ,scaleY: 1
[I] localX: 405.8865478515625 ,localY: 291.192138671875
[I] offsetX: 0 ,offsetY: 0
[I] Phase: End
官方文檔所告訴我們的幾乎就這麼多了,可是Zoom事件有一個很大的問題,就是它的靈敏度,只有當兩隻手指移動的方向嚴格地在同一條直線上時,它才會發生,總之這究竟是Flash運行環境的問題還是Windows 7的問題,我並不確定,也並不想深究。因為很顯然接下來我要做的事情是自己來通過對兩點觸摸的識別來捕捉放大動作。
自定義放大手勢事件
既然這樣,那麼我們的起點將會是基本的兩點觸摸,即是說不再需要系統捕捉手勢事件然後傳遞給我們,那麼我們只需將輸入模式設定為點觸摸(TOUCH_POINT)。
那麼我們來寫一個新的實驗程序:
package {
import flash.display.MovieClip;
import flash.display.Sprite;
import flash.display.Loader;
import flash.net.URLRequest;
import flash.ui.Multitouch;
import flash.ui.MultitouchInputMode;
import flash.events.GesturePhase;
import flash.events.Event;
import flash.events.TouchEvent;
import com.hexagonstar.util.debug.Debug;
public class main extends MovieClip{
private var _loader:Loader = new Loader();
private var _mySprite:Sprite = new Sprite();
public function main()
{
Multitouch.inputMode = MultitouchInputMode.TOUCH_POINT;
this.addEventListener(Event.ADDED_TO_STAGE, addedToStageHandler);
}
/* handlers */
private function addedToStageHandler($e:Event):void
{
this._loader.contentLoaderInfo.addEventListener(Event.COMPLETE, imgLoaded);
var $req:URLRequest = new URLRequest("polina-nefidova-deer-woman.jpg");
this._loader.load($req);
}
private function imgLoaded($e:Event):void
{
this._loader.x = - this._loader.width / 2;
this._loader.y = - this._loader.height / 2;
this._mySprite.addChild(this._loader);
//this._mySprite.scaleX = 0.25;
//this._mySprite.scaleY = 0.25;
this._mySprite.x = 500;
this._mySprite.y = 300;
this.addChild(this._mySprite);
this.stage.addEventListener(TouchEvent.TOUCH_BEGIN, touchBeginHandler);
this.stage.addEventListener(TouchEvent.TOUCH_MOVE, touchMoveHandler);
this.stage.addEventListener(TouchEvent.TOUCH_END, touchEndHandler);
}
private function touchBeginHandler($e:TouchEvent):void
{
Debug.trace("*******************");
Debug.trace("Touch Begin");
Debug.trace("Touch Point ID: " + $e.touchPointID);
Debug.trace("Touch Begin Position: [" + $e.stageX + ", " + $e.stageY + "]");
Debug.trace("\n");
}
private function touchMoveHandler($e:TouchEvent):void
{
Debug.trace("++++Touch Move++++");
Debug.trace("Touch Point ID: " + $e.touchPointID);
Debug.trace("Touch Move Position: [" + $e.stageX + ", " + $e.stageY + "]");
Debug.trace("\n");
}
private function touchEndHandler($e:TouchEvent):void
{
Debug.trace("Touch End");
Debug.trace("Touch Point ID: " + $e.touchPointID);
Debug.trace("Touch End Position: [" + $e.stageX + ", " + $e.stageY + "]");
Debug.trace("\n");
Debug.trace("*******************");
}
}
}
我們試一下,假設我們按照下圖中所示來操作:
將得到輸出:
[I] *******************
[I] Touch Begin
[I] Touch Point ID: 2
[I] Touch Begin Position: [690, 389.5]
[I]
[I] ++++Touch Move++++
[I] Touch Point ID: 2
[I] Touch Move Position: [691, 383.5]
[I]
......
[I] ++++Touch Move++++
[I] Touch Point ID: 2
[I] Touch Move Position: [892, 248.5]
[I]
[I] Touch End
[I] Touch Point ID: 2
[I] Touch End Position: [892, 248.5]
[I]
[I] *******************
接下來再試試同時兩點觸摸:
得到的輸出是:
[I] *******************
[I] Touch Begin
[I] Touch Point ID: 2
[I] Touch Begin Position: [391, 415.5]
[I]
[I] ++++Touch Move++++
[I] Touch Point ID: 2
[I] Touch Move Position: [385, 419.5]
[I]
[I] ++++Touch Move++++
[I] Touch Point ID: 2
[I] Touch Move Position: [369, 425.5]
[I]
[I] ++++Touch Move++++
[I] Touch Point ID: 2
[I] Touch Move Position: [363, 427.5]
[I]
[I] ++++Touch Move++++
[I] Touch Point ID: 2
[I] Touch Move Position: [363, 427.5]
[I]
[I] *******************
[I] Touch Begin
[I] Touch Point ID: 3
[I] Touch Begin Position: [630, 282.5]
[I]
[I] ++++Touch Move++++
[I] Touch Point ID: 2
[I] Touch Move Position: [357, 430.5]
[I]
[I] ++++Touch Move++++
[I] Touch Point ID: 3
[I] Touch Move Position: [635, 275.5]
[I]
[I] ++++Touch Move++++
[I] Touch Point ID: 2
[I] Touch Move Position: [351, 433.5]
[I]
......
[I] Touch End
[I] Touch Point ID: 2
[I] Touch End Position: [230, 467.5]
[I]
[I] *******************
[I] ++++Touch Move++++
[I] Touch Point ID: 3
[I] Touch Move Position: [753, 177.5]
[I]
[I] ++++Touch Move++++
[I] Touch Point ID: 3
[I] Touch Move Position: [754, 176.5]
[I]
[I] Touch End
[I] Touch Point ID: 3
[I] Touch End Position: [754, 176.5]
[I]
[I] *******************
雖然我不知道爲什麽,可是似乎兩點觸摸時候,觸摸點的ID值的分配總是2與3兩個值。
當然,不要急於下結論。我們試一下下面這種情況:
1.我首先用左手按在觸摸屏上,同時向左移動手指,一秒鐘之後右手也落在屏幕上同時向右移動;
2.我在右手移動的同時,先拿開左手,這之後將只有右手手指按在屏幕上面;
3.右手保持在屏幕上移動的同時,我又再次把左手手指按在屏幕上,并向左移動。
在這個情況下,Alcon的輸出為:
[I] *******************
[I] Touch Begin
[I] Touch Point ID: 2
[I] Touch Begin Position: [478, 300.5]
[I]
[I] ++++Touch Move++++
[I] Touch Point ID: 2
[I] Touch Move Position: [471, 305.5]
[I]
......(only ID 2 appears)
[I] ++++Touch Move++++
[I] Touch Point ID: 2
[I] Touch Move Position: [313, 375.5]
[I]
[I] ++++Touch Move++++
[I] Touch Point ID: 2
[I] Touch Move Position: [313, 375.5]
[I]
[I] *******************
[I] Touch Begin
[I] Touch Point ID: 3
[I] Touch Begin Position: [544, 301.5]
[I]
[I] ++++Touch Move++++
[I] Touch Point ID: 2
[I] Touch Move Position: [309, 376.5]
[I]
[I] ++++Touch Move++++
[I] Touch Point ID: 3
[I] Touch Move Position: [549, 299.5]
[I]
[I] ++++Touch Move++++
[I] Touch Point ID: 2
[I] Touch Move Position: [304, 374.5]
[I]
......(ID 3 and ID 2 appear anternatively)
[I] ++++Touch Move++++
[I] Touch Point ID: 2
[I] Touch Move Position: [307, 396.5]
[I]
[I] ++++Touch Move++++
[I] Touch Point ID: 3
[I] Touch Move Position: [564, 266.5]
[I]
[I] Touch End
[I] Touch Point ID: 2
[I] Touch End Position: [307, 396.5]
[I]
[I] *******************
[I] ++++Touch Move++++
[I] Touch Point ID: 3
[I] Touch Move Position: [564, 266.5]
[I]
[I] ++++Touch Move++++
[I] Touch Point ID: 3
[I] Touch Move Position: [569, 262.5]
[I]
......(only ID 3 appears)
[I] ++++Touch Move++++
[I] Touch Point ID: 3
[I] Touch Move Position: [729, 151.5]
[I]
[I] ++++Touch Move++++
[I] Touch Point ID: 3
[I] Touch Move Position: [731, 151.5]
[I]
[I] *******************
[I] Touch Begin
[I] Touch Point ID: 2
[I] Touch Begin Position: [443, 299.5]
[I]
[I] ++++Touch Move++++
[I] Touch Point ID: 3
[I] Touch Move Position: [731, 151.5]
[I]
[I] ++++Touch Move++++
[I] Touch Point ID: 2
[I] Touch Move Position: [440, 303.5]
[I]
[I] ++++Touch Move++++
[I] Touch Point ID: 3
[I] Touch Move Position: [731, 151.5]
[I]
[I] ++++Touch Move++++
[I] Touch Point ID: 2
[I] Touch Move Position: [436, 307.5]
[I]
......(ID 3 and ID 2 appear anternatively)
[I] ++++Touch Move++++
[I] Touch Point ID: 2
[I] Touch Move Position: [254, 437.5]
[I]
[I] ++++Touch Move++++
[I] Touch Point ID: 3
[I] Touch Move Position: [768, 124.5]
[I]
[I] Touch End
[I] Touch Point ID: 2
[I] Touch End Position: [254, 437.5]
[I]
[I] *******************
[I] ++++Touch Move++++
[I] Touch Point ID: 3
[I] Touch Move Position: [768, 124.5]
[I]
[I] ++++Touch Move++++
[I] Touch Point ID: 3
[I] Touch Move Position: [770, 123.5]
[I]
[I] Touch End
[I] Touch Point ID: 3
[I] Touch End Position: [770, 123.5]
[I]
[I] *******************
那麼看來這個結論是正確的。
那麼現在來考慮下我們需要做什麽。。。。。
簡單地講,是要追蹤兩個觸摸點之間的直線距離的變化,也就是幾何距離,這個值應該是兩個點在x和y象限上的差的平方和再開平方。所以也就是追蹤兩個點的位置的變化,這個工作顯然放在TOUCH_MOVE事件函數裡里最合適。爲了追蹤每一次放大事件,我們至少需要一些全局變量來存儲一些值:觸摸開始時的兩點初始距離,和當下的兩個觸摸點的座標值,我們還需要一個變量來追蹤是否當下同時又兩個觸摸點(即用戶有兩隻手指按在屏幕上)。或許增加一個方法來計算兩點距離更方便,好現在開始:
package {
import flash.display.MovieClip;
import flash.display.Sprite;
import flash.display.Loader;
import flash.net.URLRequest;
import flash.ui.Multitouch;
import flash.ui.MultitouchInputMode;
import flash.events.GesturePhase;
import flash.events.Event;
import flash.events.TouchEvent;
import flash.geom.Point;
import com.hexagonstar.util.debug.Debug;
public class main extends MovieClip{
private var _loader:Loader = new Loader();
private var _mySprite:Sprite = new Sprite();
private var _point1:Point = new Point();
private var _point2:Point = new Point();
private var _startDistance:uint = 0;
private var _currentDistance:uint = 0;
private var _touchPointCounter:uint = 0;
public function main()
{
Multitouch.inputMode = MultitouchInputMode.TOUCH_POINT;
this.addEventListener(Event.ADDED_TO_STAGE, addedToStageHandler);
}
private function calculateDistance():uint
{
return Math.sqrt( (_point1.x-_point2.x)*(_point1.x-_point2.x) + (_point1.y-_point2.y)*(_point1.y-_point2.y) );
}
/* handlers */
private function addedToStageHandler($e:Event):void
{
this._loader.contentLoaderInfo.addEventListener(Event.COMPLETE, imgLoaded);
var $req:URLRequest = new URLRequest("polina-nefidova-deer-woman.jpg");
this._loader.load($req);
}
private function imgLoaded($e:Event):void
{
this._loader.x = - this._loader.width / 2;
this._loader.y = - this._loader.height / 2;
this._mySprite.addChild(this._loader);
this._mySprite.x = 500;
this._mySprite.y = 300;
this.addChild(this._mySprite);
this.stage.addEventListener(TouchEvent.TOUCH_BEGIN, touchBeginHandler);
this.stage.addEventListener(TouchEvent.TOUCH_MOVE, touchMoveHandler);
this.stage.addEventListener(TouchEvent.TOUCH_END, touchEndHandler);
}
private function touchBeginHandler($e:TouchEvent):void
{
Debug.trace("*******************");
Debug.trace("Touch Begin");
Debug.trace("Touch Point ID: " + $e.touchPointID);
Debug.trace("Touch Begin Position: [" + $e.stageX + ", " + $e.stageY + "]");
this._touchPointCounter++;
if($e.touchPointID == 2)
{
this._point1.x = $e.stageX;
this._point1.y = $e.stageY;
}
else if($e.touchPointID == 3)
{
this._point2.x = $e.stageX;
this._point2.y = $e.stageY;
}
// get the init distance bewteen the two points
if(this._touchPointCounter == 2)
{
this._currentDistance = this._startDistance = this.calculateDistance();
}
Debug.trace(">>>>>>>>>>>>Init Distance: " + this._startDistance);
Debug.trace("\n");
}
private function touchMoveHandler($e:TouchEvent):void
{
Debug.trace("++++Touch Move++++");
if($e.touchPointID == 2)
{
this._point1.x = $e.stageX;
this._point1.y = $e.stageY;
}
if($e.touchPointID == 3)
{
this._point2.x = $e.stageX;
this._point2.y = $e.stageY;
}
if(this._touchPointCounter == 2)
{
this._currentDistance = this.calculateDistance();
}
Debug.trace(">>>>>>>>>>>>Current Distance: " + this._currentDistance);
Debug.trace("\n");
}
private function touchEndHandler($e:TouchEvent):void
{
Debug.trace("Touch End");
Debug.trace("Touch Point ID: " + $e.touchPointID);
Debug.trace("Touch End Position: [" + $e.stageX + ", " + $e.stageY + "]");
this._touchPointCounter--;
this._currentDistance = this._startDistance = 0;
Debug.trace(">>>>>>>>>>>>Init Distance: " + this._startDistance);
Debug.trace("\n");
Debug.trace("*******************");
}
}
}
試一下正常的兩點觸摸操作,即兩隻手指幾乎同時落在屏幕上面,也幾乎同時離開,Alcon的輸出為:
[I] *******************
[I] Touch Begin
[I] Touch Point ID: 2
[I] Touch Begin Position: [480, 350.5]
[I] >>>>>>>>>>>>Init Distance: 0
[I]
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current Distance: 0
[I]
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current Distance: 0
[I]
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current Distance: 0
[I]
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current Distance: 0
[I]
[I] *******************
[I] Touch Begin
[I] Touch Point ID: 3
[I] Touch Begin Position: [689, 258.5]
[I] >>>>>>>>>>>>Init Distance: 280
[I]
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current Distance: 296
[I]
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current Distance: 314
[I]
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current Distance: 331
[I]
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current Distance: 350
[I]
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current Distance: 359
[I]
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current Distance: 379
[I]
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current Distance: 392
[I]
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current Distance: 404
[I]
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current Distance: 426
[I]
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current Distance: 430
[I]
[I] Touch End
[I] Touch Point ID: 2
[I] Touch End Position: [353, 381.5]
[I] >>>>>>>>>>>>Init Distance: 0
[I]
[I] *******************
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current Distance: 0
[I]
[I] Touch End
[I] Touch Point ID: 3
[I] Touch End Position: [756, 231.5]
[I] >>>>>>>>>>>>Init Distance: 0
[I]
[I] *******************
然後再試一下,像剛剛那樣,在兩隻手指落下之後,先拿開一隻手指,之後再落在屏幕上,觀察Alcon的輸出:
[I] *******************
[I] Touch Begin
[I] Touch Point ID: 2
[I] Touch Begin Position: [490, 327.5]
[I] >>>>>>>>>>>>Init Distance: 0
[I]
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current Distance: 0
[I]
......
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current Distance: 0
[I]
[I] *******************
[I] Touch Begin
[I] Touch Point ID: 3
[I] Touch Begin Position: [554, 301.5]
[I] >>>>>>>>>>>>Init Distance: 128
[I]
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current Distance: 127
[I]
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current Distance: 125
[I]
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current Distance: 124
[I]
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current Distance: 134
[I]
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current Distance: 136
[I]
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current Distance: 147
[I]
......
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current Distance: 284
[I]
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current Distance: 287
[I]
[I] Touch End
[I] Touch Point ID: 2
[I] Touch End Position: [354, 396.5]
[I] >>>>>>>>>>>>Init Distance: 0
[I]
[I] *******************
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current Distance: 0
[I]
......
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current Distance: 0
[I]
[I] *******************
[I] Touch Begin
[I] Touch Point ID: 2
[I] Touch Begin Position: [512, 314.5]
[I] >>>>>>>>>>>>Init Distance: 230
[I]
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current Distance: 230
[I]
......
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current Distance: 422
[I]
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current Distance: 423
[I]
[I] Touch End
[I] Touch Point ID: 2
[I] Touch End Position: [349, 395.5]
[I] >>>>>>>>>>>>Init Distance: 0
[I]
[I] *******************
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current Distance: 0
[I]
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current Distance: 0
[I]
[I] Touch End
[I] Touch Point ID: 3
[I] Touch End Position: [729, 189.5]
[I] >>>>>>>>>>>>Init Distance: 0
[I]
[I] *******************
唯一奇怪的地方是在第二次實驗里,當一開始兩隻手指觸摸到屏幕時,兩點的距離一開始呈變小的趨勢,之後很快又變大,除了這個現象有點疑慮之外,目前我們建立的程序框架是基本完成了我們現階段要做的事情的,就是追蹤兩點的距離,而且僅僅在同時有兩點在觸摸的時候才追蹤。但是不要急,再繼續修改程序之前,再試一試縮小手勢的情況是否如我們所期待那樣:
[I] *******************
[I] Touch Begin
[I] Touch Point ID: 2
[I] Touch Begin Position: [829, 181.5]
[I] >>>>>>>>>>>>Init Distance: 0
[I]
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current Distance: 0
[I]
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current Distance: 0
[I]
[I] *******************
[I] Touch Begin
[I] Touch Point ID: 3
[I] Touch Begin Position: [454, 305.5]
[I] >>>>>>>>>>>>Init Distance: 261
[I]
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current Distance: 248
[I]
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current Distance: 227
[I]
......
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current Distance: 108
[I]
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current Distance: 93
[I]
[I] Touch End
[I] Touch Point ID: 2
[I] Touch End Position: [654, 261.5]
[I] >>>>>>>>>>>>Init Distance: 0
[I]
[I] *******************
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current Distance: 0
[I]
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current Distance: 0
[I]
[I] Touch End
[I] Touch Point ID: 3
[I] Touch End Position: [603, 286.5]
[I] >>>>>>>>>>>>Init Distance: 0
[I]
[I] *******************
[I] *******************
[I] Touch Begin
[I] Touch Point ID: 2
[I] Touch Begin Position: [586, 790]
[I] >>>>>>>>>>>>Init Distance: 0
[I]
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current Distance: 0
[I]
[I] Touch End
[I] Touch Point ID: 2
[I] Touch End Position: [596, 782]
[I] >>>>>>>>>>>>Init Distance: 0
[I]
[I] *******************
現在我們要進一步完成這個邏輯,當我們只是知道當下的兩個觸摸點的幾何距離是不夠的,因為在實際應用中,當手勢突然有放大變成縮小時,圖片會跟著即刻由放大變成縮小,即是說,操作的方向性反應在這個邏輯中,所以爲了得到這個方向,我們需要保留當前距離的上一個時刻的距離,那麼需要增加一個全局變量prevDistance來追蹤這個變量。這樣,在每一個TOUCH_MOVE事件中,我們可以根據比較它與當下距離的差值來判斷用戶現在是在放大還是縮小:
var $difference:Number = this._currentDistance - this._prevDistance;
if($difference > 0)
{
// zoom in
}
else
{
// zoom out
}
將程序修改至:
package {
import flash.display.MovieClip;
import flash.display.Sprite;
import flash.display.Loader;
import flash.net.URLRequest;
import flash.ui.Multitouch;
import flash.ui.MultitouchInputMode;
import flash.events.GesturePhase;
import flash.events.Event;
import flash.events.TouchEvent;
import flash.geom.Point;
import com.hexagonstar.util.debug.Debug;
public class main extends MovieClip{
private var _loader:Loader = new Loader();
private var _mySprite:Sprite = new Sprite();
private var _point1:Point = new Point();
private var _point2:Point = new Point();
private var _startDistance:uint = 0;
private var _currentDistance:uint = 0;
private var _touchPointCounter:uint = 0;
private var _prevDistance:uint = 0;
public function main()
{
Multitouch.inputMode = MultitouchInputMode.TOUCH_POINT;
this.addEventListener(Event.ADDED_TO_STAGE, addedToStageHandler);
}
private function calculateDistance():uint
{
return Math.sqrt( (_point1.x-_point2.x)*(_point1.x-_point2.x) + (_point1.y-_point2.y)*(_point1.y-_point2.y) );
}
/* handlers */
private function addedToStageHandler($e:Event):void
{
this._loader.contentLoaderInfo.addEventListener(Event.COMPLETE, imgLoaded);
var $req:URLRequest = new URLRequest("polina-nefidova-deer-woman.jpg");
this._loader.load($req);
}
private function imgLoaded($e:Event):void
{
this._loader.x = - this._loader.width / 2;
this._loader.y = - this._loader.height / 2;
this._mySprite.addChild(this._loader);
this._mySprite.x = 500;
this._mySprite.y = 300;
this.addChild(this._mySprite);
this.stage.addEventListener(TouchEvent.TOUCH_BEGIN, touchBeginHandler);
this.stage.addEventListener(TouchEvent.TOUCH_MOVE, touchMoveHandler);
this.stage.addEventListener(TouchEvent.TOUCH_END, touchEndHandler);
}
private function touchBeginHandler($e:TouchEvent):void
{
Debug.trace("*******************");
Debug.trace("Touch Begin");
Debug.trace("Touch Point ID: " + $e.touchPointID);
this._touchPointCounter++;
// get the init distance bewteen the two points
if($e.touchPointID == 2)
{
this._point1.x = $e.stageX;
this._point1.y = $e.stageY;
}
else if($e.touchPointID == 3)
{
this._point2.x = $e.stageX;
this._point2.y = $e.stageY;
}
if(this._touchPointCounter == 2)
{
this._prevDistance = this._currentDistance = this._startDistance = this.calculateDistance();
}
Debug.trace(">>>>>>>>>>>>Init Distance: " + this._startDistance);
Debug.trace("\n");
}
private function touchMoveHandler($e:TouchEvent):void
{
Debug.trace("++++Touch Move++++");
if($e.touchPointID == 2)
{
this._point1.x = $e.stageX;
this._point1.y = $e.stageY;
}
if($e.touchPointID == 3)
{
this._point2.x = $e.stageX;
this._point2.y = $e.stageY;
}
if(this._touchPointCounter == 2)
{
this._currentDistance = this.calculateDistance();
var $difference:Number = this._currentDistance - this._prevDistance;
if($difference > 0)
{
// zoom in
Debug.trace(">>>>>>>>>>>>Current State: zoom in");
}
else
{
// zoom out
Debug.trace(">>>>>>>>>>>>Current State: zoom out");
}
this._prevDistance = this._currentDistance;
}
Debug.trace("Current Distance: " + this._currentDistance);
Debug.trace("\n");
}
private function touchEndHandler($e:TouchEvent):void
{
Debug.trace("Touch End");
Debug.trace("Touch Point ID: " + $e.touchPointID);
this._touchPointCounter--;
this._currentDistance = this._startDistance = 0;
Debug.trace("\n");
Debug.trace("*******************");
}
}
}
用手指在屏幕上先做放大的動作,再做縮小的動作,在Alcon里觀察輸出:
[I] *******************
[I] Touch Begin
[I] Touch Point ID: 2
[I] >>>>>>>>>>>>Init Distance: 0
[I]
[I] ++++Touch Move++++
[I] Current Distance: 0
[I]
[I] *******************
[I] Touch Begin
[I] Touch Point ID: 3
[I] >>>>>>>>>>>>Init Distance: 175
[I]
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current State: zoom in
[I] Current Distance: 182
[I]
......
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current State: zoom out
[I] Current Distance: 659
[I]
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current State: zoom in
[I] Current Distance: 663
[I]
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current State: zoom out
[I] Current Distance: 663
[I]
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current State: zoom out
[I] Current Distance: 663
[I]
......
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current State: zoom out
[I] Current Distance: 130
[I]
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current State: zoom in
[I] Current Distance: 132
[I]
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current State: zoom out
[I] Current Distance: 130
[I]
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current State: zoom in
[I] Current Distance: 131
[I]
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current State: zoom out
[I] Current Distance: 129
[I]
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current State: zoom out
[I] Current Distance: 129
[I]
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current State: zoom in
[I] Current Distance: 130
[I]
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current State: zoom out
[I] Current Distance: 130
[I]
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current State: zoom out
[I] Current Distance: 130
[I]
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current State: zoom out
[I] Current Distance: 130
[I]
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current State: zoom out
[I] Current Distance: 127
[I]
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current State: zoom out
[I] Current Distance: 125
[I]
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current State: zoom out
[I] Current Distance: 121
[I]
[I] ++++Touch Move++++
[I] >>>>>>>>>>>>Current State: zoom in
[I] Current Distance: 122
[I]
[I] Touch End
[I] Touch Point ID: 2
[I]
[I] *******************
[I] ++++Touch Move++++
[I] Current Distance: 0
[I]
[I] Touch End
[I] Touch Point ID: 3
[I]
[I] *******************
這個輸出揭示一個問題,對於zoom in或zoom out的判斷看起來不准確,即使在手勢沿著一個方向運動(即放大或縮小),可數據上反應的情況確是有時在變大有時在變小。一方面這與判斷的地方if($difference > 0)並沒考慮0有關。但這不是主要的原因。總之,看起來我們需要一定程度上拋棄過於細微的變化,即設定一個閾值,當距離的差超過這個閾值時才進行縮放處理。
現在的情況是我們已經能夠捕捉兩點觸摸以及縮放的手勢,並且可以判斷出放大或縮小,以及追蹤兩點間距離的變化。那麼接下來該怎麼做呢?
我們需要一個地方來進行回應縮放動作的處理,比如說將舞臺上的那個照片放大或者縮小。在我過去見過的類似機制中,很多人將這類邏輯的處理放在onEnterFrame事件的處理函數中,因為這樣做,相對於把它放在TOUCH_MOVE事件處理函數中的好處是,可以令變化對象在用戶已經停止屏幕操作之後仍然運動,而通過增加加速度和模擬摩擦阻力等參數便可以讓用戶覺得觸摸體驗是帶有物理學特性的。但是在目前的情況來看,將這個邏輯分開的好處則是另外兩個:一是至少可以讓思路清晰,二是使用onEnterFrame過於耗費CPU,在TOUCH_MOVE或者MOUSE_MOVE里處理也有同樣的問題。所以最終,我們設定一個Timer,在Timer的處理函數里來改變圖像的尺寸。而爲了在Timer的處理函數里得到所需的操作,我們需要更多全局變量來追蹤發生的事情,即是放大或縮小,以及兩點的距離,等等。。。
package {
import flash.display.MovieClip;
import flash.display.Sprite;
import flash.display.Loader;
import flash.net.URLRequest;
import flash.ui.Multitouch;
import flash.ui.MultitouchInputMode;
import flash.events.GesturePhase;
import flash.events.Event;
import flash.events.TouchEvent;
import flash.geom.Point;
import flash.utils.Timer;
import flash.events.TimerEvent;
import com.hexagonstar.util.debug.Debug;
public class main extends MovieClip{
private var _loader:Loader = new Loader();
private var _mySprite:Sprite = new Sprite();
private var _point1:Point = new Point();
private var _point2:Point = new Point();
private var _startDistance:uint = 0;
private var _currentDistance:uint = 0;
private var _touchPointCounter:uint = 0;
private var _prevDistance:uint = 0;
private var _differenct:Number = 0;
private var _zoomin:Boolean = false;
private var _zoomTimer:Timer = new Timer(33);
public function main()
{
Multitouch.inputMode = MultitouchInputMode.TOUCH_POINT;
this.addEventListener(Event.ADDED_TO_STAGE, addedToStageHandler);
}
private function calculateDistance():uint
{
return Math.sqrt( (_point1.x-_point2.x)*(_point1.x-_point2.x) + (_point1.y-_point2.y)*(_point1.y-_point2.y) );
}
private function doZoom($e:TimerEvent):void
{
if(this._touchPointCounter != 2)
{
return;
}
if(this._differenct < 5)
{
return;
}
if(_zoomin)
{
this._mySprite.width += (this._mySprite.width * 0.03);
this._mySprite.height += (this._mySprite.height * 0.03);
}
else
{
this._mySprite.width -= (this._mySprite.width * 0.03);
this._mySprite.height -= (this._mySprite.height * 0.03);
}
}
/* handlers */
private function addedToStageHandler($e:Event):void
{
this._loader.contentLoaderInfo.addEventListener(Event.COMPLETE, imgLoaded);
var $req:URLRequest = new URLRequest("polina-nefidova-deer-woman.jpg");
this._loader.load($req);
}
private function imgLoaded($e:Event):void
{
this._loader.x = - this._loader.width / 2;
this._loader.y = - this._loader.height / 2;
this._mySprite.addChild(this._loader);
this._mySprite.x = 500;
this._mySprite.y = 300;
this.addChild(this._mySprite);
this.stage.addEventListener(TouchEvent.TOUCH_BEGIN, touchBeginHandler);
this.stage.addEventListener(TouchEvent.TOUCH_MOVE, touchMoveHandler);
this.stage.addEventListener(TouchEvent.TOUCH_END, touchEndHandler);
this._zoomTimer.addEventListener(TimerEvent.TIMER, doZoom);
}
private function touchBeginHandler($e:TouchEvent):void
{
this._touchPointCounter++;
// get the init distance bewteen the two points
if($e.touchPointID == 2)
{
this._point1.x = $e.stageX;
this._point1.y = $e.stageY;
}
else if($e.touchPointID == 3)
{
this._point2.x = $e.stageX;
this._point2.y = $e.stageY;
}
if(this._touchPointCounter == 2)
{
this._prevDistance = this._currentDistance = this._startDistance = this.calculateDistance();
this._zoomTimer.start();
}
}
private function touchMoveHandler($e:TouchEvent):void
{
Debug.trace("++++Touch Move++++");
if($e.touchPointID == 2)
{
this._point1.x = $e.stageX;
this._point1.y = $e.stageY;
}
if($e.touchPointID == 3)
{
this._point2.x = $e.stageX;
this._point2.y = $e.stageY;
}
if(this._touchPointCounter == 2)
{
this._currentDistance = this.calculateDistance();
var $difference:Number = this._currentDistance - this._prevDistance;
Debug.trace("$difference: " + $difference);
this._differenct = Math.abs($difference);
if($difference >= 0)
{
// zoom in
this._zoomin = true;
}
else
{
// zoom out
this._zoomin = false;
}
this._prevDistance = this._currentDistance;
}
}
private function touchEndHandler($e:TouchEvent):void
{
Debug.trace("Touch End");
Debug.trace("Touch Point ID: " + $e.touchPointID);
this._touchPointCounter--;
this._currentDistance = this._startDistance = 0;
this._zoomTimer.stop();
}
}
}
_difference變量的作用是用來追蹤兩點之間的距離的瞬間變化大小,如果這個差值小於5,我們便忽略它,以此來糾正之前觀察到的誤差,這個誤差多半是由於硬件的靈敏性的問題導致的。
另外,在縮放的功能部份,目前所使用的算法是寬高減少或增加當前值的3%,這個公式過於粗糙,它沒有將兩點距離的變化快慢考慮進去,也沒有設定任何加速度或者邊界限定。因為目前我們只是討論一個簡單原型。
最後,計時器的設定是每隔33毫秒執行一次,這也會影響操作體驗,理論上講,這個值越小,縮放的反應越流暢,同時也越小號CPU資源。
Refs:
Multitouch and gesture support on the Flash Platform
http://lucamezzalira.com/2009/12/02/multitouch-with-actionscript-3-and-air-2-0/
http://help.adobe.com/en_US/as3/dev/WSb2ba3b1aad8a27b0-6ffb37601221e58cc29-8000.html
http://help.adobe.com/en_US/FlashPlatform/beta/reference/actionscript/3/flash/ui/Multitouch.html
http://wiki.nuigroup.com/Building_Your_First_Application
http://active.tutsplus.com/tutorials/actionscript/using-native-multitouch-gestures-in-actionscript-3-0/
http://snipplr.com/view/37220/