概述

Dateline 是一个控件,用于在一行或多行单元格中显示实际日期(周一、周二、周三……)。Dateline 支持时区感知,会跟踪当前选中的时间区间以及当前悬停的时间区间。当可见时间范围发生变化时(例如向左或向右滚动后),它还会触发事件。

Dateline 控件截图

刻度分辨率

Dateline 可以显示一到五行。每一行称为一个“Dateline 刻度”,每个刻度显示一个“分辨率”。分辨率由时间单位(例如天、周、月)、格式化模式和数量组成。数量用于指定“5 分钟”“15 分钟”等分辨率。可以通过调用 getScaleResolutions() 获取 Dateline 当前显示的完整分辨率列表。

系统层 GridLinesLayer 提供了该方法的一个使用示例。它会调用此方法,使用分辨率来计算垂直网格线的位置。为此,Resolution 类提供了 truncate() 方法用于跳到某个单位的起点(例如一天的开始),以及 increment() 方法用于跳到下一个单位(例如下一天)。有关 Resolution 的更多信息,请参阅 Dateline 模型文档。

主要时间单位

例如,一个包含三个刻度的 Dateline 可以显示“月”“周”和“日”这三种分辨率。最小的“日”分辨率显示在 Dateline 底部。该分辨率使用的时间单位 ChronoUnit.DAYS 也称为“主要时间单位”。该单位的当前值存储在只读属性 primaryTemporalUnit 中。查询活动存储库中的活动时会使用该属性值。这样,存储库就可以决定调用结果的粒度,或者决定某些活动是否完全不显示。

WeekendCalendar 类是主要时间单位的一个良好用例。它实现了 Calendar,而 CalendarActivityRepository 的扩展。WeekendCalendar 的用途是返回给定时间区间中的周末日期(星期六、星期日)。当它被调用时,如果主要时间单位过大或过小,就不会返回任何内容。如果用户当前查看的是分钟或十年,返回周末信息就没有意义。

时区

Dateline 需要知道它正在为哪个时区显示日期(例如 EST 或 GMT)。因此,它提供了 zoneIdProperty() 属性。该属性可写,并可通过 setZoneId() 设置。可以通过调用 setZoneId(true) 让该属性的值在控件中可见。

选择模型

Dateline 控件允许用户在按住快捷修饰键(Windows / Linux 上为 CTRL,Mac 上为 Option)的同时点击主鼠标按钮,对时间区间进行单选或多选。是否支持单选或多选取决于 selectionModeProperty() 的值。

只有当前在任意行/刻度中可见的区间才能被选中。因此,如果 Dateline 当前显示周和日,用户就只能选择整周或整天。可以通过调用 getSelectedTimeIntervals() 获取已选区间列表。

悬停时间区间

当鼠标指针悬停在 Dateline 上时,也意味着它悬停在某个时间区间上。根据给定鼠标位置所在 Dateline 行/刻度显示的分辨率,该区间可能是一整周,也可能是单独一天。无论是哪一种,该区间都会存储在只读属性 hoverTimeIntervalProperty() 中。

事件

当应用程序需要响应当前可见时间范围的任何变化时,可以监听 Dateline 触发的滚动事件。可以通过向 setOnVisibleRangeChanged() 方法传入事件监听器,或调用 addEventListener(DatelineScrollingEvent.ANY, myListener) 来实现。

单元格工厂

Dateline 控件能够显示不同类型的时间单位。默认支持 ChronoUnit(周一、周二、周三……)和 SimpleUnit(1、2、3、4……)。每种单位类型都有自己的视觉表示。为适配这一点,Dateline 控件会将 Dateline 单元格的创建委托给一个可插拔工厂,该工厂已预先映射到特定的时间单位类型。

setCellFactory(SimpleUnit.class, unit -> new SimpleUnitDatelineCell());
setCellFactory(ChronoUnit.class, unit -> new ChronoUnitDatelineCell());

Dateline 模型

Dateline 模型向 Dateline 控件提供各种信息,使其能够正确布局自身。

  • 分辨率 – 分辨率定义要显示哪个时间单位(例如小时)以及如何格式化它。它还包含该分辨率是否可以显示在顶部、底部或中间刻度中的信息。每个模型通常会定义一长串此类分辨率。定义的分辨率越多,Dateline 控件在缩放时就越灵活。
  • 时区 – Dateline 控件允许用户在不同时区之间切换。模型定义哪些时区可用。
  • 刻度数量 – Dateline 控件由一组 Dateline 刻度组成(顶部、底部以及若干中间刻度)。模型可用于定义用户可选择查看的当前可见刻度数、最小刻度数和最大刻度数。
  • 时间单位 – Dateline 控件在为当前单位创建刻度失败或成功后,会回调模型以查找“下一个”时间单位。

Dateline 模型是一个类型化模型。FlexGanttFX 随附两个特化实现:ChronoUnitDatelineModelSimpleUnitDatelineModel

Chrono Unit Dateline 模型

ChronoUnitDatelineModel 类是针对 JDK 8 中 ChronoUnit 时间单位的特化实现。它需要类型为 ChronoUnitResolution 的刻度分辨率。下面的清单是该模型的实现,并展示了如何定义和添加分辨率,以及如何使用分辨率从一个时间单位前进到下一个时间单位。

package com.flexganttfx.model.dateline;

import java.time.temporal.ChronoUnit;

import static com.flexganttfx.model.dateline.Resolution.Position.*;
import static java.time.temporal.ChronoUnit.*;

/**
 * The chrono unit dateline model is a specialization of the dateline model that works
 * in combination with the {@link ChronoUnit}. The chrono unit basically represents standard
 * calendar units ranging from milliseconds to thousands of years.
 *
 * @since 1.0
 */
public final class ChronoUnitDatelineModel extends DatelineModel<ChronoUnit> {

    /**
     * Constructs a new dateline model with a long list of predefined
     * resolutions of type {@link ChronoUnitResolution}.
     *
     * @since 1.0
     */
    public ChronoUnitDatelineModel() {
        addResolution(new ChronoUnitResolution(MILLIS, "EEEE, dd. MMMM yyyy, HH:mm:ss:SSS", 1, TOP, ONLY));
        addResolution(new ChronoUnitResolution(MILLIS, "EEEE, dd.MM.yy, HH:mm:ss:SSS", 1, TOP, ONLY));
        addResolution(new ChronoUnitResolution(MILLIS, "E, dd.MM.yy, HH:mm:ss:SSS", 1, TOP, ONLY));
        addResolution(new ChronoUnitResolution(MILLIS, "dd.MM.yy, HH:mm:ss:SSS", 1, TOP, ONLY));
        addResolution(new ChronoUnitResolution(MILLIS, "dd.MM, HH:mm:ss:SSS", 1, TOP));
        addResolution(new ChronoUnitResolution(MILLIS, "SSS", 1, BOTTOM));
        addResolution(new ChronoUnitResolution(MILLIS, "SSS", 5, BOTTOM));
        addResolution(new ChronoUnitResolution(MILLIS, "SSS", 10, BOTTOM));
        addResolution(new ChronoUnitResolution(MILLIS, "SSS", 15, BOTTOM));

        addResolution(new ChronoUnitResolution(SECONDS, "EEEE, dd. MMMM yyyy, HH:mm:ss", 1, TOP, ONLY));
        addResolution(new ChronoUnitResolution(SECONDS, "EEEE, dd.MM.yy, HH:mm:ss", 1, TOP, ONLY));
        addResolution(new ChronoUnitResolution(SECONDS, "E, dd.MM.yy, HH:mm:ss", 1, TOP, ONLY));
        addResolution(new ChronoUnitResolution(SECONDS, "dd.MM.yy, HH:mm:ss", 1, TOP, ONLY));
        addResolution(new ChronoUnitResolution(SECONDS, "dd.MM, HH:mm:ss", 1, MIDDLE));
        addResolution(new ChronoUnitResolution(SECONDS, "HH:mm:ss", 1, MIDDLE));
        addResolution(new ChronoUnitResolution(SECONDS, "ss", 1, BOTTOM));
        addResolution(new ChronoUnitResolution(SECONDS, "ss", 5, BOTTOM));
        addResolution(new ChronoUnitResolution(SECONDS, "ss", 10, BOTTOM));
        addResolution(new ChronoUnitResolution(SECONDS, "ss", 15, BOTTOM));

        addResolution(new ChronoUnitResolution(MINUTES, "EEEE, dd. MMMM yyyy, HH:mm", 1, TOP, ONLY));
        addResolution(new ChronoUnitResolution(MINUTES, "EEEE, dd.MM.yy, HH:mm", 1, TOP, ONLY));
        addResolution(new ChronoUnitResolution(MINUTES, "E, dd.MM.yy, HH:mm", 1, TOP, ONLY));
        addResolution(new ChronoUnitResolution(MINUTES, "dd.MM.yy, HH:mm", 1, TOP, ONLY));
        addResolution(new ChronoUnitResolution(MINUTES, "dd.MM, HH:mm", 1, TOP));
        addResolution(new ChronoUnitResolution(MINUTES, "HH:mm", 1, MIDDLE));
        addResolution(new ChronoUnitResolution(MINUTES, "mm", 1, BOTTOM));
        addResolution(new ChronoUnitResolution(MINUTES, "mm", 5, BOTTOM));
        addResolution(new ChronoUnitResolution(MINUTES, "mm", 10, BOTTOM));
        addResolution(new ChronoUnitResolution(MINUTES, "mm", 15, BOTTOM));

        addResolution(new ChronoUnitResolution(HOURS, "EEEE, dd. MMMM yyyy, HH:mm", 1, TOP, ONLY));
        addResolution(new ChronoUnitResolution(HOURS, "EEEE, dd.MM.yy, HH:mm", 1, TOP, BOTTOM, ONLY));
        addResolution(new ChronoUnitResolution(HOURS, "E, dd.MM.yy, HH:mm", 1, TOP, ONLY));
        addResolution(new ChronoUnitResolution(HOURS, "dd.MM.yy, HH:mm", 1, TOP, ONLY));
        addResolution(new ChronoUnitResolution(HOURS, "dd.MM, HH:mm", 1, TOP, ONLY));
        addResolution(new ChronoUnitResolution(HOURS, "H:mm", 1, MIDDLE, BOTTOM));
        addResolution(new ChronoUnitResolution(HOURS, "H:mm", 3, MIDDLE, BOTTOM));
        addResolution(new ChronoUnitResolution(HOURS, "H:mm", 6, MIDDLE, BOTTOM));

        addResolution(new ChronoUnitResolution(DAYS, "EEEE d. MMMM yyyy", 1, TOP, ONLY));
        addResolution(new ChronoUnitResolution(DAYS, "EEEE d. MMMM yy", 1, TOP, ONLY));
        addResolution(new ChronoUnitResolution(DAYS, "E, d. MMMM yy", 1, TOP, ONLY));
        addResolution(new ChronoUnitResolution(DAYS, "E, d. MMMM", 1, TOP, ONLY));
        addResolution(new ChronoUnitResolution(DAYS, "E, dd.MM.yy", 1, TOP, ONLY));
        addResolution(new ChronoUnitResolution(DAYS, "EEEE dd", 1, MIDDLE, BOTTOM));
        addResolution(new ChronoUnitResolution(DAYS, "E dd", 1, MIDDLE, BOTTOM));
        addResolution(new ChronoUnitResolution(DAYS, "dd.MM", 1, MIDDLE, BOTTOM));
        addResolution(new ChronoUnitResolution(DAYS, "dd", 1, BOTTOM));

        addResolution(new ChronoUnitResolution(WEEKS, "'W' w, EEEE d. MMMM yy", 1, TOP, ONLY));
        addResolution(new ChronoUnitResolution(WEEKS, "'W' w, d. MMMM yy", 1, TOP, ONLY));
        addResolution(new ChronoUnitResolution(WEEKS, "'W' w, d. MMMM", 1));
        addResolution(new ChronoUnitResolution(WEEKS, "'W' w, E, dd.MM.yy", 1, TOP, ONLY));
        addResolution(new ChronoUnitResolution(WEEKS, "'W' w, dd.MM.yy", 1, TOP, ONLY));
        addResolution(new ChronoUnitResolution(WEEKS, "'W' w, dd.MM", 1, BOTTOM));
        addResolution(new ChronoUnitResolution(WEEKS, "'W' w", 1, MIDDLE, BOTTOM));

        addResolution(new ChronoUnitResolution(MONTHS, "MMMM yyyy", 1, TOP, ONLY));
        addResolution(new ChronoUnitResolution(MONTHS, "MMMM", 1, MIDDLE, BOTTOM));
        addResolution(new ChronoUnitResolution(MONTHS, "MMM", 1, MIDDLE, BOTTOM));
        addResolution(new ChronoUnitResolution(MONTHS, "M", 1, MIDDLE, BOTTOM));

        addResolution(new ChronoUnitResolution(YEARS, "yyyy", 1));
        addResolution(new ChronoUnitResolution(DECADES, "yyyy", 1));
        addResolution(new ChronoUnitResolution(CENTURIES, "yyyy", 1));
        addResolution(new ChronoUnitResolution(MILLENNIA, "yyyy", 1));
    }

    @Override
    public final ChronoUnit nextTemporalUnit(ChronoUnit unit) {
        switch (unit) {
            case NANOS:
                return MICROS;
            case MICROS:
                return MILLIS;
            case MILLIS:
                return SECONDS;
            case SECONDS:
                return MINUTES;
            case MINUTES:
                return HOURS;
            case HOURS:
                return DAYS;
            case DAYS:
                return WEEKS;
            case WEEKS:
                return MONTHS;
            case MONTHS:
                return YEARS;
            case YEARS:
                return DECADES;
            case DECADES:
                return CENTURIES;
            case CENTURIES:
                return MILLENNIA;
            default:
            /*
             * We are ignoring HALF DAYS.
             */
                return null;
        }
    }
}

Simple Unit Dateline 模型

SimpleUnitDatelineModel 类是针对 FlexGanttFX 随附的 SimpleUnit 时间单位的特化实现。它需要类型为 SimpleUnitResolution 的刻度分辨率。下面的模型类实现展示了为什么该单位被称为“简单”。

package com.flexganttfx.model.dateline;


import com.flexganttfx.model.util.SimpleUnit;

public final class SimpleUnitDatelineModel extends DatelineModel<SimpleUnit> {

    public SimpleUnitDatelineModel() {
        for (SimpleUnit unit : SimpleUnit.values()) {
            addResolution(new SimpleUnitResolution(unit, "", 1));
        }
    }

    @Override
    public SimpleUnit nextTemporalUnit(SimpleUnit unit) {
        int ordinal = unit.ordinal();
        if (ordinal < SimpleUnit.values().length - 1) {
            return SimpleUnit.values()[ordinal + 1];
        }
        return null;
    }
}

时区

Dateline 模型管理一个时区 ID 列表,UI 使用该列表为每个 ID 创建菜单项。这样用户就可以轻松在它们之间切换。默认列表在 DatelineModel 类中按如下方式设置:

/**
 * Constructs a new model and populates the list of available zone IDs.
 */
protected DatelineModel() {
    addZoneId("Europe/Berlin");
    addZoneId("America/New_York");
    addZoneId("Australia/Darwin");
    addZoneId("Australia/Sydney");
    addZoneId("America/Argentina/Buenos_Aires");
    addZoneId("Africa/Cairo");
    addZoneId("America/Anchorage");
    addZoneId("America/Sao_Paulo");
    addZoneId("Asia/Dhaka");
    addZoneId("Africa/Harare");
    addZoneId("America/St_Johns");
    addZoneId("America/Chicago");
    addZoneId("Asia/Shanghai");
    addZoneId("Africa/Addis_Ababa")
    addZoneId("Europe/Paris");
    addZoneId("America/Indiana/Indianapolis");
    addZoneId("Asia/Kolkata");
    addZoneId("Asia/Tokyo");
    addZoneId("Pacific/Apia");
    addZoneId("Asia/Yerevan");
    addZoneId("Pacific/Auckland");
    addZoneId("Asia/Karachi");
    addZoneId("America/Phoenix");
    addZoneId("America/Puerto_Rico");
    addZoneId("America/Los_Angeles");
    addZoneId("Pacific/Guadalcanal");
    addZoneId("Asia/Ho_Chi_Minh");
}