ローディングアイコンの作成

本日のJavaFXはローディング中などに表示されるアイコンです。

サンプルはこちらから。サンプルは大量にアニメーションをかけているので、環境によっては少々重いかもしれませんがご了承ください。

使い方

例のごとくCustomeNodeを継承しているので、自然にノードとしてシーングラフに追加できます。バータイプとサークルタイプがありますが、使い方の違いは幅と高さの指定くらいです。

    VBox {
        spacing: 5
        content: [
            LoadingBar {}
            LoadingBar {
                width: 400, height: 10
                span: 0.05s
            }
            LoadingBar {
                color: Color.TRANSPARENT
                colors: [Color.BLACK, Color.DARKGRAY, Color.GRAY, Color.LIGHTGRAY]
            }
            LoadingCircle {}
            LoadingCircle {
                size: 100
                color: Color.TRANSPARENT
                span: 0.4s
            }
            LoadingCircle {
                size: 20
                colors: [Color.BLACK, Color.DARKGRAY, Color.GRAY, Color.LIGHTGRAY]
                color: Color.LIGHTGRAY
                background: Color.WHITE
            }
        ]
    }

このように、spanでアニメーションの早さを指定します。また、colorで非アクティブな箱(円)の色を指定し、colorsではColorのシーケンスを指定しアクティブにアニメーションする箱(円)の色になります。
また、LoadingBarでは幅と高さを指定可能で、LoadingCircle は正方形となるためサイズのみを指定します。

実装

アニメーションに関わる部分はスーパークラスのLoadingIconに定義しました。

public abstract class LoadingIcon extends CustomNode {
    public-init var color:Color = COLOR;
    public-init var colors:Color[] = COLORS;
    public-init var background:Color = Color.TRANSPARENT;
    public-init var span:Duration = 0.1s;
    override var visible on replace {
        if(visible) {
            animation.play();
        } else {
            animation.stop();
        }
    }
    protected var shapes: Shape[];
    var sizeofShapes: Integer = bind sizeof(shapes);
    var sizeofColors: Integer = sizeof(colors);
    var index: Integer = 0;
    var animation:Timeline = Timeline {
        repeatCount: Timeline.INDEFINITE
        keyFrames: [
            KeyFrame {
                time: span
                action: function() {
                    for (shape in shapes) {
                        var idx = index - indexof shape;
                        shape.fill = if (0 <= idx and idx < sizeofColors) {
                            colors[idx];
                        } else if (idx < 0 and idx + sizeofShapes < sizeofColors) {
                            colors[idx + sizeofShapes];
                        } else {
                            color;
                        }
                    }
                    index++;
                    if (index == sizeofShapes) index = 0;
                }
            }
        ]
    };
    postinit {
        animation.play();
    }
}

shapesにはサブクラスで指定された箱や円が代入される事を想定しています。
後はアニメーションですが、うまくサイクリックに可変個の領域を描画するように外部インデックスを使ってまわしています。指定した単位時間毎に各シェイプのfillをかえている為、自由な色を指定できるようになっており、サブクラスで似たようなアイコンは量産できるようにしました。

サブクラスは片方だけ乗せておきます(Circleの方はリポジトリでもみてください)。

public class LoadingBar extends LoadingIcon {
    public-init var width:Number = 200;
    public-init var height:Number = 20;
    override function create():Node {
        var size:Number = height;
        var num:Integer = (((width) / (size + 4)) as Integer) - 2;
        Group {
            content: [
                Rectangle {
                    width: bind width, height: bind height
                    fill: background
                }
                VBox {
                    width: bind width, hpos: HPos.CENTER
                    content: [
                        HBox {
                            spacing: 4
                            content: shapes = for (i in [0..num]) {
                                Rectangle {
                                    width: size, height: size
                                    fill: color
                                }// Rectangle -box
                            }
                        }// HBox
                    ]
                }// VBox (for centering)
            ]
        }
    }
}

箱(ボックス)の数が可変になっているくらいで、たいしたことはやってませんね。

と、アニメーションの部分でサイクリックになるようなロジックを組むのにちょっとはまりましたが、基本的な構造は非常にシンプルです。