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)

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