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.
W skład naszej aplikacji wejdą 3 ważne obiekty (klasy dwóch ostatnich stworzone będą przez nas):
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).
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.
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.
Zaczynamy od:
Implementacją zaznaczonej metody zajmiemy się za chwilę. Najpierw dodamy pola naszych trzech niezbędnych obiektów, o którym wspominałem powyżej:
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.
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:
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:
Idą kolejno:
Powyżej zaprezentowany został kompletny kod MainActivity.java.
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:
Cały projekt dostępny jest na naszym Githubie – HelloProgressDialog.
Zaloguj się lub skorzystaj z profilu:
Możesz również zostawić komentarz bez rejestracji, korzystając z poniższego formularza:
Musisz być zalogowany aby móc pisać komentarze.
ProgressDialog, wyświetlanie paska postępu w oknie dialogowym
Powiązane linki
O szczegółach technicznych
Przykładowa aplikacja
Przygotowania
Kod źródłowy głównej Aktywności
ProgressThread
Screeny
Kompletny kod źródłowy
Komentarze (0)
Prześlij komentarz
Klasa Message
- 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.
- int arg1,
- int arg2,
- Object obj
- 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.
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();
}
});
}
}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();
}
});
}
}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;
}
}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);
}
}