想想Java2D中给我们提供的线的样式着实很少,除了直线,虚线,好像就没有其他的什么样式了,如果细心的童鞋还会发现,TWaver中倒是提供了一种比较特殊的连线,波浪曲折式的连线。
这种波浪曲折的连线如果让我们自己来实现也是有多种实现的方式,还记得之前几篇文章中定制过的LinkUI么,也是各式各样的方式,比如:
五彩斑斓的Link
流动点式的Link
今天给大家介绍的是箭头流动式的Link,何为箭头流动,我们就先来看看效果图:
这是一个从from节点流向to节点的连线,连线是以一个一个箭头组建而成,这样的连线方式看上去比传统的那种流动漂亮多了,也有不少客户提及到这种样式。本篇我将详细给大家讲解一下实现的细节。
首先需要定制一个ArrowLink继承于Link,在ArrowLink中需要给它定义几个变量,例如:线的宽度、颜色、每段箭头的长度、需要填充的流动箭头的数量、透明度、是否是从from流向to以及偏移(用于显示流动)等等。 不说这么多了,直接看代码:
public class ArrowLink extends Link { public ArrowLink() { super(); init(); } public ArrowLink(Object id) { super(id); init(); } public ArrowLink(Node from, Node to) { super(from, to); init(); } public ArrowLink(Object id, Node from, Node to) { super(id, from, to); init(); } private void init() { this.putLinkColor(new Color(0, 0, 0, 0)); this.putLinkOutlineWidth(0); this.setLinkType(TWaverConst.LINK_TYPE_PARALLEL); this.putLinkAntialias(true); this.putClientProperty("lineWidth", 3.0f); this.putClientProperty("lineColor", Color.blue); this.putClientProperty("offset", 0.0); this.putClientProperty("segmentLength", 8.0); this.putClientProperty("fillSegmentCount", 5); this.putClientProperty("defaultAlpha", 0.2); this.putClientProperty("from", true); } public String getUIClassID() { return ArrowLinkUI.class.getName(); } }
定制完连线之后,最主要的是需要重画LinkUI,自定义ArrowLinkUI类继承于LinkUI并重载paintBody方法,paintBody中我们需要画出一个一个的箭头,箭头实现起来其实还是比较简单的,我们能获取Link的长度并且知道Link上每段的长度,就可以计算出需要绘制箭头的数量。
int count = (int)(length/segmentLength);
根据箭头的数量可以获取到需要绘制箭头的每个点的位置:
List points = TWaverUtil.divideShape(this.path, count);
获取到这个位置之后,我们就可以以这个点为中心点,分别计算出箭头的其他两个点的位置:
Point2D p0 = new Point.Double(); transform.transform(point, p0); Point2D p1 = new Point.Double(); transform.transform(new Point.Double(point.getX() + segmentLength/2 * sign, point.getY() - segmentLength/2), p1); Point2D p2 = new Point.Double(); transform.transform(new Point.Double(point.getX() + segmentLength/2 * sign, point.getY() + segmentLength/2), p2);
这样一个箭头就可以绘制出来了
其他的箭头也可以以同样方式循环绘制出来,需要注意的是箭头是需要随着node的位置旋转的,因此我们需要计算出箭头旋转的角度和旋转点的位置:
AffineTransform transform = new AffineTransform(); transform.translate(point.getX(), point.getY()); transform.rotate(angle); transform.translate(-point.getX(), -point.getY());
最后还有流动的效果,这里我们设置了一个offset的参数,可以表示流动的偏移量,根据偏移量以及填充的流动箭头的数量来确定当前这个箭头的透明度:
double alpha = (Double)this.element.getClientProperty("defaultAlpha"); if(offset * count >= i && offset * count - fillSegmentCount <= i){ alpha = 1 - (offset * count - i)/fillSegmentCount * 0.5; }
完整绘制箭头的代码如下:
public class ArrowLinkUI extends LinkUI { public ArrowLinkUI(TNetwork network, Link link) { super(network, link); } public void paintBody(Graphics2D g2d) { super.paintBody(g2d); this.drawFlowing(g2d); } private void drawFlowing(Graphics2D g2d) { double length = TWaverUtil.getLength(this.path); if(length < =0 ){ return; } double segmentLength = (Double)this.element.getClientProperty("segmentLength"); int count = (int)(length/segmentLength); List points = TWaverUtil.divideShape(this.path, count); if(points.size() < 2){ return; } int fillSegmentCount = (Integer)this.element.getClientProperty("fillSegmentCount"); double offset = (Double)this.element.getClientProperty("offset"); Color lineColor = (Color)this.element.getClientProperty("lineColor"); boolean from = (Boolean)this.element.getClientProperty("from"); boolean fromLeft = this.getFromPoint().x <= this.getToPoint().x; g2d.setStroke(new BasicStroke((Float)this.element.getClientProperty("lineWidth"))); for(int i=0; i Point2D point = (Point2D)points.get(i); Point2D point1, point2; double angle = 0; if(i == points.size()-1){ point1 = (Point2D)points.get(i-1); point2 = point; } else { point1 = point; point2 = (Point2D)points.get(i+1); } angle = getAngle(point1, point2); int sign = (fromLeft && from || !fromLeft && !from) ? -1 : 1; if(angle == -Math.PI/2){ sign = point2.getY() > point1.getY() ? 1 : -1; }else if(angle == Math.PI/2){ sign = point2.getY() > point1.getY() ? -1 : 1; } double alpha = (Double)this.element.getClientProperty("defaultAlpha"); if(offset * count >= i && offset * count - fillSegmentCount < = i){ alpha = 1 - (offset * count - i)/fillSegmentCount * 0.5; } g2d.setColor(new Color(lineColor.getRed(), lineColor.getGreen(), lineColor.getBlue(), (int)(255 * alpha))); AffineTransform transform = new AffineTransform(); transform.translate(point.getX(), point.getY()); transform.rotate(angle); transform.translate(-point.getX(), -point.getY()); Point2D p0 = new Point.Double(); transform.transform(point, p0); Point2D p1 = new Point.Double(); transform.transform(new Point.Double(point.getX() + segmentLength/2 * sign, point.getY() - segmentLength/2), p1); Point2D p2 = new Point.Double(); transform.transform(new Point.Double(point.getX() + segmentLength/2 * sign, point.getY() + segmentLength/2), p2); GeneralPath path = new GeneralPath(); path.moveTo(p1.getX(), p1.getY()); path.lineTo(p0.getX(), p0.getY()); path.lineTo(p2.getX(), p2.getY()); g2d.draw(path); } } private static double getAngle(Point2D p1, Point2D p2) { if(p1.getX() == p2.getX()){ if(p2.getY() == p1.getY()){ return 0; } else if(p2.getY() > p1.getY()){ return Math.PI/2; } else{ return -Math.PI/2; } } return Math.atan((p2.getY() - p1.getY()) / (p2.getX() - p1.getX())); } }
有了这种流动式的箭头,我们就可以绘制出更多丰富多彩的界面,最后给出一个完整的例子供大伙学习参考:
注:附件中还给出了另一种Link的实现效果。 见原文最下方