java - 即使不可见,如何让tableRow闪烁? (JavaFX)

标签 java animation javafx tableview

起初我的问题是如何让JavaFx中新添加的行闪烁,然后我遇到了很多与该主题相关的问题(例如 javafx: table row flashing )。他们中的大多数都使用 setRowFactory 并通过添加改变伪类状态的时间轴动画来覆盖 updateItem 方法行的。 下面是我的代码,我正在尝试构建一个可以重用的FlashControl。

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;
                        }

                    }
                }

            }
        });
    }
}

这里ListChangeListenertable.getItems()关联,它可以帮助我记录新插入的行。

可以在一次操作中添加多行,这意味着 newAdded.size() 可以大于 1。此外,行是从 tableView 的顶部插入的(因为我用数字对其进行排序。)

在 tableView 中,并非所有行都是可见的,并且 updateItem 方法仅更新那些可见的行。当这两种情况发生时我的问题就出现了(见下文)。

第一个场景

The first scenario

在第一种情况下,只有4行可见,如果用户一次插入5行,我无法记录最后一行更新(new_row_5不会调用updateItem) )。因此,我无法清除 newAdded 列表(通过执行 newAdded.clear())

第二种情况

The second scenario

在第二种情况下,只有 4 行再次可见。但是,可见行的顶部和底部都有不可见的行。如果用户插入 2 行,一行将可见,另一行将不可见。就我而言,new_row_2 将闪烁,而 new_row_1 保持不可见。如果用户在 new_row_2 闪烁时向上滚动 tableView,他将看到 new_row_2 正在闪烁,而 new_row_1确实很奇怪。

我也想知道是否有办法找到可见行数。

我对JavaFx还是个新手,不知道这个方法好不好。我希望有人能帮助我解决我的问题。非常感谢!

最佳答案

您的方法似乎不是一个干净的方法。动画取决于项目所在的 TableRow,并且似乎不支持同时发生多个动画。此外,它依赖于项目类的 equals 方法未被覆盖,并且依赖于用户不向 TableView 多次添加项目。此外,您还可能创建大量的 Timeline(顺便说一句,不必从单独的线程启动它们,因为 Timeline.play() 不会阻塞)。

最好让动画依赖于索引。此外,跟踪创建的 TableRow 允许您访问现有单元格(如果它们被分配了需要动画的索引)。您还可以通过将数据存储在合适的数据结构中,使用单个 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));
        }

    }

}

关于java - 即使不可见,如何让tableRow闪烁? (JavaFX),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40542788/

相关文章:

安卓 : animating button on press through xml

jquery - CSS显示:inline animation not working using jquery addClass

css - 如何使单选按钮和复选框相对于系统分辨率改变大小?

java - 无法使用 Eclipse OpenJ9+181 构建 OpenJFX

java - 多个不同数据库环境的连接池

java - 整数的 JTextfield 监听器

java - 什么是NumberFormatException,我该如何解决?

ios - 我可以使用 PNG 序列作为 SceneKit 对象上的动画 Material 吗?

java - appengine spring aop java.security.AccessControlException : access denied ("java.lang.RuntimePermission" "accessClassInPackage.sun.reflect")

javascript - 选择的 RIA 技术