JavaJavaFX Доза вторая

Сегодня я напишу как в javafx создаются (наследуются) классы, как работать с массивами и немного всякой всячины. Сделаю я это на примере меню, которое будет оформлено в стиле MacOS дока. Итак поехали.
Часть 1 — Создаем свои кнопки
Создадим новый JavaFX class который будет представлять из себя кастомную кнопку в доке. делается это легко и просто (импорты я упущу ибо бумагу надо экономить):
ImageButton.fx

package lesson2;

public class ImageButton extends CustomNode
{
    // Подпись кнопки
    public var title: String;
    // Изображение из которого будем делать кнопку
    public var buttonImage: Image;
    // URL того самого изображения <b>*</b>
    public var imageUrl: String on replace {
        buttonImage = Image{
            url: imageUrl
            };
    }
    // Степень увеличения размера когда курсор не наведен (больше единицы 
    //уменьшение реального размера, меньше увеличение измеряются в процентах)
    public var nonRolloverScale:Number = 0.9;
    // Степень прозрачности когда курсор не наведен 
    public var nonRolloverOpacity:Number = .80;
    // параметр который нам пригодится когда будем менять размер
    // кнопки которая активна
    public var fade:Number = 1.0;
    // параметр, который говорит курсор мыши на объекте или нет
    var mouseInside = false;

    //собственно анимация <b>**</b>
    public var fadeTimeLine =
    Timeline {
        keyFrames: [
            KeyFrame {
                time: 0ms
                values: [
                    fade => 0.0
                ]
            },
            KeyFrame {
                time: 600ms
                values: [
                    fade => 1.0 tween Interpolator.LINEAR
                ]
            }
        ]
    };
    //функция которая будет выполнятся при клике на кнопку
    //у нас она не будет делать ничего.
    public var action:function():Void;
    //подпись кнопки
    public var textRef:Text;
}

Теперь немного пояснений по пунктам.
* on replace означает что при изменении параметра imageUrl нужно изменить сам параметр buttonImage
** Класс Timeline отвечает за изменение некоторых параметров с течением времени. Должен содержать массив объектов KeyFrame — которые отвечают за изменение определенных параметром с течением времени. В нашем простом случае будет 2 KayFrame`а логика действия следующая. Когда вызывается метод стартующий анимацию (у нас он будет написан чуть позже play() или playFromStart())вызывается первый KeyFrame (c минимальным значением параметра time)в нем будет изменет параметр (fade => 0.0), затем через 600ms вызывается второй и меняет параметр на 1.0 линейно. Если бы был третий KeyFrame то затем вызвался бы он. Все до безобразия просто. Надо отметить что по-умолчанию все переменные имею модификатор видимости script-only т.е. private.
Перейдем к последнему методу который вы должны обязятельно реализовать если наследуете класс CustomNode — метод create().
ImageButton.fx

//функция create которая должна вернуть объект типа Node
override public function create():Node {
        // мы же вернем группу (т.е. несколько виджетов объединены в один)
        return Group {
            content: [
                // выделяем прямоугольник размером с картинку
                Rectangle {
                    width: bind buttonImage.width
                    height: bind buttonImage.height
                    opacity: 0.0
                },
                //затем добавляем собственно картинку. ничего интересного с точки зрения
                //изучения языка тут нет кроме обработки событий мыши, которые я поясню ниже <b>*</b>
                ImageView {

                    var scale = bind if (mouseInside) fade * (1.0 - nonRolloverScale) +
                    nonRolloverScale
                    else 1.0 - fade * (1.0 - nonRolloverScale);
                    image: buttonImage
                    opacity: bind if (mouseInside) fade * (1.0 - nonRolloverOpacity) + 
                    nonRolloverOpacity
                    else 1.0 - fade * (1.0 - nonRolloverOpacity)
                    scaleX: bind scale
                    scaleY: bind scale
                    translateX: bind buttonImage.width / 2 - buttonImage.width * scale / 2
                    translateY: bind buttonImage.height - buttonImage.height * scale
                    onMouseEntered:
                    function(me:MouseEvent):Void {
                        mouseInside = true;
                        fadeTimeLine.playFromStart();
                    }
                    onMouseExited:
                    function(me:MouseEvent):Void {
                        mouseInside = false;
                        fadeTimeLine.playFromStart();
                        me.node.effect = null
                    }
                    onMousePressed:
                    function(me:MouseEvent):Void {
                        me.node.effect = Glow {
                            level: 0.9
                        };
                    }
                    onMouseReleased:
                    function(me:MouseEvent):Void {
                        me.node.effect = null;
                    }
                    onMouseClicked:
                    function(me:MouseEvent):Void {
                        action();
                    }
                },
                //добавляем подпись картинки
                textRef = Text {
                    translateX: bind buttonImage.width / 2
                    translateY: bind buttonImage.height
                    textOrigin: TextOrigin.TOP
                    content: title
                    fill: Color.WHITE
                    opacity: bind if (mouseInside) fade else 1.0 - fade
                    font: Font.font("Segoe",16)
                },
            ]
        };
    }

* если вы хотите обрабатывать то или иное событие то просто нужно вписать свою функцию-обработчик в нужное вам поле. Функция обязательно должна принимать событие которым вызвана (т.е. у нас принимается me:MouseEvent, т.к. обрабатываем события мыши). При нажатии на нашу кнопку будет использован эффект Glow (осветление).
Часть 2 — создаем панельку в которой будут кнопки
DockNode.fx

package lesson2;

public class DockNode extends CustomNode{

    //Высота панели
    public var height: Float;
    //Ширина панели
    public var widght: Float;
    var spacing = 9;
    public var buttons: ImageButton[];
    //Уже известный нам метод create()
    override public function create(): Node{
        height = 0.0;
        //Мы впервые используем цикл :) <b>*</b>
        for (i in buttons){
             widght = widght + buttons[{indexof i}].buttonImage.width + 3*spacing;
             var loc_height = buttons[{indexof i}].buttonImage.height*2 + 16;
             if(height<loc_height)
                height = loc_height;
        }
        //Возвращаем горизонтальную патель с эффектом отражения
        return HBox{
            hpos: HPos.CENTER
            vpos: VPos.CENTER
            spacing: spacing
            content: buttons
            effect: Reflection {
                fraction: 0.75
                topOffset: 0.0
                topOpacity: 0.5
                bottomOpacity: 0.0
            }
        }

    };
}

Тут по большому счету ничего интересного нет, кроме того что мы использовали цикл for. Формат этого оператора может быть либо

for( <variable> in <sequence> ){
    // Тело цикла
}

ну или

for (i in [1..100]){
   println("Iteration: {i}");
}

Первый случай похож на реализацию for`а в языке lua — на каждой итерации вы получаете очередное значение из последовательности (массива). Узнать положение элемента a в массиве array можно так: array[indexof a].
Во втором случае вы просто указываете что переменная i изменяется от 1 до 100.
Ну и наконец главный скрипт который будет все это запускать:
Main.fx

package lesson2;

Stage {

    var dock = DockNode{
        buttons: [
                    ImageButton{
                        title: "iCal"
                        imageUrl: "{__DIR__}button1.png"
                    },
                    ImageButton{
                        title: "button 2"
                        imageUrl: "{__DIR__}button2.png"
                    },
                    ImageButton{
                        title: "asd"
                        imageUrl: "{__DIR__}button3.png"
                    },
                    ImageButton{
                        title: "iTunes"
                        imageUrl: "{__DIR__}button4.png"
                    }
                ]
    }
    title: "Simple MacOS X Dock-like ButtonsPanel"
    width: 250
    height: 80
    scene: Scene {
        
        content: [
            Rectangle{
              height: bind dock.height
              width: bind dock.widght
              fill: LinearGradient {
                      startX : 0.5
                      startY : 0.0
                      endX : 1.0
                      endY : 1.0
                      stops: [
                          Stop {
                              color : Color.LIGHTGRAY
                              offset: 0.0
                          },
                          Stop {
                              color : Color.DARKGRAY
                              offset: 1.0
                          },

                      ]
                  }
            },
               dock
        ]
    }
}

Тут в пояснениях может нуждаться только запись вида __DIR__. Так же как и __FILE__ означают директорию и файл сответственно, скрипта в котором это написано.
В результате таких мучений у нас получится что-то вроде такого:

Когда наведена мышь

Когда кликнули


Всем спасибо, за внимание. В следующем топике постараюсь ответить на пожелание Reorcs:«но так как меня мало интересует КАК это писать, кроме основных вещей. мне было бы больше интересно читать о том КАК это дизайнится, и насколько сложно поддерживается, и как БЫСТРО работает»

На последок ссылочка с другим типом меню

Комментарии (1)

Круто.
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.