Code
class AnimatedBorder extends EmptyBorder {
private static final int BOTTOM_SPACE = 20;
private static final double PLAY_TIME = 300d;
private static final int BORDER = 4;
private final Timer animator = new Timer(10, null);
private final transient Stroke stroke = new BasicStroke(BORDER);
private final transient Stroke bottomStroke = new BasicStroke(BORDER / 2f);
private long startTime = -1L;
private final transient List points = new ArrayList<>();
private transient Shape shape;
public AnimatedBorder(JComponent c) {
super(BORDER, BORDER, BORDER + BOTTOM_SPACE, BORDER);
animator.addActionListener(e -> {
if (startTime < 0) {
startTime = System.currentTimeMillis();
}
long playTime = System.currentTimeMillis() - startTime;
double progress = playTime / PLAY_TIME;
boolean stop = progress > 1d || points.isEmpty();
if (stop) {
startTime = -1L;
((Timer) e.getSource()).stop();
c.repaint();
return;
}
Point2D pos = new Point2D.Double();
pos.setLocation(points.get(0));
Path2D border = new Path2D.Double();
border.moveTo(pos.getX(), pos.getY());
int idx = Math.min(Math.max(0, (int) (points.size() * progress)), points.size() - 1);
for (int i = 0; i <= idx; i++) {
pos.setLocation(points.get(i));
border.lineTo(pos.getX(), pos.getY());
border.moveTo(pos.getX(), pos.getY());
}
border.closePath();
shape = border;
c.repaint();
});
c.addFocusListener(new FocusListener() {
@Override public void focusGained(FocusEvent e) {
Rectangle r = c.getBounds();
r.height -= BOTTOM_SPACE + 1;
Path2D p = new Path2D.Double();
p.moveTo(r.getWidth(), r.getHeight());
p.lineTo(r.getWidth(), 0d);
p.lineTo(0d, 0d);
p.lineTo(0d, r.getHeight());
p.closePath();
makePointList(p, points);
animator.start();
}
@Override public void focusLost(FocusEvent e) {
points.clear();
shape = null;
c.repaint();
}
});
}
@Override public void paintBorder(Component c, Graphics g, int x, int y, int w, int h) {
super.paintBorder(c, g, x, y, w, h);
Graphics2D g2 = (Graphics2D) g.create();
g2.setPaint(c.isEnabled() ? Color.ORANGE : Color.GRAY);
g2.translate(x, y);
g2.setStroke(bottomStroke);
g2.drawLine(0, h - BOTTOM_SPACE, w - 1, h - BOTTOM_SPACE);
g2.setStroke(stroke);
if (shape != null) {
g2.draw(shape);
}
g2.dispose();
}
private static void makePointList(Shape shape, List points) {
points.clear();
PathIterator pi = shape.getPathIterator(null, .01);
Point2D prev = new Point2D.Double();
double delta = .02;
double threshold = 2d;
double[] coords = new double[6];
while (!pi.isDone()) {
int segment = pi.currentSegment(coords);
Point2D current = createPoint(coords[0], coords[1]);
if (segment == PathIterator.SEG_MOVETO) {
points.add(current);
prev.setLocation(current);
} else if (segment == PathIterator.SEG_LINETO) {
double distance = prev.distance(current);
double fraction = delta;
if (distance > threshold) {
Point2D p = interpolate(prev, current, fraction);
while (distance > prev.distance(p)) {
points.add(p);
fraction += delta;
p = interpolate(prev, current, fraction);
}
} else {
points.add(current);
}
prev.setLocation(current);
}
pi.next();
}
}
private static Point2D createPoint(double x, double y) {
return new Point2D.Double(x, y);
}
private static Point2D interpolate(Point2D start, Point2D end, double fraction) {
double dx = end.getX() - start.getX();
double dy = end.getY() - start.getY();
double nx = start.getX() + dx * fraction;
double ny = start.getY() + dy * fraction;
return new Point2D.Double(nx, ny);
}
}
References