Ich spüre, wie die Zeit mit Emacs-Qiita vergeht Ich fand die Idee sehr gut, aber leider war ich kein Emacs-Benutzer Ich konnte den Fluss von nicht fühlen.
Um den Lauf der Zeit zu spüren, habe ich mit dem bekannten Java eine Desktop-Zubehör-ähnliche Anwendung erstellt. Ich habe vom ursprünglichen Autor, @zk_phi, die Erlaubnis erhalten, es zu veröffentlichen, daher möchte ich es hier vorstellen.
Sie können die vorgefertigte JAR-Datei (littlesky.jar
) von hier herunterladen.
Der Quellcode wird auf GitHub> https://github.com/opengl-8080/little-sky veröffentlicht
OS Wir haben bestätigt, dass es unter Windows 10 funktioniert.
Ich habe keine Mac-Umgebung zur Hand, daher bin ich mir nicht sicher, ob sie auf einem Mac funktioniert. Ich werde nichts schreiben, was vom Betriebssystem abhängt, aber wenn es auf Ihrem Mac nicht funktioniert, stellen Sie bitte eine Pull-Anfrage.
JRE Es sollte unter Java 8u40 und höher funktionieren (da Sie [Dialog] verwenden (https://docs.oracle.com/javase/jp/8/javafx/api/javafx/scene/control/Dialog.html)). .. Ich habe gesehen, dass es auch unter Java 9 funktioniert.
$ java -jar littlesky.jar
Die Konfigurationsdatei wird zur Laufzeit im aktuellen Verzeichnis gespeichert (littlesky.xml
).
Der zweite und nachfolgende Start bezieht sich auf die Einstellungsdatei im aktuellen Verzeichnis. Wenn Sie den Startort ändern möchten, verschieben Sie auch die Konfigurationsdatei.
Beim ersten Start wird ein Dialog zur Eingabe von Standortinformationen angezeigt.
Geben Sie den Breitengrad (Breitengrad
) und den Längengrad ( Längengrad
) ein.
(Wird verwendet, um Sonnenaufgangs- / Sonnenuntergangszeiten und Wetterinformationen zu erhalten.)
Wie beim Original werden das Himmelssymbol (leeres Symbol) sowie Zeit- und Temperaturinformationen angezeigt. Das Himmelssymbol zeigt die Mondphase, wenn es sonnig ist, und das Wetter, wenn es regnet oder schneit.
Die Hintergrundfarbe ändert sich mit der Zeit.
Klicken Sie mit der rechten Maustaste auf die Uhrzeit, um das Kontextmenü anzuzeigen. Sie können die Anwendung schließen und die Einstellungen ändern.
Wie beim Original können Sie die Wetterfunktion mit OpenWeatherMap verwenden.
Holen Sie sich den API-Schlüssel von OpenWeatherMap.
Den API-Schlüssel erhalten Sie, indem Sie ein Konto bei OpenWeatherMap registrieren. Wenn Sie nach "Open Weather Map API-Schlüssel" suchen, finden Sie verschiedene Informationen.
Wählen Sie im Kontextmenü Optionen, um ein Formular zur Eingabe von API-Schlüsseln anzuzeigen.
Geben Sie den erhaltenen API-Schlüssel in [API-Schlüssel] ein und klicken Sie auf die Schaltfläche [Speichern], um die Einstellungen zu speichern.
Nach Eingabe des API-Schlüssels können Sie im Kontextmenü Wetterdienst> Start auswählen. Wählen Sie diese Option, um die Wetterfunktion zu aktivieren.
Einmal alle 15 Minuten werden die Wetter-, Wolkenvolumen- und Temperaturinformationen basierend auf den eingestellten Positionsinformationen erfasst und in der Anzeige angezeigt.
Sobald Sie den API-Schlüssel festgelegt haben, wird die Wetterfunktion beim nächsten Start der Anwendung automatisch gestartet.
Wenn Sie die Wetterfunktion beenden möchten, wählen Sie im Kontextmenü Wetterdienst> Stopp oder leeren Sie den API-Schlüssel und speichern Sie ihn.
In einer Umgebung, in der ein Proxy für den Zugriff auf das Internet erforderlich ist, muss der Proxy festgelegt werden.
Öffnen Sie den Einstellungsdialog mit Optionen im Kontextmenü.
Geben Sie unter HTTP-Proxy die Proxy-Einstellungen ein.
Wenn kein Port eingegeben wird, wird standardmäßig "80" verwendet. Geben Sie den Benutzernamen und das Kennwort nur ein, wenn für den Proxy eine Authentifizierung erforderlich ist.
littkesky.xml
) im Klartext gespeichert ist.Sie können die Anzeigeeinstellungen unter [Ansicht] im Kontextmenü ändern.
Wenn Sie [Immer oben] aktivieren, befindet sich das Fenster immer im Vordergrund des Desktops. Die Standardeinstellung ist deaktiviert.
Durch Umschalten der Option [Sekunden anzeigen] können Sie die Zeitsekunden ein- / ausblenden. Der Standardwert ist aktiviert.
Durch Umschalten der Prüfung [Temperatur anzeigen] können Sie die Temperatur ein- oder ausblenden. Der Standardwert ist aktiviert.
Durch Umschalten der Option [Himmelsstatussymbol anzeigen] können Sie die Anzeige / Nichtanzeige des Himmelssymbols (monatliche Phase / Wettersymbol) umschalten. Der Standardwert ist aktiviert.
Ich wurde von Bukome darauf hingewiesen und fand es etwas schlecht. Es tut mir leid, dass ich es später hinzufügen muss, aber ich möchte zusammenfassen, was ich bei der Erstellung dieser Anwendung gelernt habe.
package sample.javafx;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
public class NoFrameWindow extends Application {
public static void main(String[] args) {
launch(NoFrameWindow.class, args);
}
@Override
public void start(Stage primaryStage) throws Exception {
Pane pane = new Pane();
pane.setPrefWidth(200);
pane.setPrefHeight(100);
pane.setStyle("-fx-background-radius: 50; -fx-background-color: yellow;");
Scene scene = new Scene(pane);
scene.setFill(Color.TRANSPARENT);
primaryStage.initStyle(StageStyle.TRANSPARENT);
primaryStage.setScene(scene);
primaryStage.show();
}
}
** Ausführungsergebnis **
Erläuterung
Color.TRANSPARENT
mit setFill ()
, um Scene
transparent zu machenStellen Sie den Hintergrund programmgesteuert ein
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.CornerRadii;
import javafx.scene.paint.Color;
...
BackgroundFill backgroundFill = new BackgroundFill(Color.YELLOW, new CornerRadii(50), null);
Background background = new Background(backgroundFill);
pane.setBackground(background);
Wenn Sie den Fensterrahmen entfernen, können Sie das Fenster nicht so ziehen, wie es ist. Diese Implementierung erfordert, dass Sie Ihr Bestes selbst geben.
Bei der Suche nach Stackoverflow gibt es einige ähnliche Fragen, und die Implementierungsmethode wird ebenfalls eingeführt, sodass ich sie so verwenden werde, wie sie ist.
Moving an undecorated stage in javafx 2 - Stack Overflow
package sample.javafx;
...
public class NoFrameWindow extends Application {
...
private double mouseX;
private double mouseY;
@Override
public void start(Stage primaryStage) throws Exception {
Pane pane = new Pane();
...
pane.setOnMousePressed(e -> {
this.mouseX = primaryStage.getX() - e.getScreenX();
this.mouseY = primaryStage.getY() - e.getScreenY();
});
pane.setOnMouseDragged(e -> {
primaryStage.setX(e.getScreenX() + this.mouseX);
primaryStage.setY(e.getScreenY() + this.mouseY);
});
...
}
}
** Ausführungsergebnis **
Für die Hintergrundfarbe des Himmels wird die Hintergrundfarbe nur für einige Schlüsselzeiten festgelegt (unter Bezugnahme auf die ursprüngliche Implementierungsmethode). Die Farbe zwischen einer bestimmten Schlüsselzeit A und der Schlüsselzeit B interpoliert die Farbe zwischen den beiden Farben A und B.
Die Farbinterpolation zwischen zwei Farben wird direkt auf die Klasse "Farbe" angewendet, bei der es sich um die JavaFX-Farbklasse handelt, da es sich um [interpolieren (Farbe, doppelt)] handelt (https://docs.oracle.com/javase/jp/8/javafx/). Es gab eine Methode namens api / javafx / scene / paint / Color.html # interpolate-javafx.scene.paint.Color-double-) und ich habe sie verwendet.
interpolate ()
berechnet die Farbe an der Position des durch das zweite Argument angegebenen Verhältnisses (Wert von 0.0
zu 1.0
) aus der Farbe, die zum Empfänger wird, zu der durch das erste Argument angegebenen Farbe. Rückkehr.
Zum Beispiel berechnet "color1.interporate (color2, 0.5)" einen Wert, der genau zwischen "color1" und "color2" ("0.5") liegt, und gibt ihn zurück.
Das Verhältnis basiert auf der für jeden Schlüssel festgelegten Zeit und der aktuellen Uhrzeit [Dauer] der Datums- und Zeit-API (https://docs.oracle.com/javase/jp/8/docs/api/java/time/Duration.html). Es wurde berechnet mit. Die spezifische Implementierung befindet sich in der Klasse SkyColorGradation.
Kürzlich [habe ich JavaFX intensiv studiert](https://qiita.com/opengl-8080/items/51bef25aa05ecd929a3d#javafx-%E3%83%97%E3%83%AD%E3%83%91%E3%83% 86% E3% 82% A3) In dieser Implementierung habe ich die damals erlernten JavaFX-Eigenschaften aktiv genutzt.
Unter Ausnutzung der Tatsache, dass das Beobachtermuster einfach mithilfe der JavaFX-Eigenschaft implementiert werden kann, sieht die Gesamtstruktur wie ↓ aus.
Zum Beispiel ist die Zeitanzeige wie folgt.
model
ClockBase.java
package littlesky.model.clock;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import java.time.LocalDate;
import java.time.LocalTime;
public class ClockBase implements Clock {
protected final ReadOnlyObjectWrapper<LocalDate> date = new ReadOnlyObjectWrapper<>();
protected final ReadOnlyObjectWrapper<LocalTime> time = new ReadOnlyObjectWrapper<>();
@Override
public LocalDate getDate() {
return this.date.get();
}
@Override
public LocalTime getTime() {
return this.time.get();
}
@Override
public ReadOnlyObjectProperty<LocalDate> dateProperty() {
return this.date.getReadOnlyProperty();
}
@Override
public ReadOnlyObjectProperty<LocalTime> timeProperty() {
return this.time.getReadOnlyProperty();
}
}
ReadTimeClock.java
package littlesky.model.clock;
import javafx.application.Platform;
import java.time.LocalDateTime;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class RealTimeClock extends ClockBase {
public RealTimeClock() {
this.updateDateTime();
}
public void start() {
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor((runnable) -> {
Thread thread = new Thread(runnable);
thread.setDaemon(true);
return thread;
});
executor.scheduleAtFixedRate(() -> {
Platform.runLater(this::updateDateTime);
}, 0, 500, TimeUnit.MILLISECONDS);
}
private void updateDateTime() {
LocalDateTime now = LocalDateTime.now();
if (this.getDate() == null || !this.getDate().equals(now.toLocalDate())) {
this.date.set(now.toLocalDate());
}
this.time.set(now.toLocalTime());
}
}
Diese Klasse zählt die aktuelle Zeit. Es erzeugt einen vom UI-Thread getrennten Thread und zählt die aktuelle Zeit in Intervallen von 500 Millisekunden.
Für die aktuelle Uhrzeit werden das Datum ("Datum") und die Uhrzeit ("Uhrzeit") mit "ReadOnlyObjectProperty" nach außen offengelegt.
ClockBase.java
protected final ReadOnlyObjectWrapper<LocalDate> date = new ReadOnlyObjectWrapper<>();
protected final ReadOnlyObjectWrapper<LocalTime> time = new ReadOnlyObjectWrapper<>();
...
@Override
public ReadOnlyObjectProperty<LocalDate> dateProperty() {
return this.date.getReadOnlyProperty();
}
@Override
public ReadOnlyObjectProperty<LocalTime> timeProperty() {
return this.time.getReadOnlyProperty();
}
ReadOnlyObjectWrapper
ist eine Klasse, mit der schreibgeschützte Eigenschaften für die Außenwelt verfügbar gemacht werden können.
Sie können dies verwenden, um zu verhindern, dass der Wert von außerhalb des Modells neu geschrieben wird.
Beachten Sie, dass der Wert in einem vom UI-Thread getrennten Thread aktualisiert wird. Sie müssen daher immer "Platform.runLater ()" verwenden, um den Eigenschaftswert aus dem UI-Thread zu aktualisieren. .. Wenn Sie den Wert mit "runLater ()" im Modell aktualisieren, kann die Ansichtsseite die Eigenschaft mit Vertrauen verwenden.
view
TimeLabelViewModel.java
package littlesky.view.main;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.ReadOnlyStringWrapper;
...
import littlesky.model.clock.Clock;
import littlesky.model.option.ViewOptions;
...
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import static littlesky.util.BindingBuilder.*;
public class TimeLabelViewModel {
private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss");
private static final DateTimeFormatter formatterWithoutSeconds = DateTimeFormatter.ofPattern("HH:mm");
private final ReadOnlyStringWrapper text = new ReadOnlyStringWrapper("00:00:00");
...
public void bind(Clock clock, SkyColor skyColor, ViewOptions viewOptions) {
this.text.bind(
binding(clock.timeProperty(), viewOptions.showSecondsProperty())
.computeValue(() -> this.formatClockTime(clock, viewOptions))
);
...
}
private String formatClockTime(Clock clock, ViewOptions viewOptions) {
LocalTime time = clock.getTime();
return time.format(viewOptions.isShowSeconds() ? formatter : formatterWithoutSeconds);
}
...
public ReadOnlyStringProperty textProperty() {
return this.text.getReadOnlyProperty();
}
...
}
TimeLabelViewModel
ist eine Klasse, die die Zeitanzeige steuert.
In der Methode "bind ()" definieren wir den Wert des Textes (Eigenschaft "text"), der auf der Zeitbeschriftung angezeigt werden soll, abhängig von "Clock" und "ViewOptions".
public void bind(Clock clock, SkyColor skyColor, ViewOptions viewOptions) {
this.text.bind(
binding(clock.timeProperty(), viewOptions.showSecondsProperty())
.computeValue(() -> this.formatClockTime(clock, viewOptions))
);
...
}
--binding (...). ComputeValue (...)
ist ein Builder, mit dem Sie Binding
-Instanzen mit weniger Beschreibung schreiben können. Sie erstellen lediglich eine anonyme Klasse für ObjectBinding
. ist.
time
oder showSeconds
ändert, wird die MethodeformatClockTime ()
ausgeführt und der Wert der Eigenschaft text
aktualisiert. private String formatClockTime(Clock clock, ViewOptions viewOptions) {
LocalTime time = clock.getTime();
return time.format(viewOptions.isShowSeconds() ? formatter : formatterWithoutSeconds);
}
--formatClockTime ()
bezieht sich auf die aktualisierte Zeit
, showSeconds
, um den Zeittext zu generieren, der auf dem Bildschirm angezeigt werden soll.
private final ReadOnlyStringWrapper text = new ReadOnlyStringWrapper("00:00:00");
...
public ReadOnlyStringProperty textProperty() {
return this.text.getReadOnlyProperty();
}
--Die exponierte Eigenschaft "Text" ist dem Element auf dem Bildschirm in der Steuerung zugeordnet.
controller
MainController.java
package littlesky.controller.main;
...
public class MainController implements Initializable {
...
@FXML
private Label timeLabel;
...
@Override
public void initialize(URL url, ResourceBundle resources) {
...
this.replaceClockAndWeather(this.realTimeClock, this.openWeatherMap);
}
...
private void replaceClockAndWeather(Clock newClock, Weather weather) {
...
TimeLabelViewModel timeLabelViewModel = new TimeLabelViewModel();
timeLabelViewModel.bind(newClock, skyColor, this.options.getViewOptions());
this.timeLabel.textProperty().bind(timeLabelViewModel.textProperty());
...
}
...
}
TimeLabelViewModel
erkennt es und aktualisiert den angezeigten Text unter Berücksichtigung der Anzeigeeinstellungen zu diesem Zeitpunkt.Dies ist nicht die einzig richtige Antwort, da es sich um eine Methode handelt, zu der wir bei der Implementierung durch Ausprobieren gelangt sind. Ich bin jedoch der Meinung, dass diese Methode zur Verwendung des Überwachungsmechanismus für JavaFX-Eigenschaften als Methode zur Weitergabe von Modelländerungen an die Ansicht in der Richtung nicht falsch ist.
** Was ich für gut hielt **
――Ich habe das Gefühl, dass die Rollen jeder Klasse klar voneinander getrennt sind. --Modell aktualisiert die Hauptlogik und -werte --View bestimmt anhand der Modellwerte, was angezeigt werden soll --Control ist eine Brücke zwischen Ansicht und Modell ――Ich bin der Meinung, dass die Zusammenarbeit dieser Klassen durch den Mechanismus der Überwachung der JavaFX-Eigenschaften einfach gehalten wird.
** Punkte, die ich verbessern möchte **
In Bezug auf die Berechnung der Sonnenauf- und -untergangszeiten suchte ich in Java nach einer Bibliothek und fand die folgenden beiden.
Ersteres berechnet die Zeit von Sonnenaufgang und Sonnenuntergang, und letzteres berechnet auch den Sonnenstand, den Mondstand und die Mondphase.
Ersteres scheint seit mehreren Jahren nicht mehr gepflegt worden zu sein, und letzteres scheint angesichts dieser Verwendung besser zu sein.
Als ich es tatsächlich benutzte, wurden sowohl die Sonnenaufgangs- als auch die Sonnenuntergangszeiten mit angemessener Genauigkeit berechnet (einige Minuten Fehler), aber die Berechnung der Mondphase durch die letztere Bibliothek unterschied sich erheblich von der tatsächlichen. Es war.
Am Ende entschied ich mich, die ehemalige Bibliothek zu verwenden, um den Sonnenaufgang und den Sonnenuntergang zu berechnen.
SunriseSunsetTime
package littlesky.model.sun;
import com.luckycatlabs.sunrisesunset.SunriseSunsetCalculator;
import com.luckycatlabs.sunrisesunset.dto.Location;
import littlesky.model.location.UserLocation;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
public class SunriseSunsetTime {
private final LocalDate localDate;
private final SunriseSunsetCalculator calculator;
private final TimeZone timeZone = TimeZone.getDefault();
public SunriseSunsetTime(UserLocation userLocation, LocalDate localDate) {
this.localDate = localDate;
Location location = new Location(userLocation.getLatitude(), userLocation.getLongitude());
this.calculator = new SunriseSunsetCalculator(location, this.timeZone);
}
public LocalTime sunriseTime() {
Calendar today = this.toCalendar(this.localDate);
Calendar sunriseTime = this.calculator.getOfficialSunriseCalendarForDate(today);
return this.toLocalTime(sunriseTime);
}
public LocalTime sunsetTime() {
Calendar today = this.toCalendar(this.localDate);
Calendar sunsetTime = this.calculator.getOfficialSunsetCalendarForDate(today);
return this.toLocalTime(sunsetTime);
}
private Calendar toCalendar(LocalDate localDate) {
Date date = Date.from(localDate.atStartOfDay(this.timeZone.toZoneId()).toInstant());
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
return calendar;
}
private LocalTime toLocalTime(Calendar calendar) {
return calendar.getTime().toInstant().atZone(this.timeZone.toZoneId()).toLocalTime();
}
}
Das von der Bibliothek benötigte Datumsobjekt ist vom Typ "Kalender", der möglicherweise mehrere Jahre lang nicht gepflegt wurde. Da dies Java 8 ist, musste es in die Date & Time-API konvertiert und mit dieser verwendet werden.
Was geschah dann mit der Berechnung der Mondphase? Ich konnte die Bibliothek nicht finden und beschloss, sie selbst zu berechnen.
Es ist jedoch nicht möglich, die Flugbahn zu berechnen
In Bezug auf dieses Gebiet habe ich ein Programm geschrieben, um das Alter des Mondes mit angemessener Genauigkeit zu ermitteln.
Insbesondere wird ab dem Alter vom 1. Januar 1999 (13.17) die Anzahl der Tage bis zum aktuellen Datum berechnet und das Alter des in diesem Zeitraum fortschreitenden Mondes unter Berücksichtigung des Metonenzyklus berechnet.
Die Implementierung erfolgt in MoonPhase.
Recommended Posts