Try English version of Quizful



Раздаем бесплатные Q! подробности в группе Quizful.Alpha-test
Топ контрибуторов
loading
loading
Знаете ли Вы, что

Вы можете подписаться на RSS ленту новых тестов сервиса Quizful, в том числе и отдельно по каждой категории

Лента обновлений
ссылка 12:31:40
Комментарий от log4456602:
Я так понял для варианта :
String a1 = "Test";
String...
ссылка 12:05:43
Добавлен вопрос в тест Базы данных (теория)
ссылка 11:22:41
Комментарий от log4456602:
Действительно!)))
ссылка 00:08:51
Добавлен вопрос в тест XML
ссылка 00:06:23
Добавлен вопрос в тест XML
Статистика

Тестов: 153, вопросов: 8597. Пройдено: 417957 / 2039343.

JavaFX. Перезагрузка.

head tail Статья
категория
Java
дата11.10.2014
авторcyber_1
голосов24
JavaFX

Итак, перед вами вторая моя статья про Java и JavaFX.

Сегодня мы попытаемся разобраться зачем JavaFX нужна каскадная таблица стилей, и попытаемся порисовать на Саnvas

Начнем. Я буду отталкиваться от предыдущей статьи, и попытаться добавить нового функционала. Прошлый раз я использовал метод, который создавал нужный нам Label, но если мы будем постепенно навешивать на него новые возможности, луче всего вынести это в отдельный класс.

Итак, имея богатую фантазию я назвал этот класс MyLabel:

public class MyLabel extends Label{ 
 
private static Random r = new Random(); // пригодиться всегда 
private boolean isSelected = false;    // флажок фокуса на наш обьект 
private Point2D diff;                 // координаты курсора мыши относительно нашего обьекта, 
                            // для плавного передвижения 
 
public MyLabel(String text){      //Конструктор 
   super(text); 
   setPrefWidth(100); 
   setPrefHeight(50); 
    setAlignment(Pos.CENTER); 
    setLayoutY(r.nextDouble() * 500); 
    setLayoutX(r.nextDouble() * 500); 
    setRotate(r.nextDouble() * 180); 
} 
 
} 
        
    

а вот как стал выглядеть главный класс:

public class Main extends Application{ 
 
   private int windowWidth = 600; 
    private int windowHeight = 600; 
    
     @Override 
    public void start(Stage primaryStage) throws Exception{ 
        stage = primaryStage; 
        stage.setTitle("Simple Application"); 
        stage.setScene(InitScene()); 
        stage.show(); 
    } 
    
     private Scene InitScene(){ 
        Group root = new Group(); 
        Scene myScene = new Scene(root, windowWidth, windowHeight); 
        for(int i =0; i < 5; i++)  
      root.getChildren().add(new MyLabel("Some Text")); 
        return myScene; 
    } 
    
   public static void main(String... args){ 
      launch(args); 
   } 
} 
        
    

Думаю, что ничего нового тут вы не увидите, пишем дальше. Нам нужны обработчики событий, таких как нажатие кнопки мыши, перетаскивание курсора по поверхности нашего элемента, и так далее. Проще всего, реализовать эти обработчики в нашем классе, для этого модифицируем шапку класса:

public class MyLabel extends Label implements EventHandler<MouseEvent> 

Теперь компилятор просто умоляет нас реализовать метод handle(MouseEvent) для корректной работы программы. Будучи добрыми людьми выполним его просьбу.

public void handle(MouseEvent mouseEvent){ 
        if (mouseEvent.getEventType() == MouseEvent.MOUSE_CLICKED){ 
           /* код */ 
        } else 
        if (mouseEvent.getEventType() == MouseEvent.MOUSE_PRESSED){ 
           /* код */ 
        } else 
        if (mouseEvent.getEventType() == MouseEvent.MOUSE_RELEASED){ 
            /* код */ 
        } else 
        if (mouseEvent.getEventType() == MouseEvent.MOUSE_DRAGGED){ 
            /* код */          
        } 
    } 
        
    

Все это конечно можно было запихнуть в конструкцию switch, но это не главное в моей статье. Настало время обсудить что будут делать все эти обработчики: если MOUSE_CLICKED - то мы получили фокус если MOUSE_PRESSED - если у нас есть фокус, можно начать перемещение если MOUSE_RELEASED - прекращаем передвижение если MOUSE_DRAGGED - если есть фокус, перемещаем, Все это было в предыдущей статьи, поэтому сразу к коду

public void handle(MouseEvent mouseEvent){ 
        if (mouseEvent.getEventType() == MouseEvent.MOUSE_CLICKED){ 
            if (!isSelected) select(true); 
        } else 
        if (mouseEvent.getEventType() == MouseEvent.MOUSE_PRESSED){ 
            if (isSelected) { 
                diff = new Point2D(-mouseEvent.getSceneX() + getLayoutX(), -mouseEvent.getSceneY() + getLayoutY()); 
            } 
        } else 
        if (mouseEvent.getEventType() == MouseEvent.MOUSE_RELEASED){ 
            diff = null; 
        } else 
        if (mouseEvent.getEventType() == MouseEvent.MOUSE_DRAGGED && diff != null && c == null){ 
            setLayoutX(mouseEvent.getSceneX() + diff.getX()); 
            setLayoutY(mouseEvent.getSceneY() + diff.getY()); 
        } 
    } 
        
    

Все старое, кроме метода select(boolean), вот его реализация:

public void select(boolean fl){ 
        isSelected = fl; 
        if (fl) 
            toFront(); // ставиться поверх остальных обьектов 
        }  
   }   

Этот метод нам пригодится в будущем. Что мы имеем на данный момент, На окне несколько надписей, которые могут таскаться. Но как понять, которая выделенная? И тут нам поможет СSS. В JavaFX реализованная поддержка стилей, подробней ее можно изучить на сайте Oracle Чтобы придать стиль объекту, можно воспользоваться методом setStyle(String), был использован в пред. статье. Этот вариант подойдет вам, когда нужно оформить пару кнопочек и все. Если же вы хотите забабахать приложуху с over9000 элементами, то вам нужен другой подход. Он заключается в создании файла где будут описаны стили, прям как и для обычного СSS, но здесь он немного отличается, а именно в атрибутах, все они начинаются с "-fx-". Также не обязательно делать так:

styles.css:
.button{
...
}
#button_rosy{
-fx-background-color: #FF00C8
}
        
    
code: 
Button b = new Button(); 
b.getStyleClass().add("button"); // Необязательно, так как JavaFX это делает по умолчанию. 
// Если имя класса в СSS совпадает с названием Класс элемента интерфейса. 
        
    

Также есть поддержка id для элементов, используется метод setId(String), для задания іd элементу интерфейса. b.setId("button_rosy"). Есть еще много ништяков, которые предлагает JavaFX для работы со стилями, и про их в этой статьи будет написано аж ничего. Это было лирическое отступление, а теперь к делу. А для нас пока будет достаточно сделать следующее

styles.css:
#my_label{
-fx-background-color: #0f8dd9;
}
#my_label_selected{
-fx-background-color: #1a4d71;
-fx-border-color: rgba(0, 0, 0, 0.4);
-fx-border-width: 1;
}
        
code: 
class MyLabel ... { 
 ... 
    public void select(boolean fl){ 
        isSelected = fl; 
        if (fl){ 
            setId("my_label_selected"); 
            toFront(); 
        } else{ 
            setId("my_label"); 
        } 
    } 
} 
        
    

И так, наблюдаем следующую картину:

Ну конечно, если мы так разместим блоки.

Но тут мы встречаем первую загвоздку: выделить мы можем несколько штук сразу, а это уже не хорошо. Один из способов решить эту проблему, создать массив наших объектов, для контроля их состояний. Для этого я создал класс с фантастическим именем LabelPool, ниже приведена его реализация.

public class LabelPool { 
 
    private ArrayList<MyLabel> pool; // массив  обьектов 
    private MyLabel selected; // ссылка на обьект с фокусом(null, если фокуса нету) 
 
    public LabelPool(){ //Конструктор 
        pool = new ArrayList<>(); 
    } 
 
    public void add(MyLabel... labels){ // Метод добавления новых обьектов 
        for(MyLabel label : labels) 
            pool.add(label); 
    } 
 
    public MyLabel getSelected(){ // Возвращает ссылку на обьект с фокусом 
        return selected; 
    } 
 
    public void select(MyLabel label){ //Обределяет фокус другому обьекту 
        if (label == null){   // Сброс фокуса 
            if (selected != null) selected.select(false); 
        } 
 
        if (pool.contains(label) ){ //Если в массиве есть такой обьект, ставим ему фокус 
            if (selected != null) selected.select(false); 
                selected = label; 
                selected.select(true); 
            } 
    } 
} 
        
    

Вы можете сказать, а почему не сделать просто статический массив для класса MyLabel, да потому, что мы можем разделить объекты MyLabel на группы, да и опять же, код класса MyLabel будет более чист и понятен. Изменим немного кода в нашем классе

class MyLabel ... { 
 
   private LabelPool pool; 
    ... 
   public MyLabel(String text, LabelPool pool){ 
      ... 
   this.pool = pool; 
   pool.add(this); 
   } 
    
   ... 
   public void handle(MouseEvent mouseEvent){ 
        if (mouseEvent.getEventType() == MouseEvent.MOUSE_CLICKED){ 
            if (!isSelected) pool.select(this); 
        } 
      ... 
  } 
   ... 
} 
        
    

Ну и создать объект pool надо, это задание я оставлю вам. В результате увидим:

Ну вот, мы и подобрались к концу первой статьи, код с которой мы немного переделали.

Перезагрузка

И так, я хочу сделать что-то такого типа:

То-есть, у нас есть возможность перетаскивать элементы, а линии перерисовуются автоматически. Как это можно сделать? Первое что приходить в голову 2 варианта: использовать Саnvas, или же попытаться использовать объекты Line. Если выбрать полотно(Canvas), то можно очень просто рисовать всякие фигуры, и все такое, но придется жестко разрабатывать дизайн, хотя можно что-то придумать чтобы он как-то использовал стили. Если же выбрать второе нам придется мучится с объектами, рассчитывать их позиции, но на их можно наложить стили, слушатели, что очень кстати удобно. Сегодня я буду использовать ... барабанная дробь ... Сanvas. Я реализую это в отдельном классе под мистическим названием MyCanvas.

public class MyCanvas extends Canvas { 
 
    private ArrayList<MyLabel> f = new ArrayList<>(); // листы, в которых хранятся пары 
    private ArrayList<MyLabel> s = new ArrayList<>(); // связанных между собой елементов 
    private GraphicContext gc; // инструмент для рисования 
 
    public MyCanvas(Scene scene){ 
        super(scene.getWidth(), scene.getHeight()); 
        widthProperty().bind(scene.widthProperty()); //Делаем привязку высоты полотна к высоте сцены 
        //Делаем привязку ширины полотна к ширине сцены 
        gc = getGraphicsContext2D(); 
    } 
    
   public void addPair(MyLabel n1, MyLabel n2){ 
        f.add(n1); 
        s.add(n2); 
    } 
    
   public void repaintContext(){ 
       // Сердце нашего класса. 
       // Здесь будет происходить перерисовка  
       // связей между компонентами.  
   } 
} 
 
        
    

Ну и осталось вызывать этот метод когда нужно, а нужно почти всегда. Идем в наш класс и пишем

class MyLabel ...{ 
   ... 
   private MyCanvas canvas; 
   ... 
   public MyLabel(String s, LabelPool pool, MyCanvas canvas){ 
        super(s); 
      this.canvas = canvas; 
      ... 
   } 
    
    
    public void handle(MouseEvent mouseEvent){ 
   ... 
   if (mouseEvent.getEventType() == MouseEvent.MOUSE_DRAGGED && diff != null && c == null){ 
            setLayoutX(mouseEvent.getSceneX() + diff.getX()); 
            setLayoutY(mouseEvent.getSceneY() + diff.getY()); 
            canvas.repaintContext(); 
        } 
   } 
} 
        
    

И да, код с основной программы

class Main ...{ 
   private MyCanvas canvas; 
   private LabelPool pool = new // Если не нашли где написать 
   ... 
    
   private Scene InitScene(){ 
      ... 
      canvas = new MyCanvas(); 
      MyLabel a = new MyLabel("Some text", pool, canvas); 
      root.getChildren().add(a); 
      for(int i = 0; i < 5; i++){ 
          MyLabel b = new MyLabel("Some text", pool, canvas); 
          canvas.addPair(a, b); 
          root.getChildren().addA(b); 
      } 
       
      return scene; 
   } 
} 
        
     

Теперь давайте писать прорисовку линий.

public void repaintContext(){ 
 
   double widht = getWidth(); 
   double height = getHeight(); 
   gc.clearRect(0, 0, width, height); // Очищаем полотно 
    
   for(int i = 0; i < f.size(); i++){  
      MyLabel n1 = f.get(i); 
      MyLabel n2 = s.get(i); 
      //Рисуем линию 
      gc.strokeLine(n1.getLayoutX(), n1.getLayoutY(), n2.getLayoutX(), n2.getLayoutY()); 
       
} 
        
    

В результате:

Как можно увидеть координаты не совпадают с центром, по-этому пишем для нашего класса два новых метода

class MyLabel ... { 
      ... 
   public double getCenterX(){ 
        return getLayoutX() + getPrefWidth()2; 
    } 
 
 
    public double getCenterY(){ 
        return getLayoutY() + getPrefHeight()2; 
    } 
    
}  
        
    

Ну и исправляем немного метод repaintContext:

        
public void repaintContext(){ 
   ... 
   { 
      gc.strokeLine(n1.getCenterX(), n1.getCenterY(), n2.getCenterX(), n2.getCenterY()); 
   } 
} 
        
    

Уже луче, не правда ли?

Ну на этом уже можно и закончить, все довольно просто и красиво. Но это не то, что я хотел получить. Я хочу изгибов и плавности, и поэтому будем пытаться немного усложнить задачу. Во-первых, нужно выбрать способ решения. Я выбрал наложения какой-то гармонической функции на прямую. Во-вторых, нужно выбрать функцию. Я выбрал синус, так ка в точке 0, его значение 0, и нам не придется изощрятся, чтобы линия выходила ровно из объекта. В-третьих, нужно придумать как это реализовать. Я буду разбивать линию от первого элемента ко второму, на мелкие, на которых можно изобразить функцию как прямую. По сути дела, проинтегрируем синус от 0 до Xright, который мы зададим. Набросок метода:

private void strokeLine(double x1, double y1, double x2, double y2){ 
   double xR = 2 * Math.PI; // функция поменяется от 0 до 2PI 
   double n = 20; // количество мелких итервалов 
    
    
   if (x1 > x2){   // меняем местами, чтобы не использовать многократно abs(double, double) 
      double b = x1;   x1 = x2; x2 = b; 
            b = y1;  y1 = y2; y2 = b; 
   } 
   double l = (x2 - x1) //длинна проэкции на ось Х 
   double dWidth = (x2 - x1)  n; // приращение х 
    
   double bx = x1; // буферные переменные 
   double by = y1; // хранятся предыдущие значения  
    
   for(double x = x1; x < x2; x+=dWidth){ 
      double xSin = (x - x1)  l * xR; // находим кодированное значение х є [0..xR] 
      double ySin = Math.sin(xSin)*50  + (x - x1)  l * Math.abs(y1 - y2) + Math.min(y1, y2); // wtf? 
      gc.strokeLine(bx, by, x, ySin); // рисуем линию на маленьком участке 
       
      bx = x;   // сохранаем текущие значения, чтобы не считать 2 раза. 
      by = ySin; 
   } 
} 
        
    

И это уже впринцыпе можно запустить и увидить:

Как видим для более четкой картинки нужно увеличить n, если поставить 30, то будет уже хорошо? но нам нужно учитывать следующее - если мы растянем на весь экран наши блоки, увидим, что плавность немного падает

int n = lenght  200 * // length - длинна, 200 это длинна при которой 30 точек справляются со своей работой на отлично. 
        
    

Давайте добавим изгибов, представивши xR как функцию от длинны.

double xR = Math.max(2 *Math.PI * Math.abs(x2 - x1)  200, Math.PI * 2); 
// Если за 2PI взять полный оборот, то сколько будет таких оборотов шириной в 200px умножить на 2PI, 
// или же если ширина меньше 200px, то берет 2Pi; 
        
    

Пытаемся запустить.

Как видим, изгибов хватает Последнее что хотелось сделать наложить текстуру на линию. Сначала надо текстуру где-то взять, недолго размышляя создал в фотошопе простую текстурку, она выглядит так

C

Теперь размышляем, как наложить картинку на линию. Самый простой способ, это вместо мелких промежутков рисовать текстуру, давайте это и сделаем. При Этом нужно еще уччитывать растяжение текстуры, так как длинны промежутков будут разные. Но с эти м мы справимся немного позже. Так как не сущетвует метода который бы рысовал повернутую и растянутую картинку, то мы будем поворачиваьт полотно, а именно задавать ему Translate. Это очень мощная штука, и если серьозно заниматся разработкой приложений на JavaFX, то нужно очень хорошо ею овладеть. Иначе нам бы пришлось писать метод, который может поворачевать текстуру, это конечно не ультра сложно, но немного пришлось бы пописать.

class MyCanvas ... { 
   private Image line; 
    
   public MyCanvas(...){ 
      ... 
      try{ 
         line = new Image(new FileInputStream(new File("srcgraphicsline.png"))); 
         //можно использовать getClass.getRosurces(String) также как параметр для конструктора 
      }catch(...){ 
         ... 
      } 
   } 
} 
 
//Считает длинну отрезка 
private double dist(double x1, double y1, double x2, double y2){ 
   return Math.sqrt((x1-x2)*(x1-x2) + (y1 - y2)*(y1-y2)); 
} 
 
private void drawImage(Image im, double x1, double y1, double x2, double y2) { 
        double angle = Math.atan(((y2-y1)(x2-x1))); // Узнаем угол наклона отрезка 
 
        Rotate r = new Rotate(Math.toDegrees(angle), x1, y1); // Считаем поворот на угол angle относительно (x1, y1) 
        Transform t = gc.getTransform(); // Повертаем полоно 
 
        gc.setTransform(r.getMxx(), r.getMyx(), r.getMxy(), r.getMyy(), r.getTx(), r.getTy()); 
 
        double d = dist(x1, y1, x2, y2); // узнаем длинну отрезка 
        gc.drawImage(im, x1, y1 - 2.5, d, 5); // рисуем текстуру в точке (x1, y1 - 2.5) шириной d, и высотой  5px. 
 
        gc.setTransform(t.getMxx(), t.getMyx(), t.getMxy(), t.getMyy(), t.getTx(), t.getTy()); // возвращаем полотно в исходное положение 
       
  } 
            
    

После этого пробуем запустить.

Что-бы увидеть следующую проблему я уменьшил n, и теперь видно, что нарушается масштаб текстуры, то она растянута, то она очень сжата, что делать в таком случае? так как метод drawImage(), рисует прямую линию, то мы можем просчитать сколько нужно текстур нарисовать на этом участке, чтобы не терять масштаб. Изменим метод drawIMage():

private void drawImage(...){ 
   ... 
   double d = dist(x1, y1, x2, y2); 
    double w = im.getWidth(); 
    double dx = w; 
 
    for(double x = x1; d > 0;){ // пока незарисованная длинна > 0 
       if (d > w) {  // если незарисованная длинна   >  длинну изображения, рисуем его полностью 
           gc.drawImage(im, x, y1 - 2.5, w+1, 5); 
            x += dx; 
            d-=w; 
        }else {      // иначе рисуем часть текстуры, а именно часть от 0 до d. 
            gc.drawImage(im, 0, 0, d, im.getHeight(), x, y1-2.5, d+1, 5); 
            d = 0; 
            } 
        } 
    ... 
} 
 
        
    

Теперь уже, когда запустим, то увидим, что качество не меняется, что уже очень хорошо.

И напоследок так как эта штука мне напоминает робота из матрици, которого вы могли видеть в шапке статьи, то я хочу добавить некоторую анимацию движения этих "щупалец". Это будет не анимация средствами JavaFX, а сделанная ручками, которая требует нажимания на кнопку Enter. Модифицируем код на такой:

class MyCanvas ... { 
   private double dx;  
   ... 
    
   public void strokeLine(...){ 
      ... 
      double ysin = 50*Math.sin(xsin - dx) * // sin с отклонением
                 (x2 - x > 200 ? 1 : (x2 - x)  200) + ... //Если значение начала синуса равно 0,
//то не факт, что и конец тоже будет 0
// а этот кусок кода, будет опускать значение y до нуля, около конца отрезка 
      ... 
      dx += 0.02; 
   } 
    
} 
 
class Main{ 
   ... 
    
   private Scene initScene(){ 
      ... 
       
      myScene.setOnKeyPressed(new EventHandler<KeyEvent>() { // Обработчик  
            @Override 
            public void handle(KeyEvent keyEvent) { 
                if (keyEvent.getCode() == KeyCode.ENTER)// Если зажата кнопка Ентер  
                    pool.select(null);               //  Снять выделение 
                   canvas.repaintContext();         // Перерисовать 
            } 
            } 
        }); 
   } 
} 
        
    

Вот и все. Если ты дочитал аж сюда, то целых 200 к карме твоей и мою благодарность) Статья получилась очень сжата, и наполнена в основном кодовыми вставками, я не приводил мат. обоснований и формул, так как для большинства они останутся не интересными, да и места они занимали бы. Если кто-то спросит, с радостью напишу. Замечания и идеи для обсуждений жду в комментариях.

Если Вам понравилась статья, проголосуйте за нее

Голосов: 24  loading...
Heorhi_Puhachou   dazerty   Byte_Runchik   profesor   busikpk   FLEX   nurs_politeh   Mashiro   Serhii   Stydent007   Aruy   chernichenko   olegsemeniuk   DorianGray   RabbitInq   vadimati   Venedey   infoLil   SamTan   vinogradov97   piegas   Den_b   Ivanradist   the_alator