iPhoneみたいなscrollをKinetic Scrollingって言うの?

Flashで、画像をドラッグ移動させるプログラムを作ったら「iPhoneみたいに惰性移動しないの?」って言われた。
まあ、仕様書にそう書いてないからねw
これだけ一般的になってるんだからググッてみれば、使えるコードも多いんじゃないかと思って調べてみたけど、意外と見つからない。
javascriptの方が、ヒットする件数は多いかな。
Flexのライブラリで、近い動きをするライブラリがあったので、Flash用に改造してみた。
完全に同じ動きさせるのは、凄く調整が大変なので、それっぽい動きをするだけです。

Main.as

package 
{
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.geom.Rectangle;
	import manager.FlickDragManager;
	
	/**
	 * 使う側のコード
	 * @author takanemu
	 */
	public class Main extends Sprite 
	{
		private var _manager:FlickDragManager = new FlickDragManager();
		
		public function Main():void 
		{
			if (stage) init();
			else addEventListener(Event.ADDED_TO_STAGE, init);
		}
		private function init(e:Event = null):void 
		{
			removeEventListener(Event.ADDED_TO_STAGE, init);
			// entry point
			var child:Sprite = new Sprite();
			
			child.graphics.beginFill(0xFF0000);
			child.graphics.drawRect(0, 0, 200, 200);
			child.graphics.endFill();
			
			child.x = 200;
			child.y = 200;
			
			addChild(child);
			
			_manager.target = child;
			_manager.dragRect = new Rectangle(10, 10, 780, 580);
			
			this.graphics.beginFill(0x222222, 1);
			this.graphics.drawRect(0, 0, this.stage.stageWidth, this.stage.stageHeight);
		}
	}
}

FlickDragManager.as

package manager 
{
	import caurina.transitions.Tweener;
	import flash.display.DisplayObject;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import flash.utils.getTimer;

	/**
	 * フリックドラッグマネージャー
	 * KineticScrollManagerを参考に、改良を加えています。
	 * 元のコードのライセンスは、GNU Lesser GPLなので、それに準じます。
	 * @see http://code.google.com/p/nbflexlib/
	 * @author takanemu
	 */
	public class FlickDragManager 
	{
		// 履歴保持数
		private static const HISTORY_LENGTH:uint = 6;
		// ターゲットオブジェクト
		private var _target:DisplayObject;
		// 惰性移動値
		private var _velocity:Point;
		// 引きずり履歴位置
		private var _previousPoints:Vector.<Point> = new Vector.<Point>();
		// 引きずり履歴時間
		private var _previousTimes:Vector.<int> = new Vector.<int>();
		// 移動範囲
		public var dragRect:Rectangle;
		
		/**
		 * ターゲットアクセッサー
		 */
		public function set target(value:DisplayObject):void {
			_target = value;
			
			if (target) {
				// マウスダウンイベントリスナー追加
				target.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
			}
		}
		public function get target():DisplayObject {
			return _target;
		}
		/**
		 * コンストラクタ
		 */
		public function FlickDragManager() 
		{
			
		}
		/**
		 * マウスボタン押下処理
		 * @param	event	イベント
		 */
		private function mouseDownHandler(event:MouseEvent):void 
		{
			// 惰性移動強制停止
			stop();
			// 初回位置設定
			_previousPoints = Vector.<Point>([new Point(event.stageX, event.stageY)]);
			_previousTimes = Vector.<int>([getTimer()]);
			// マウス移動イベントリスナー追加
			target.stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
			// マウスボタン解除イベントリスナー追加
			target.stage.addEventListener(MouseEvent.MOUSE_UP,	 mouseUpHandler);
		}
		/**
		 * マウスカーソル移動処理
		 * @param	event	イベント
		 */
		private function mouseMoveHandler(event:MouseEvent):void
		{
			var currPoint:Point = new Point(event.stageX, event.stageY);
			var currTime:int = getTimer();
			var previousPoint:Point = Point(_previousPoints[_previousPoints.length - 1]);
			var previousTime:int = int(_previousTimes[_previousTimes.length - 1]);
			var diff:Point = currPoint.subtract(previousPoint);

			target.x += diff.x;
			target.y += diff.y;
			
			_previousPoints.push(currPoint);
			_previousTimes.push(currTime);

			if (_previousPoints.length >= HISTORY_LENGTH) {
				_previousPoints.shift();
				_previousTimes.shift();
			}
		}
		/**
		 * マウスボタン解除処理
		 * @param	event	イベント
		 */
		private function mouseUpHandler(event:MouseEvent):void
		{
			// 移動&ボタン解除のリスナーを解除
			target.stage.removeEventListener(MouseEvent.MOUSE_UP,	mouseUpHandler);
			target.stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
			// 惰性移動アニメーション用イベントリスナー追加
			target.addEventListener(Event.ENTER_FRAME, enterFrameHandler);

			var currPoint:Point = new Point(event.stageX, event.stageY);
			var currTime:int = getTimer();
			var firstPoint:Point = _previousPoints[0];
			var firstTime:int = _previousTimes[0];
			var diff:Point = currPoint.subtract(firstPoint);
			var time:Number = (currTime - firstTime) / (1000 / target.stage.frameRate);

			_velocity = new Point(diff.x / time, diff.y / time);
		}
		/**
		 * 惰性移動処理
		 * @param	event	イベント
		 */
		private function enterFrameHandler(event:Event):void
		{
			_velocity.x *= 0.8;
			_velocity.y *= 0.8;
			if (Math.abs(_velocity.x) < 0.1) _velocity.x = 0;
			if (Math.abs(_velocity.y) < 0.1) _velocity.y = 0;
			if (!_velocity.x && !_velocity.y) {
				stop();
				return;
			}
			target.x += _velocity.x;
			target.y += _velocity.y;
		}
		/**
		 * 停止処理
		 */
		public function stop():void
		{
			if (target.hasEventListener(Event.ENTER_FRAME)) {
				// 惰性移動アニメーション用のリスナーを解除
				target.removeEventListener(Event.ENTER_FRAME, enterFrameHandler);

				if (dragRect.containsRect(new Rectangle(target.x, target.y, target.width, target.height)) == false) {
					// 移動範囲矩形から逸脱していたら戻す
					var px:Number = target.x;
					var py:Number = target.y;
					
					if (dragRect.left > target.x) {
						px = dragRect.left;
					}
					if (dragRect.right < (target.x + target.width)) {
						px = dragRect.right - target.width;
					}
					if (dragRect.top > target.y) {
						py = dragRect.top;
					}
					if (dragRect.bottom < (target.y + target.height)) {
						py = dragRect.bottom - target.height;
					}
					Tweener.removeTweens(target);
					Tweener.addTween(target, { x:px, y:py, useFrames:true, time:24, transition:"easeOutElastic" } );
				}
			}
			_velocity = null;
			_previousPoints.length = 0;
			_previousTimes.length = 0;
		}
	}
}