Swing/FocusBorderAnimation の変更点
- 追加された行はこの色です。
- 削除された行はこの色です。
- Swing/FocusBorderAnimation へ行く。
- Swing/FocusBorderAnimation の差分を削除
--- category: swing folder: FocusBorderAnimation title: JTextFieldがFocusを取得したらBorderの右上左辺を順に描画する tags: [Border, Animation, FocusListener, JTextField] author: aterai pubdate: 2021-01-11T07:46:47+09:00 description: JTextFieldがキーボードフォーカスを取得したら右上左辺を直線で順に描画するBorderアニメーションを開始します。 image: https://drive.google.com/uc?id=1QFbs_eWetOYY37ic7VLmBwgRoGOTIXxY hreflang: href: https://java-swing-tips.blogspot.com/2021/01/draw-upper-right-left-and-right-borders.html lang: en --- * 概要 [#summary] `JTextField`がキーボードフォーカスを取得したら右上左辺を直線で順に描画する`Border`アニメーションを開始します。 #download(https://drive.google.com/uc?id=1QFbs_eWetOYY37ic7VLmBwgRoGOTIXxY) * サンプルコード [#sourcecode] #code(link){{ 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<Point2D> 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); 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) { @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<Point2D> 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) { 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); } } }} * 解説 [#explanation] - フォーカスなし -- `Color.ORANGE`の下線を描画 - 選択不可 -- `Color.GRAY`の下線を描画 - フォーカスあり -- `FocusListener#focusGained(...)`で`Timer`を起動 -- `Path2D.Double()`で`JTextField`の右上左辺を表す折れ線を作成 -- 折れ線をその上に存在する点の`ArrayList`に変換 --- [[Shapeから取得したPathIteratorに沿って図形を移動する>Swing/MotionPathAnimation]] -- `Timer`でインデックスを更新し`ArrayList`の始点からインデックス番目の点を辿るパス(`Path2D.Double()`)を生成 -- `paintBorder(...)`をオーバーライドして上記のパスを`Color.ORANGE`で描画 * 参考リンク [#reference] - [[Borderのアニメーション>Swing/RippleBorder]] - [[Shapeから取得したPathIteratorに沿って図形を移動する>Swing/MotionPathAnimation]] * コメント [#comment] #comment #comment