2016-11-11 5 views
0

Сначала я задал вопрос о том, как добавить недавно добавленную строку flash в JavaFx, затем я рассмотрел множество вопросов, связанных с этой темой (например, javafx: table row flashing). Большинство из них использует setRowFactory и переопределенияupdateItem метода путем добавления анимации временной шкалы, которая изменять состояние pseudoClass ряда. Ниже мой код, я пытаюсь создать FlashControl, который можно использовать повторно.как позволить tableRow flash, даже если он не виден? (JavaFx)

public class TableFlashControl<T> { 

    private PseudoClass flashHighlight = PseudoClass.getPseudoClass("flash-highlight"); 
    private List<T> newAdded = new ArrayList<>(); 
    private boolean isFilterApplied = false; 
    private boolean isSorted = false; 

    public void setIsFilterApplied(boolean isFilterApplied) { 
     this.isFilterApplied = isFilterApplied; 
    } 

    public void add(TableView<T> table){ 
     ListChangeListener<T> change = c -> { 
      while (c.next()) { 
       if (c.wasAdded()) { 
        List<? extends T> added = c.getAddedSubList(); 
        T lastAdded = added.get(0); 
        if (!isFilterApplied) { 
         newAdded.add(lastAdded); 
        } 
       } 
      } 
     }; 
     table.getItems().addListener(change); 
     table.setRowFactory(param -> new TableRow<T>() { 
      @Override 
      protected void updateItem(T item, boolean empty) { 
       super.updateItem(item, empty); 
       if (item == null || empty) { 
        return; 
       } 
       if (newAdded.contains(item)) { 
        if (isSorted) { 
         new Thread(()->{ 
          Timeline flasher = new Timeline(
            new KeyFrame(Duration.seconds(0.4), e -> pseudoClassStateChanged(flashHighlight, true)), 
            new KeyFrame(Duration.seconds(0.8), e -> pseudoClassStateChanged(flashHighlight, false)) 
          ); 
          flasher.setCycleCount(2); 
          flasher.play(); 
         }).start(); 
         if (item == newAdded.get(0)) { 
          newAdded.clear(); 
          isSorted = false; 
         } 

        }else{ 
         if(item == newAdded.get(0)){ 
          isSorted = true; 
         } 

        } 
       } 

      } 
     }); 
    } 
} 

Здесь ListChangeListener связан с table.getItems(), который помогает мне записать вновь вставленной строки.

В одну операцию можно добавить несколько строк, что означает, что newAdded.size() может быть больше 1. Более того, строки вставляются из верхней из Tableview (потому что я отсортировать его с номером.)

В Tableview, не все строки являются видимыми и updateItem методы обновлять только те видимые строки. Моя проблема возникает, когда происходят эти две ситуации (см. Ниже).

Первый сценарий

The first scenario

В первом сценарии, только 4 строки являются видимыми, если пользователь вставляет 5 строк в одно время, не могу записать обновление последней строки (updateItem вон 't вызывается для new_row_5). Таким образом, я не могу ясно newAdded список (делая newAdded.clear())

Второй сценарий

The second scenario

Во втором сценарии, только 4 строки видны снова. Однако есть невидимые строки как сверху, так и снизу видимых строк. Если пользователь вставляет 2 строки, один будет виден, а другой будет невидимым. В моем случае new_row_2 будет мигать, пока new_row_1 остается невидимым. Если пользователь прокручивает в Tableview когда new_row_2 мигает, то он увидит new_row_2 является мигания в то время как new_row_1 является не, который действительно странно.

Я также хочу знать, есть ли способ найти количество видимых строк.

Я все еще новичок в JavaFx, и я не знаю, хороший ли этот метод или нет. Надеюсь, кто-то может помочь мне исправить мои проблемы. Большое спасибо!

+0

Немного OT, но почему вы обертываете 'Timeline' в потоке? Новый поток просто запустит временную шкалу и немедленно выйдет. Вы можете отлично справиться с этой проблемой. –

+0

@James_D Извините, это была моя ошибка ... Я должен удалить новую тему. – ZanderXu

ответ

0

Ваш подход не выглядит как чистый способ сделать это.Анимация зависит от TableRow, элемент находится внутри и, похоже, не поддерживает несколько анимаций, происходящих одновременно. Кроме того, он полагается на метод equals класса предметов, который не переопределяется, и на пользователя, не добавляющего элемент несколько раз к TableView. Также вы можете создать большое количество Timeline s (не обязательно запускать их из отдельного потока btw, так как Timeline.play() не блокирует).

Лучше сделать анимацию зависимой от индексов. Также отслеживание созданного TableRow s позволяет вам обращаться к существующим ячейкам, должен ли им быть присвоен индекс, который необходимо анимировать. Также вы можете обрабатывать анимации, используя один AnimationTimer, сохраняя данные в подходящей структуре данных.

Также было бы очень удобно использовать класс rowFactory для реализации этой логики.

В следующем примере строки мигают, являются ли они на экране или нет.

public class FlashTableRowFactory<T> implements Callback<TableView<T>, TableRow<T>> { 

    private final static PseudoClass FLASH_HIGHLIGHT = PseudoClass.getPseudoClass("flash-highlight"); 

    public FlashTableRowFactory(TableView<T> tableView) { 
     tableView.getItems().addListener((ListChangeListener.Change<? extends T> c) -> { 
      while (c.next()) { 
       if (c.wasPermutated()) { 
        int from = c.getFrom(); 
        int to = c.getTo(); 
        permutationUpdate(scheduledTasks, c, from, to); 
        permutationUpdate(unscheduledTasks, c, from, to); 
       } 
       if (c.wasReplaced()) { 
        addRange(c.getFrom(), c.getTo()); 
       } else if (c.wasRemoved()) { 
        int from = c.getFrom(); 
        int removed = c.getRemovedSize(); 
        removeRange(scheduledTasks, from, from + removed); 
        removeRange(unscheduledTasks, from, from + removed); 
        modifyIndices(unscheduledTasks, from, -removed); 
        modifyIndices(scheduledTasks, from, -removed); 
       } else if (c.wasAdded()) { 
        int from = c.getFrom(); 
        int to = c.getTo(); 
        modifyIndices(unscheduledTasks, from, to - from); 
        modifyIndices(scheduledTasks, from, to - from); 
        addRange(from, to); 
       } 
      } 

      // remove all flashTasks that are older than the youngest for a 
      // given index 
      Set<Integer> indices = new HashSet<>(); 
      removeDuplicates(unscheduledTasks, indices); 
      removeDuplicates(scheduledTasks, indices); 

      flashingIndices.clear(); 
      updateFlash(lastUpdate); 
      refreshFlash(); 
      if (!unscheduledTasks.isEmpty()) { 
       flasher.start(); 
      } 
     }); 
     this.tableView = tableView; 
    } 

    private static void removeDuplicates(List<FlashTask> list, Set<Integer> found) { 
     for (Iterator<FlashTask> iterator = list.iterator(); iterator.hasNext();) { 
      FlashTask next = iterator.next(); 
      if (!found.add(next.index)) { 
       iterator.remove(); 
      } 
     } 
    } 

    private static void modifyIndices(List<FlashTask> list, int minModify, int by) { 
     for (FlashTask task : list) { 
      if (task.index >= minModify) { 
       task.index += by; 
      } 
     } 
    } 

    private void addRange(int index, int to) { 
     for (; index < to; index++) { 
      unscheduledTasks.add(new FlashTask(index)); 
     } 
    } 

    private static void removeRange(List<FlashTask> list, int from, int to) { 
     for (Iterator<FlashTask> iterator = list.iterator(); iterator.hasNext();) { 
      FlashTask next = iterator.next(); 
      if (next.index >= from && next.index < to) { 
       iterator.remove(); 
      } 
     } 
    } 

    private static void permutationUpdate(List<FlashTask> list, ListChangeListener.Change<?> c, int from, int to) { 
     for (FlashTask task : list) { 
      if (task.index < to && task.index >= from) { 
       task.index = c.getPermutation(task.index); 
      } 
     } 
    } 

    // set of item indices that should flash 
    private final Set<Integer> flashingIndices = new HashSet<>(); 

    // references to every row created by this factory 
    private final List<SoftReference<TableRow<T>>> rows = new LinkedList<>(); 

    // tasks waiting to be scheduled 
    private final List<FlashTask> unscheduledTasks = new LinkedList<>(); 

    // tasks currently being animated 
    private final List<FlashTask> scheduledTasks = new LinkedList<>(); 

    private static class FlashTask { 

     int index; 
     long schedulingTime; 

     public FlashTask(int index) { 
      this.index = index; 
     } 

    } 

    private final TableView<T> tableView; 
    private long lastUpdate; 

    /** 
    * Updates flashingIndices set 
    * @param now the current timestamp 
    * @return true if the set has been modified, otherwise false. 
    */ 
    private boolean updateFlash(long now) { 
     boolean modified = false; 
     for (Iterator<FlashTask> iterator = scheduledTasks.iterator(); iterator.hasNext();) { 
      FlashTask next = iterator.next(); 

      // running time in seconds 
      double runningTime = (now - next.schedulingTime)/(1000d * 1000d * 1000d); 

      // slows down the animation for demonstration 
      final double animationSpeed = 0.1; 

      if (runningTime < 0.4/animationSpeed) { 
       // no need to handle tasks that run for less than 0.4 seconds 
       break; 
      } else if (runningTime > 1.6/animationSpeed) { 
       // end of task reached 
       iterator.remove(); 
       modified |= flashingIndices.remove(next.index); 
      } else if (runningTime > 0.8/animationSpeed && runningTime < 1.2/animationSpeed) { 
       // second "inactive" interval during animation 
       modified |= flashingIndices.remove(next.index); 
      } else { 
       // activate otherwise 
       modified |= flashingIndices.add(next.index); 
      } 
     } 
     return modified; 
    } 

    private final AnimationTimer flasher = new AnimationTimer() { 

     @Override 
     public void handle(long now) { 
      lastUpdate = now; 

      // activate waiting flash tasks 
      for (FlashTask task : unscheduledTasks) { 
       task.schedulingTime = now; 
      } 
      scheduledTasks.addAll(unscheduledTasks); 
      unscheduledTasks.clear(); 

      if (updateFlash(now)) { 
       refreshFlash(); 
       if (scheduledTasks.isEmpty()) { 
        // stop, if there are no more tasks 
        stop(); 
       } 
      } 
     } 

    }; 

    /** 
    * Sets the pseudoclasses of rows based on flashingIndices set 
    */ 
    private void refreshFlash() { 
     for (Iterator<SoftReference<TableRow<T>>> iterator = rows.iterator(); iterator.hasNext();) { 
      SoftReference<TableRow<T>> next = iterator.next(); 
      TableRow<T> row = next.get(); 
      if (row == null) { 
       // remove references claimed by garbage collection 
       iterator.remove(); 
      } else { 
       row.pseudoClassStateChanged(FLASH_HIGHLIGHT, flashingIndices.contains(row.getIndex())); 
      } 
     } 
    } 

    @Override 
    public TableRow<T> call(TableView<T> param) { 
     if (tableView != param) { 
      throw new IllegalArgumentException("This factory can only be used with the table passed to the constructor"); 
     } 
     return new FlashRow(); 
    } 

    private class FlashRow extends TableRow<T> { 

     { 
      rows.add(new SoftReference<>(this)); 
     } 

     @Override 
     public void updateIndex(int i) { 
      super.updateIndex(i); 

      // update pseudoclass based on flashingIndices set 
      pseudoClassStateChanged(FLASH_HIGHLIGHT, flashingIndices.contains(i)); 
     } 

    } 

} 
+0

Спасибо, что дал время ответить на мой вопрос. Этот метод отлично работает: D. Требуется время, чтобы понять, как это работает, но я действительно многому научился. Еще раз спасибо! @fabian – ZanderXu