在继 auzn经典Flex教程–KingnareStyle皮肤制作简介 后 ,auzn又出品了经典作品–扇形菜单。本文英文


版:Tutorials: Step by Step to Create your Sector Menu 。 下面我们就来做个扇形菜单,首先来看看效果吧:


[PieMenu.swf ]


只要设定好起始位置和总角度,似乎可以画出很多种组合.


Demo | Download Full Project


这样:

这样:

或者这样:

 

菜单的重点是如何画空心弧及过渡动画的控制.

很明显,菜单是由多个空心弧组成的,菜单形成也就是多个空心弧的产生并进行动画过渡的过程,所以我们先从单个空心弧入手.空心弧在过渡动画 中可能是在任意一个角度开始绘制,任意一个角度结束绘制,我们随意取一个状态:

 

 

上图是从-30度起始画图,画到-135度结束,空心弧角度为-105度,内圆半径50,外圆半径100,中心圆坐标(x,y).

 

可以看出这个图形边线由两条直线和两条弧组成,按逆时针为直线P1P2, 弧P2P3, 直线P3P4, 弧P4P1. 只要画好边线再进行填充即可.

 

我们先来计算四个点的坐标:

P1: (x+r*cos(start), y+r*sin(start))

P2: (x+R*cos(start), y+R*sin(start))

P3: (x+r*cos(start+angle), y+r*sin(start+angle))

P4: (x+R*cos(start+angle), y+R*sin(start+angle))

 

接下来的问题就是圆弧的算法了,可以知道,在圆心角小于45度的范围内是可以画出圆弧的.我们把圆弧的圆心角以小于 45 度 为单位 均分,对每个部分进行画弧,这样拼接起来就是完整的圆弧了.

 

目前已经有比较成熟的程序代码了.

修改了Geordi的DrawSector方法(如果知道作者及出处,请留言):

 

//angle为圆心角的大小,startA为起始角度 
//以45度为最大角度值均分,取得可以划分数值 
var n:Number = Math.ceil(Math.abs(angle) / 45); 
//计算每份圆弧的圆心角(均小于45度) 
var angleA:Number=angle / n; 
angleA = angleA * Math.PI / 180; 
startA = startA * Math.PI / 180 
//循环绘制圆弧 
for (var i=1; i <= n; i++) 
{ 
startA += angleA; 
var angleMid1:Number=startA – angleA / 2; 
var bx:Number = x + R / Math.cos(angleA / 2) * Math.cos(angleMid1); 
var by:Number = y + R / Math.cos(angleA / 2) * Math.sin(angleMid1); 
var cx:Number = x + R * Math.cos(startA); 
var cy:Number = y + R * Math.sin(startA); 
sector.graphics.curveTo(bx, by, cx, cy); 
}

如果理解上有些困难,可参见下图:

[tween_with_angle.swf ]

 

切点P就是画曲线方法curveTo的控制点.

 

OK,理论部分结束.我们在Flash cs3中新建Flash文件,在第一帧的动作面版写上如下代码:

 

var sector:Sprite = new Sprite(); 
addChild(sector); 
drawSector(200, 200, 50, 100, -105, -30); 
function drawSector( x:Number, y:Number, r:Number, R:Number, angle:Number, startA:Number) 
{ 
sector.graphics.clear(); 
sector.graphics.lineStyle (1, 0, 1, true); 
sector.graphics.beginFill(0, 0.5); 
if (Math.abs(angle) > 360) 
{ 
angle=360; 
} 
var n:Number = Math.ceil(Math.abs(angle) / 45); 
var angleA:Number=angle / n; 
angleA = angleA * Math.PI / 180; 
startA = startA * Math.PI / 180; 
var startB:Number = startA; 
//起始边 
sector.graphics.moveTo(x + r * Math.cos(startA), y + r * Math.sin(startA)); 
sector.graphics.lineTo(x + R * Math.cos(startA), y + R * Math.sin(startA)); 
//外圆弧 
for (var i=1; i <= n; i++) 
{ 
startA += angleA; 
var angleMid1:Number=startA – angleA / 2; 
var bx:Number = x + R / Math.cos(angleA / 2) * Math.cos(angleMid1); 
var by:Number = y + R / Math.cos(angleA / 2) * Math.sin(angleMid1); 
var cx:Number = x + R * Math.cos(startA); 
var cy:Number = y + R * Math.sin(startA); 
sector.graphics.curveTo(bx, by, cx, cy); 
} 
//内圆起点 
sector.graphics.lineTo(x + r * Math.cos(startA),y + r * Math.sin(startA)); 
//内圆弧 
for (var j = n; j >= 1; j–) 
{ 
startA-= angleA; 
var angleMid2:Number=startA + angleA / 2; 
var bx2:Number=x + r / Math.cos(angleA / 2) * Math.cos(angleMid2); 
var by2:Number=y + r / Math.cos(angleA / 2) * Math.sin(angleMid2); 
var cx2:Number=x + r * Math.cos(startA); 
var cy2:Number=y + r * Math.sin(startA); 
sector.graphics.curveTo(bx2, by2, cx2, cy2); 
} 
//内圆终点 
sector.graphics.lineTo(x + r * Math.cos(startB),y + r * Math.sin(startB)); 
//完成 
sector.graphics.endFill(); 
}

CTRL+ENTER测试(坐标系为笔者自行加入,坐标中心点为(200, 200)).

 

[draw_sector.swf ]

 

这样就可以完成我们的空心弧了.

至此,绘制空心弧完成.

 

接下来我们将介绍如何生成过渡动画.

通过上面的程序可知,只要改变空心弧的起始角度和圆心角角度,就能达到“展开并旋转”这一效果了.

 

首先是展开效果.所谓展开,就是起始角度不变,只改变圆心角角度.以前面的数据为例,起始角度为-30度,圆心角数值由0过渡到-105 度.加入以下代码:

 

import fl.transitions.Tween; 
import fl.transitions.TweenEvent; 
import fl.transitions.easing.Strong; 
var obj:Object = {}; 
obj.angle = 0; 
var tween:Tween=new Tween(obj, “angle”, Strong.easeInOut, 0, -105, 5, true); 
tween.addEventListener(TweenEvent.MOTION_CHANGE, changeHandler); 
function changeHandler(event:TweenEvent):void 
{ 
drawSector(200, 200, 50, 100, obj.angle, -30); 
}

 

使用了Flash CS3内置的Tween类,除此之外还可以用TweenMax 代替.

CTRL+ENTER测试,[tween_with_angle.swf ].

 

可以观查到程序不断的在改变圆心角角度.

展开没问题了,接下来是改变起始角度.这次设定圆心角夹角仍为105度,起始角度75度, 旋转到-30停止.

 

对刚才的展开代码稍加改动,缓部分代码如下:

 

import fl.transitions.Tween; 
import fl.transitions.TweenEvent; 
import fl.transitions.easing.Strong; 
var obj:Object = {}; 
obj.start = 0; 
var tween:Tween=new Tween(obj, “start”, Strong.easeInOut, 75, -30, 5, true); 
tween.addEventListener(TweenEvent.MOTION_CHANGE, changeHandler); 
function changeHandler(event:TweenEvent):void 
{ 
drawSector(200, 200, 50, 100, -105, obj.start); 
}

CTRL+ENTER测试,[tween_with_start.swf ].

 

可以观查到程序不断的在改变起始角度.

 

由上面两个过程,可以推导出子菜单的生成过程:首先起始角度固定,不断变化的只有圆心角,当圆心角达到目标值后,开始改变起始角度,这个时 候圆心角是固定不变的,空心弧此时是在以围绕圆心做旋转运动,起始角度达到目标值后,整个过程结束.

 

但是有一个重要的问题:如果先展开再旋转,两个动画中间的衔接是个问题.那么如何产生比较流畅的动画?我们现在用另一种方法重新分析一下:

 

我们设定圆心角度数值为正值,那么这时起始边应该在结束边的逆时针方向上,起始边逆时针旋转一定的角度 angle(angle>0),此时将其圆心角数值也更新为angle,这样看起来结束边不动,角度在变大.可以模拟展开效果.当圆心角角度达到目 标值后,将圆心角角度固定,而此时起始边继续旋转,最后旋转到目标值,整个过程结束.

 

根据以上分析,我们修改刚才的代码:

 

import fl.transitions.Tween; 
import fl.transitions.TweenEvent; 
import fl.transitions.easing.Strong; 
var obj:Object = {}; 
obj.start = 0; 
obj.angle = 0; 
var tween:Tween; 
//begin:起始角度初始值 
// end:起始角度终止值 
// angle:圆心角绝对值 
function create (begin:Number, end:Number, angle:Number, tweenTime:Number):void 
{ 
var oldStart:Number=0; 
tween=new Tween(obj, “start”, Strong.easeInOut, begin, end, tweenTime, true); 
tween.addEventListener(TweenEvent.MOTION_CHANGE, changeHandler); 
function changeHandler(event:TweenEvent):void 
{ 
if (Math.abs(obj.angle) >= angle) 
{ 
drawSector(200, 200, 50, 100, angle, obj.start); 
} 
else 
{ 
obj.angle = Math.abs(oldStart – obj.start) 
drawSector(200, 200, 50, 100, obj.angle, obj.start); 
} 
} 
} 
//由0开始,起始边转到-135度,动画时间为5秒 
create (0, -135, 105, 5);

CTRL+ENTER测试.[tween_without_filters.swf ].

OK,剩下的工作就是加上合适的滤镜,让菜单看上去更加美观.

 

可以选择BevelFilter及DropShadowFilter增加立体效果.

增加两个方法:

 

function getBevelFilter():BitmapFilter 
{ 
var distance:Number = 6; 
var angleInDegrees:Number = 45; 
var highlightColor:Number = 0xFFFFFF; 
var highlightAlpha:Number = 0.6; 
var shadowColor:Number = 0xFFFFFF; 
var shadowAlpha:Number = 0; 
var blurX:Number = 10; 
var blurY:Number = 10; 
var strength:Number = 0.8; 
var quality:Number = BitmapFilterQuality.LOW; 
var type:String = BitmapFilterType.INNER; 
var knockout:Boolean = false; 
return new BevelFilter(distance,angleInDegrees,highlightColor,highlightAlpha,shadowColor,shadowAlpha,blurX,blurY,strength,quality,type,knockout); 
} 
//获取DropShadow滤镜 
function getDropShadowFilter():BitmapFilter 
{ 
var color:Number = 0×000000; 
var angle:Number = 45; 
var alpha:Number = 0.9; 
var blurX:Number = 5; 
var blurY:Number = 5; 
var distance:Number = 5; 
var strength:Number = 0.9; 
var inner:Boolean = false; 
var knockout:Boolean = false; 
var quality:Number = BitmapFilterQuality.LOW; 
return new DropShadowFilter(distance,angle,color,alpha,blurX,blurY,strength,quality,inner,knockout); 
}

然后给sector设定滤镜.

sector.filters = [getBevelFilter(), getDropShadowFilter()];

最后把create方法中的填充换为更醒目的色彩:

sector.graphics.beginFill(0×0066FF, 0.8);

 

CTRL+ENTER测试,[tween_with_filters.swf ].

 

接下来就是由总角度及菜单数确定各个菜单的起始角度值变化范围及圆心角数值,然后分别“启动”他们的create方法.我们已经把这部分编 写完成了,大家可以在文章尾部的链接下载到所有示例程序源代码.

若要改用到FLEX中,需要替换代码中的Tween类,因为Flash CS3和FLEX中的Tween是不同的.

 

伪代码如下:

 

import mx.effects.Tween; 
import mx.effects.easing.*; 
private var tween:Tween; 
public function create (value:Number ,delta:Number, tweenTime:Number):void 
{ 
//… 
var listener:Object = {}; 
listener.onTweenUpdate = function(val:Object):void 
{ 
//更新圆心角度 
} 
listener.onTweenEnd = function(val:Object):void 
{ 
// 
} 
tween = new Tween(listener, obj.start, value, tweenTime*1000); 
tween.easingFunction = Quadratic.easeInOut; 
}

 

这样就可以应用到FLEX程序中了.

 

OK,菜单的核心部分就讲到这里,希望能对大家有些许帮助.

 

可以在here 下 载全部源文件.