MouseGesture

Mouseの動きをタッチパネルでおなじみのswipeなどのイベントとして出力するクラス。

MouseGesture – wonderfl build flash online

とれるイベントは、swipe、tap(回数カウントあり)、longPress。
タッチした後、移動すればswipeで、移動せずに離したら、tapとする。swipeとtapはは排他関係にあり、同時にイベントを送出することはない。tapには回数カウントをするので、ダブルタップ、トリプルタップでの動作も可能。ただし、トリプルタップ時も、1,2回目のtap時のイベント送出も行われるので、注意が必要。

すでにあるSpriteに対してイベントをくっつけるイメージで作ったけど、Spriteを継承した、MouseGestureSpriteを作って、そこでMouseGestureが取得できるようにして、マウスジェスチャーが必要なSpriteはかならず、MouseGestureSpriteを継承するようにしたほうが、良かったのかな。
いろいろ考え中。だけど、これはこれでちゃんと動いているので、頭の整理も含めて、公開。

tapとswipeの閾値.(Number)
gesture.distance = 3;
タッチパネルの場合、同じ座標でup/downするのは難しいので、許容範囲を作る。
このピクセル以下だとtap、以上だとswipeと検出される。

前のtapとの差がこの時間(ミリ秒)以下であれば、マルチタップとして認識ます.(Number)
gesture.mulitiTapDelay = 700;

この値の分のmouseMoveを保持したのちswipeであるかの判定をします.(int)
interval = 3;
この値の回数を受け取ったあと、距離を測る。
swipeの検出には、interval/FPSの時間がかかることになる。

longTap(長押し)と判断されるまでの時間(ミリ秒)を設定します.(Number)
gesture.delay = 1000;

名前とか考え方は「iPadプログラミングガイド」を参考にした。

[sourcecode language=”as3″]
package
{

import flash.display.Sprite;
import flash.events.Event;
/**
* …
* @author umhr
*/
[SWF(width = 465, height = 465, backgroundColor = 0x000000, frameRate = 30)]
public class WonderflMain extends Sprite
{
public function WonderflMain()
{
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);

}

private function init(e:Event = null):void
{
var canvas:Sprite = new Sprite();
canvas.graphics.beginFill(0xFFFFFF);
canvas.graphics.drawRoundRect(40, 40, stage.stageWidth – 80, stage.stageHeight – 80, 16, 16);
canvas.graphics.endFill();
addChild(canvas);

addChild(new Logger(0, 0, 200, 150, false));

var gesture:MouseGesture = new MouseGesture(canvas);

// tapとswipeの閾値.(Number)
// gesture.distance = 3;
// タッチパネルの場合、同じ座標でup/downするのは難しいので、許容範囲を作る。
// このピクセル以下だとtap、以上だとswipeと検出される。

// 前のtapとの差がこの時間(ミリ秒)以下であれば、マルチタップとして認識ます.(Number)
// gesture.mulitiTapDelay = 700;

// この値の分のmouseMoveを保持したのちswipeであるかの判定をします.(int)
// interval = 3;
// この値の回数を受け取ったあと、距離を測る。
// swipeの検出には、interval/FPSの時間がかかることになる。

// longTap(長押し)と判断されるまでの時間(ミリ秒)を設定します.(Number)
// gesture.delay = 1000;

gesture.addEventListener(MouseGestureEvent.TAP, gesture_tap);
gesture.addEventListener(MouseGestureEvent.SWIPE, gesture_swipe);
gesture.addEventListener(MouseGestureEvent.TOUCH, gesture_touch);
gesture.addEventListener(MouseGestureEvent.LONG_PRESS, gesture_longPress);

}

private function gesture_longPress(e:MouseGestureEvent):void
{
Logger.log(e.type, e.gestureTarget);
}

private function gesture_touch(e:MouseGestureEvent):void
{
Logger.log(e.type, e.x, e.y, e.point);
}

private function gesture_swipe(e:MouseGestureEvent):void
{
Logger.log(e.type,e.point);
}

private function gesture_tap(e:MouseGestureEvent):void
{
Logger.log(e.type, e.tapCount);
}
}

}

import flash.display.Sprite;
import flash.text.TextField;
import flash.text.TextFormat;

class Logger extends Sprite
{
private var _textField:TextField;
private var _isTrace:Boolean;

static private var _instance:Logger;
public function Logger(x:Number = 0, y:Number = 0, width:int = 200, height:int = 150, isTrace:Boolean = false)
{
this.x = x;
this.y = y;
_isTrace = isTrace;
_instance = this;

init(width, height);
}

private function init(width:int, height:int):void
{
graphics.beginFill(0x333333, 0.8);
graphics.drawRect(0, 0, width, height);
graphics.endFill();
mouseChildren = mouseEnabled = false;

_textField = new TextField();
_textField.multiline = true;
_textField.width = width;
_textField.height = height;
_textField.defaultTextFormat = new TextFormat("_sans", 12, 0xFFFFFF);
addChild(_textField);
}

/**
* 出力内容を投げます。
* @param … rest
*/
static public function log(… rest):void
{

if (_instance._isTrace) {
trace.apply(null, rest);
}

var text:String = rest.join(" ");

if(_instance._textField.text == ""){
_instance._textField.text = text;
}else{
_instance._textField.appendText("\n" + text);
}
var re:String = _instance._textField.text;
var textList:Array = re.split("\r");
while(_instance._textField.textHeight + 5 > _instance._textField.height){
textList.shift();
_instance._textField.text = textList.join("\n");
}

}
}

import flash.display.Sprite;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.MouseEvent;
import flash.events.TimerEvent;
import flash.utils.Timer;
/**
* …
* @author umhr
*/
class Core extends EventDispatcher
{
private var _target:Sprite;
private var _timer:Timer;
/**
* longTap(長押し)と判断されるまでの時間(ミリ秒)を設定します.
*/
public var delay:Number = 1000;
private var _isStop:Boolean;
public function Core(target:Sprite) {
_target = target;
if (_target.parent) {
init();
}
_target.addEventListener(Event.ADDED_TO_STAGE, addedToStageHandler);
_target.addEventListener(Event.REMOVED_FROM_STAGE, removedFromStageHandler);
}
protected function addedToStageHandler(event:Event):void {
if (_isStop) { return };
init();
}
protected function init():void {
_target.removeEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
_target.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
}
protected function removedFromStageHandler(event:Event):void {
_target.removeEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
_target.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
_target.removeEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
if(_timer){
_timer.reset();
_timer.removeEventListener(TimerEvent.TIMER_COMPLETE, onTimerComp);
}
}

/**
* mouseDownイベントが発生すると実行されます.
*/
public var onMouseDown:Function = function(event:MouseEvent):void { };
private function atMouseDown(event:MouseEvent):void {
onMouseDown(event);
}

protected function mouseDownHandler(event:MouseEvent):void {
if (_isStop) { return };
atMouseDown(event);
_target.removeEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
_target.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
if (this.hasEventListener(MouseGestureEvent.SWIPE)) {
_target.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
_target.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
}
if(this.hasEventListener(MouseGestureEvent.LONG_PRESS)){
_timer = new Timer(delay, 1);
_timer.delay = delay;
_timer.removeEventListener(TimerEvent.TIMER_COMPLETE, onTimerComp);
_timer.addEventListener(TimerEvent.TIMER_COMPLETE, onTimerComp);
_timer.start();
}
}
protected function mouseUpHandler(event:MouseEvent):void {
if (_isStop) { return };
_target.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
_target.removeEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
if(_timer){
_timer.reset();
_timer.removeEventListener(TimerEvent.TIMER_COMPLETE, onTimerComp);
}
}

protected function mouseMoveHandler(event:MouseEvent):void {
if (_isStop) { return };
if (!event.buttonDown) {
if(_timer){
_target.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
_timer.reset();
_timer.removeEventListener(TimerEvent.TIMER_COMPLETE, onTimerComp);
}
}
}
protected function onTimerComp(event:TimerEvent):void {
if (_isStop) { return };
_timer.removeEventListener(TimerEvent.TIMER_COMPLETE, onTimerComp);
}

/**
* 二度と使えないように破棄します.
*
*/
public function dispose():void {
removedFromStageHandler(null);
_target.removeEventListener(Event.ADDED_TO_STAGE, addedToStageHandler);
_target.removeEventListener(Event.REMOVED_FROM_STAGE, removedFromStageHandler);
_target = null;
}

/**
* 一時停止します.
*
*/
public function stop():void {
_isStop = true;
removedFromStageHandler(null);
}

/**
* 一時停止したものを再開します.
* stop()の後に使います。
*/
public function play():void {
_isStop = false;
if (_target.parent) {
init();
}
}

}

import flash.display.Sprite;
import flash.events.MouseEvent;
import flash.events.TimerEvent;
/**
* マウスジェスチャーを検知するクラス.
* 管理しているもの
* ・イベントのジェスチャー化
* ・swipe,tap,doubleTap,longPressが取れる
* ・swipeとtapは排他関係にある。
* (swipeとtapを同時に示すことは無い)
*
* イベントの参考
* http://developer.apple.com/jp/devcenter/ios/library/documentation/iPadProgrammingGuide.pdf
* …
* @author umhr
*/
class MouseGesture extends Core
{
private var _distanceListX:Array = [];
private var _distanceListY:Array = [];
private var _downTarget:Object;
private var _currentTarget:Object;
private var _currentTapTarget:Object;
private var _isSwipe:Boolean;
private var _isLongTap:Boolean;
private var _currentTapTime:Number;
private var _isStop:Boolean;
private var _tapCount:int;
private var _target:Sprite;

public function MouseGesture(target:Sprite) {
super(target);
_target = target;
}
override protected function onTimerComp(event:TimerEvent):void {
//swipが実行された後だと、longPressのイベント送出がなされない。
//ただし、longPressの後にswipeが送出される可能性はある
if (!_isSwipe && _downTarget == _currentTarget) {
super.onTimerComp(event);
this.dispatchEvent(new MouseGestureEvent(_target.mouseX, _target.mouseY, 0, _currentTarget, MouseGestureEvent.LONG_PRESS));
}
}

override protected function mouseDownHandler(event:MouseEvent):void {
super.mouseDownHandler(event);
if (_isStop) { return };
this.dispatchEvent(new MouseGestureEvent(_target.mouseX, _target.mouseY, 0, _currentTarget, MouseGestureEvent.TOUCH));
_distanceListX = [];
_distanceListY = [];
_isSwipe = false;
_downTarget = event.target;
_currentTarget = event.target;
}
override protected function mouseUpHandler(event:MouseEvent):void {
super.mouseUpHandler(event);
if (_isStop) { return };
//swipが実行された後だと、tapのイベント送出がなされない。
if (!_isSwipe && _downTarget == event.target) {
var tapTime:Number = new Date().getTime();
if (_currentTapTime && tapTime-_currentTapTime < mulitiTapDelay && _currentTapTarget == event.target) {
_tapCount ++;
}else {
_tapCount = 1;
}
this.dispatchEvent(new MouseGestureEvent(_target.mouseX, _target.mouseY, _tapCount,event.target, MouseGestureEvent.TAP));
_currentTapTarget = event.target;
_currentTapTime = tapTime;
}else {
_tapCount = 0;
}
}

/**
* tapとswipeの閾値.
* タッチパネルの場合、同じ座標でup/downするのは難しいので、許容範囲を作る。
* このピクセル以下だとtap、以上だとswipeと検出される。
*
* @default 3;
* */
public function get distance():Number { return Math.sqrt(_distance) };
public function set distance(value:Number):void {
_distance = value * value;
}
/*
* 3pixelの半径の場合、9になる。
* これは、mouseMoveHandler内で距離を出すときにsqrtするよりも、
* あらかじめ二乗しておいた方が計算コストが低いから
* */
private var _distance:Number = 9;

/**
* 前のtapとの差がこの時間(ミリ秒)以下であれば、マルチタップとして認識ます.
*
* @default 700
*/
public var mulitiTapDelay:Number = 700;

/**
* この値の分のmouseMoveを保持したのちswipeであるかの判定をします.
*
* @default 3
*/
public var interval:int = 3;
override protected function mouseMoveHandler(event:MouseEvent):void {
super.mouseMoveHandler(event);
if (_isStop) { return };
if (!event.buttonDown) { return; };
_currentTarget = event.target;
_distanceListX.push(_target.mouseX);
_distanceListY.push(_target.mouseY);
//既定の回数を受け取ったあと、距離を測る。
if (_distanceListX.length > interval) {
var dx:Number = 0;
var dy:Number = 0;
var n:int = _distanceListX.length – 1;
for (var i:int = 0; i < n; i++) {
dx += _distanceListX[i + 1] – _distanceListX[i];
dy += _distanceListY[i + 1] – _distanceListY[i];
}
if (dx * dx + dy * dy > _distance) {
_isSwipe = true;
this.dispatchEvent(new MouseGestureEvent(dx, dy, 0, event.target, MouseGestureEvent.SWIPE));
}
_distanceListX = [];
_distanceListY = [];
}
}

/**
* 二度と使えないように破棄します.
*
*/
override public function dispose():void {
super.dispose();
_downTarget = null;
_currentTarget = null;
_currentTapTarget = null;
_target = null;
}

/**
* 一時停止します.
*
*/
override public function stop():void {
super.stop();
_isStop = true;
_tapCount = 0;
}

/**
* 一時停止したものを再開します.
* stop()の後に使います。
*/
override public function play():void {
super.play();
_isStop = false;
}
}

import flash.events.Event;
import flash.geom.Point;

class MouseGestureEvent extends Event {

/**
* swipeのイベント.
*
* @return "swipe"
*/
public static const SWIPE:String = "swipe";

/**
* tapのイベント.
*
* @return "tap"
*/
public static const TAP:String = "tap";

/**
* longPressのイベント.
*
* @return "longPress"
*/
public static const LONG_PRESS:String = "longPress";

/**
* touchのイベント
*
* @return "touch"
*/
public static const TOUCH:String = "touch";

private var _x:Number;
private var _y:Number;
private var _gestureTarget:Object;
private var _tapCount:int;

public function MouseGestureEvent(x:Number, y:Number, tapCount:int, target:Object, type:String, bubbles:Boolean = false, cancelable:Boolean = false):void {
super(type, bubbles, cancelable);

this._x = x;
this._y = y;
this._gestureTarget = target;
this._tapCount = tapCount;
}

/**
* tapの場合は発生したx座標、swipeの場合は変化ベクトルのx値.
*
* 複数回tapの場合、直近のmauseUp時のmouseX
*
* @return tapの場合は発生したx座標、swipeの場合は変化ベクトルのx値
*/
public function get x():Number { return _x; };

/**
* tapの場合は発生したy座標、swipeの場合は変化ベクトルのy値.
*
* 複数回tapの場合、直近のmauseUp時のmouseY
*
* @return tapの場合は発生したy座標、swipeの場合は変化ベクトルのy値
*/
public function get y():Number { return _y; };

/**
* tapの回数.
*
* mulitiTapDelayの時間内に次のtapが行われると、この値が加算されます。
*
* @return tapの回数
*/
public function get tapCount():int { return _tapCount; };

/**
* ジェスチャーを設定したオブジェクト.
*
* @return ジェスチャーを設定したオブジェクト
*/
public function get gestureTarget():Object { return _gestureTarget; };

/**
* tapの場合は発生した座標、swipeの場合は変化ベクトル.
*
* 複数回tapの場合、直近のmauseUp時の座標
*
* @return tapの場合は発生した座標、swipeの場合は変化ベクトル
*/
public function get point():Point { return new Point(_x, _y); };
}

[/sourcecode]