0 komentarzy

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) Subskrybuj

Prześlij komentarz

Zaloguj się lub skorzystaj z profilu:

[rpxlogin redirect="http://www.android4devs.pl" prompt="" style="large"]

Możesz również zostawić komentarz bez rejestracji, korzystając z poniższego formularza:

Musisz być zalogowany aby móc pisać komentarze.