ProgressDialog, wyświetlanie paska postępu w oknie dialogowym
Lipiec 28, 2011 Okna dialogowe Tutoriale UI
Szczególnym przypadkiem okna dialogowego w Androidzie jest ProgressDialog. Obiektem tej klasy jest bowiem okienko informujące o postępie wykonywanej czynności (lub po prostu o tym, że coś się dzieje gdzieś „w tle”). Jego zastosowanie w interfejsie każdej aplikacji jest niezastąpione bowiem kto z nas lubi zastanawiać się czy nasza aplikacja jeszcze coś robi, czy już się „zwiesiła”?
Dzisiejszy artykuł będzie demonstracją wykorzystania okna dialogowego wyświetlającego pasek postępu pracy nowego wątku naszej aplikacji.
O szczegółach technicznych
W skład naszej aplikacji wejdą 3 ważne obiekty (klasy dwóch ostatnich stworzone będą przez nas):
- ProgressDialog – okno dialogowe z paskiem postępu,
- ProgressThread – wątek wykonujący jakąś czynność w tle,
- ProgressHandler – obiekt, który służy do komunikacji pomiędzy dwoma powyższymi.
I teraz pytanie – w jakim celu wykorzystywany jest ProgressHandler? Czy nie lepiej byłoby modyfikować ProgressDialog bezpośrednio z poziomu wątku ProgressThread? Nie.
Jak się bowiem okazuje, wszelkie obiekty interfejsu użytkownika działają w tzw. UI thread, czyli głównym wątku naszej aplikacji. Nie jest on thread-safe, w związku z czym nie powinniśmy przeprowadzać w nim żadnych asynchronicznych operacji. I w związku z tym nie jest możliwe modyfikowanie elementów UI z poziomu wątku innego niż wątek główny.
Aby umożliwić takie modyfikacje (np. aktualizacje postępu w naszym pasku) powinniśmy wykorzystywać obiekt Handler, który nasłuchuje powiadomień przychodzących z osobnego wątku i na ich podstawie modyfikuje elementy interfejsu (bo działa w wątku głównym).
Przykładowa aplikacja
Aby uniknąć nadmiaru tłumaczenia zakładam, że przed przystąpieniem do tego artykułu czytelnik zapoznał się z tematem wprowadzającym do okien dialogowych – link.
Przygotowania
Nasza aplikacja będzie składała się z 1 przycisku, który po kliknięciu uruchomi wątek wykonujący jakąś czynność w tle. Informacje o postępie tej czynności mają być wyświetlane w naszym oknie dialogowym.
Kod źródłowy głównej Aktywności
Zaczynamy od:
public class MainActivity extends Activity { private static final int PROGRESS_DIALOG = 1; private Button btnStart; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); initButton(); } private void initButton() { btnStart = (Button) findViewById(R.id.btnStart); btnStart.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { startWorkerThread(); } }); } }
Implementacją zaznaczonej metody zajmiemy się za chwilę. Najpierw dodamy pola naszych trzech niezbędnych obiektów, o którym wspominałem powyżej:
public class MainActivity extends Activity { private static final int PROGRESS_DIALOG = 1; private Button btnStart; private ProgressDialog progressDialog; private ProgressThread progressThread; final private Handler progressHandler = new Handler() { public void handleMessage(Message msg) { int progress = msg.arg1; int state = msg.arg2; progressDialog.setProgress(progress); if(ProgressThread.STATE_COMPLETE == state) { dismissDialog(PROGRESS_DIALOG); removeDialog(PROGRESS_DIALOG); } }; }; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); initButton(); } private void initButton() { btnStart = (Button) findViewById(R.id.btnStart); btnStart.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { startWorkerThread(); } }); } }
W tym momencie interesuje nas przede wszystkim obiekt naszego Handlera. Stworzyliśmy tu bowiem klasę anonimową, która obsługuje w zdefiniowany przez nas sposób wiadomości przychodzące z osobnego wątku.
Klasa Message
Nim omówimy naszą implementację, zatrzymamy się na chwilę by przyjrzeć się możliwościom jakie daje nam klasa Message. Posiada ona 3 istotne pola:
- int arg1,
- int arg2,
- Object obj
W wielu przypadka bowiem wiadomości przesyłane do Handlera zawierają niewielką ilość informacji. W takich wypadkach powinniśmy wykorzystać powyższe pola do ich przenoszenia (oczywiście jeżeli tylko jest to możliwe).
W przypadku, gdy potrzebujemy przesłać większą ilość informacji, wystarczy do naszej wiadomości dołączyć obiekt Bundle z wszystkimi danymi.
Po więcej danych na temat klasy Message odsyłam do dokumentacji.
Wracając do naszej implementacji… Obiekt Message, który odbiera nasz Handler będzie zawierał dwie informacje. Postęp (liczba całkowita 0-100) oraz stan w jakim aktualnie jest nasz wątek. Postęp będzie wstawiany bezpośrednio do okna dialogowego (metoda setProgress(…) – linijka 23), natomiast stan będzie służył do sprawdzenia, czy przypadkiem wątek nie skończył pracować. Jeżeli tak się stanie, nasze okno dialogowe najpierw zostanie ukryte (metoda dismissDialog(…) ), a następnie usunięte z pamięci (metoda removeDialog(…) ). O drugiej czynności musimy pamiętać szczególnie wtedy gdy kolejne uruchomienie okna dialogowego będzie wymagało nowych danych. W naszym przypadku, gdybyśmy nie usunęli go z pamięci, okno wyświetliłoby się ze stanem z poprzedniego wyświetlenia (czyli z paskiem ustawionym na 100%).
Teraz pozostało nam tylko zaimplementowanie metody uruchamiającej nasz wątek oraz wyświetlającej okno dialogowe:
package pl.froger.helloprogressdialog; import android.app.Activity; import android.app.Dialog; import android.app.ProgressDialog; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; public class MainActivity extends Activity { private static final int PROGRESS_DIALOG = 1; private Button btnStart; private ProgressDialog progressDialog; private ProgressThread progressThread; final private Handler progressHandler = new Handler() { public void handleMessage(Message msg) { int progress = msg.arg1; int state = msg.arg2; progressDialog.setProgress(progress); if(ProgressThread.STATE_COMPLETE == state) { dismissDialog(PROGRESS_DIALOG); removeDialog(PROGRESS_DIALOG); } }; }; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); initButton(); } private void initButton() { btnStart = (Button) findViewById(R.id.btnStart); btnStart.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { startWorkerThread(); } }); } private void startWorkerThread() { showDialog(PROGRESS_DIALOG); initProgressThread(); progressThread.start(); } private void initProgressThread() { progressThread = new ProgressThread(progressHandler); } @Override protected Dialog onCreateDialog(int id) { if(PROGRESS_DIALOG == id) return createProgressDialog(); return null; } private Dialog createProgressDialog() { progressDialog = new ProgressDialog(this); progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); progressDialog.setMessage("Loading..."); progressDialog.setProgress(0); progressDialog.setCancelable(false); return progressDialog; } }
Idą kolejno:
- linijka 49 – wyświetlenie okna dialogowego, które konstruowane jest w dwóch metodach – onCreateDialog(…) oraz createProgressDialog(). Ten krok został w większości omówiony w artykule związanym z oknami AlertDialog.
Uwagę należy zwrócić jedynie na linijkę 67, gdzie wskazujemy na jeden z dwóch styli naszego okna dialogowego. Pierwszy z nich to ProgressDialog.STYLE_HORIZONTAL wyświetlający pasek postępu. Drugi natomiast to ProgressDialog.STYLE_SPINNER wyświetlający kręcący się okrąg. - linijka 50,51 - utworzenie obiektu wątku (wraz z przekazaniem mu naszego obiektu progressHandler) i wystartowanie go.
Powyżej zaprezentowany został kompletny kod MainActivity.java.
ProgressThread
Pozostała nam jeszcze implementacja naszego wątku. Nie ma tam jednak tak naprawdę nic ciekawego, w związku z czym zrobimy szybki przegląd.
Wątek po uruchomieniu wykonuje w pętli czynność, która jest nieco opóźniana (Thread.sleep()). W każdym przebiegu inkrementuje on wartość postępu, opcjonalnie zmienia swój stan i wysyła te dane do naszego wątku głównego. W praktyce wygląda to tak:
package pl.froger.helloprogressdialog; import android.os.Handler; import android.os.Message; import android.util.Log; public class ProgressThread extends Thread{ public static final int STATE_BEGIN = 0; public static final int STATE_MIDDLE = 1; public static final int STATE_END = 2; public static final int STATE_COMPLETE = 3; private Handler handler; private int state; int progress; public ProgressThread(Handler handler) { this.handler = handler; } @Override public void run() { state = STATE_BEGIN; progress = 0; while (state != STATE_COMPLETE) { pause(); increaseProgress(); sendProgressToHandler(); } } private void pause() { try { Thread.sleep(50); } catch (Exception e) { Log.e("HelloProgress", e.getLocalizedMessage()); } } private void increaseProgress() { switch (++progress) { case 30: state = STATE_MIDDLE; break; case 60: state = STATE_END; break; case 100: state = STATE_COMPLETE; break; default: break; } } private void sendProgressToHandler() { Message msg = handler.obtainMessage(); msg.arg1 = progress; msg.arg2 = state; handler.sendMessage(msg); } }
Screeny
Kompletny kod źródłowy
Cały projekt dostępny jest na naszym Githubie – HelloProgressDialog.
Komentarze (0)