• Najnowsze pytania
  • Bez odpowiedzi
  • Zadaj pytanie
  • Kategorie
  • Tagi
  • Zdobyte punkty
  • Ekipa ninja
  • IRC
  • FAQ
  • Regulamin
  • Książki warte uwagi

Odliczanie w JavieFX - timer

Object Storage Arubacloud
0 głosów
357 wizyt
pytanie zadane 5 sierpnia 2018 w Java przez periedynek Obywatel (1,320 p.)

Cześć. Chciałem stworzyć prosty timer w JavieFX.
Nie wiem czy mój sposób jest dobry.
Tworze sobie formatkę z przyciskiem START/STOP, oraz 3 Labelami: 1) Minuty 2) ":" 3) Sekundy, jakby połączyć te 3 labele wychodzi np: 3:34.


W wyniku czego mogę łatwo pobrać liczbe minut i sekund i przekonwertować to na sekundy. Wyświetlanie za pomocą np. minuty:

labelOfMinutes.setText(amountOfMinutes)

sekundy:

labelOfSeconds.setText(amountOfSeconds)

gdzie int amountOfMinutes = time/60;, amountOfSeconds = time%60.

Nie potrafię wymyślić innego dobrego pomysłu jak rozwiązać to odliczanie w dół.

Może ma ktoś inny pomysł?

1 odpowiedź

0 głosów
odpowiedź 5 sierpnia 2018 przez periedynek Obywatel (1,320 p.)

O czymś takim mówiłem:

 @FXML
    void startTimer() throws InterruptedException {

        int amountOfMinutes = Integer.parseInt(displayMinutes.getText());
        int amountOfSeconds = Integer.parseInt(displaySeconds.getText());

        int time = amountOfMinutes * 60 + amountOfSeconds;
        System.out.println(time);
        for (int i = 0; i < time; time--) {
            displayMinutes.setText(String.valueOf(amountOfMinutes / 60));
            displaySeconds.setText(String.valueOf(amountOfSeconds % 60));
            Thread.sleep(1000);
            displaySeconds.setText("");
            displayMinutes.setText("");
        }
    }

Program się wywala w pętli, nie doszedłem jeszcze do tego dlaczego.

komentarz 5 sierpnia 2018 przez mbabane Szeryf (79,280 p.)
edycja 5 sierpnia 2018 przez mbabane

Pewnie dostajesz błąd, że zatrzymujesz główny wątek JavyFX tak? Musisz zdaje się skorzystać z Tasków:

https://docs.oracle.com/javafx/2/api/javafx/concurrent/Task.html

Albo jeszcze jest taka klasa Timelinedzięki której tworzy się sekwencje zdarzeń następujących po sobie, z danym opóźnieniem. Dodaje się klatki (obiekty typy KeyFrame), które mogą być dowolnym zdarzeniem z danym opóźnieniem. Następnie, wywoluje się  tylko metodę play, i cala sekwencja się wykonuje - bez żadnego blokowania innych elementów GUI. Dzięki Timeline nie trzeba bawić się w wątki.

komentarz 5 sierpnia 2018 przez periedynek Obywatel (1,320 p.)

hej, o coś takiego chodziło?

@FXML
    void startTimer() throws InterruptedException {

        int amountOfMinutes = Integer.parseInt(displayMinutes.getText());
        int amountOfSeconds = Integer.parseInt(displaySeconds.getText());

        int time = amountOfMinutes * 60 + amountOfSeconds;
        final KeyFrame keyFrame = new KeyFrame(Duration.seconds(1), e -> showCountDown(time, amountOfMinutes, amountOfSeconds));
        final Timeline timeline = new Timeline(keyFrame);
        Platform.runLater(timeline::play);
    }

Metoda showCountDown

public void showCountDown(int time, int amountOfMinutes, int amountOfSeconds) {
        for (int i = 0; i < time; time--) {

            displayMinutes.setText(String.valueOf(amountOfMinutes / 60));
            displaySeconds.setText(String.valueOf(amountOfSeconds % 60));
            // Thread.sleep(1000);
            displaySeconds.setText("");
            displayMinutes.setText("");
        }
    }

Niby wszystko działa, ale nie do końca. Program się już nie wywala, ale gdy dochodzi do momentu: 

displaySeconds.setText("");
displayMinutes.setText("");

To tak jakby już się zatrzymuje zmienna time z tego co widze spada o jeden w dół 140,139,138.... a środek pętli się nie wykonuje.

///edit 

W sensie się wykonuje, ale 

displayMinutes.setText(String.valueOf(amountOfMinutes / 60));
            displaySeconds.setText(String.valueOf(amountOfSeconds % 60));

juz nie dziala

komentarz 5 sierpnia 2018 przez mbabane Szeryf (79,280 p.)
edycja 6 sierpnia 2018 przez mbabane

Masz sam przykład jak z tego korzystać. Jutro postaram się objaśnić, jeśli nie uda Ci się samemu dojść jak to działa.

    public void buttonOnAction()
    {
        Timeline timeline = new Timeline();

        long nextFrameTime = 0;

        for(int i = 0; i < 5; i++)
        {
            long time= nextFrameTime;
            timeline.getKeyFrames().add( new KeyFrame(
                    Duration.millis(nextFrameTime),
                    action -> label.setText( Long.toString(time) )
                    )
            );
            nextFrameTime += 1000;
        }

        timeline.setCycleCount(1);
        timeline.play();
    }
komentarz 6 sierpnia 2018 przez periedynek Obywatel (1,320 p.)

Twoje rozwiązanie wydaje mi się bardzo podobne do mojego.

Nie rozumiem tych rzeczy: 

Duration.millis(nextFrameTime) - dlaczego nie ustalisz od razu Duration.seconds(1)? Zrobiłeś coś takiego: nextFrameTime += 1000, więc w pewnym momencie czasu pętla zatrzyma się na 4 sekundy(?)

timeline.setCycleCount(1) - nie moge dojść do tego co to robi.


Long time= nextFrameTime; - przypisałeś do czasu obojętne co, żeby było czy konkretnie to musi byc nextFrameTime?

1
komentarz 6 sierpnia 2018 przez mbabane Szeryf (79,280 p.)
edycja 6 sierpnia 2018 przez mbabane

Klasa Timeline działa na zasadzie osi czasu (w końcu Timeline) - tak jak w Sony Vegasie układasz kawałki filmików na tej poziomej osi (bądź w jakimś programie muzycznym), tylko tutaj tymi kawałkami będą jakieś zaimplementowane zdarzenia.

Gdybyś ustawił czas na Duration.seconds(1) (secodns, czy millis to już bez różnicy) to wtedy wszystkie dodane zdarzenia wykonają się po upływie 1 sekundy (na raz) - klikasz upływa sekunda, uruchamia się zdarzenie. Trzeba więc ustawiać, które zdarzenie ma się uruchomić w danej sekundzie, bo inaczej uruchomią się wszystkie na raz . Tak jak w Sony Vegasie ustawiasz klipy jeden po drugim w poziomie, aby odtwarzały się jeden po drugim (pierwszy się kończy drugi się zaczyna). Gdybyś ustawił klipy w jednej pionowej, równoległej  linii to odtworzyły by się wszystkie na raz w tym samym czasie - to się dzieje gdy w Duration jest stała. W linii 7 (petla for) jest własnie wstawienie klatek na oś czasu, tak żeby uruchamiała się jedna po drugiej, w odstępie jednej sekundy.

W tabeli zamieściłem przepływ zdarzeń, po uruchomieniu:

nextFrameTime 0 1000 2000 3000
time 0 1000 2000 3000
label 0 1000 2000 3000

Long time= nextFrameTime;  To jest dlatego, że używam lambdy w KeyFrame (linia 12), a każda zmienna, która "wchodzi" do lambdy staje się niejawnie finalną więc nie można by inkrementować nextFrameTIme.

timeline.setCycleCount(1) - zmień na 2 i zobacz co się stanie.

 

Inaczej jeszcze mówiąc Timeline ma w sobie pętle. Ty podajesz tylko co ma się wykonać i kiedy (z jakim opóźnieniem).

komentarz 6 sierpnia 2018 przez periedynek Obywatel (1,320 p.)

Okej. Rozumiem wszystko, prócz tego: Long time= nextFrameTime. Z tego względu, że dla mnie zmienna time przechowuje ilość sekund, czyli ilość obrotu pętli

int time = amountOfMinutes * 60 + amountOfSeconds;

Później robiłem 

displayMinutes.setText(String.valueOf(time/60));

displaySeconds.setText(String.valueOf(time%60));

I w taki sposób mi się to wszystko wyświetlało.

Napisałem coś takiego:

public void showCountDown(int time) {
    int nextFrameTime = 0;

    for (int i = 0; i < time; time--) {
        int finalTime = time;

        timeline.getKeyFrames().add(new KeyFrame(
                        Duration.millis(nextFrameTime),
                        action -> displayMinutes.setText(String.valueOf(finalTime / 60))
                )
        );
        timeline.getKeyFrames().add(new KeyFrame(
                        Duration.millis(nextFrameTime),
                        action -> displaySeconds.setText(String.valueOf(finalTime % 60))
                )
        );
        nextFrameTime += 1000;

        displaySeconds.setText("");
        displayMinutes.setText("");
    }
    timeline.setCycleCount(1);
    timeline.play();
}

I faktycznie działa, coś powinienem poprawić?

1
komentarz 6 sierpnia 2018 przez mbabane Szeryf (79,280 p.)

Można zdaje się uprościć dodawanie klatek do jednej operacji, oraz lepiej zmień nazwy displayMinutes displaySeconds ponieważ nie jest dobrze jeśli zmienne zawierają czasownik. Czasownik stosuje się do metod, ponieważ one zasadniczo coś wykonują, natomiast zmienna jest tylko pewnym narzędziem. Co do zmiennej time (tej mojej) po prostu zmień nazwę jeśli Ci nie odpowiada - nikt się nie pogniewa ;). I zdaje się, że linie:

displaySeconds.setText("");
displayMinutes.setText("");

nie są potrzebne.

Całość po ewentualnych zmianach:

    public void showCountDown(int time) {
        Timeline timeline = new Timeline();

        int nextFrameTime = 0;

        for (int i = 0; i < time; time--) {
            int finalTime = time;

            timeline.getKeyFrames().add(new KeyFrame(
                            Duration.millis(nextFrameTime),
                            action -> 
                            {
                                minutesLabel.setText(String.valueOf(finalTime / 60));
                                secondsLabel.setText(String.valueOf(finalTime % 60));
                            }
                    )
            );
            nextFrameTime += 1000;
        }
        timeline.setCycleCount(1);
        timeline.play();
    }

 

komentarz 6 sierpnia 2018 przez periedynek Obywatel (1,320 p.)
Okej, załapałem wszystko.

Dzięki wielkie. Szkoda, ze pisałeś w kometarzach. Nie mam możliwosci dać najlepszej odpowiedzi.
komentarz 7 sierpnia 2018 przez periedynek Obywatel (1,320 p.)

Ostatnie pytanie, jak mogę zapobiec temu, że jak ktoś wciśnie "START", to jeżeli wciśnie ponownie start to nic się nie zrobi, tylko czas będzie lecieć dalej?

Chciałem to zrobić za pomocą warunku:

if (minutesLabel.getText().equals("25") && secondsLabel.getText().equals("00")) ||(minutesLabel.getText().equals("00") && secondsLabel.getText().equals("00")) 

to wtedy się odpala. Nie wziąłem jednak pod uwagę tego, że jeżeli ktos wciśnie stop, czyli:

timeline.stop();

to gdy ponownie wciśnie start, aplikacja nie ruszy z odliczaniem ze względu na warunek powyżej.

Pytam przez to, ponieważ jeżeli klikne start, zacznie się odliczanie, klikne znów i jeszcze raz, to czas odliczania spada o jakies tam sekundy.

komentarz 7 sierpnia 2018 przez mbabane Szeryf (79,280 p.)
edycja 7 sierpnia 2018 przez mbabane

Można to zrobić np. wyłączając przycisk przed rozpoczęciem odliczania, a jako ostatnią klatkę w timeline ustawić dodatkowo, żeby przycisk się włączał - ewentualnie jakaś flaga i całą metodę objąć ifem (przy czym też chyba trzeba będzie ustawiać jako ostatnią klatkę zmianę flagi).

Istnieje jeszcze taka możliwość, chyba najbardziej czytelna:



button.setDisable(true);

//petla for z ustawianiem klatek


timeline.setOnFinish( action -> button.setDisable(false) );
//powyzsza linia musi być po petli for

timeline.setCycleCount(1);
timeline.play();

 

komentarz 7 sierpnia 2018 przez periedynek Obywatel (1,320 p.)

Tak, trzeba było pododawać jeszcze do metod odpowiedzialnych za przyciski reset i stop 

start.setDisable(false);

Finalnie działa.

Dzięki.

Podobne pytania

0 głosów
1 odpowiedź 125 wizyt
pytanie zadane 19 czerwca 2018 w Java przez blofeld Użytkownik (700 p.)
0 głosów
1 odpowiedź 190 wizyt
pytanie zadane 29 listopada 2017 w Nasze projekty przez Kapi2222 Obywatel (1,220 p.)
+1 głos
1 odpowiedź 83 wizyt
pytanie zadane 26 lutego 2017 w Java przez matir85 Bywalec (2,410 p.)

92,580 zapytań

141,432 odpowiedzi

319,664 komentarzy

61,965 pasjonatów

Motyw:

Akcja Pajacyk

Pajacyk od wielu lat dożywia dzieci. Pomóż klikając w zielony brzuszek na stronie. Dziękujemy! ♡

Oto polecana książka warta uwagi.
Pełną listę książek znajdziesz tutaj.

Akademia Sekuraka

Kolejna edycja największej imprezy hakerskiej w Polsce, czyli Mega Sekurak Hacking Party odbędzie się już 20 maja 2024r. Z tej okazji mamy dla Was kod: pasjamshp - jeżeli wpiszecie go w koszyku, to wówczas otrzymacie 40% zniżki na bilet w wersji standard!

Więcej informacji na temat imprezy znajdziecie tutaj. Dziękujemy ekipie Sekuraka za taką fajną zniżkę dla wszystkich Pasjonatów!

Akademia Sekuraka

Niedawno wystartował dodruk tej świetnej, rozchwytywanej książki (około 940 stron). Mamy dla Was kod: pasja (wpiszcie go w koszyku), dzięki któremu otrzymujemy 10% zniżki - dziękujemy zaprzyjaźnionej ekipie Sekuraka za taki bonus dla Pasjonatów! Książka to pierwszy tom z serii o ITsec, który łagodnie wprowadzi w świat bezpieczeństwa IT każdą osobę - warto, polecamy!

...