流程设计器里面,最复杂的其实不是拖来拖去的图片,而是线条的绘制包括锚点、箭头,如果把线条绘制好了,那设计器就没什么难度了。
绘制线条会涉及到一些数学知识,主要是三角函数、极坐标等,如果不记得了,赶紧恶补一下吧,这个是必须的。
线条绘制又分多种场景:
1 在一个节点和鼠标所在坐标之间绘制(寻找目标节点过程),这个线条要跟随鼠标的移动而不断重绘。线条的起点为节点中心点坐标与鼠标位置的连线再与节点边缘的交点(有点抽象,等会看图就明白)
可能很多人都以为是在拖动线条,其实这是一个假象,当鼠标移动时,会先擦除线条,然后重新绘制一条节点和鼠标当前位置的连线,当鼠标移动一段距离后,实际上重绘了无数次,这样线条的“拖动”就有连贯性,如果只在鼠标移动结束之后再绘制一条也可以,这样只需要重绘一次,但视觉上的效果要查很多。
2 在两个节点之间绘制,起点和终点坐标分别为两个节点中心点坐标之间的连线分别与节点边缘的交点。
3 拖动锚点(一条线条被分成几段时,子线条之间的交点)绘制,和第1点有些类似,稍微复杂点。
下面举第1种场景中节点在鼠标左上方的例子进行说明:
?上图中x的坐标即为线条的起点
X坐标的计算过程:
X横坐标X.x3=O.x+len
根据相似三角形,存在关系 (x2-x1)/(len/2)=(y2-y1)/H
H=(len/2)(y2-y1)/(x2-x1)
X点的纵坐标X.y3=O.y+len/2+H
计算X坐标的代码如下(考虑了各种场景):
?
/** * 获取起始点与图片边框的交点坐标 * 需按矩形对角线划分四个区域(此处为正方形,每90度为一个边界) * acrossNode 参照节点对象(为上图中的节点对象) */ public static function getLinkPoint(fromPoint:Point,targetPoint:Point,acrossNode:BaseNode):Point { var angle:Number=getAngel(fromPoint,targetPoint); /**计算交点与中心点的垂直和水平距离*/ var distanceX:Number=Math.abs((targetPoint.x-fromPoint.x)*(acrossNode.width/2)/(targetPoint.y-fromPoint.y)); var distanceY:Number=Math.abs((targetPoint.y-fromPoint.y)*(acrossNode.width/2)/(targetPoint.x-fromPoint.x)); if(targetPoint.x<fromPoint.x) { distanceX=-distanceX; } if(targetPoint.y<fromPoint.y) { distanceY=-distanceY; } /**最终xy坐标*/ var x:Number=0; var y:Number=0; if(angle>=45&&angle<135) { x=fromPoint.x+distanceX; y=acrossNode.y; } else if(angle>=135&&angle<225) { x=acrossNode.x; y=fromPoint.y+distanceY; } else if(angle>=225&&angle<315) { x=fromPoint.x+distanceX; y=fromPoint.y+acrossNode.height/2; } else { x=fromPoint.x+acrossNode.width/2; y=fromPoint.y+distanceY; } return new Point(x,y); } /**获取两点夹角的角度*/ private static function getAngel(fromPoint:Point,targetPoint:Point):Number { /**为了与三角坐标一致,y坐标的值要反过来*/ /**通过反正切计算弧度*/ var radian:Number=Math.abs(Math.atan((targetPoint.x-fromPoint.x)/(targetPoint.y-fromPoint.y))); /**计算角度*/ var angle:Number=radian*180/Math.PI; if((targetPoint.x>=fromPoint.x)&&(targetPoint.y<=fromPoint.y))//0~90区域 { angle=90-angle; } else if((targetPoint.x<=fromPoint.x)&&(targetPoint.y<=fromPoint.y))//90~180区域 { angle=90+angle; } else if((targetPoint.x<=fromPoint.x)&&(targetPoint.y>=fromPoint.y))//180~270区域 { angle=270-angle; } else if((targetPoint.x>=fromPoint.x)&&(targetPoint.y>=fromPoint.y))//270~360区域 { angle=270+angle; } return angle; }
?
?
具体绘制线条还有一个技巧,线条绘制出来不仅是摆看的,还要加事件,可以被选取,太细了选择太困难,太粗了又太难看,一个很简单的办法就是绘制两条线,一条1个像素的实线,一条比较粗的透明线,透明线实际上是为了扩大选择区域。
?
graphics.clear(); graphics.moveTo(fromPoint.x,fromPoint.y); graphics.lineStyle(8,color,0,true,LineScaleMode.NORMAL,CapsStyle.NONE); graphics.lineTo(targetPoint.x,targetPoint.y); graphics.moveTo(fromPoint.x,fromPoint.y); graphics.lineStyle(DesignerConstances.LINE_THICKNESS,color); graphics.lineTo(targetPoint.x,targetPoint.y);
?
?
绘制箭头
为了简化可绘制等边三角形
?三角形被线条平分,先计算3个顶点的坐标,然后绘制3条线,最后填充三角形
平分后的弧度各为PI/6。
1)以下为A点坐标的计算过程:
正切值tan(a)=(y2-y1)/(x2-x1)
通过反正切得弧度a=atan(tan(a))
角AP2O的弧度θ为a+b=a+PI/6
H=len*Sin(θ)
W=len*Cos(θ)
A.x3=P2.x2-W,A.y3=P2.y2-H
2)同理,通过弧度c=PI/6-a可计算出B的坐标.
?
?
/** * 根据线条起始点绘制三角形 */ public function draw(ui:UIComponent,fromPoint:Point,toPoint:Point,color:uint):void { var vDistance:Number= toPoint.y-fromPoint.y;//起始点垂直距离 var sDistance:Number=Point.distance(fromPoint,toPoint);//起始点直线距离 var sinValue:Number=vDistance/sDistance;//起始点间的夹角的sin值 /**两点间直线与水平线的角度(弧度)*/ var radian:Number=Math.asin(sinValue); /**用于计算三角形顶点与目标点水平距离的夹角(弧度)*/ var hRadian:Number=radian-Math.PI/6; /**用于计算三角形顶点与目标点垂直距离的夹角(弧度)*/ var vRadian:Number=radian+Math.PI/6; /**上顶点与目标点的垂直距离*/ var topYDis:Number=8*Math.sin(vRadian); /**上顶点与目标点的水平距离*/ var topXDis:Number=8*Math.cos(vRadian); /**下顶点与目标点的垂直距离*/ var botYDis:Number=8*Math.sin(hRadian); /**下顶点与目标点的水平距离*/ var botXDis:Number=8*Math.cos(hRadian); /**计算三角形上下顶点坐标*/ var topPointX:Number=toPoint.x-topXDis; var topPointY:Number=toPoint.y-topYDis; var botPointX:Number=toPoint.x-botXDis; var botPointY:Number=toPoint.y-botYDis; if(toPoint.x<fromPoint.x) { topPointX=toPoint.x+topXDis; botPointX=toPoint.x+botXDis; } ui.graphics.beginFill(color); ui.graphics.moveTo(topPointX,topPointY); ui.graphics.lineTo(toPoint.x,toPoint.y); ui.graphics.lineTo(botPointX,botPointY); ui.graphics.lineTo(topPointX,topPointY); }
?
?
?
绘制锚点
以下列出开始节点在结束节点左上方时,起始位置的锚点为例,所有锚点坐标通过极坐标换算(x = r*cos(θ) y = r*sin(θ),r为半径)
前置条件:矩形为正方形,边长为len,矩形被连线平分,即两边中心点与两个个交叉点重合
1)?计算锚点与连线的第二个交叉点X的坐标
?以下为X点的坐标的计算过程
正切值 tan(a)= (y2-y1)/(x2-x1)
由反正切得弧度a=atan(tan(a))
由平行线得出 b=a
极坐标半径 r=len
极坐标弧度θ=2PI-b
X的极坐标X.x3 = r*cos(θ),X.y3 = r*sin(θ)
X的实际坐标为 (P1.x1+ X.x3,P1.y1+X.x3)
?计算四个顶点值
? ? ?
?以下为计算A点坐标过程:
正切值 tan(a)=(y2-y1)/(x2-x1)
由反正切得弧度a=atan(tan(a));
由平行线得出 b=a
极坐标弧度c=PI/4-b
极坐标半径r即线AO为 len*len/4 的平方根
极坐标A.x = r*cos(c),A.y = r*sin(c)
实际坐标为 (x0+A.x,y0-A.y)/**
* 极坐标计算矩形四个顶点
* x = r*cos(θ) y = r*sin(θ)
*/
private function drawAnchor(holder:UIComponent,fromPoint:Point,targetPoint:Point):void
{
var tan:Number=(fromPoint.x-targetPoint.x)/(targetPoint.y-fromPoint.y);
var leftTopRadian:Number=Math.PI-(Math.PI/4-Math.atan(tan));//左上顶点弧度
var r:Number=getRadius();//半径
var centerPoint:Point=this.getCenterPoint(fromPoint,targetPoint);
/**计算四个顶点坐标*/
var leftTopPoint:Point=new Point(r*Math.cos(leftTopRadian)+centerPoint.x,r*Math.sin(leftTopRadian)+centerPoint.y);
var leftBottomPoint:Point=new Point(r*Math.cos(leftTopRadian+Math.PI/2)+centerPoint.x,r*Math.sin(leftTopRadian+Math.PI/2)+centerPoint.y);
var rightBottomPoint:Point=new Point(r*Math.cos(leftTopRadian+Math.PI)+centerPoint.x,r*Math.sin(leftTopRadian+Math.PI)+centerPoint.y);
var rightTopPoint:Point=new Point(r*Math.cos(leftTopRadian+3*Math.PI/2)+centerPoint.x,r*Math.sin(leftTopRadian+3*Math.PI/2)+centerPoint.y);
/**画线*/
with(holder)
{
graphics.lineStyle(1,DesignerConstances.COLOR_LINE_SELECTION);
graphics.beginFill(0xFFFFFF);
graphics.moveTo(leftTopPoint.x,leftTopPoint.y);
graphics.lineTo(leftBottomPoint.x,leftBottomPoint.y);
graphics.lineTo(rightBottomPoint.x,rightBottomPoint.y);
graphics.lineTo(rightTopPoint.x,rightTopPoint.y);
graphics.lineTo(leftTopPoint.x,leftTopPoint.y);
graphics.endFill();
}
}
/**极坐标半径*/
private function getRadius():Number
{
return Math.sqrt(Math.pow(DesignerConstances.ANCHOR_LENGTH/2,2));
}
/**连线中心点坐标 */
private function getCenterPoint(fromPoint:Point,targetPoint:Point):Point
{
return new Point((targetPoint.x+fromPoint.x)/2,(targetPoint.y+fromPoint.y)/2);
}
?
有空一起探讨探讨