用TweenLite制作窗口拖动特效(1)

本教程讲解窗口(图片)拖动特效的制作.
主要是利用drawTriangles绘制变形位图,用TweenLite实现移动过程的缓动.

先看下最终的效果(鼠标点击拖动面板):
[kml_flashembed publishmethod="static" fversion="10.0.0" movie="http://www.itamt.com/wp-content/uploads/2011/10/Step4.swf" width="550" height="400" targetclass="flashmovie"]
Get Adobe Flash player
[/kml_flashembed]

大体的思路是:把图片矩形分段,把矩形的运动分解为各个顶点的运动,结合各个顶点利用drawTriangles绘制变形位图,最后用TweenLite加入缓动效果.
null
接下来逐步讲解:

1.图片矩形的分段

如果图片位置是pos,要分的段数是segments,取得分段后的顶点,我们通过buildSegments方法来实现.具体如下:

		/**
		 * 对面板分段, 返回顶点(Point)的二维数组
		 */
		private function buildSegments(pos : Point) : Array
		{
			var pts : Array = new Array();

			// 分段后平均每段的宽/高
			var unitW : Number = this.panel.width / segments;
			var unitH : Number = this.panel.height / segments;

			// 遍历, 把分段后的顶点存放在pts数组(二维)中.
			for (var col : int = 0; col < segments + 1; col++)
			{
				pts[col] = new Array();
				for (var row : int = 0; row < segments + 1; row++)
				{
					pts[col][row] = new Point(pos.x + col * unitW, pos.y + row * unitH);
				}
			}

			return pts;
		}

通过buildSegments方法,取得分段后的顶点,并以二维数组([列,行])的形式保存下来.

2.drawTriangles绘制图片

drawTriangles是FP 10后Graphics增加的方法.可用来绘制三角形,并进行贴图.它提供三个参数

  • vertices(Vector.<Point>),三角形的顶点,顶点的数据形式不是Point而是x,y的数值(所以长度必定是6的倍数).
  • indices(Vector.<int>),三角形的边,根据顶点在数组中的索引,两个索引表示一条边.
  • uvData(Vector.<Number>),三角形顶点对应的贴图比例.

把面板分段后,循环绘制出每一个网格矩形.每一个矩形有四个顶点(tl,tr,br,bl),切分成两个三角形来绘制:
null
绘制(render)方法具体如下:

		/**
		 * 绘制面板
		 */
		public function render() : void
		{
			var g : Graphics = this.graphics;
			g.clear();
			var tl : Point, tr : Point, bl : Point, br : Point;
			// 使用drawTriangles绘制分段矩形
			var vertices : Vector. = new Vector.();
			var indices : Vector. = new Vector.();
			var indiceIndex : int;
			var uvData : Vector. = new Vector.();
			var seg : Number = segments;
			for (var col : int = 0; col < segments; col++)
			{
				for (var row : int = 0; row < segments; row++)
				{
					// 单个矩形的四个顶点
					tl = pts[col][row];
					tr = pts[col + 1][row];
					br = pts[col + 1][row + 1];
					bl = pts[col][row + 1];

					vertices.push(tl.x, tl.y, tr.x, tr.y, br.x, br.y, bl.x, bl.y);

					var nextU : Number = (col != segments - 1) ? (col + 1) / seg : (1 + 1 / this.panel.width);
					var nextV : Number = (row != segments - 1) ? (row + 1) / seg : (1 + 1 / this.panel.height);
					uvData.push(col / seg, row / seg, nextU, row / seg, nextU, nextV, col / seg, nextV);

					indices.push(indiceIndex, indiceIndex + 1, indiceIndex + 3, indiceIndex + 1, indiceIndex + 2, indiceIndex + 3);
					indiceIndex += 4;
				}
			}
			g.beginBitmapFill(new IMAGE().bitmapData);
			g.drawTriangles(vertices, indices, uvData);
			g.endFill();
		}

这里有个问题,我在实际书写drawTriangles的uvData并测试最终效果时,发现右边缘与下边缘都是少一个像素的.所以在遍历到边缘(col == segments – 1 || row == segments – 1)时, 把这一个像素转成百分比(1/panel.width, 1/panel.height)加到uv中.

3.实现鼠标拖动

鼠标拖动的实现主要是侦听处理鼠标的三个事件:MOUSE_DOWN,MOUSE_UP,MOUSE_MOVE

		/**
		 * 侦听鼠标交互
		 */
		private function buildInteractor() : void
		{
			this.stage.addEventListener(MouseEvent.MOUSE_DOWN, mouseEventHandler);
			this.stage.addEventListener(MouseEvent.MOUSE_UP, mouseEventHandler);
		}

		/**
		 * 处理鼠标事件,实现拖动
		 */
		private function mouseEventHandler(event : MouseEvent) : void
		{
			switch(event.type)
			{
				case MouseEvent.MOUSE_DOWN:
					this.mouseOffset = new Point(this.mouseX - this.posPt.x, this.mouseY - this.posPt.y);
					// 开始拖动
					this.stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseEventHandler);
					break;
				case MouseEvent.MOUSE_UP:
					// 结束拖动
					this.stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseEventHandler);
					break;
				case MouseEvent.MOUSE_MOVE:
					this.move(this.mouseX - this.mouseOffset.x, this.mouseY - this.mouseOffset.y);
					break;
				default:
			}
		}

在鼠标移动时,调用move方法传递目标位置,移动面板.看下一步.

4.移动面板,实现缓动

面板移动时,我们先把各个顶点的目标位置通过buildSegments计算出来,然后遍历对每个顶点添加缓动.每个顶点的移动时间需要不一样(如果一样的话,最终效果还是整个面板在平移),这里顶点的移动时间根据它与鼠标的距离来计算得出.

		/**
		 * 实现移动动过程缓动
		 */
		private function move(tx : Number, ty : Number) : void
		{
			this.endPt = new Point(tx, ty);

			// 计算各个分段顶点的目标位置
			var endPts : Array = this.buildSegments(this.endPt);

			// 创建缓动引擎
			this.engine = new TimelineLite({onUpdate:this.render, onComplete:this.onMoveComplete});

			// 遍历, 把分段后的顶点存放在pts数组(二维)中.
			for (var col : int = 0; col < segments + 1; col++)
			{
				for (var row : int = 0; row < segments + 1; row++)
				{
					var end : Point = endPts[col][row];
					var time : Number = Math.sqrt((end.x - mouseX) * (end.x - mouseX) + (end.y - mouseY) * (end.y - mouseY)) / 400;
					this.engine.insert(new TweenLite(this.pts[col][row], time, {x:end.x, y:end.y}));
				}
			}

			// 播放缓动
			this.engine.play();
		}

注意代码在TimelineLite.onUpdate时执行render重新绘制面板.

源码下载

源码打包,包里不含有TweenLite库.

后续

好了,初步完成,还有很多优化的空间,下回继续.