2016-11-11 1 views
0

Zuerst ist meine Frage, wie man eine neu hinzugefügte Zeile in JavaFx flashen lässt, dann habe ich eine Menge Fragen zu diesem Thema (wie javafx: table row flashing). Die meisten von ihnen verwenden setRowFactory und Überschreibung die updateItem Verfahren durch eine Zeitleiste Animation Zugabe, die den Zustand der Pseudo der Reihe ändern. Unten ist mein Code, ich versuche ein FlashControl zu erstellen, das wiederverwendet werden kann.wie tableRow blinken lassen, auch wenn es nicht sichtbar ist? (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; 
         } 

        } 
       } 

      } 
     }); 
    } 
} 

Hier ListChangeListener ist im Zusammenhang mit table.getItems() der ich die neu eingefügten Zeile aufnehmen kann.

Es ist möglich, mehrere Zeilen innerhalb einer Operation hinzuzufügen, was bedeutet, dass newAdded.size() größer als 1 sein kann. Was mehr ist, Zeilen aus den oben des Tableview eingefügt werden (weil ich es mit der Nummer sortieren.)

In Tableview, nicht alle Zeilen sichtbar sind und updateItem Methoden nur die sichtbaren Zeilen aktualisieren. Mein Problem tritt auf, wenn diese beiden Situationen eintreten (siehe unten).

Das erste Szenario

The first scenario

Im ersten Szenario nur vier Zeilen sichtbar sind, wenn der Benutzer 5 Reihen innerhalb einer Zeit einfügt, kann ich nicht die letzte Zeile Update (das updateItem won aufnehmen nicht für die new_row_5 aufgerufen werden. Dadurch kann ich nicht klar newAdded Liste (von newAdded.clear tun())

Das zweite Szenario

The second scenario

Im zweiten Szenario, nur 4 Zeilen sind wieder sichtbar. Es gibt jedoch unsichtbare Zeilen sowohl oben als auch unten in den sichtbaren Zeilen. Wenn der Benutzer 2 Zeilen einfügt, wird eins sichtbar und das andere wird unsichtbar sein. In meinem Fall wird new_row_2 blinken, während new_row_1 bleibt unsichtbar. Wenn Benutzer einen Bildlauf bis der Tableview, wenn new_row_2 blinkt, wird er sehen, new_row_2blinkt während new_row_1 ist nicht, die wirklich seltsam ist.

Ich möchte auch wissen, ob es eine Möglichkeit gibt, die Anzahl der sichtbaren Zeilen zu finden.

Ich bin immer noch neu in JavaFx und ich weiß nicht, ob diese Methode gut ist oder nicht. Ich hoffe, dass jemand mir helfen kann, meine Probleme zu beheben. Danke vielmals!

+0

Leicht OT, aber warum wickeln Sie die 'Timeline' in einem Thread? Der neue Thread wird einfach die Timeline starten und sofort beenden. Sie können dies sehr gut auf dem FX Application Thread tun. –

+0

@James_D Sorry, das war meine Schuld ... Ich sollte den neuen Thread entfernen. – ZanderXu

Antwort

0

Ihr Ansatz scheint nicht eine saubere Möglichkeit, dies zu tun.Die Animation hängt von der TableRow Position ab, in der das Element positioniert ist, und scheint nicht mehrere gleichzeitig stattfindende Animationen zu unterstützen. Darüber hinaus verlässt es sich auf die equals Methode der Elementklasse, die nicht überschrieben wird, und darauf, dass der Benutzer ein Objekt nicht mehrfach in die TableView einfügt. Auch Sie möglicherweise eine große Anzahl von Timeline s erstellen (nicht notwendig, sie von einem separaten Thread BTW zu starten, da Timeline.play() nicht blockiert).

Es ist besser, die Animation von den Indizes abhängig zu machen. Wenn Sie auch die erstellten TableRow s verfolgen, können Sie auf vorhandene Zellen zugreifen, falls ihnen ein Index zugewiesen wird, der animiert werden muss. Auch könnten Sie die Animationen mit einem einzigen AnimationTimer behandeln, indem Sie die Daten in einer geeigneten Datenstruktur speichern.

Auch wäre es IMHO am besten, die rowFactory Klasse zu verwenden, um diese Logik zu implementieren.

Das folgende Beispiel lässt die Zeilen blinken, ob sie auf dem Bildschirm angezeigt werden oder nicht.

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

Danke, dass du mir Zeit gegeben hast, meine Frage zu beantworten. Diese Methode funktioniert perfekt: D. Es braucht Zeit zu verstehen, wie es funktioniert, aber ich lerne wirklich viel. Danke noch einmal! @fabian – ZanderXu

Verwandte Themen