Stasiewicz A. - Android. Podstawy Tworzenia Aplikacji

You might also like

Download as pdf or txt
Download as pdf or txt
You are on page 1of 215

Spis treści

Wstęp .............................................................................................. 5
Rozdział 1. Instalowanie środowiska programistycznego ..................................... 9
Instalowanie Android SDK, Javy i edytora Eclipse .......................................................... 9
Konfiguracja środowiska programistycznego Eclipse .................................................... 11
Pierwsza aplikacja .......................................................................................................... 15
Rozdział 2. Wygląd pierwszej aplikacji .............................................................. 19
Katalog res — zasoby aplikacji ...................................................................................... 19
Layouts, czyli wyglądy aplikacji .................................................................................... 22
LinearLayout — obiekty ułożone obok siebie .......................................................... 24
TableLayout — obiekty ułożone w oczkach sieci .................................................... 27
AbsoluteLayout — rozłożenie swobodne ................................................................. 32
Rozdział 3. Graficzne zasoby aplikacji ............................................................... 39
Struktura katalogów drawable ........................................................................................ 39
Emulatory o ekranach różnej jakości .............................................................................. 41
Bitmap, czyli mapa bitowa ............................................................................................. 43
Mapa bitowa opakowana w atrybuty XML .................................................................... 48
Wiele map bitowych w jednym opakowaniu XML ........................................................ 52
Rozdział 4. Więcej o wyglądzie aplikacji ........................................................... 57
ScrollView — ekran z gumy .......................................................................................... 57
Kolory ............................................................................................................................. 61
Shapes — kształty .......................................................................................................... 68
Rozdział 5. Programowanie czas zacząć! .......................................................... 77
Przycisk „Koniec” .......................................................................................................... 77
Zegarek dla ubogich ....................................................................................................... 82
Kółko i krzyżyk, a przy okazji definiowanie stylów ....................................................... 87
Rozdział 6. Efekty specjalne ............................................................................ 97
Przygotowanie animacji ................................................................................................. 98
Przygotowanie interfejsu użytkownika ......................................................................... 103
Uruchomienie animacji ................................................................................................. 105
Animacja innych komponentów ................................................................................... 108
Łączenie animacji ......................................................................................................... 110
Animacja poklatkowa map bitowych ............................................................................ 113
4 Android. Podstawy tworzenia aplikacji

Rozdział 7. Własne komponenty graficzne ...................................................... 121


Komponenty rozszerzają bazowy superkomponent View ............................................ 122
Przegląd możliwości graficznych ................................................................................. 132
Rozdział 8. Mapy bitowe ................................................................................ 145
Mapa bitowa zaczerpnięta z zasobów aplikacji ............................................................ 145
Rysowanie na mapie bitowej zaczerpniętej z zasobów aplikacji .................................. 152
Przekształcenie mapy bitowej techniką piksel po pikselu ............................................ 154
Uzyskanie nowej, czystej mapy bitowej ....................................................................... 156
Rozdział 9. Wątek w drugim planie ................................................................. 167
Klasa AsyncTask i rysowanie w drugim planie ............................................................ 167
Ściąganie danych z internetu ........................................................................................ 181
Rozdział 10. Więcej ekranów dla aplikacji ........................................................ 191
Okno główne ................................................................................................................ 192
Ta sama aplikacja napisana lepiej, bo krócej ................................................................ 205
Słowniczek ................................................................................... 211
Skorowidz .................................................................................... 213
Wstęp
W 2012 roku miałem przyjemność prowadzić zajęcia w Klubie Szkolnych Programi-
stów INFA. Zajęcia były poświęcone programowaniu w Androidzie i regularnie przy-
chodzili na nie zainteresowani uczniowie białostockich gimnazjów i liceów. Poziom
potrzebnej na starcie wiedzy był niemal zerowy. Szkolni programiści znali trochę
różne języki programowania (głównie C++), ale nikt nie znał ani Androida, ani Javy.
Szybko się jednak okazało, że jako prowadzący muszę się do kolejnych zajęć solidnie
przygotowywać — tak powstała większość materiału na tę publikację.

Android
Android jest systemem operacyjnym dla małych, przenośnych komputerów, czyli tzw.
urządzeń mobilnych. Jest dość trudnym systemem operacyjnym.

Urządzenia mobilne stanowią bardzo bogatą grupę sprzętu. Różnią się od siebie wszyst-
kim — rodzajami ekranów, klawiatur, procesorami, ilością pamięci, wyposażeniem
dodatkowym, takim jak aparaty fotograficzne czy czujniki pozycji. Wielki sukces
urządzeń mobilnych stał się możliwy głównie dzięki temu, że pojawił się system opera-
cyjny próbujący zapanować nad tym mrowiem różnorodności — czyli właśnie Android.

Aplikacje androidowe muszą więc mieć procedury doskonałego orientowania się, jakimi
możliwościami sprzętowymi akurat dysponuje dane urządzenie. Gdyby na każde urzą-
dzenie miała powstawać nowa, przeznaczona tylko na nie aplikacja, prawdopodobnie
urządzenia mobilne nie byłyby tak popularne.

Drugim problemem z rozpoczęciem pracy nad Androidem jest konieczność stosowa-


nie programowania obiektowego. Jest ono przedmiotem zaawansowanych studiów in-
formatycznych, a w przypadku Androida od samego początku należy wiedzieć, co to jest
klasa, właściwości, metody, konstruktory, interfejsy itd. Nie są to rzeczy trudne, ale
młody programista raczej nie ma kontaktu z językiem Java czy C#. Prędzej ma kontakt
z C, Delphi, PHP albo JavaScript. Są to języki obiektowe, ale nie całkowicie obiektowe.

Przyszłemu programiście Androida brakuje jakiegokolwiek wyczucia w pełni obiekto-


wego języka Java. Ale niektórzy uważają, że to dobrze...
6 Android. Podstawy tworzenia aplikacji

Co Ci da przeczytanie tej książki?


Za cel postaw sobie takie wgryzienie się w Androida, abyś mógł dość swobodnie
analizować liczne przykładowe aplikacje dostępne w internecie. Ta książka może być
pierwszą pozycją, którą weźmiesz do ręki. Najwięcej uwagi poświęcę elementarzowi
programowania w Androidzie, na który w innych publikacjach zazwyczaj brakuje
miejsca.

Duża część pracy nad nową aplikacją polega na przygotowywaniu tzw. zasobów — tek-
stów, obrazków, kolorów, dźwięków, animacji, ale także wyglądów poszczególnych
ekranów czy przejść między nimi. Przygotowanie sprowadza się do rozlokowania od-
powiednich plików w odpowiednich folderach i opisaniu ich w nietrudnym standar-
dzie XML. Kompilator czyta pliki XML i na podstawie zawartych w nich informacji
włącza zasoby do aplikacji. Na tym etapie pracy nie jest wymagana umiejętność pro-
gramowania.

Przez kilka pierwszych rozdziałów będziesz się uczyć tworzyć i opisywać zasoby apli-
kacji. Potem zauważysz, że niektóre elementy interfejsu użytkownika — np. przyciski
— mogą reagować na naciśnięcia, jeśli potrafisz opisać tę reakcję w języku Java. W ten
sposób, mając już aplikację na ekraniku urządzenia mobilnego, dodasz jej dynamiki.
Aplikacja zacznie reagować na różne akcje operatora.

Programowania w Javie nie będzie tutaj zbyt dużo. Ograniczę się do zasygnalizowania
najważniejszych mechanizmów, takich jak: obsługa elementów interfejsu użytkowni-
ka po stronie Javy, grafika dwuwymiarowa i odrobina wiedzy o mapach bitowych. Na
elementarnym poziomie omówię najważniejsze mechanizmy w programowaniu urzą-
dzeń mobilnych — procesy uruchamiane w tle, tak aby ani na moment nie zawiesić
maszyny, oraz ściąganie danych z internetu. Na zakończenie przygotujesz aplikację
wieloekranową i nauczysz się zmieniać w niej ekrany.

Jak pracować z tą książką?


Nie musisz być programistą — być może po przeczytaniu tego materiału dopiero zła-
piesz bakcyla programowania. Zakładam, że zupełnie nie znasz Javy.

Android wymaga porządku. Najlepiej by było, gdybyś miał osobny komputer, przezna-
czony na dość skomplikowane oprogramowanie deweloperskie, co rzadko jest możliwe.
Dlatego ze zdwojoną uwagą powinieneś się zapoznać z pierwszym rozdziałem, w którym
nauczę Cię instalować środowisko programistyczne i uruchamiać pierwszą aplikację
na emulatorze telefonu. Być może będziesz zmuszony wszystkie te drobiazgowe czyn-
ności wykonywać wielokrotnie, naprawiając instalację, która nagle przestała działać
poprawnie. Pamiętaj, aby na czas instalowania mieć uprawnienia administratora kom-
putera.
Wstęp 7

Środowisko programistyczne składa się z trzech filarów: oprogramowania Android


SDK, pakietu programistycznego Java i edytora Eclipse. Po zainstalowaniu oprogra-
mowania uruchomisz pierwszą aplikację androidową. Musisz sobie poradzić z kwestiami
technicznymi, które — mam nadzieję — opisałem i zilustrowałem na licznych rysunkach
dostatecznie szczegółowo. Jeśli przebrniesz przez liczne technikalia w pierwszym
rozdziale, potem będzie już tylko lepiej.

Java jest trudnym językiem, jeśli nie czuje się jej zapachu. Tutaj najważniejszy będzie
ten zapach. Dowiesz się, jak i po co poszerza się klasy biblioteczne oraz jak i po co
implementuje się w nich własne algorytmy. Chętnie wykorzystywanym tworzywem
będzie grafika i mapy bitowe — nie dlatego, że są to elementy w jakiś sposób ważniej-
sze w programowaniu, ale dlatego, że po prostu je widać, bywają ładne i obfitują w cie-
kawe, proste algorytmy.

Z dalszej części książki poznasz trzy mechanizmy, które zawsze się przydają: urucha-
mianie algorytmów w tle, ściąganie danych z internetu i uruchamianie aplikacji kilku-
okienkowych. Te mechanizmy będą pretekstami do niezobowiązującej dyskusji nad
Javą, są to także rzeczy podstawowe w warsztacie androidowego programisty. Nie ma
lepszego momentu, aby rzucić na nie trochę światła.

Wszystkie ważne punkty wykładu są zilustrowane rysunkiem. Takie elementy, jak np.:
instalowanie środowiska programistycznego, uruchomienie pierwszej aplikacji czy
przygotowanie zasobów aplikacji, są wyjaśniane głównie na ilustracjach i wyjaśnieniach
do nich. Mam nadzieję, że czytanie opisów do rysunków uatrakcyjni pracę, którą mu-
sisz włożyć w studia nad Androidem. Ilustracje także bardzo ułatwią poruszanie się
po książce, gdy trzeba będzie wrócić po jakąś informację.

Mam nadzieję, że w trakcie pracy z tą książką zauważysz ze zdumieniem, jak gigan-


tyczną pracę wykonali ludzie z Google. Zauważysz też, że programiście pozostaje coś
jakby budowa programu z gotowych klocków. Wreszcie — gdzieś pod koniec — za-
pewne uznasz, że programowanie w Androidzie to w gruncie rzeczy nic trudnego.

Android obrasta słowami slangowymi, nieznanymi z wcześniejszych przygód progra-


mistycznych. Na końcu książki znajduje się krótki słowniczek takich terminów.
8 Android. Podstawy tworzenia aplikacji
Rozdział 1.
Instalowanie środowiska
programistycznego
System operacyjny Android został zbudowany na podstawie innego, powszechnie
znanego systemu — Linuksa. Na bazie tak skonstruowanego systemu została uru-
chomiona tzw. maszyna wirtualna o nazwie Dalvik. Jest to oprogramowanie, które
pozwala na uruchamianie aplikacji przygotowanych w Javie i przetłumaczonych
przez kompilatory na specjalny kod zwany kodem pośrednim. Każdy producent urzą-
dzenia mobilnego, na którym ma zostać zainstalowany Android, musi zadbać, aby na
jego sprzęcie działała maszyna Dalvik. Jeśli zrobi to zgodnie z wytycznymi firmy
Google, na jego urządzeniu uruchomi się Android i tysiące aplikacji.

Środowisko deweloperskie dla Androida jest złożonym oprogramowaniem. Zawiera:


biblioteki Javy, specyficzne biblioteki Androida przygotowane przez firmę Google,
środowisko programistyczne, skomplikowane emulatory różnych urządzeń mobil-
nych, samouczki, przykłady, pomoce.

Instalując zestaw narzędzi Android SDK, zaloguj się jako administrator.

Wszelkie podawane tutaj adresy internetowe odpowiadają stanowi na drugą połowę 2012
roku i mogły się zmienić. Jeśli coś nie działa, skoncentruj się na podawanych w tekście
informacjach, zbuduj odpowiednie zapytania dla wyszukiwarki internetowej i spróbuj
uaktualnić niezbędne adresy.

Instalowanie Android SDK, Javy


i edytora Eclipse
Instalowanie zestawu narzędzi Android SDK rozpoczniesz od wizyty na stronie
developer.android.com (rysunek 1.1). Ściągnij instalator (ok. 70 MB objętości) i uru-
chom go. Wykonując te czynności, bądź zalogowany na swoim komputerze jako admi-
nistrator, a nie jako użytkownik z ograniczeniami dostępu do zasobów komputera.
10 Android. Podstawy tworzenia aplikacji

Rysunek 1.1. Instalowanie zestawu narzędzi Android SDK

Instalator Android SDK prawdopodobnie stwierdzi, że na Twoim komputerze nie ma


pakietu Java SE SDK, i zaproponuje Ci wizytę w firmie Oracle — czyli u obecnego wła-
ściciela Javy. Na moment odłóż instalowanie Androida, ze stron firmy Oracle ściągnij
pakiet Javy i zainstaluj go w proponowanych lokalizacjach (rysunek 1.2). Jeśli masz
Javę, warto sprawdzić na stronach internetowych Oracle’a, czy nie ma nowszej edycji,
i ewentualnie zaktualizować wersję. Na zakończenie instalowania Oracle zaproponuje
Ci utworzenie konta i zarejestrowanie Cię jako nowego użytkownika. Nie jest to ko-
nieczne dla prawidłowego działania oprogramowania.

Rysunek 1.2.
By zainstalować zestaw
narzędzi Android SDK,
konieczne jest
zainstalowanie
pakietu Javy
Rozdział 1.  Instalowanie środowiska programistycznego 11

Po krótkiej dygresji na temat instalowania Javy wracam do instalatora Androida, który


powinien nadal oczekiwać na pulpicie gdzieś pod innymi okienkami. Prawdopodob-
nie będzie potrzebne wykonanie operacji Back (wstecz) i po chwili Next (dalej), aby
instalator dostrzegł, że na urządzeniu pojawiła się Java. Jeśli instalator nadal nie widzi
Javy, należy go wyłączyć, zrestartować komputer i na nowo rozpocząć instalowanie ze-
stawu Android SDK. System Android — jak i przedtem Javę — instaluj w domyślnych,
proponowanych przez instalator lokalizacjach (rysunek 1.3).

Rysunek 1.3.
Powrót do instalowania
zestawu narzędzi
Android SDK po
zainstalowaniu Javy

Trzecim składnikiem (po Javie i Androidzie SDK) jest Eclipse, czyli środowisko, w któ-
rym będziesz pracować. Są też inne, alternatywne środowiska, wreszcie można pra-
cować bez środowiska, używając poleceń wpisywanych bezpośrednio z klawiatury.
Jest to jednak co najmniej nieprzyjemne. Ze strony www.eclipse.org pobierz pakiet
instalacyjny (rysunek 1.4). Firma Google zaleca pakiet Eclipse Classic, który za chwilę
skonfigurujesz do pracy z Androidem. Wśród dostępnych pakietów na stronie Eclipse
jest m.in. pakiet przeznaczony na urządzenia mobilne, ale nie próbowałem pracować
z tą wersją, trzymając się klasycznej ścieżki postępowania zalecanej przez Google.
Instalowanie środowiska Eclipse w zasadzie polega na jego rozpakowaniu. Wybierz
wygodną lokalizację i rozpakuj plik .zip, zezwalając mu na utworzenie wszystkich
potrzebnych ścieżek.

Konfiguracja środowiska
programistycznego Eclipse
Po zainstalowaniu trzech filarów, czyli Androida SDK, Javy SE (jeśli nie było jej na
komputerze) i IDE Eclipse, na wszelki wypadek zrestartuj komputer. Po ponownym
uruchomieniu komputera przystąpisz do uruchomienia środowiska Eclipse i podłączenia
do niego nowych narzędzi programistycznych.
12 Android. Podstawy tworzenia aplikacji

Rysunek 1.4. Instalowanie środowiska Eclipse

Ręczne konfigurowanie Eclipse do pracy z Androidem czy jakimś innym językiem/


systemem jest możliwe, ale trudne i wymagające dokładności. Jeśli chodzi o Androida,
firma Google opracowała specjalną wtyczkę do Eclipse, która wykonuje automatycz-
nie wszystkie czynności. Uruchom więc Eclipse, wejdź do menu Help i wybierz pozycję
Install New Software. Kliknij Add u góry ekranu (rysunek 1.5).

W okienku pobierania rozszerzeń wpisz nazwę ADT Plugin (Android Development


Tools, wtyczka do narzędzi Androida) i adres internetowy (rysunek 1.6). Te dane —
gdyby w przyszłości miało się coś zmienić — znajdziesz na stronach Androida, wyszu-
kując np. hasło „Plugin for Eclipse” albo „ADT Plugin”. W kolejnym okienku zaznacz
Developer Tools (narzędzia programistyczne), a potem zaakceptuj postanowienia li-
cencyjne. Po zakończeniu instalowania wtyczki powinieneś zrestartować komputer.

Uruchom ponownie Eclipse. Prawdopodobnie już podczas uruchamiania wyskoczy


okienko tzw. SDK Managera i upomni Cię, że musisz określić, jakie urządzenia mobil-
ne będą Cię interesowały. Jeśli SDK Manager się nie pojawi, wejdź do menu Window,
następnie Preferences, zaznacz pole Android i sprawdź, czy SDK Location, czyli lokali-
zacja oprogramowania Androida na Twoim komputerze, jest wskazana prawidłowo
(rysunek 1.7).
Rozdział 1.  Instalowanie środowiska programistycznego 13

Rysunek 1.5.
Konfigurowanie
środowiska Eclipse
do pracy z Androidem

Rysunek 1.6.
Kolejne kroki przy
instalowaniu wtyczki
do narzędzi Androida

Skoro SDK Manager nie pojawił się automatycznie (być może ścieżka SDK Location
wymagała naprawy), z menu Window wybierz operację Android SDK Manager (za-
rządca systemów Android) – rysunek 1.8. SDK Managera można też uruchamiać spo-
za Eclipse, tak jak każdy program windowsowy. Proponuję w menedżerze odznaczyć
domyślnie zaznaczaną najnowszą wersję systemu, a wybrać którąś ze starszych wersji
(firma Google poleca wersję 2.2 i sugeruję właśnie ją zainstalować), ponieważ pro-
gramując w najnowszych edycjach, nie uruchomisz swojej aplikacji na starszych
urządzeniach mobilnych. Oprócz elementów powiązanych menedżer sugeruje zaznacze-
nie Accept All — czyli chce zainstalować nie tylko to, co konieczne, ale i to, co może
się przydać, np. obsługę kabla USB do przesyłania gotowego programu bezpośrednio
na urządzenie mobilne. Zaznacz Accept All. Operacja ta nie jest krytyczna — zawsze
możesz uruchomić Android SDK Managera i zmienić obraz wirtualnych urządzeń
mobilnych, z którymi pracujesz.
14 Android. Podstawy tworzenia aplikacji

Rysunek 1.7.
Sprawdzanie,
czy lokalizacja
oprogramowania
Androida jest wskazana
prawidłowo
na komputerze

Rysunek 1.8. Wybieranie wersji systemu Android

W menu Window utwórz AVD — Android Virtual Device, czyli wirtualne urządzenie
mobilne (rysunek 1.9). Wywołaj AVD Managera i zaprojektuj nową maszynę o jej
własnej nazwie. Proponuję wskazać Target (czyli platformę) jako Android 2.2 i zaak-
ceptować domyślne średnie wartości ekranu.
Rozdział 1.  Instalowanie środowiska programistycznego 15

Rysunek 1.9.
Tworzenie AVD,
czyli wirtualnego
urządzenia mobilnego

Po wykonaniu tych czynności jesteś już gotowy do pisania aplikacji w popularnej,


choć nie najnowszej wersji Android 2.2. Możesz też je uruchamiać na wirtualnej ma-
szynie (w moim przykładzie: o nazwie AS2_2). Aby nie wprowadzać dodatkowego
bałaganu z wyborem platform i urządzeń, proponuję, byś na razie poprzestał na śro-
dowisku 2.2.

Pierwsza aplikacja
Z menu File wybierz New, Other, a następnie Android Application Project (rysunek 1.10).
Po krótkim czasie środowisko Eclipse utworzy skomplikowany układ folderów i plików
składających się na aplikację i jej materiały źródłowe.

W kreatorze nowej aplikacji nadaj jej tytuł, ustal również tytuł projektu w Eclipse (na
ogół są to te same słowa). Kolejne pole — Package Name — jest pojęciem typowym
dla języka Java — określa hierarchię unikalnych folderów, w których będą umieszczone
klasy źródłowe. Jeśli masz domenę internetową, zaleca się odwrócić kolejność wyrazów
składających się na jej nazwę i na końcu dodać nazwę aplikacji, np. mając domenę
bialystok.com.pl, utwórz napis pl.com.bialystok.ptaki. Ponieważ aplikacja androidowa
posługuje się zestawem ikon przygotowanych na różne rozdzielczości ekranu, skorzy-
staj z opcji wykreowania takiego zestawu (rysunek 1.11).
16 Android. Podstawy tworzenia aplikacji

Rysunek 1.10.
Początki pracy nad
utworzeniem aplikacji

Rysunek 1.11.
Nadawanie tytułu
aplikacji i tytułu
projektu w środowisku
Eclipse

Configure Launcher Icon to przyjemne narzędzie do kreowania głównej ikony aplikacji,


czyli wersji ikony w różnych rozmiarach (rysunek 1.12). Gotowe ikony automatycznie
zostaną rozmieszczone w odpowiednich folderach projektu.
Rozdział 1.  Instalowanie środowiska programistycznego 17

Rysunek 1.12.
Tworzenie głównej
ikony aplikacji

Sprawdź, czy po zakończeniu pracy kreatora nowego projektu we wskazanej lokaliza-


cji powstała duża grupa folderów. W folderze src znajduje się hierarchia podkatalo-
gów zawierających pliki w Javie. W folderze bin widać podobną hierarchię, zawiera-
jącą skompilowane pliki class (są to wykonywalne pliki Javy; najpierw należy
skompilować projekt — tego jeszcze nie robiłeś). Folder res (skrót od ang. słowa resource
— zasoby, czyli miejsce na różne teksty, obrazki, opisy) zawiera inne zasoby —
znajdziesz tam swoje ikony przygotowane w różnych rozmiarach dla różnych urzą-
dzeń mobilnych.

W okienku z lewej strony kliknij prawym klawiszem myszki nazwę nowego projektu
i wybierz Run As…, a następnie Android Application. Powinien uruchomić się emu-
lator urządzenia, a w nim Twój pierwszy program (rysunek 1.13). Program ten został
zainstalowany na urządzeniu. Nawigując po urządzeniu, możesz uruchomić bądź usu-
nąć swoją aplikację.

Twoja pierwsza aplikacja androidowa w zasadzie… napisała się sama. Na pierwsze uru-
chomienie aplikacji zazwyczaj czeka się dość długo, bo emulator smartfonu startuje bar-
dzo wolno. Kolejne uruchomienia trwają dużo krócej, dlatego po przyjrzeniu się aplikacji
nie zamykaj okienka emulatora! (rysunek 1.14).
18 Android. Podstawy tworzenia aplikacji

Rysunek 1.13. Twoja pierwsza aplikacja została zainstalowana na urządzeniu

Rysunek 1.14. Okienko emulatora aplikacji


Rozdział 2.
Wygląd pierwszej
aplikacji
Po automatycznym wygenerowaniu pierwszego projektu we wskazanej lokalizacji
(w Eclipse lokalizacja ta nazywa się Workspace) otrzymałeś dość skomplikowany ze-
staw plików. Okazuje się, że zmiany kluczowe dla wyglądu aplikacji można wprowadzać
w odpowiednich plikach tekstowych, na podstawie których kompilator generuje od-
powiednie klasy w języku Java.

Spróbuj zatem zbudować aplikację, choć nie umiesz programować w Javie…

Katalog res — zasoby aplikacji


Jeden z podkatalogów w projekcie androidowym ma nazwę res (skrót od ang. słowa re-
source — zasoby, czyli miejsce na różne teksty, obrazki, opisy). Znajdziesz tam rzeczy,
które wprawdzie nie są czystym kodem w języku Java, ale to one odpowiadają w głów-
nej mierze za wygląd programu.

Niekoniecznie trzeba korzystać z zasobów, wszystkie elementy można opisać w kodzie


Javy, jednak oczywiście jest to trudniejsze. Jest też mniej czytelne i trudniej się zarządza
takim opisem kodu, gdyż ewentualne poprawki wymagałyby wprowadzania zmian
w różnych miejscach kodu źródłowego zamiast do jednego folderu res.

Zanim przejdę do omówienia struktury katalogu res, chcę zwrócić Ci uwagę na to, że za
Twoją wygodę w operowaniu zasobami zapłaci kompilator, który w momencie kom-
pilacji na podstawie zgromadzonych w nim plików utworzy odpowiednie klasy Javy
z odpowiednimi parametrami.

W katalogu res znajdują się podkatalogi drawable z rysunkami, przygotowanymi z my-


ślą o różnych rodzajach ekranów urządzeń mobilnych (rysunek 2.1). Na przykład fol-
der drawable-ldpi można rozszyfrować jako low dot per inch (rysunki w niskiej roz-
dzielczości). Zajrzyj koniecznie do folderów drawable!
20 Android. Podstawy tworzenia aplikacji

Rysunek 2.1. W katalogu res znajdują się podkatalogi drawable, do których warto zajrzeć

Wejdź do któregoś z podkatalogów z zasobami, wybierz dowolny zasób i kliknij go pra-


wym klawiszem myszki. Z menu wybierz operację Open (otwarcie w najbardziej za-
lecanym edytorze) albo poszukaj jakiegoś lepszego narzędzia klikając przycisk Open
With (rysunek 2.2). Dwukrotne kliknięcie zasobu od razu otwiera go w najbardziej zale-
canym edytorze.

Przyjrzyj się katalogowi values i plikowi strings.xml (rysunek 2.3). Plik ten powinien
zawierać wszystkie teksty, które występują w aplikacji i są oznaczone unikalnymi na-
zwami. Oczywiście mógłbyś te teksty wpisywać bezpośrednio gdzieś w Javie, ale ze-
branie ich w jednym pliku ułatwia konserwację programu. Wybierz najwłaściwszy
(zdaniem Eclipse) edytor, dwukrotnie klikając zasób strings.xml. Edytując treść tekstów,
nie uszkodź ich nazw, bo nazwy te występują w kodzie Javy!

Ponieważ zasób typu XML jest czytelny, spróbuj dokonać zmian na „żywym” pliku
strings.xml, wybierając zakładkę u dołu pola edycji w Eclipse (rysunek 2.4). Oczywiście
nie wolno popsuć struktury pliku strings.xml, bo kompilator nie byłby w stanie go
przeczytać i nie wygenerowałby odpowiedniego kodu w Javie.
Rozdział 2.  Wygląd pierwszej aplikacji 21

Rysunek 2.2. Otwieranie zasobów z katalogu res

Rysunek 2.3. Plik strings.xml w katalogu values


22 Android. Podstawy tworzenia aplikacji

Rysunek 2.4. Dokonywanie zmian w pliku strings.xml

Najciekawszy edytor otworzy się wtedy, gdy dwa razy klikniesz zasób layout (wygląd)
— rysunek 2.5. Znajdziesz tam elementy potrzebne do zbudowania interfejsu oraz in-
spektor do precyzyjnej obróbki właściwości każdego elementu. Wszystkie ustalenia są
zapisywane w pliku XML, który też możesz bezpośrednio (ostrożnie!) edytować. Przej-
ścia między edycją bezpośrednią a wizualną dokonasz, wybierając odpowiednią za-
kładkę u dołu pola edytora.

Layouts, czyli wyglądy aplikacji


Teraz omówię edycję pliku XML, znajdującego się w folderze layout (wygląd). Edy-
tując plik, częściowo będziesz pracować myszą (czyli wizualnie), a częściowo bezpo-
średnio (rysunek 2.6).

Do wyboru masz następujące wyglądy aplikacji (jest ich trochę więcej, ale te Ci na
razie wystarczą):
 LinearLayout,
 AbsoluteLayout,
 TableLayout.
Rozdział 2.  Wygląd pierwszej aplikacji 23

Rysunek 2.5. Edytor zasobu layout, czyli wygląd aplikacji

Rysunek 2.6. Bardzo często trzeba opracowywać określone skrawki pliku XML, który odpowiada za
wygląd aplikacji. Przełączając się za pomocą myszki między bezpośrednią edycją a edycją wizualną,
łatwo można odszyfrować tajemnice wyglądu aplikacji androidowej
24 Android. Podstawy tworzenia aplikacji

Wybierając odpowiedni wygląd aplikacji, decydujesz, jak w jej okienku będą się za-
chowywały komponenty zabudowy ekranu, zwane tutaj Views (widoki) — czy będą
ułożone jeden pod drugim, czy może jeden obok drugiego (LinearLayout), czy może
znajdą się jakby w oczkach regularnej siatki (TableLayout), czy wreszcie zostaną do-
wolnie rozmieszczone na ekraniku urządzenia mobilnego (AbsoluteLayout).

LinearLayout — obiekty ułożone obok siebie


Wygląd tej aplikacji (zwany liniowym) zakłada, że kolejne elementy interfejsu poja-
wiają się jeden obok drugiego lub jeden pod drugim. Aby to sprawdzić, w folderze
layout w miejsce istniejącej treści pliku XML wprowadź za pomocą ręcznej edycji na-
stępującą treść:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Przycisk" />
</LinearLayout>

Po wpisaniu tej treści wróć do edytora wizualnego i sprawdź efekty na podglądzie gra-
ficznym (rysunek 2.7). Oczywiście możesz też skompilować program i go uruchomić,
czego jednak nie będziesz robić zbyt często, bo emulator urządzenia mobilnego działa
raczej powoli.

Jeśli uważnie przyjrzysz się plikowi XML, przy jednej linii powinieneś zauważyć ma-
lutki żółty trójkącik. Nie jest to znak błędu, ale uwagi. Otóż linia:
android:text="Przycisk" />

powinna być napisana inaczej, a mianowicie jako odwołanie do zasobnika tekstów


strings.xml, z którym już się spotkałeś:
android:text="@string/przycisk_1" />

W zasobniku tekstów strings.xml powinien też się znaleźć nowy wpis. Wykonaj go albo
metodą bezpośredniej edycji, albo wykorzystaj w tym celu specjalizowany edytor do
dodawania napisów i ich nazw:
<resources>
<string name="app_name">AndroPtaki</string>
<string name="hello_world">To ja!</string>
<string name="menu_settings">Settings</string>
<string name="title_activity_main">Ptaki</string>
<string name="przycisk_1">Przycisk</string>
</resources>
Rozdział 2.  Wygląd pierwszej aplikacji 25

Rysunek 2.7.
LinearLayout: elementy
interfejsu ułożone
ciasno jeden obok
drugiego, czyli
w orientacji poziomej.
Warto zmienić
orientację poziomą na
pionową (trzeba słowo
„horizontal” zastąpić
słowem „vertical”)
i porównać różnice

Rysunek 2.8. W aplikacji przybywa nowy napis. Jego nazwę oraz wartość należy zaimplementować w pliku
strings.xml, zaś tam, gdzie jest potrzebny (tutaj w sekcji layout), należy umieścić odwołanie do jego nazwy
26 Android. Podstawy tworzenia aplikacji

Napisy wrap_content oznaczają: „dobierz tak rozmiar, aby tylko otoczyć treść”. Ich
pewnym przeciwieństwem jest fill_parent („bez względu na treść rozciągnij rozmiar
na cały dostępny obszar”).

Sytuację pokazaną na rysunkach 2.9 i 2.10 opisuje następujący plik z zasobu layout:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:background="#0000a0">
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello_world"
android:textColor="#FFFFFF"
android:textSize="20" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/przycisk_1"
android:textColor="#ff0000" />
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/checkbox_1"/>
</LinearLayout>

Rysunek 2.9. LinearLayout ma teraz orientację pionową. Dopisano też kolejny komponent, tzw. pole
wyboru CheckBox. Wiąże się z tym dopisanie kolejnego tekstu do zasobnika strings.xml. Ustalono kolor
tła aplikacji. Za pomocą inspektora z prawej strony środowiska Eclipse należy precyzyjnie regulować
wybrane atrybuty komponentów — tutaj kolor tekstu na przycisku przerobiono na czerwony
Rozdział 2.  Wygląd pierwszej aplikacji 27

Rysunek 2.10. LinearLayout o orientacji pionowej, czyli komponenty są umieszczane jeden pod
drugim. Dwa pierwsze mają szerokość wypełniającą całe dostępne pole ekranu. Klawisze PageUp
i PageDown służą do „obracania” emulatora. Dzięki zastosowaniu automatycznych wyglądów Layout
(w przeciwieństwie do podania wprost współrzędnych każdego komponentu) aplikacja potrafi przeliczyć
rozmiary komponentów w nowej sytuacji i odświeżyć swój wygląd. Ekran przebudował się w nowej
sytuacji, ale komponenty nadal leżą jeden pod drugim

TableLayout — obiekty ułożone w oczkach sieci


Teraz przygotujesz interfejs programu typu „podaj kilka danych”. Ekran niech będzie
podzielony na dwie kolumny: w pierwszej będą pytania w rodzaju: „Jak nazywa się
Twój kot?”, w drugiej — edytorki oczekujące na wpisanie tekstu. Interfejs taki mógłbyś
wprawdzie zbudować, określając położenie wszystkich elementów (za chwilę napiszę
więcej o wyglądzie AbsoluteLayout), ale lepiej wykorzystać TableLayout. TableLayout
dzieli bowiem ekran na niewidoczne komórki, w których mogą znajdować się ele-
menty interfejsu. Na rysunku 2.11 widać siedem wierszy (w tym dwa puste odgrywają
rolę marginesów), każdy złożony z dwóch komórek, czyli są dwie kolumny. Szerokość
kolumny wyznacza najszerszy element, napotkany w którymkolwiek z wierszy tej
kolumny.
28 Android. Podstawy tworzenia aplikacji

Rysunek 2.11.
Przykładowy wygląd
TableLayout

Plik zasobu layout jest dość długi, ale ma czytelną strukturę:


<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:stretchColumns="1"
android:background="#003000" >
<TableRow>
<TextView
android:layout_width="150dp"
android:gravity="right"
android:paddingRight="5dp"
android:text="@string/imie_kota"
android:textColor="#FFFFFF"
android:textSize="20dp"
android:textStyle="bold" />
<EditText
android:inputType="text"
android:text="@string/imie_kota_edit" />
</TableRow>
<TableRow>
<TextView
android:gravity="right"
android:paddingRight="5dp"
android:text="@string/wiek"
android:textColor="#FFFFFF"
android:textSize="20dp"
android:textStyle="bold" />
<EditText
android:inputType="numberSigned"
android:text="@string/wiek_kota_edit" />
Rozdział 2.  Wygląd pierwszej aplikacji 29

</TableRow>
<TableRow android:minHeight="10dp">
<TextView />
<TextView />
</TableRow>
<TableRow>
<TextView
android:gravity="right"
android:paddingRight="5dp"
android:text="@string/imie_psa"
android:textColor="#FFFFFF"
android:textSize="20dp"
android:textStyle="bold" />
<EditText
android:inputType="text"
android:text="@string/imie_psa_edit" />
</TableRow>
<TableRow>
<TextView
android:gravity="right"
android:paddingRight="5dp"
android:text="@string/wiek"
android:textColor="#FFFFFF"
android:textSize="20dp"
android:textStyle="bold" />
<EditText
android:inputType="numberSigned"
android:text="@string/wiek_psa_edit" />
</TableRow>
<TableRow android:minHeight="10dp">
<TextView />
<TextView />
</TableRow>
<TableRow>
<Button
android:textColor="#FFFF00"
android:textSize="20dp"
android:text="@string/button_1"/>
<TextView/>
</TableRow>
</TableLayout>

Jest to typowy plik opisu wyglądu, przygotowany w języku XML. Z atrybutów wyglądu
tabelarycznego (TableLayout) odczytasz, że kontener ten ma zająć cały dostępny obszar
ekranu i ma mieć kolor ciemnozielony. Linia:
android:stretchColumns="1"

oznacza, że druga kolumna (kolumny numeruje się od zera) ma się rozciągać na całą
dostępną przestrzeń (rysunek 2.12), co będzie ważne przy obracaniu urządzenia mo-
bilnego (czyli podczas naciskania klawisza PageDown na emulatorze).
30 Android. Podstawy tworzenia aplikacji

Rysunek 2.12.
Druga kolumna
rozciąga się na cały
ekran. Warto
wypróbować warianty
android:stretchColumns
="0,1" (rozciągają się
obydwie) albo
android:stretchColumns
="0" (rozciąga się tylko
pierwsza)

Wewnątrz głównego tagu <TableLayout> (tag główny slangowo nazywa się w XML
rootem) znajduje się siedem tagów <TableRow>, tzn. że tabela ma siedem wierszy. Tagi
<TableRow> zawsze towarzyszą tagowi <TableLayout>. Pierwszy i drugi (a także kilka
następnych) deklarują obiekty będące napisem TextView i edytorem EditText. Pierwszy
napis ma określoną szerokość (tutaj 150 pikseli), następne już nie. Taką szerokość bę-
dzie miał każdy element w lewej kolumnie, czyli pierwszy w każdym z wierszy (chyba
że któryś byłby szerszy niż 150 pikseli — wtedy to on decydowałby o szerokości ca-
łej kolumny). Elementy z prawej kolumny, czyli edytorki, nie mają określonej szero-
kości, ale pamiętasz, że druga kolumna ma być rozciągana na całą dostępną przestrzeń.

Edytowanie pliku wyglądu aplikacji staraj się przeprowadzać za pomocą specjalizo-


wanego edytora wizualnego, a nie edycji tekstowej. Za to po każdej operacji podgląd-
nij bezpośrednią treść w pliku XML. Edytor wizualny — jak na tę chwilę — nie jest
zbyt dobry. Niekiedy zostawia w pliku XML jakieś śmieci albo zezwala na wpisywanie
wartości, które się potem nie kompilują. Jednak mimo wszystko jest, i to dzięki niemu
można pisać programy ulubioną techniką wizualną.

Na rysunku 2.13 pokazano, jak można definiować zawartość edytorków. Określony


komponent kliknięto prawym klawiszem myszki i dokonano wyboru, którego efektem
jest automatyczne wstawienie do pliku XML, do właściwości edytorka wieku kota, linii
android:inputType="numberSigned", czyli „niech edytorek przyjmuje liczby całkowite”.

Warto wyjaśnić jeszcze, że atrybut:


android:gravity="right"

oznacza, iż jeśli element interfejsu ma wolne miejsce w poziomie (w omawianym


przypadku — wolne miejsce w komórce tabeli), powinien przesunąć się na prawo
(ang. gravity — ciążenie). Napisy w rodzaju Wiek są wyrównane do prawej strony.
Rozdział 2.  Wygląd pierwszej aplikacji 31

Rysunek 2.13. Wybór jednego z komponentów i określenie sposobu zapisywania wstawianych


w nim wartości

Z kolei atrybut:
android:paddingRight="5dp"

(znany z języka do budowy stron internetowych) określa wewnętrzny odstęp między


napisem a granicą elementu. Dzięki temu oraz dzięki poprzedniemu ustawieniu napis
Wiek jest wyrównany do prawej strony, ale nie klei się nadmiernie do edytorka.

Wśród siedmiu wierszy są dwa wiersze puste:


<TableRow android:minHeight="10dp">
<TextView />
<TextView />
</TableRow>
32 Android. Podstawy tworzenia aplikacji

Wiersze te nie są całkiem puste, bo znajdują się w nich dwa obiekty TextView (), choć
nie zawierające żadnych znaków. Dzięki tej sztuczce unikniesz uwagi kompilatora, że
tabela zawiera pusty (niepotrzebny) wiersz. Wiersz ma podaną minimalną wysokość
i w opisywanym interfejsie służy do oddzielenia kota od psa.

Ostatni wiersz zawiera przycisk Button, a w drugiej jego kolumnie także umieszczono
pusty tekst Każda komórka tabeli jest czymś wypełniona, z tym że niektóre z nich są
wypełnione pustymi tekstami.

W pliku XML odpowiadającym za wygląd aplikacji pojawia się kilka nowych tekstów,
zdefiniowanych jako odnośniki do pliku strings.xml — zasobnika z tekstami:
android:text="@string/wiek"

Oto nowe wpisy do pliku strings.xml:


<string name="imie_kota">Kot:</string>
<string name="imie_psa">Pies:</string>
<string name="imie_kota_edit">Mruczek</string>
<string name="imie_psa_edit">Rex</string>
<string name="wiek">Wiek:</string>
<string name="wiek_kota_edit">3</string>
<string name="wiek_psa_edit">7</string>
<string name="button_1">Przetwarzaj</string>

Te wpisy mają prostą strukturę: nazwę tekstu i jego brzmienie. Omawiane wpisy de-
finiują teksty, wykorzystywane przy budowie aplikacji. Mógłbyś pominąć np. linię:
<string name="imie_kota">Kot:</string>

i odpowiednią linię w pliku wyglądu przebudować następująco:


android:text="@string/imie_kota"
android:text="Kot:"

Tekst został określony nie przez nazwę zmiennej, ale jest wpisany bezpośrednio. Mi-
mo pozornego uproszczenia programu wadą tego rozwiązania jest rozproszenie tek-
stów po całej aplikacji. Taką aplikację będzie trudniej konserwować.

AbsoluteLayout — rozłożenie swobodne


Projekt ten znajduje się w pliku Rozdzial_2.zip.

Rozpocznij nowy projekt androidowy, wybierając z menu File opcję New i dalej Android
Project. Wybierz nazwę dla aplikacji i jej projektu w środowisku Eclipse (zazwyczaj
jest to ta sama krótka nazwa). Wybierz unikalną w skali świata nazwę pakietu, w któ-
rym będą się znajdowały Twoje klasy Javy. Jak wspominałem, nazwę tę zazwyczaj
tworzy się, odwracając kolejność wyrazów swojego adresu WWW i na końcu dopi-
sując nazwę aplikacji. Skoro adresy internetowe są unikalne, to tak utworzona nazwa
pakietu też będzie jedyna.
Rozdział 2.  Wygląd pierwszej aplikacji 33

Kreator nowego projektu założy w lokalizacji zwanej Workspace (możliwej do zmia-


ny w menu File — ale po co?) folder o nazwie projektu, a w nim umieści wszystkie,
znane Ci już podfoldery (rysunek 2.14).

Rysunek 2.14.
Nowy projekt zostaje
utworzony za pomocą
kreatora i zapisany
w folderze określonym
jako Workspace
środowiska Eclipse

Być może zechcesz opracować własną ikonę główną do nowego programu (rysunek 2.15).
Kreator nowego projektu przechodzi przez narzędzie, które w prosty sposób tworzy
odpowiednie pliki i umieszcza je w zasobach res/drawable folderu projektu.

W wyglądzie AbsoluteLayout każdy element interfejsu użytkownika będzie miał okre-


śloną lokalizację. W folderze res/layout znajdź główny plik wyglądu i w następujący
sposób przygotuj jego treść:
<AbsoluteLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_x="50dp"
android:layout_y="100dp"
android:text="@string/napis" />
</AbsoluteLayout>

Cały ekranik jest zorganizowany w stylu zwanym rozkładem swobodnym — świadczą


o tym wartości match_parent albo fill_parent (wypełnij cały ekran). Na ekranie
znajduje się jeden komponent interfejsu — napis. Jego rozmiar jest ograniczony do jego
zawartości (wrap_content — otaczaj zawartość). Kluczowymi wartościami są: layout_x
i layout_y. Określają one położenie komponentu na ekranie.
34 Android. Podstawy tworzenia aplikacji

Rysunek 2.15.
Opracowywanie ikony
głównej do nowego
programu

Spójrz jeszcze na znany Ci już plik strings.xml, definiujący brzmienia wszystkich tekstów:
<resources>
<string name="app_name">Koty</string>
<string name="napis">Swobodne pozycjonowanie elementów interfejsu.</string>
<string name="menu_settings">Settings</string>
<string name="title_activity_main">Podręcznik - koty.</string>
</resources>

Styl AbsoluteLayout jest łatwy w użyciu, ale szybko prowadzi do kłopotów, i to co naj-
mniej z dwóch powodów. Po pierwsze, głównym założeniem Androida jest obsługa
urządzeń o różnej jakości ekranów. Style poprzednio dyskutowane, które zamiast ab-
solutnym pozycjonowaniem operowały terminami w rodzaju „leż pod innym kom-
ponentem”, „dotykaj prawego marginesu” itp., potrafią zależnie od sytuacji przeli-
czyć i znaleźć położenia zarówno na małym ekranie telefonu, jak i na dużym tablecie.
Styl AbsoluteLayout tego nie zrobi.

Po drugie, omawiany styl prawdopodobnie będzie źle pracował podczas obracania urzą-
dzenia, co widzisz na rysunku 2.16. Ekran zachowuje się źle przy obracaniu urządze-
nia. Można to poprawić technikami programistycznymi, których jeszcze nie znasz, ale
lepiej nie używać stylu AbsoluteLayout i zastąpić go stylami relatywnymi, które po-
trafią przeliczać położenia komponentów zależnie od sytuacji.

Na zakończenie tego rozdziału proponuję Ci przygotowanie aplikacji, która będzie


zbudowana z kilku obrazków i podpisów. Wykorzystasz rozkład AbsoluteLayout.
Rozdział 2.  Wygląd pierwszej aplikacji 35

Rysunek 2.16.
Styl AbsoluteLayout
rozmieszcza komponenty
w wybranych punktach
ekranu

Dwukrotnie kliknij myszką w plik XML, znajdujący się w folderze layout, otwierając
tym samym domyślny edytor graficzny widoku (rysunek 2.17). W obszar ekranu urzą-
dzenia mobilnego przeciągnij komponent ImageView.

Rysunek 2.17. Umieszczanie wybranego komponentu na ekranie


36 Android. Podstawy tworzenia aplikacji

Po przeciągnięciu komponentu ImageView pojawia się okienko wyboru odpowiedniego


zasobu — w omawianym przypadku: obrazka (rysunek 2.18). Wybierz opcję Utwórz
nową ikonkę (na rysunku Create New Icon) i przejdź do edytorka zestawu ikonek
w różnej rozdzielczości. Następnie określ nazwę ikonki (rysunek 2.19) i wskaż lokalizację
ikonki (rysunek 2.20).

Rysunek 2.18.
Tworzenie nowej ikonki

Rysunek 2.19.
Wybór nazwy nowego
rysunku
Rozdział 2.  Wygląd pierwszej aplikacji 37

Rysunek 2.20.
Lokalizacja pliku
graficznego (ikonki)

Na rysunku 2.21 pokazano, że o ile omawiany interfejs mógłby od biedy służyć przy
pionowej orientacji ekranu, o tyle po obróceniu urządzenia komponenty wychodzą
poza ekran. Jak widać, styl AbsoluteLayout nie powinien być nadużywany.

Rysunek 2.21.
Wygląd interfejsu
po obróceniu urządzenia
38 Android. Podstawy tworzenia aplikacji

Właściwości komponentu ImageView zostają dopisane do pliku wyglądu. Plik wyglądu


ma mniej więcej następującą treść:
<AbsoluteLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_x="50dp"
android:layout_y="100dp"
android:text="@string/napis" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_x="36dp"
android:layout_y="154dp"
android:src="@drawable/rys_owca" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_x="41dp"
android:layout_y="230dp"
android:src="@drawable/rys_pies" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_x="104dp"
android:layout_y="181dp"
android:text="@string/txt_owca" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_x="108dp"
android:layout_y="259dp"
android:text="@string/txt_pies" />
</AbsoluteLayout>

Zapewne dostrzegasz tutaj obrazki i napisy, umieszczone w ściśle określonych miejscach


ekranu. Każdy napis i obrazek odwołuje się do zasobów umieszczonych w odpowied-
nich podfolderach katalogu res (zasoby). Powinieneś z łatwością odszukać pliki zasobów
w drzewie projektu, widocznym z lewej strony edytora Eclipse.
Rozdział 3.
Graficzne
zasoby aplikacji
W tym rozdziale omówię dokładniej, jakie zasoby można przechowywać w podfolderach
res/drawable. Materiały do tego projektu znajdują się w pliku o nazwie Rozdzial_3.

Struktura katalogów drawable


Już wiesz, że wyglądu aplikacji w Androidzie raczej się nie programuje (chociaż można),
ale opisuje się go w specjalny sposób, umieszczając odpowiednie informacje w odpo-
wiednich folderach i plikach. Folder o nazwie res (skrót od ang. wyrazu resources —
zasoby) jest tym miejscem, w którym umieszczasz swoje zasoby. Folder ten ma dalszą
strukturę podfolderów i teraz omówię podfoldery o nazwach drawable, co należy prze-
tłumaczyć jako „elementy rysowalne”. Podfoldery drawable zawierają elementy graficzne
aplikacji, a także pewne ich opisy, jakby opakowania, przygotowane w języku XML.

Rozpocznij nowy projekt. Wybierz opcję File, a dalej New i Android Application Project.
Określ nazwę aplikacji (nazwa ta będzie widoczna w Twoim telefonie), nazwę projektu
w środowisku Eclipse, wymyśl nazwę dla pakietu Javy, najlepiej stosując zasadę od-
wracania kolejności wyrazów z nazwy domeny internetowej. Jeśli zainstalowałeś kilka
różnych wersji Androida, wskaż jedną z nich jako zalecaną dla Twojej aplikacji. Określ
też minimalne parametry platformy, rozwojowo najstarszej i najuboższej, dla których
Twoja aplikacja powinna działać.

Środowisko Eclipse utworzy rodzinę folderów. W tym rozdziale najważniejsze będą


dla Ciebie foldery drawable, znajdujące się w folderze res.

Foldery drawable zawierają elementy graficzne aplikacji, przygotowane do wyświetla-


nia na różnej klasy urządzeniach. Jeszcze niedawno były trzy foldery drawable, teraz
jest ich więcej, bo pojawiają się coraz lepsze urządzenia mobilne. Postfiks -ldpi nale-
ży rozwinąć jako low dot per inch, co w tym wypadku oznacza „urządzenie o małej
40 Android. Podstawy tworzenia aplikacji

gęstości pikseli”. W folderze drawable-ldpi umieścisz wersje grafik dla kiepskich


ekraników. Grafiki te powinny być mniejsze, ale także mogą zawierać mniej detali.

Katalogi z postfiksami -mdpi, -hdpi i -xhdpi zawierają zasadniczo te same grafiki, ale
coraz większe, staranniejsze, z większą ilością detali.

Widzisz więc, jaką ścieżką poszli twórcy systemu. Zamiast zastosować algorytm do-
pasowywania grafiki do rzeczywistego rozmiaru ekranu, dali programiście szansę,
aby sam przygotował kilka wersji rozwiązań graficznych. Takie posunięcie umożli-
wia przygotowanie równie pięknych wersji aplikacji na malutki telefon i na olbrzymi
tablet.

Rodzina folderów drawable powinna zawierać zasoby o tych samych nazwach. System
przeanalizuje dane o jakości ekranu urządzenia mobilnego i sam ustali, do którego
folderu drawable skierować program po zasób o konkretnej nazwie (rysunek 3.1).

Rysunek 3.1.
Rodzina folderów
drawable

Nie ma dokładnych wytycznych, jak sporządzać i dzielić zasoby na poszczególne foldery


drawable. Przyjmuje się, że jeśli dla urządzenia typowego, czyli średniego -mldpi, przy-
gotuje się mapę bitową o boku np. 400 pikseli, to dla urządzenia małego -ldpi trzeba
będzie ją zmniejszyć do 75 procent, dla urządzenia dużego -hdpi powiększyć do 150 pro-
cent, a dla ekstradużego -xhdpi do 200 procent. Oprócz samego zmieniania rozmiarów
możesz „ręcznie” zadbać o więcej albo mniej szczegółów. To jest już praca artystyczna.
Rozdział 3.  Graficzne zasoby aplikacji 41

Emulatory o ekranach różnej jakości


Żeby móc testować zachowanie się programu na urządzeniach różnej klasy, należy zde-
finiować kilka wirtualnych AVD (Android Virtual Device), różniących się klasą ekra-
nów. Prawdopodobnie pierwsze urządzenie zdefiniowałeś jako default, czyli domyśl-
ne, teraz więc postaraj się o wyraźnie lepszy albo gorszy telefon (rysunek 3.2). W tym
celu kliknij ikonkę menedżera urządzeń i stwórz sobie wirtualne urządzenie z małym
i dużym ekranem.

Podczas opisywania typu nowego urządzenia zwróć uwagę na techniczne oznaczenia


rodzajów wyświetlaczy. Oznaczenie najgorszego ekranu to QVGA, lepszego to HVGA
i wreszcie najlepszego WVGA (rysunek 3.3). Na rysunku 3.4 widać różnice między
najgorszym a najlepszym ekranem.

Rysunek 3.2. Tworzenie wirtualnego urządzenia z małym i dużym ekranem


42 Android. Podstawy tworzenia aplikacji

Rysunek 3.3.
Nadanie nazwy nowemu
urządzeniu, wybór
platformy i co
najważniejsze —
określenie jej ekranu

Rysunek 3.4.
Urządzenie QVGA na tle
urządzenia WVGA800
Rozdział 3.  Graficzne zasoby aplikacji 43

Bitmap, czyli mapa bitowa


Po tych czynnościach technicznych spróbujesz wreszcie pracy z grafikami, których ja-
kości są dopasowane do możliwości technicznych urządzenia.

Znajdź jakiś obrazek, którego większy bok ma rozmiar ok. 400 pikseli. Będzie to pod-
stawowa wersja grafiki, którą umieścisz w katalogu z postfiksem -mdpi. Za pomocą
edytora grafiki — np. IrfanView — przygotuj wersję -ldpi tego obrazka o boku 300 pik-
seli i wersję -hdpi o boku 500 pikseli. Możesz przygotować jeszcze wersję ekstra -xhdpi
dla superurządzeń, ale zalecany tutaj, trochę przestarzały, choć ciągle najpopularniejszy
Android 2.2 nie obsłuży takiego standardu.

Wersje obrazka należy jakoś oznaczyć, np. nadrukowując na nie w programie graficz-
nym napisy ldpi, mdpi, hdpi (rysunek 3.5). Dzięki temu będziesz mógł śledzić, jak kon-
kretne urządzenie wybiera sobie grafikę do wyświetlenia.

Rysunek 3.5.
Obrazki o identycznych
nazwach, ale różniące
się rozmiarem
(w proporcjach mniej
więcej 0,75:1:1,5)

Przygotowane obrazy należy zapisać pod tą samą nazwą, ale w różnych folderach
drawable, koniecznie zwracając uwagę, aby obrazek o odpowiedniej jakości znalazł
się w odpowiednim folderze (rysunek 3.6). Po rozmieszczeniu plików w folderach śro-
dowisko Eclipse może początkowo ich nie dostrzec — wybierz z menu File opcję Refresh
(odśwież widok). Zapamiętaj, bo jest to ważne podczas przygotowywania zasobów
aplikacji.

Gdy teraz program przywoła obrazek o nazwie np. motor1.jpg, system Android spraw-
dzi, jakim ekranem dysponuje urządzenie, skieruje się do odpowiedniego folderu
drawable i wyciągnie stamtąd odpowiednią wersję pliku motor.1. Sprawdź to.
44 Android. Podstawy tworzenia aplikacji

Rysunek 3.6.
Rodzina folderów
drawable zawiera pliki
o takich samych nazwach,
ale różniące się jakością
i przeznaczone na różne
typy wyświetlaczy
urządzeń mobilnych

Zbuduj najprostszą aplikację, która wyświetli jeden z obrazków. Tak jak poprzednio, za-
sadniczym „polem bitwy” jest główny plik wyglądu, umieszczony w folderach res/layout.
Jeśli nie zmieniałeś nazw w kreatorze nowego projektu, plik ten powinien się nazywać
activity_main.xml. Kliknij go dwukrotnie, otwierając go do edycji. Na dole edytora wy-
bierz edycję wizualną (nie zaś zwykłą, tekstową).

Jeśli już zaczyna Cię denerwować napis „Hello World”, reprezentowany przez kompo-
nent TextView, kliknij go prawym klawiszem myszki i usuń z aplikacji.

W zakładce Images & Media znajdź komponent ImageView i przeciągnij go w pole wy-
świetlacza urządzenia mobilnego. Jeśli umieścisz go dokładnie w rogu pola, system
od razu ustawi właściwości alignParent... (wyrównaj w dostępnej przestrzeni — tutaj:
w przestrzeni całego ekranu). Możesz umieścić ImageView w inny sposób — wtedy
system zaproponuje właściwości center... albo margin... (rysunek 3.7).

Po umieszczeniu komponentu w polu ekranu prawdopodobnie automatycznie pojawi


się Resource Chooser (wybieranie zasobu). Jeśli zaznaczono Project Resources (a nie
System Resources — przebogate zasoby własne Androida), okienko Choosera zajrzy do
Twoich katalogów drawable i zaproponuje wybór któregoś obrazka (rysunek 3.8).

Jeśli postępowałeś tak, jak pokazałem to na kilku ostatnich rysunkach, plik wyglądu apli-
kacji activity_main.xml powinien mieć następującą, Zapewne już Ci znaną treść (wi-
doczną po wybraniu odpowiedniej zakładki pod edytorkiem wizualnym):
Rozdział 3.  Graficzne zasoby aplikacji 45

Rysunek 3.7.
Praca na zakładce
Images & Media

Rysunek 3.8.
Wybieranie zasobu
z katalogów drawable

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >

<ImageView
android:id="@+id/imageView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:src="@drawable/motor1" />
</RelativeLayout>
46 Android. Podstawy tworzenia aplikacji

W tej aplikacji obowiązuje nieomawiany tutaj rozkład RelativeLayout (określaj położe-


nia kolejnych komponentów przez odwołania do innych, wcześniej rozlokowanych).
Rozkład ten ma jedną szczególną cechę — wszystkie komponenty powinny być na-
zwane, tak aby inne mogły się do nich odwoływać, np. wskazując „chcę być wyrów-
nany do lewej strony tamtego buttona”. W następnym kroku dodasz więcej kompo-
nentów i zobaczysz, jak pracuje przyjemny w edycji RelativeLayout.

Nazwa naszego obrazka brzmi imageView1 i określa ją linia:


android:id="@+id/imageView1"

Od tej pory będziesz nazywać wszystkie komponenty, czego w zasadzie wymaga rozkład
RelativeLayout i czego bezwzględnie będą wymagały techniki programowania w Javie,
do których nieuchronnie się zbliżasz. Rozmiary komponentu w omawianym przypad-
ku są zdefiniowane frazami wrap_content, czyli „otaczaj swoją zawartość” albo „bądź
tak duży, jak twoja zawartość” — w tym przypadku mapka bitowa z motocyklem.

Z kolei położenie komponentu definiują linie:


android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"

które należy czytać jako: „bądź w lewym górnym rogu swego rodzica”, czyli kompo-
nentu RelativeLayout, który wypełnia cały dostępny dla aplikacji ekran.

Najważniejszą linią jest połączenie komponentu o nazwie imageView1 z mapą bitową


motor1.jpg znajdującą się w folderach drawable:
android:src="@drawable/motor1" />

Zobacz, jak wygląda omawiana aplikacja na ekraniku AVD, czyli wirtualnej maszyny
Androida. Pamiętaj jednak, że jest kilka maszyn AVD. Jak wybrać konkretną? W menu
Run znajdziesz polecenie Run Configurations i tam wskaż maszynę, na której uruchomisz
program (rysunek 3.9). Na zakładce Target (na czym uruchomić) powinieneś znaleźć
swoje urządzenia AVD i zaznaczyć jedno z nich.

Teraz pozostało sprawdzić działanie programu (rysunek 3.10). Wszystko gra! Po uru-
chomieniu programu na dwóch różnych urządzeniach dostajesz tę samą fotografię
motocykla. Czy na pewno tę samą? Napis w lewym górnym rogu odkrywa tajemnicę
— mapy bitowe zostały pobrane z różnych folderów drawable!

Być może zauważyłeś, że przy komponencie obrazka albo przy jego opisie w pliku XML
znajduje się malutki żółty trójkącik uwagi, którą chce Ci podpowiedzieć kompilator:
„Missing contentDescription attribute.on image” (zapomniałeś o parametrze contentDe-
scription) — rysunek 3.11. Każdy element wizualny powinien być opisany, a opis ten
jest przeznaczony do automatycznego odczytywania syntetycznym głosem dla kogoś,
kto nie może zobaczyć motocykla, np. dla osoby niewidomej. W dalszej części nie
będziesz dodawać atrybutu contentDescription.
Rozdział 3.  Graficzne zasoby aplikacji 47

Rysunek 3.9. Menu Run i dalej Run Configurations pozwala opisać szczegóły uruchamiania aplikacji

Rysunek 3.10.
Dwa różne urządzenia
i taka sama fotografia?
Napis w lewym górnym
rogu wskazuje, że mapy
bitowe zostały pobrane
z różnych folderów
drawable
48 Android. Podstawy tworzenia aplikacji

Rysunek 3.11.
Trójkącik uwagi, którą
podpowiada kompilator

Mapa bitowa opakowana


w atrybuty XML
To, co zrobiłeś do tej pory z mapą bitową, można nazwać wyświetleniem surowego
obrazka. Było to dobre i zazwyczaj jest wystarczające. Jednak można pójść o krok dalej,
mianowicie: w folderach drawable, gdzieś obok obrazków, umieścić informację w ro-
dzaju: „zrób z tego kafelki”, „zastosuj antyaliasing”, czyli wygładzanie, „zawsze rozcią-
gaj, gdy jest miejsce” albo „przycinaj, gdy trzeba”. Jest to jakby wstępna obróbka surowej
mapy bitowej za pomocą samych opisów, czyli jeszcze bez programowania w Javie.

Żeby zapoznoać się tą technologią, sporządź bitmapowe tło ekranu. Przygotuj najpierw
odpowiedni „kafelek” w wersjach -ldpi, -mdpi i -hdpi (tak naprawdę może to być do-
wolny, mały obrazek) i poszczególne wersje umieść w katalogach drawable.

Potem przygotuj plik XML, który dodatkowo opisze naszą mapę bitową. Plik taki po-
winien znaleźć się w jednym, wspólnym dla wszystkich maszyn folderze drawable. Kre-
ator tego pliku — opakowania na mapę bitową — automatycznie utworzy odpowiedni
folder. Jak dodać zasób XML do folderów drawable? Kliknij prawym klawiszem który-
kolwiek folder drawable, wybierz opcję New i dalej Android XML File (rysunek 3.12).

W kreatorze pliku XML dokonaj kilku zaznaczeń (albo — gdy zostanie już utworzony
— zmień w pliku kilka rzeczy ręcznie). Wpisz nazwę nowego zasobu, którym będzie
teraz plik XML. Typem zasobu niech będzie bitmap (rysunek 3.13).
Rozdział 3.  Graficzne zasoby aplikacji 49

Rysunek 3.12.
Dodawanie zasobu XML
do folderów drawable

Rysunek 3.13.
Tworzenie nazwy
i wybór typu nowego
zasobu XML

Plik XML nie został wstawiony do znanej już Ci rodziny folderów drawable-..., ale do
nowego, wspólnego folderu o nazwie po prostu drawable. Jak się pewnie domyślasz,
folder drawable zawiera zasoby graficzne wspólne dla wszystkich maszyn, niezależnie
od kategorii ich wyświetlaczy.

Nowy zasób będzie wspólny dla wszystkich maszyn, ale nie do końca. Okaże się (jak
znów nietrudno się domyślić), że ostateczna wersja obrazka nadal będzie wyjmowana
z odpowiedniego folderu. Wspólne jest opakowanie XML, ale prawdziwa fotografia
nadal ma wiele wersji.
50 Android. Podstawy tworzenia aplikacji

Przyjrzyj się więc plikowi XML opakowującemu mapkę bitową. Najpierw, po zakoń-
czeniu pracy kreatora, plik ten wygląda tak:
<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android" >
</bitmap>

Do wnętrza tagu <bitmap> wpisz najważniejszy i bezwzględnie konieczny atrybut,


którego wartością jest nazwa opakowywanej mapki bitowej, tutaj kafelki:
<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@drawable/kafelki">
</bitmap>

Zatem zdefiniowałeś zasób nowego typu — jest to plik XML opakowujący prawdzi-
wy zasób graficzny. Teraz go wykorzystasz jako tło aplikacji (rysunek 3.14). Niech
główny plik wyglądu, o nazwie prawdopodobnie activity_main.xml (taką nazwę na-
daje mu na samym początku kreator nowej aplikacji), ma następującą treść, napisaną
„ręcznie” albo zbudowaną w edytorze wizualnym:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/kafelki_xml">
<ImageView
android:id="@+id/imageView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:src="@drawable/motor1" />
</RelativeLayout>

Jest to w zasadzie to samo, co było poprzednio, zmieniono tylko sposób ułożenia obraz-
ka na ekranie — teraz nie znajduje się on w lewym górnym rogu, ale na środku. I co
najważniejsze, pojawiła się linia:
android:background="@drawable/kafelki_xml">

Podobną frazę już kiedyś stosowałeś, nadając kolor podłożu aplikacji:


android:background="#000080">

Nie byłoby sensu opakowywać w taki sposób plików graficznych, gdyby po drodze nie
pojawiły się nowe możliwości. Uzupełnij plik XML o atrybut gravity (czyli o informację
w którą stronę obrazek „ciąży”, gdy ma na ekranie miejsce):
<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@drawable/kafelki"
android:gravity="fill">
</bitmap>
Rozdział 3.  Graficzne zasoby aplikacji 51

Rysunek 3.14.
Mapa bitowa została
opakowana w plik XML,
który z kolei został użyty
do budowy tła aplikacji

Atrybut gravity może przyjmować dość oczywiste wartości: top, bottom, left, right,
center_vertical, fill_vertical, center_horizontal, fill_horizontal, center, fill (jest
to wartość domyślna, ustawiana, gdy nie użyjesz atrybutu gravity), clip_vertical,
clip_horizontal.

Jeszcze ciekawszym atrybutem jest tileMode. Atrybut ten nie może być użyty razem
z poprzednim gravity — czyli stosujesz albo jeden, albo drugi:
<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@drawable/kafelki"
android:tileMode="repeat">
</bitmap>

Atrybut tileMode oprócz wartości repeat może przyjmować stany clamp (nie kafelkuj,
tylko rozciągaj krawędzie) i mirror (kafelkuj, ale lustrzanie obracając co drugie rzędy
i wiersze kafelków).

Oprócz atrybutów gravity (jak ułożyć obrazek na ekranie) oraz tileMode (jak zbudować
kafelki z obrazka) masz do dyspozycji trzy atrybuty (rysunek 3.15) definiujące jakość
obrazu i przyjmujące wartości true albo false: android:antialias (wygładzanie),
android:dither (poprawianie palety kolorów) i android:filter (poprawianie obrazu
przy ściskaniu albo rozciąganiu mapy bitowej).
52 Android. Podstawy tworzenia aplikacji

Rysunek 3.15.
Przy opakowywaniu
pliku graficznegomożna
zastosować różne
atrybuty (gravity,
tileMode,
android:antialias,
android:dither,
android:filter)

Wiele map bitowych


w jednym opakowaniu XML
A jeśli chciałbyś mieć przycisk, na którym znajduje się zmieniająca się mapa bitowa:
inna, gdy myszka wisi nad przyciskiem, inna, gdy go klika, inna, gdy go opuszcza? Za-
gadnienie to da się zrealizować bez programowania, za pomocą opisów w odpowied-
nich plikach XML. Gdybyś chciał poczytać o tym więcej, wpisz w Google frazy „Android
State List” (lista stanów).

Zacznij od przygotowania prawdziwych zasobów, czyli teraz serii mapek bitowych,


przedstawiających np. rybkę w trzech różnych kolorach. Potem przygotuj tę serię ry-
bek w różnych rozmiarach i, być może, różnych jakościach, przeznaczonych na urzą-
dzenia o różnej jakości wyświetlaczy (rysunek 3.16). Te pliki — tak jak poprzednio
— umieść w folderach drawable-ldpi, drawable-mdpi itd. W zależności od stanu przy-
cisku (zaraz odpowiedni przycisk zostanie wprowadzony do gry) system Android umieści
na nim inną wersję mapki bitowej. Powinieneś też przygotować pomniejszone i powięk-
szone wersje obrazków i porozmieszczać je w folderach drawable-ldpi, drawable-mdpi
i drawable-hdpi.
Rozdział 3.  Graficzne zasoby aplikacji 53

Rysunek 3.16.
Przygotowanie obrazka
o boku długości kilkuset
pikseli i różnej
kolorystyce albo
różnicy dotyczącej
jakiegoś innego
czytelnego szczegółu

Mając w folderach drawable-... serię rybek o różnych kolorach, możesz przystąpić do


opisywania Listy stanów. Kliknij prawym przyciskiem myszki folder drawable i z po-
jawiającego się menu wybierz New i dalej Android XML File, czyli plik XML (rysu-
nek 3.17). Przed chwilą w takim pliku opisywałeś jedną mapkę bitową, teraz opiszesz
cały ich stos.

Rysunek 3.17.
Nowy plik XML,
opisujący cały stos
mapek bitowych

Wykorzystaj pojawiający się kreator pliku XML, choć mógłbyś przygotować odpowiedni
plik całkowicie ręcznie. Podaj nazwę pliku, oznacz, że główny element ma być typu
Selector i zakończ przyciskiem Finish (rysunek 3.18). W folderze drawable powinien
pojawić się nowy zasób — plik stos_rybek_xml.
54 Android. Podstawy tworzenia aplikacji

Rysunek 3.18.
Tworzenie nowego
zasobu XML — plik
stos_rybek_xml

Jak zwykle dwa razy kliknij nowy plik, aby otworzyć go do edycji. Uzupełnij jego
treść następująco:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:state_pressed="true"
android:drawable="@drawable/ryba_r" />
<item android:state_focused="true"
android:drawable="@drawable/ryba_b" />
<item android:drawable="@drawable/ryba" />
</selector>

Do wnętrza tagu <selector> wpisz kilka tagów <item>, odpowiadających różnym sta-
nom przycisku, który za chwilę zaimplementujesz. Gdy przycisk jest w stanie pressed,
czyli naciśnięty, powinna się wyświetlać mapka o nazwie ryba_r (rybka o czerwona-
wym odcieniu). Gdy przycisk jest w stanie focused, czyli gotowy do naciśnięcia —
ryba_b. Gdy z przyciskiem nic się nie dzieje albo coś się dzieje, ale tego tutaj nie opisa-
łem, wyświetli się wersja zapisana jako ryba. Jest to tzw. normalny stan przycisku, który
opisuje się zawsze za pomocą ostatniego tagu <item> w powyższym wyliczeniu stanów.

Należy jeszcze zaimplementować sam przycisk oraz podpiąć do niego ten oryginalny
sposób kolorowania jego powierzchni. Główny plik wyglądu z folderu layout powi-
nien mieć następującą zawartość:
Rozdział 3.  Graficzne zasoby aplikacji 55

Rysunek 3.19.
Do poprzedniej aplikacji
należy dodać przycisk
i opracować jego
właściwość background
(tło). Niech tłem będzie
nowy zasób XML zwany
Listą stanów

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/kafelki_xml" >
<ImageView
android:id="@+id/imageView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_marginTop="50dp"
android:src="@drawable/motor3" />
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/imageView1"
android:layout_centerHorizontal="true"
android:background="@drawable/stos_rybek_xml"/>
</RelativeLayout>

Duża część tego pliku jest Ci znana. Jest tu rozkład relatywny, czyli kolejne komponenty
są układane względem poprzednich, ułożonych wcześniej. Rzeczywiście, komponent
Button leży pod komponentem ImageView:
android:layout_below="@+id/imageView1"

Natomiast stos obrazków — zagadnienie opisane na kilku ostatnich stronach — po-


jawia się w linii opisującej tło przycisku:
android:background="@drawable/stos_rybek_xml"/>

Jest to odwołanie do właśnie stworzonego zasobu XML, umieszczonego w folderze


drawable.
56 Android. Podstawy tworzenia aplikacji

Pora na podsumowanie. Znów powtórzył się następujący schemat: gdzieś w pierwotnych


folderach drawable-... znajdują się prawdziwe mapy bitowe, różniące się rozmiarami,
może także jakością grafiki. Różnice między nimi konieczne są z tego względu, że na lep-
sze urządzenia przygotowuje się inną grafikę (grafika, która ma więcej detali i szcze-
gółów) niż na gorsze.

Jakby nad surowymi mapami bitowymi znajdują się pliki XML definiujące specjalne
właściwości rysunku. Pliki te znajdują się we wspólnym dla wszystkich urządzeń katalogu
drawable. Dopiero te pliki wywoływane są w głównym pliku wyglądu. Tak pojawiły
się wcześniej kafelki, a teraz aktywny przycisk zmieniający swój rysunek, gdy klikniesz
go myszką (rysunek 3.20).

Rysunek 3.20.
Tłem przycisku jest
zasób XML, zwany
State List (lista stanów).
Każdy stan to jakiś inny
obrazek — kliknięcie
czy zatrzymanie się na
obrazku zmienia jego
wygląd
Rozdział 4.
Więcej
o wyglądzie aplikacji
W poprzednich rozdziałach omówiłem, w jaki sposób definiuje się tzw. statyczną część
aplikacji — czyli jej wygląd, na który przede wszystkim składają się elementy interfejsu
i ich rozłożenie na ekranie urządzenia. W poprzednim rozdziale, w tym zresztą też,
nie będzie jeszcze programowania.

Zajmę się sytuacją, gdy elementy aplikacji nie mieszczą się na ekranie i — jeśli chce
się zobaczyć całość — trzeba je przesuwać w górę i w dół albo w lewo i w prawo. Po
opanowaniu tej techniki omówię jeszcze kilka nowych nieskomplikowanych zasobów,
reprezentowanych przez pliki XML.

ScrollView — ekran z gumy


Projekt ten znajduje się w pliku Rozdzial_4_a.zip.

Nietrudno wyobrazić sobie sytuację, gdy komponenty do komunikacji z użytkowni-


kiem nie mieszczą się na niedużym ekraniku urządzenia przenośnego. W takim przy-
padku Android proponuje ekran przesuwany, tak aby użytkownik — przesuwając pal-
cem lub kursorem jego zawartość — mógł dostrzec wszystkie elementy.

Ważniejszy jest przesuw ekranu w pionie. Od niego zacznę badania.

Rozpocznij nowy projekt. Po utworzeniu przez znany Ci już kreator wszystkich folde-
rów i zapisaniu wszystkich plików przejdź do ręcznej edycji głównego pliku wyglądu,
znajdującego się w folderze res/layout. Jeśli nie zmieniałeś nazwy, główny plik wy-
glądu to activity_main.xml. Niech ten plik ma następującą zawartość:
58 Android. Podstawy tworzenia aplikacji

<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >

</ScrollView>

Głównym tagiem (zwanym slangowo rootem) niech będzie ScrollView — przesuwany


ekran. Jest to w zasadzie jedyna zmiana w stosunku do pliku wygenerowanego auto-
matycznie przez kreator nowej aplikacji.

Po „ręcznym” wpisaniu tagu głównego ScrollView i otwarciu pliku activity_main.xml


w edytorku wizualnym (zakładka u dołu edytorka), w grupie Layouts znajdź wygląd
tabelaryczny i przeciągnij go do okienka ekranu urządzenia mobilnego. System od razu
powinien wstawić cztery puste wiersze w rozkładzie tabelarycznym (rysunek 4.1).

Rysunek 4.1.
Wybór wyglądu
tabelarycznego skutkuje
tym, że system od razu
wstawia cztery puste
wiersze w rozkładzie
tabelarycznym

Następnym krokiem jest wybór wyglądu całej aplikacji, czyli layoutu. Przypomnij
sobie o rozkładzie TableLayout, w którym poszczególne komponenty są umieszczane
jakby w oczkach niewidocznej tabeli. Jeśli rozkład tabelaryczny wbudowałeś do apli-
kacji za pomocą edytorka wizualnego, uzupełniony plik activity_main.xml ma teraz
następującą zawartość:
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TableLayout
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TableRow
android:id="@+id/tableRow1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
</TableRow>
<TableRow
android:id="@+id/tableRow2"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
</TableRow>
Rozdział 4.  Więcej o wyglądzie aplikacji 59

<TableRow
android:id="@+id/tableRow3"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
</TableRow>
<TableRow
android:id="@+id/tableRow4"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
</TableRow>
</TableLayout>
</ScrollView>

System Eclipse wstawił do środka tagu TableLayout cztery puste wiersze, które teraz
trzeba zabudować czymkolwiek, tak aby prześledzić działanie ekranu przesuwanego
ScrollView.

Proponuję w każdym wierszu tabeli wstawić przycisk i obrazek, czyli komponenty


Button i ImageView. Aby zrobić to sprawnie, musisz teraz zadbać o mapki bitowe.
Postaraj się więc o kilka mapek bitowych o boku np. 300 pikseli i umieść je w folderze
res/drawable-mdpi (pamiętasz, że jest to folder z zasobami dla urządzenia średniej
klasy), a potem te pliki pomniejsz do ok. 75 procent i powiększ do ok. 150 procent
oraz rozmieść w folderach res/drawable_ldpi i res/drawable_hdpi. Możesz te pliki sko-
piować z przestrzeni roboczej — czyli w slangu Eclipse z Workspace — poprzednie-
go projektu.

Po zgromadzeniu zasobów wróć do edytorka wizualnego i starannie zbuduj interfejs


aplikacji, w poszczególne wiersze wyglądu tabelarycznego wstawiając Button i tuż
obok ImageView, i tak czterokrotnie (bo tyle wierszy otrzymałeś od kreatora tabeli —
oczywiście nie jest to jakaś święta liczba). Istotne jest, byś uważnie obserwował sytu-
ację, aby położyć komponenty dokładnie w to miejsce, gdzie potrzeba (rysunek 4.2).
Kolejność przeciągania komponentów ma kolosalne znaczenie, co natychmiast zoba-
czysz w surowym pliku XML.

Rysunek 4.2.
Przeciąganie
komponentu Button
w poszczególne wiersze
tabeli
60 Android. Podstawy tworzenia aplikacji

Plik XML, opracowany technikami wizualnymi, powinien mieć następującą strukturę


(przytaczam opis tylko jednego wiersza tabeli — pozostałe wiersze też zawierają pary
Button i ImageView):
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TableLayout
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TableRow
android:id="@+id/tableRow1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button" />
<ImageView
android:id="@+id/imageView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/motor1" />
</TableRow>
<!-- Tu pozostałe wiersze -->
</TableLayout>
</ScrollView>

Po wykonaniu tych czynności z boku ekranu pojawi się suwak, dzięki któremu można
będzie przesuwać w pionie zawartość ekranu urządzenia mobilnego. Przesuwaj — al-
bo palcem po ekranie dotykowym, czyli myszką na emulatorze, albo przyciskami
strzałek (rysunek 4.3).

Rysunek 4.3.
Z boku ekranu widać
suwak, dzięki któremu
można przesuwać
zawartość ekranu
urządzenia mobilnego
w pionie
Rozdział 4.  Więcej o wyglądzie aplikacji 61

A co z przesuwem ekranu w poziomie, dzięki któremu można implementować „sze-


rokie” interfejsy? Tu musisz już ręcznie wpisać do pliku wyglądu następujące linie
komponentu <HorizontalScrollView>:
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<HorizontalScrollView
android:id="@+id/horizontalScrollView1"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TableLayout
...
</TableLayout>
</HorizontalScrollView>
</ScrollView>

Teraz kombinacja tagów <ScrollView> i <HorizontalScrollView> obejmuje obszar


o wyglądzie zgodnym z tagiem <TableLayout>. Taka kombinacja umożliwia przesu-
wanie ekranu zarówno w pionie, jak i w poziomie.

Kolory
Projekt ten znajduje się w pliku Rozdzial_4_b.zip.

Zauważ, że aplikacje androidowe niezbyt często posługują się „surowymi” zasobami


graficznymi, choćby mapami bitowymi, raczej opakowując je w tzw. zasoby xml. „Su-
rowy” plik graficzny zostaje w ten sposób opatrzony dodatkowymi atrybutami, np.
sposobem kafelkowania. Nie inaczej jest z kolorami.

Rozpocznij nowy projekt i gdy Eclipse utworzy komplet folderów i plików, kliknij pra-
wym klawiszem folder res/values — zasoby będące różnymi danymi. Z menu wybierz
operację New/Android XML File, w wyniku czego otrzymasz nowy plik XML (rysu-
nek 4.4).

W kreatorze nowego pliku XML oznacz rodzaj zawartości values, nadaj mu nazwę
(tutaj: kolory) i wybierz root tag (tag główny XML) resources (rysunek 4.5). Po za-
kończeniu tych operacji w folderze values powinieneś dostrzec nowy plik o nazwie
kolory.xml.

Po zakończeniu pracy kreatora w folderze values pojawi się nowy plik i prawdopodob-
nie od razu otworzy się do edycji w specjalistycznym edytorze zasobów (rysunek 4.6),
z którego to edytora jednak w bardzo prostym zagadnieniu kolorów nie ma zbyt dużego
pożytku... Jak zwykle u dołu edytorka masz dwie zakładki — wykorzystaj je i przejdź
do bezpośredniej edycji „ręcznej”.
62 Android. Podstawy tworzenia aplikacji

Rysunek 4.4.
Folder values
zawiera zasoby
będące jakimiś
wartościami
(np. napisy)

Rysunek 4.5.
Tworzenie pliku
kolory.xml
Rozdział 4.  Więcej o wyglądzie aplikacji 63

Rysunek 4.6.
Po zakończeniu pracy
kreatora w folderze
values pojawi się nowy
plik, a u dołu edytorka
widać dwie zakładki,
które warto wykorzystać
w dalszej pracy

Plik kolory.xml ma bardzo prostą i czytelną strukturę:


<?xml version="1.0" encoding="utf-8"?>
<resources>

</resources>

Wewnątrz tagu <resources> znajdzie się dowolna liczba kolorów, definiowanych


w następujący sposób:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="czerwony">#FF0000</color>
<color name="zielony">#00FF00</color>
<color name="niebieski">#0000FF</color>
</resources>

Są tutaj nazwane kolory. Barwy są zbudowane w tzw. kodzie szesnastkowym. Wartość


FF0000 należy widzieć jako trzy wartości FF, 00 i 00, a każda z nich określa amplitudę
kolejno: czerwieni, zieleni i niebieskiego. Wartości te oznaczają kolor złożony z mak-
symalnego udziału czerwieni oraz zerowego zieleni i błękitu — zatem będzie to kolor
czysto czerwony.

Żeby sprawdzić, jak działają zasoby kolorów, napisz program, który będzie się skła-
dać z dziewięciu różnobarwnych przycisków ułożonych w trzy wiersze i trzy kolumny —
jak plansza do gry w kółko i krzyżyk.
64 Android. Podstawy tworzenia aplikacji

Narzucającym się rozkładem elementów na ekranie jest TableLayout. Przejdź więc do


bezpośredniej edycji głównego pliku wyglądu, jak zwykle znajdującego się w folderze
layout i mającego prawdopodobnie nazwę activity_main.xlm, i zamień RelativeLayout
(wprowadzony przez kreator nowego projektu jako najczęściej używany przez pro-
gramistów Androida) na TableLayout:
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >

</TableLayout>

Po tej „ręcznej” zmianie globalnego rozkładu komponentów wróć (za pomocą zakładek
pod edytorkiem) do edycji wizualnej wyglądu, odszukaj w grupie elementów Layouts
(wyglądy) komponent TableRow i wstaw trzy wiersze do tabeli (rysunek 4.7). Ponie-
waż w samym polu ekranu niewiele widać — wkładane komponenty są tzw. niewi-
docznymi kontenerami pod właściwe komponenty interfejsu użytkownika — kątem
oka obserwuj hierarchię obiektów. Pamiętaj, że źle osadzony obiekt zawsze możesz
usunąć klawiszem Delete. Wreszcie możesz przejść do edytorka tekstowego i ostrożnie
poprawić treść pliku XML.

Rysunek 4.7.
Wstawianie wierszy
w pole ekranu

Plik wyglądu powinien w tym momencie mieć następującą zawartość:


<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TableRow
android:id="@+id/tableRow1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
</TableRow>
<TableRow
Rozdział 4.  Więcej o wyglądzie aplikacji 65

android:id="@+id/tableRow2"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
</TableRow>
<TableRow
android:id="@+id/tableRow3"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
</TableRow>
</TableLayout>

Do każdego wiersza przeciągnij trzykrotnie przycisk Button. Łatwiej przeciągać przyci-


ski na nazwę wiersza w widoku hierarchii obiektów niż w pole ekranu. W każdym razie
zanim upuścisz przycisk, upewnij się, że trafisz nim we właściwy kontener — w tym
przypadku wiersz TableRow (rysunek 4.8).

Rysunek 4.8.
Przeciąganie
przycisku Button
do konteneru
TableRow

Załóżmy, że oczekujesz, aby każde pole miało rozmiar 100×100 jednostek dp (ang.
device independent pixel), czyli tzw. umownych pikseli. Rzecz dość żmudna, gdyby
trzeba było niezależnie opracowywać odpowiednie właściwości dla każdego przycisku...

Trzymając wciśnięty klawisz Shift obklikaj i zaznacz wszystkie przyciski i niczego


więcej (rysunek 4.9). Wtedy kliknij prawym klawiszem myszki i z menu wybierz
właściwość, której nadasz wartość wspólną dla wszystkich zaznaczonych elementów
— tutaj chodzi o rozmiary. W pole wartości rozmiaru wpisz np. 100 dp (100 umownych
pikseli).

Nie będę przytaczać całego, dość długiego i monotonnego kodu. Trzeba tylko wiedzieć,
że każda sekcja wiersza tabeli z poprzedniego listingu zawiera teraz trzy przyciski:
<TableRow
android:id="@+id/tableRow2"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
66 Android. Podstawy tworzenia aplikacji

Rysunek 4.9.
Jednoczesne nadawanie
wartości wspólnej dla
wszystkich zaznaczonych
elementów — tutaj:
rozmiary

<Button
android:id="@+id/button4"
android:layout_width="100dp"
android:layout_height="100dp"
android:text="Button" />

<Button
android:id="@+id/button5"
android:layout_width="100dp"
android:layout_height="100dp"
android:text="Button" />
<Button
android:id="@+id/button6"
android:layout_width="100dp"
android:layout_height="100dp"
android:text="Button" />
</TableRow>

Kolejnym zadaniem jest wykorzystanie kolorów i przypisanie ich do przycisków.


Można to zrobić metodą bezpośredniej edycji pliku XML. Należy do definicji każdego
przycisku dodać jedną linię, np. taką:
<Button
android:id="@+id/button1"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@color/czerwony"
android:text="Button" />

Ale można też wykorzystać możliwości edycji wizualnej, wspierając się tzw. inspek-
torem właściwości komponentu, który służy do precyzyjnego opracowania właściwo-
ści komponentu. Zaznacz komponent (rysunek 4.10). W inspektorze odszukaj właści-
wość Background (kolor tła) — może przy tym pomóc ikonka alfabetycznego
sortowania wszystkich właściwości. Wartości niektórych właściwości trzeba wpisać
Rozdział 4.  Więcej o wyglądzie aplikacji 67

Rysunek 4.10.
Precyzyjne
opracowanie
właściwości
(tutaj: kolor tła)
komponentu za
pomocą inspektora
właściwości
komponentu

bezpośrednio, ale w tym przypadku inspektor domyśla się, o co nam chodzi, i zagląda
do folderów z zasobami. Wybierz więc zasób kolory i kolor o nazwie np. niebieski.
Efekt widać na rysunku 4.11.

Rysunek 4.11.
Różnokolorowe
przyciski

Kolor jest prostym zasobem, ale pokazuje styl pracy androidowego programisty: cier-
pliwe definiowanie kolorów, tekstów, map bitowych dla różnych klas ekranów, potem
opisywanie ich w plikach XML i wreszcie przejście do programowania w Javie.
68 Android. Podstawy tworzenia aplikacji

Shapes — kształty
Projekt ten znajduje się w pliku Rozdzial_4_c.zip.

Oto kolejny zasób reprezentowany przez pliki XML — Shape, czyli kształt. Kształty
służą do urozmaicania wyglądu ekranu.

Rozpocznij nowy projekt, nadaj nazwy aplikacji, projektowi i pakietowi Javy. Ta


ostatnia nazwa musi być unikalna w skali całego świata i przyjęło się ją budować z nazwy
domeny internetowej — tak robi większość programistów. Po drodze utwórz ikonę
dla nowego programu oraz nadaj tytuł aplikacji, który będzie widoczny u góry ekranika
urządzenia mobilnego. Wykonaj wszystkie czynności niezbędne do rozpoczęcia no-
wego zadania programistycznego.

Gdy kreator nowej aplikacji utworzy wszystkie foldery i pliki, kliknij prawym przyci-
skiem myszki którykolwiek folder drawable i wybierz operację New i dalej Android
XML File. W kreatorze pliku XML wpisz nazwę i główny tag określ jako shape —
kształt (rysunek 4.12). Powinieneś otrzymać następującą treść pliku XML:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" >

</shape>

Rysunek 4.12.
Nowy plik XML będzie
zawierał definicję
elementu shape —
kształt
Rozdział 4.  Więcej o wyglądzie aplikacji 69

Niech nowy, jeszcze nie do końca przygotowany zasób XML definiuje tło aplikacji
(rysunek 4.13). W edytorku wizualnym zaznacz tło i opracuj właściwość Background
— obraz tła. Jako Background wskaż nowy plik XML, zawierający kształt (shape).

Rysunek 4.13.
Definiowanie tła
aplikacji

Plik ten podłącz do głównego pliku z folderu layout — definicji wyglądu:


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/tlo_aplikacji" >
</RelativeLayout>

Wróć do edycji pliku z zasobem shape. Być może nieskomplikowaną edycję tego pli-
ku powinieneś przeprowadzić ręcznie, ale przyjrzyj się pewnym ułatwieniom ofero-
wanym przez środowisko Eclipse. Kolejne rysunki (rysunki 4.14 – 4.16) ilustrują no-
wą technikę edycyjną, polegającą przede wszystkim na wyszukiwaniu potrzebnych
elementów na listach. Należy pamiętać przy tym o dwóch rzeczach:
 niech kursor stoi dokładnie w tym miejscu pliku XML, w którym oczekujesz
dopisania czegokolwiek;
 klawisze Ctrl+Spacja otwierają listę możliwych do wstawienia w to miejsce
fraz XML.

Taka edycja pliku XML polega na wskazywaniu fraz, które powinny być wstawione
w jakieś konkretne miejsce. „Ręczna” edycja jest potrzebna tylko w tych momentach,
kiedy należy wpisać jakąś konkretną wartość — np. szerokość komponentu.
70 Android. Podstawy tworzenia aplikacji

Rysunek 4.14.
Kursor znajduje się
wewnątrz otwierającego
plik tagu <shape>,
np. przed znakiem jego
końca, po naciśnięciu
klawiszy Ctrl+Spacja
pojawi się menu
możliwych atrybutów

Rysunek 4.15.
Kursor ustawiony
dokładnie pomiędzy
cudzysłowami,
przeznaczonymi
na wartość ostatnio
wprowadzonego atrybutu.
Po naciśnięciu klawiszy
Ctrl+Spacja tutaj jest
wybierany rectangle
(prostokąt)

Rysunek 4.16.
Kursor ustawiony między
tagami <shape>, po
naciśnięciu Ctrl+Spacja
wybierane corners
(narożniki). Jeśli narożniki
mają być zaokrąglone,
należy wybrać atrybut
radius (promień) i nadać
mu określoną wartość
(np. 10 dp) — wtedy rogi
prostokąta zaokrąglą się.
Można zaokrąglić nie
wszystkie rogi, a tylko
któryś wybrany

Odpowiednio przesuwając kursor i operując klawiszami Ctrl+Spacja, uzyskaj mniej


więcej następującą treść pliku definiującego prostokąt:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="10dp"/>
Rozdział 4.  Więcej o wyglądzie aplikacji 71

<gradient android:startColor="#0000FF"
android:endColor="#000040"
android:angle="90"/>
<stroke android:width="10dp"
android:color="#FFFFFF"/>
</shape>

Definicja ta zawiera opis jednego z kształtów — prostokąta. Prostokąt ma rogi za-


okrąglone promieniem 10 umownych pikseli. Występuje tzw. gradient kolorów, czyli
przelewanie barwy, w tym przypadku od jasnoniebieskiej do ciemnoniebieskiej. Atry-
but angle określa kierunek przelewania i dla wartości 90 jest to przelewanie od dołu
do góry (wypróbuj inne kąty gradientu). Tag <stroke> definiuje obwódkę — tutaj
określone zostały wartości jej atrybutów szerokość i kolor (rysunek 4.17).

Rysunek 4.17.
Prostokąt tła ma
gradient koloru,
zaokrąglone rogi i biały
margines

Pobaw się dalej kształtami. Przygotuj dwa nowe pliki XML definiujące dwa kształty,
które przeznaczysz do innych zadań.

Plik tekst.xml:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient android:startColor="#807000"
android:endColor="#F0A000"
android:angle="90"/>
<stroke android:width="1dp"
android:color="#FFFFFF"/>
</shape>
72 Android. Podstawy tworzenia aplikacji

Kształt ten będzie tłem komponentów ekranowych — np. pola tekstowego. Drugi plik
o nazwie linia.xml zostanie wykorzystany do zbudowania odkreślnika — linii poziomej.

Plik linia.xml ma następującą treść:


<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient android:startColor="#202020"
android:endColor="#202020"
android:angle="0"
android:centerColor="#d0d0d0"/>
</shape>

W plikach tych są kształty prostokątne, zawierające kolor gradientowy. W pierwszym


pliku kolor zmienia się pionowo (android:angle = "90"), w drugim poziomo. Drugi
kształt jest najjaśniejszy na środku (android:centerColor = "#d0d0d0"). Dodatkowo
pierwszy kształt ma obwódkę, zdefiniowaną tagiem <stroke>.

Wykorzystaj edytor wizualny i dodaj do głównego pliku wyglądu (plik ten znajduje
się w folderze layout) dwa pola tekstowe z właściwością Background (tło) równą zdefi-
niowanym kształtom. Potem dodaj pole tekstowe bez tekstu, szerokie na cały ekran i wy-
sokie na kilka pikseli — będzie to linia oddzielająca pozioma (rysunek 4.18).

Rysunek 4.18.
Trzy pola tekstowe
TextView (jedno tak
wąskie, że stało się
linią) oraz kontener
LinearLayout
z wypełnienieniami
typu shape

Jeśli budujesz interfejs aplikacji w edytorku wizualnym, staraj się od razu opracowy-
wać szczegóły komponentu w inspektorze właściwości (rysunek 4.19). Zaznacz kom-
ponent, upewnij się, spoglądając kątem oka na widok drzewa XML, że zaznaczyłeś wła-
ściwy obiekt, i odszukaj potrzebną właściwość w inspektorze (w omawianym przypadku:
Background).
Rozdział 4.  Więcej o wyglądzie aplikacji 73

Rysunek 4.19.
Przy budowaniu
interfejsu aplikacji
w edytorku
wizualnym dobrze
jest od razu
opracowywać
szczegóły
komponentu
w inspektorze
właściwości

Opracowując właściwość, możesz też postępować inaczej. Kliknij wybrany kompo-


nent prawym klawiszem myszki i w potężnym menu (bo właściwości jest dużo) od-
szukaj potrzebną właściwość oraz opracuj jej wartość (rysunek 4.20).

Rysunek 4.20.
Inny sposób
opracowywania
wartości wybranej
właściwości
74 Android. Podstawy tworzenia aplikacji

Być może lepszym sposobem na zbudowanie cieniowanej linii oddzielającej jest wy-
korzystanie kontenera LinearLayout. Kontenery oczekują na umieszczenie w nich in-
nych komponentów, ale można wykorzystać je do budowy cieniowanej linii oddzie-
lającej, ponieważ mają atrybut Background.

Treść głównego pliku wyglądu activity_main.xlm może wyglądać mniej więcej tak:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/tlo_aplikacji" >

<TextView
android:id="@+id/textView1"
android:layout_width="80dp"
android:layout_height="30dp"
android:layout_marginLeft="50dp"
android:layout_marginTop="50dp"
android:padding="5dp"
android:background="@drawable/tekst"
android:text="Tekst 1" />

<TextView
android:id="@+id/textView2"
android:layout_width="match_parent"
android:layout_height="5dp"
android:layout_below="@+id/textView1"
android:layout_marginTop="10dp"
android:background="@drawable/linia" />

<TextView
android:id="@+id/textView3"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_alignLeft="@+id/textView1"
android:layout_below="@+id/textView2"
android:layout_marginTop="10dp"
android:padding="5dp"
android:background="@drawable/tekst"
android:text="Tekst2" />

<LinearLayout
android:layout_width="match_parent"
android:layout_height="20dp"
android:layout_marginTop="10dp"
android:layout_below="@+id/textView3"
android:orientation="vertical"
android:background="@drawable/linia">
</LinearLayout>
</RelativeLayout>
Rozdział 4.  Więcej o wyglądzie aplikacji 75

Aplikacja posługuje się rozkładem ekranowym RelativeLayout i można spodziewać się,


że pierwszy komponent będzie rozmieszczony we wskazanym miejscu ekranu, a po-
zostałe mogą się do niego odwoływać na zasadzie: „bądź umieszczony pod poprzed-
nim komponentem”, „wyrównaj swoją lewą stronę do lewej strony poprzedniego
komponentu” itp.

RelativeLayout zazwyczaj wymusza też nazywanie komponentów:


android:id="@+id/textView1"

Wtedy następny komponent może odwołać się do bieżącego za pomocą linii:


android:layout_below="@+id/textView1"

co akurat oznacza „leż pod textView1”.

Pierwszy komponent TextView ma bezpośrednio podane rozmiary i odległości od lewego


i górnego brzegu ekranu (w omawianym przykładzie są to odpowiednio wartości 80×30
oraz 50 i 50 umownych pikseli). Wewnątrz pola tekstowego tekst ma być odległy o 5 pik-
seli od krawędzi. Tło jest kształtem określonym w pliku tekst.xml i umieszczonym
w folderze drawable.

Drugi komponent TextView jest trochę dziwny — rozciąga się na całą szerokość ekra-
nu, ale ma tylko 5 pikseli wysokości. Nad sobą ma poprzedni komponent (o nazwie
textView1), ale z marginesem 10 pikseli u góry — zatem nie dotyka do poprzedniego
pola tekstowego. Barwiony jest kształtem z najjaśniejszym odcieniem w środku. Nie
ma określonego tekstu do wyświetlania, bo też raczej nie do tego służy — jest to linia
oddzielająca inne komponenty od siebie.

Trzeci komponent też jest typu TextView, ma ustalone rozmiary, ale położenie jest okre-
ślone relatywnie (czyli zgodnie z duchem obowiązującego tu wyglądu RelativeLayout).
Z lewej strony jest wyrównany do pierwszego tekstu, nad sobą ma drugi tekst (czyli:
linię). Nie dotyka jednak tego drugiego tekstu, bo nad nim pozostawiono 10 pikseli
marginesu.

Ostatni komponent też jest elementem graficznym, ale zbudowanym na podstawie


kontenera LinearLayout. Kontener ten wypełnia całą szerokość ekranu, ma bezwzględnie
określoną wysokość na 20 pikseli, nad sobą ma poprzedni tekst, ale oddalony o margines
równy 10 pikseli. Do barwienia użyto kształtu z pliku linia.xml.

Ostatni komponent — linia zbudowana z wąskiego rozkładu LinearLayout — to pew-


nego rodzaju sztuczka. LinearLayout nie jest komponentem wizualnym (jak przycisk
czy tekst), ale ma właściwość Background (tło) i może zająć dowolne miejsce na ekranie.
To wystacza, aby z kontenera na inne komponenty zrobić kształt — cieniowaną linię
oddzielającą elementy zabudowy ekranu.
76 Android. Podstawy tworzenia aplikacji
Rozdział 5.
Programowanie
czas zacząć!
W poprzednich rozdziałach poznawałeś zasoby aplikacji — mapy bitowe, definicje kolo-
rów, kształty. Omawiane też były komponenty zabudowy ekranu — w slangu zwane
widgetami, takie jak: przyciski, pola tekstowe, obrazy. Opracowywana aplikacja mogła co
najwyżej ładnie wyglądać, ale jeszcze nie działała — nic się nie działo np. po naciśnięciu
przycisku.

Była to statyczna część programowania. Przyszedł moment na zajęcie się częścią dy-
namiczną.

Przycisk „Koniec”
Projekt ten znajduje się w pliku Rozdzial_5_a.zip.

Zacznij od oprogramowania akcji, która kończy działanie programu, czyli zamyka aplika-
cję. Niech inicjatorem tej akcji będzie zdarzenie kliknięcia przycisku, czyli: niech
program zakończy działanie, gdy klikniesz wybrany przycisk.

W znany Ci już sposób rozpocznij nowy projekt, wybierając z menu File operację New
i dalej Android Application Project. Gdy kreator utworzy wszystkie foldery i pliki no-
wego projektu, przejdź do wizualnego edytorka wyglądu i wstaw w pole ekranu przycisk.
Niech na przycisku będzie napis „Koniec” (rysunek 5.1). Kliknięcie tego przycisku
spowoduje zamknięcie aplikacji.
78 Android. Podstawy tworzenia aplikacji

Rysunek 5.1.
Wstawianie
przycisku z napisem
„Koniec” w nowym
projekcie

Plik wyglądu activity_main.xml (jeśli nie zmieniłeś nazwy w kreatorze nowego projek-
tu) może wyglądać następująco:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >

<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_marginBottom="30dp"
android:layout_marginRight="30dp"
android:onClick="kliknieto"
android:text="Koniec" />

</RelativeLayout>

Sekcja opisująca przycisk nadaje mu identyfikator button1, a widoczny tam dziwny


zlepek znaków @+id/ oznacza polecenie, aby środowisko Eclipse zajęło się tym auto-
matycznie. Identyfikator zostanie dopisany do istniejącej listy identyfikatorów. Iden-
tyfikowanie wszelkich przycisków, tekstów, obrazków — czyli możliwość ich przy-
woływania, gdy będą potrzebne, staje się coraz ważniejsza, bo właśnie zaczynasz
programować!

Przycisk ma być tak duży, aby zmieścił treść napisu Koniec.

Przycisk jest wyrównany do dołu i do prawej strony ekranu, ale z pozostawieniem 30-
pikselowych marginesów. Można to rozegrać inaczej, jednak w Androidzie raczej
wystrzegaj się bezwzględnych określeń w rodzaju „leż na 450. pikselu!”, bo nigdy nie
wiesz, na jakim ekraniku program będzie uruchamiany. Wspomniany tu 450. piksel
Rozdział 5.  Programowanie czas zacząć! 79

może znaleźć się poza ekranem... Jeśli to tylko możliwe, opisuj lokalizacje ekranowe
relatywnie, np. mówiąc „leż w odległości 30 pikseli od prawej strony”.

W pliku wyglądu uzupełnij definicję przycisku o nazwę metody, która wejdzie do


gry, gdy ktoś kliknie przycisk (rysunek 5.2). Metodę o tej właśnie nazwie za chwilę
napiszesz w języku Java. Ta metoda będzie zawierać algorytm, który wykona się po
kliknięciu przycisku.

Rysunek 5.2.
Uzupełnianie (w pliku
wyglądu) definicji
przycisku o nazwę
metody, która zostanie
użyta, gdy ktoś kliknie
przycisk

Najważniejszą, nową i rewolucyjną linią jest:


android:onClick="kliknieto"

Oto otworzyłeś wrota do niezwykłego świata programowania. Gdy ktoś kliknie przycisk,
niech sterowanie przejdzie do funkcji (tutaj lepiej będzie napisać w slangu progra-
mowania obiektowego: do metody) o nazwie kliknieto().

Program powinien się uruchomić, ale w momencie kliknięcia przycisku zgłosi wyjątek
(rysunek 5.3). W programie brakuje metody, której nazwę przypisałeś do właściwości
onClick („kliknięto przycisk”). Gdzie jest metoda kliknieto()? Cóż — musisz ją na-
pisać w języku Java.

Przejdź do pliku źródłowego Javy i ostrożnie, niczego tam nie uszkadzając, spójrz
w jego treść (rysunek 5.4).

Plik MainActivity.java (jeśli nie zmieniłeś nazw w kreatorze nowego projektu) ma na-
stępującą treść:
package pl.twojadomena.rozdzial_5_a;

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
80 Android. Podstawy tworzenia aplikacji

Rysunek 5.3.
Program uruchamia się,
ale w momencie
kliknięcia przycisku
zgłasza wyjątek. Trzeba
odnaleźć plik z kodem
źródłowym Javy
i dopisać w nim nową
funkcję

Rysunek 5.4. Edycja pliku źródłowego Javy

import android.view.View;

public class MainActivity extends Activity


{
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
Rozdział 5.  Programowanie czas zacząć! 81

setContentView(R.layout.activity_main);
}

@Override
public boolean onCreateOptionsMenu(Menu menu)
{
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}

public void kliknieto (View v)


{
finish();
}
}

W kodzie programu, w większości napisanym przez kreator nowego projektu, dopisz


dwie rzeczy:
1. Import algorytmu bibliotecznego (tutaj: klasy) android.view.View.
2. Metodę o nazwie wcześniej przytoczonej w definicji przycisku i o całym
nagłówku (lepiej powiedzieć: o prototypie) jak w powyższym listingu.
We wnętrzu tej metody wywołujesz biblioteczną metodę finish(),
która kończy życie aplikacji. I to już koniec oprogramowywania przycisku.

Warto przy tej okazji wspomnieć o Javie. Każdy program będzie się składał z publicz-
nej klasy o nazwie wpisanej w kreator nowego projektu. W tym przypadku jest to kla-
sa MainActivity, co można by przetłumaczyć jako „akcja główna”. Jest świętą zasadą
Javy, że algorytm klasy publicznej o nazwie np. MojaKlasa musi być spisany w pliku
o nazwie MojaKlasa.java. Rzeczywiście — w pliku MainActivity.java znajduje się de-
finicja klasy publicznej o nazwie MainActivity (rysunek 5.5).

Rysunek 5.5. Algorytm klasy publicznej musi być spisany w pliku o takiej samej nazwie jak nazwa klasy.
Wynika z tego, że w jednym pliku Javy może być tylko jedna klasa publiczna
82 Android. Podstawy tworzenia aplikacji

Linię tytułową klasy należy przeczytać: „publiczna klasa MainActivity rozszerza bi-
blioteczną klasę Activity”. Słowo kluczowe extends (rozszerza) należy do najważ-
niejszych słów w Javie. W efekcie napisałeś bardzo mało, choć gdyby się przyjrzeć,
Twój program liczy kilkaset linii kodu. Napisałeś tylko to, co poszerzyło ustrój wiel-
kiej klasy Activity.

Dzięki mechanizmowi rozszerzania funkcjonalności programowanie w Androidzie


będzie dodawaniem elementów do już działającego ustroju. To zasadniczo prostsze
niż pisanie czegokolwiek od zera. Budując swoje klasy, będziesz poszerzać (ang.
extends) i implementować (ang. implements) już istniejące klasy biblioteczne. Czasem
też napiszesz własną czystą klasę, która niczego nie poszerza ani nie implementuje.

Zegarek dla ubogich


Projekt ten znajduje się w pliku Rozdzial_5_a.zip.

Przygotuj następny przykład. Niech program po kliknięciu przycisku wyświetli aktualną


datę i czas. Raczej nie zaczynaj nowego projektu, ale dodaj nowe elementy do poprzed-
niego. Umieszczony na ekraniku w poprzednim ćwiczeniu przycisk Koniec niczemu
nie zaszkodzi.

Interfejs nowego projektu będzie zawierać trzy elementy (rysunek 5.6):


 przycisk z napisem Data i godzina;
 pole tekstowe przygotowane do wyświetlenia daty;
 pole tekstowe do wyświetlenia godziny.

Oto możliwa treść pliku wyglądu znajdującego się w folderze layout. Skrócono defi-
nicję przycisku Koniec z poprzedniego przykładu:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:onClick="data_i_godzina" >
<Button
android:id="@+id/button1"
<!-- tutaj jest definicja przycisku Koniec z poprzedniego przykładu -->

<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="40dp"
android:onClick="data_i_godzina"
android:text="Data i godzina" />
Rozdział 5.  Programowanie czas zacząć! 83

Rysunek 5.6. Interfejs programu do odczytu daty i godziny składa się z przycisku aktywującego
cały proces i dwóch pól tekstowych, do których zostaną wpisane teksty aktualnej daty i czasu po ich
odczytaniu. Osadzając elementy w polu ekranu, trzeba zadbać o opracowywanie ich właściwości.
Nie zawsze można ufać widocznemu rysunkowi — lepiej wybierać komponent za pomocą drzewa
w górnym prawym rogu środowiska Eclipse

<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/button2"
android:layout_centerHorizontal="true"
android:layout_marginTop="20dp"
android:text="Data"
android:textColor="#FF0000"
android:textSize="20dp"
android:textStyle="bold" />
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/textView1"
android:layout_centerHorizontal="true"
android:layout_marginTop="20dp"
android:text="Godzina"
android:textColor="#0000FF"
android:textSize="20dp"
android:textStyle="bold" />
</RelativeLayout>
84 Android. Podstawy tworzenia aplikacji

Zgodnie z ideą rozmieszczania komponentów we wnętrzu kontenera RelativeLayout


pierwszy komponent jest ustawiony arbitralnie: na środku w poziomie i u góry ekranu,
ale z marginesem 40 pikseli w pionie, dzięki czemu nie dotyka do górnej krawędzi.

Pierwszy tekst jest ustawiony pod przyciskiem, ale także z marginesem. Drugi tekst
jest ustawiony pod pierwszym tekstem, także z marginesem.

Najważniejszą linią w powyższym jest linia w definicji przycisku:


android:onClick="data_i_godzina"

Rysunek 5.7. Plik z algorytmami w Javie nazywa się MainActivity.java i znajduje się w folderze src.
Do pliku tego należy dopisać ciała dwóch metod — reakcji na kliknięcia dwóch aktywnych przycisków.
Jedna z tych metod zakończy program (jeśli kontynuujesz poprzednie ćwiczenie, metoda ta już jest
napisana), druga zmodyfikuje teksty, aby odpowiadały bieżącej dacie i czasowi

Linia ta decyduje, że po kliknięciu przycisku zostanie wykonany algorytm zapisany


w metodzie o nazwie data_i_godzina(). Zatem przejdź do programowania i przygotuj
w pliku źródłowym Javy definicję takiej metody:
package pl.twojadomena.rozdzial_5_a;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.View;
import android.widget.TextView;
import java.util.*;

public class MainActivity extends Activity


{
@Override
Rozdział 5.  Programowanie czas zacząć! 85

public void onCreate(Bundle savedInstanceState)


{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}

public void kliknieto(View v)


{
finish();
}

public void data_i_godzina(View v)


{
int r, m, d, h, min, s;
Calendar c = Calendar.getInstance( TimeZone.getTimeZone( "GMT+2"));
r = c.get(Calendar.YEAR);
m = c.get(Calendar.MONTH) + 1;
d = c.get(Calendar.DAY_OF_MONTH);
h = c.get(Calendar.AM) + c.get(Calendar.HOUR);
min = c.get(Calendar.MINUTE);
s = c.get(Calendar.SECOND);

TextView d_txt = (TextView) findViewById(R.id.textView1);


d_txt.setText(r + " - " + m + " - " + d);
TextView t_txt = (TextView) findViewById(R.id.textView2);
t_txt.setText(h + " : " + min + " : " + s);
}
}

W tekście powyższym doimportowałem trzy klasy: klasę pola tekstowego TextView


oraz klasy Calendar i TimeZone. Dwie ostatnie z tych klas są spisane w jednym pakiecie,
zatem wystarczyła jedna linia importu:
import java.util.*;

Metoda kliknieto() ma identyczną postać jak poprzednio — nie będę jej omawiać.

Metoda data_i_godzina(), jak pamiętasz: zgłoszona w definicji drugiego przycisku,


ma zmienić teksty dwóch komponentów TextView, tak aby pierwszy wskazywał datę,
a drugi godzinę. W metodzie tej deklarujesz sześć zmiennych typu całkowitego.
Zmienne te przechowają elementy daty i godziny — rok, miesiąc, dzień itd. Potem
deklarujesz egzemplarz klasy Calendar. Z kalendarzem jest dość skomplikowana sprawa,
bo czas zależy od miejsca na kuli ziemskiej, czyli strefy czasowej. Widoczna tam funkcja
getInstance() (daj egzemplarz) jako argument otrzymuje naszą strefę czasową. Po-
tem odczytujesz wartości roku, miesiąca dnia itd. dla naszej strefy czasowej. Są to kwe-
stie techniczne, do których nie musisz przywiązywać teraz wielkiej wagi. Po prostu:
tak można odczytać czas.

Potem po raz pierwszy spotykasz się z bardzo ważnymi funkcjami, których rolę mu-
sisz teraz dokładnie zrozumieć:
TextView d_txt = (TextView) findViewById(R.id.textView1);
86 Android. Podstawy tworzenia aplikacji

Zapewne pamiętasz, że w pliku wyglądu (o nazwie prawdopodobnie activi-


ty_main.xml) umieściłeś pola tekstowe o identyfikatorach:
...
android:id="@+id/textView1"
...
android:id="@+id/textView2"
...

Obiekty o takich dziwnie zapisanych identyfikatorach są możliwe do znalezienia za


pomocą metody findViewById(). Nazwę tej metody można próbować tłumaczyć jako
„znajdź obiekt wizualny, mając jego identyfikator”.

Warto zapamiętać: niezwykle ważną rolę gra mechanizm komunikacji na linii zasoby
XML — Java.
XML: android:id="@+id/textView1"

Java: TextView d_txt = (TextView) findViewById(R.id.textView1);

Znaleziony obiekt, w tym przypadku: pole tekstowe, zostaje przypięty do zmiennej


o nazwie d_txt. Ta zmienna od tej pory reprezentuje Twoje pole tekstowe opisane
w pliku XML. Za pomocą wewnętrznej metody pola tekstowego setText() przypisu-
jesz do niego dowolny tekst, w omawianym przypadku datę (rysunek 5.8).

Rysunek 5.8. Zapewne pamiętasz, że zmienna Text odpowiada za brzmienie napisu w komponencie
TextView. Tę zmienną modyfikowałeś w bezpośredniej edycji albo za pomocą inspektora. Teraz ustawiasz ją
programowo w Javie za pomocą metody setText(). Warunek — musisz znaleźć po stronie Javy komponent
o identyfikatorze textView1
Rozdział 5.  Programowanie czas zacząć! 87

Podsumowując: aplikacja zawiera dwie akcje, spisane w języku Java i aktywowane


przez kliknięcia przycisków (rysunek 5.9). Koniecznie musisz zapamiętać, w jaki sposób
„podpina” się konkretną akcję w Javie do kliknięcia konkretnego przycisku. Musisz
też zapamiętać, w jaki sposób po stronie Javy odszukuje się przycisk (czy inny kom-
ponent), zdefiniowany w zasobach XML.

Rysunek 5.9.
Aplikacja zawiera dwie
akcje (profesjonaliści
powiedzą: metody),
spisane w języku Java
i aktywowane
kliknięciami dwóch
przycisków. Jedna
przygotowuje teksty
o znaczeniu bieżącej daty
i godziny i wyświetla je
w zawczasu
przygotowanych polach
tekstowych. Druga
metoda kończy życie
programu

Kółko i krzyżyk, a przy okazji


definiowanie stylów
Projekt ten znajduje się w pliku Rozdzial_5_b.zip.

Teraz napiszesz aplikację z trochę bardziej złożonym interfejsem — planszę do gry


w kółko i krzyżyk na dziewięciu polach (rysunek 5.10). Pola będą równo ułożonymi
przyciskami. Przy okazji definiowania tych przycisków skorzystasz z nowego pojęcia
— stylu. Style stosuje się wtedy, gdy jest dość dużo tak samo wyglądających kompo-
nentów i twórcy aplikacji nie chce się każdego z nich opisywać niezależnie. Zamiast
opisywać każdy przycisk, programista definiuje styl i przypisuje go do każdego przy-
cisku. Program skraca się, jest także czytelniejszy.
88 Android. Podstawy tworzenia aplikacji

Rysunek 5.10.
Gra w kółko i krzyżyk
wymaga dziewięciu
aktywnych
komponentów
do stawiania krzyżyków
albo kółek. Aby nie
napracować się za dużo,
warto zdefiniować
uniwersalny wygląd
zwany stylem i przypisać
go do każdego
z dziewięciu przycisków

Zaimplementujesz tylko planszę do gry dla dwóch osób. Mając już planszę, któregoś
dnia strategię gry zbudujesz w Javie sam...

Gra będzie polegała na klikaniu wybranych pól, którymi będą zwykłe przyciski. Pro-
gram będzie pilnować, aby na przemian stawiać kółka i krzyżyki, żeby nie można
było przerobić już postawionego kółka na krzyżyk albo odwrotnie i żeby tylko klik-
nięcie przycisku z kropeczką (czyli pustego pola) przynosiło jakiś efekt.

Po zbudowaniu interfejsu, gdzie wprowadzisz nowe pojęcie stylu, napiszesz metodę


do obsługi kliknięć. Praca będzie złożona z dwóch wyraźnie oddzielonych etapów:
 części statycznej polegającej na opisaniu wyglądu aplikacji;
 części dynamicznej polegającej na przygotowaniu algorytmów w Javie,
zmieniających stan planszy.

Rozpocznij nowy projekt. Po wykonaniu standardowych czynności powinieneś uzyskać


typową aplikację z napisem Hello World na środku ekranu. Na początek postaraj się
zrobić ładne tło dla gry w kółko i krzyżyk (rysunek 5.11). Zdefiniuj więc w folderze
drawable nowy zasób typu shape o cieniowanym kolorze (rysunek 5.12).
Rozdział 5.  Programowanie czas zacząć! 89

Rysunek 5.11.
Kliknij prawym
klawiszem myszki
którykolwiek folder
drawable i z menu
wybierz plik typu XML.
W pliku tym opiszesz tło
aplikacji

Rysunek 5.12.
Nadanie nazwy plikowi
XML (w omawianym
przypadku nazwa brzmi
„tlo”) i ustalenie, że plik
ma zawierać znany już
element shape
90 Android. Podstawy tworzenia aplikacji

Plik tlo.xml może mieć mniej więcej następującą treść:


<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
<gradient android:startColor="#2020f0"
android:endColor="#000030"
android:angle="90"/>
</shape>

Pamiętaj, że plik możesz po prostu pisać, ale możesz też korzystać z kombinacji klawiszy
Ctrl+Spacja, podpowiadających, co można w danym kontekście wpisać.

Element shape zawiera tzw. gradient — przelewanie się koloru od barwy startowej do
końcowej pod określonym kątem. Kolor startowy jest tutaj jasnoniebieski, końcowy
— ciemnoniebieski, a kąt 90 stopni oznacza, że mechanizm startuje od dołu do góry
ekranu.

Nowy element graficzny, czyli tło, przypisz do właściwości Background elementu


RelativeLayout.

Oto treść pliku wyglądu, prawdopodobnie o nazwie activity_main.xml:


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/tlo" >

</RelativeLayout>

W pliku tym rozkładowi RelativeLayout przypisujesz dwie rzeczy: rozmiar na cały ekran
i gradientowe tło jako wypełnienie (rysunek 5.13).

Pora zacząć rozkładać na ekranie przyciski — oczka planszy do gry w kółko i krzyżyk.
Przycisków jest dużo, zapowiada się żmudne opisywanie ich wyglądu, ale sprytnie
wesprzesz się nowym elementem — stylem.

Niech każdy przycisk ma rozmiary 80×80 umownych pikseli. Niech na każdym przy-
cisku znajduje się napis złożony z pojedynczej kropki i niech rozmiar czcionki tego
napisu wynosi 30 umownych pikseli. Niech kliknięcie każdego z przycisków powo-
duje wywołanie metody o nazwie kliknieto. Żądania takie spełniałeś już wielokrot-
nie, odpwiednio dobierając atrybuty komponentu Button. Ale teraz zrób inaczej —
przygotuj zasób zwany stylem i przypisz go do każdego z dziewięciu przycisków (ry-
sunek 5.14). W kreatorze nowego zasobu XML nadaj nazwę plikowi .xml (w tym
przypadku nazwa brzmi styl) oraz zadecyduj, że plik ma zawierać element resource
(rysunek 5.15).
Rozdział 5.  Programowanie czas zacząć! 91

Rysunek 5.13. Po otwarciu głównego pliku wyglądu znajdującego się w folderze layout należy zaznaczyć
komponent RelativeLayout (decydujący o rozłożeniu innych komponentów na ekranie, w tym przypadku będzie
to rozłożenie komponentów w odniesieniu do siebie nawzajem) i opracować jego właściwość Background (tło)

Rysunek 5.14. Jak zdefiniować zasób zwany stylem? Kliknij prawym klawiszem folder values (wartości
wykorzystywane przez aplikację) i wybierz z menu operację New, a następnie Android XML File.
92 Android. Podstawy tworzenia aplikacji

Rysunek 5.15.
Nadawanie nazwy
plikowi .xml (w tym
przypadku nazwa brzmi
„styl”) oraz ustalenie,
że plik ma zawierać
element resource

Wspomniane przed chwilą wartości atrybutów przycisku opisz mniej więcej w taki
sposób (pamiętaj o posługiwaniu się sekwencją podpowiadającą Ctrl+Spacja):
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<style name="moj_styl">
<item name="android:layout_height">80dp</item>
<item name="android:layout_width">80dp</item>
<item name="android:textSize">30dp</item>
<item name="android:text">.</item>
<item name="android:onClick">kliknieto</item>
</style>
</resources>

Teraz jesteś gotowy do zabudowy ekranu dziewięcioma przyciskami z wykorzysta-


niem stylu. Wróć do głównego pliku wyglądu activity_main.xml i przejdź do edytorka
graficznego. W żadnym wypadku nie rozmieszczaj przycisków w jakiejś konkretnej,
sztywnej lokalizacji. Proponuję, abyś najpierw umieścił środkowy przycisk w środku
ekranu, a potem dodawał kolejne z góry, dołu i boków (rysunek 5.16). W tej aplikacji
w całej okazałości pracuje rozkład RelativeLayout zorientowany na rozmieszczanie
komponentów właśnie na zasadzie: „leż z boku tamtego”, „leż nad tamtym” itd. Wy-
starczy prawidłowo rozlokować pierwszy element (w omawianym przypadku: umiej-
scowiony centralnie), aby poprawnie zabudować całą przestrzeń.

Najważniejsze — dla każdego przycisku opracuj właściwość Style, wskazując ją w in-


spektorze właściwości. W ten sposób oszczędzasz pięć linii na każdym przycisku, ale
też możesz je hurtem powiększyć albo zmienić ich czcionkę. Stylu używaj zawsze,
gdy kilka elementów ma takie same właściwości.
Rozdział 5.  Programowanie czas zacząć! 93

Rysunek 5.16.
Sposób rozmieszczania
przycisków
przy rozkładzie
RelativeLayout

Po edycji główny plik wyglądu activity_main.xml powinien mieć mniej więcej nastę-
pującą treść:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/tlo" >

<Button
android:id="@+id/button1"
style="@style/styl"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"/>

<Button
android:id="@+id/Button01"
style="@style/styl"
android:layout_above="@+id/button1"
android:layout_alignLeft="@+id/button1"/>

<Button
android:id="@+id/Button02"
style="@style/styl"
android:layout_alignLeft="@+id/button1"
android:layout_below="@+id/button1"/>

<Button
android:id="@+id/Button03"
style="@style/styl"
android:layout_alignBottom="@+id/Button01"
android:layout_toLeftOf="@+id/Button01"/>

<Button
android:id="@+id/Button04"
style="@style/styl"
android:layout_above="@+id/Button02"
android:layout_toLeftOf="@+id/button1"/>
94 Android. Podstawy tworzenia aplikacji

<Button
android:id="@+id/Button05"
style="@style/styl"
android:layout_alignBottom="@+id/Button02"
android:layout_toLeftOf="@+id/Button02"/>

<Button
android:id="@+id/Button06"
style="@style/styl"
android:layout_alignBottom="@+id/Button01"
android:layout_toRightOf="@+id/Button01"/>

<Button
android:id="@+id/Button07"
style="@style/styl"
android:layout_above="@+id/Button02"
android:layout_toRightOf="@+id/button1"/>

<Button
android:id="@+id/Button08"
style="@style/styl"
android:layout_alignBaseline="@+id/Button02"
android:layout_alignBottom="@+id/Button02"
android:layout_alignLeft="@+id/Button07"/>

</RelativeLayout>

Widzisz, że pierwszy przycisk jest wycentrowany, a wszystkie pozostałe trzymają się


jego boków? Widzisz, że większość cech przycisków jest określana za pomocą stylu?

Co jeszcze? Drobiazg — programowanie w Javie. Ten program działa, ale tylko do mo-
mentu kliknięcia któregoś przycisku. Wtedy wychodzi na jaw, że w zasobie stylu za-
deklarowano istnienie metody onClick o nazwie kliknieto, ale dotychczas nie pojawiła
się ona w Twoim programie (rysunek 5.17).

Przejdź do okna Javy i wewnątrz publicznej klasy, np. na samym jej końcu, dopisz
definicję nowej metody:
package pl.twojadomena.rozdzial_5_b;

import pl.twojadomena.rozdzial_5_b.R;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.View;
import android.widget.Button;

public class MainActivity extends Activity


{
private boolean moj_ruch=true;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
Rozdział 5.  Programowanie czas zacząć! 95

Rysunek 5.17. Do publicznej klasy Javy należy dopisać metodę zgłoszoną do obsługi zdarzenia onClick
(kliknięto przycisk)

public void kliknieto(View v)


{
Button b=(Button)v;
if( b.getText().equals("."))
{
if( moj_ruch)
b.setText("X");
else
b.setText("O");
moj_ruch = !moj_ruch;
}
}
}

Ze względu na nową metodę dodano dwa ostatnie importy klas bibliotecznych — bez
nich kompilator Javy nie rozpoznaje elementu View występującego w prototypie nowej
funkcji i Button.

Sama metoda kliknieto() jest napisana trochę inaczej niż poprzednio. Metoda ta jako
argument otrzymuje obiekt typu View. Ten obiekt, który kliknięto, tylko żeby otrzymać
Button, należy wykonać tzw. rzutowanie:
Button b=(Button)v;

Po rzutowaniu pod zmienną o nazwie b jest przycisk, który kliknięto, a nie jakiś ogólny
View.

Sytuację powyższą można ściślej opisać następująco: każdy obiekt, który można klik-
nąć, rozszerza typ biblioteczny View:
96 Android. Podstawy tworzenia aplikacji

public class Button extends View


{
....

Dzięki zjawisku rozszerzania klasy bazowej (tutaj: View) przez klasę potomną (tutaj:
Button) metodę do obsługi kliknięcia można zaprojektować uniwersalnie:
public void kliknieto(View v)
{
...

Argument View może być zarówno Buttonem, jak i np. polem tekstowym TextView.
Właściwość ta nazywa się polimorfizmem.

Polimorfizm umożliwia pisanie zwięzłego kodu. Taka sama metoda obsłuży kliknięcie
w klasy Button, jak i TextView. Należy tylko pamiętać o rzutowaniu ogólnego typu bazo-
wego View na typy szczegółowe.

Rysunek 5.18.
Dzieki umieszczeniu
na ekranie rozkładu
RelativeLayout
aplikacja jest nieczuła
na obrót ekranu
(w emulatorze ekran
obraca się klawiszem
PageUp na klawiaturze
bocznej). Centralne pole
planszy nadal jest
na środku ekranu,
pozostałe są przyklejone
do jego boków
Rozdział 6.
Efekty specjalne
W poprzednim rozdziale rozpocząłeś programowanie w Javie. Pora na podsumowanie
stanu posiadanej wiedzy i umiejętności.

Komponenty widoczne, takie jak: przyciski (Button), obrazki (ImageView) czy napisy
(TextView), a także inne, dostępne z palety edytorka zabudowy ekranu, służą do komu-
nikacji użytkownika z urządzeniem. Te komponenty należy opisać w odpowiednich
plikach XML.

W głównym pliku wyglądu aplikacji, znajdującym się w folderze layout (co oznacza
właśnie wygląd), rozmieść komponenty na ekranie, zazwyczaj decydując się na jakiś
„firmowy” rozkład — najchętniej RelativeLayout (rozkład względny). Rozkład
względny najlepiej budować, wskazując najpierw położenie jednego komponentu (np.
środkowego pola w grze w kółko i krzyżyk), a pozostałe elementy układać względem
tego pierwszego („leż powyżej pierwszego”, a może „leż poniżej pierwszego” itd.). Za
wszelką cenę unikaj bezwzględnych lokalizacji, bo nie wiesz, jakim ekranem będzie
dysponować użytkownik Twojej aplikacji. Pamiętaj, że bezwzględna lokalizacja kom-
ponentu może go wynieść poza obszar ekranu!

Wszystkie widoczne komponenty poszerzają biblioteczny komponent bazowy (lepiej


mówić: klasę bazową) View:
public class Button extends View
{
...

Ta zasadnicza dla Javy linia mówi, że wspomniany w niej Button dziedziczy wszyst-
kie cechy (ściślej: właściwości i metody) klasy podstawowej View.

Zadaniem programisty nie jest pisanie algorytmów różnych przycisków, tekstów, obraz-
ków, ale rozszerzanie tego, co już napisali twórcy systemu Android. W tym rozdziale
postarasz się ożywić Twoje interfejsy, dodając do bibliotecznych komponentów ele-
menty animacji. Programowania w Javie ciągle będzie niewiele. Będziesz musiał się
tylko nauczyć, jak uruchomić przygotowaną w plikach XML animację. Programowo
wyzwolisz ruch.
98 Android. Podstawy tworzenia aplikacji

Przygotowanie animacji
Projekt ten znajduje się w pliku Rozdzial_6_a.zip.

Android przewiduje kilka gotowych efektów animacyjnych:


 obroty,
 przesunięcia,
 ściskania, rozciągania, powiększenia i pomniejszenia, czyli zmiany skali,
 zanikania i pojawiania się.

Możliwe są też kombinacje tych efektów, co również omówię.

Zacznij od założenia w znany Ci już sposób folderów i plików nowego projektu i przy-
gotowania zasobów, które będą wykorzystane w animacji. Przede wszystkim niech
będzie to jakiś obrazek, przygotowany w kilku wersjach dla urządzeń mobilnych róż-
nej jakości. Na rysunku 6.1 pokazano wycinek drzewa plików projektu ilustrujący roz-
mieszczenie trzech wersji pliku — obrazu do wyświetlenia na ekranie. Folder -hdpi
zawiera wersję grafiki w wysokiej jakości, w tym przypadku o boku ok. 300 pikseli.
Folder -ldpi mieści zasoby dla najgorszych urządzeń — grafika jest tu obrazem o boku
ok. 150 pikseli

Rysunek 6.1.
Wycinek drzewa plików
projektu, ilustrujący
rozmieszczenie trzech
wersji obrazu do
wyświetlenia na ekranie

Zasoby takie należy cierpliwie i starannie przygotować w jakimś edytorze grafiki, roz-
mieścić w odpowiednich folderach drawable-... i wstępnie opisać w odpowiednim pliku
XML. Zatem po spreparowaniu grafik i rozmieszczeniu ich w odpowiednich folderach
postępuj jak zwykle, gdy chcesz znaleźć kreator, który pomoże Ci w konstrukcji odpo-
wiedniego pliku XML, opisującego nowy zasób. Kliknij prawym klawiszem myszki
którykolwiek folder drawable i wybierz operację New/Android XML File (rysunek 6.2).
Rozdział 6.  Efekty specjalne 99

Rysunek 6.2.
Tworzenie nowego pliku
XML, opisującego nowy
zasób

Wykorzystując kreator, którego usługi proponuje Ci środowisko Eclipse, wpisz jakąś


nazwę dla nowego pliku XML oraz wybierz główny tag zasobu — w tym wypadku
bitmapę (rysunek 6.3).

Rysunek 6.3.
Nazywanie nowego
pliku XML oraz
wybieranie głównego
tagu zasobu — w tym
wypadku bitmapa

Po wykreowaniu pliku XML, opisującego zasób graficzny, pozostaje jego bezpośrednia


edycja. Pamiętaj, że Eclipse oferuje bardzo pomocną funkcję (sufler). Stań kursorem
tuż przed klamrą zamykającą tag bitmap i naciśnij klawisze Ctrl+Spacja. Z sugero-
wanych atrybutów wybierz src — opis pliku źródłowego obrazu (rysunek 6.4).
100 Android. Podstawy tworzenia aplikacji

Rysunek 6.4.
Eclipse oferuje bardzo
pomocną funkcję
sufler (Ctrl+spacja)
— w tym przypadku
z sugerowanych
atrybutów należy
wybrać src (opis pliku
źródłowego obrazu)

Plik ryba.xml powinien mieć mniej więcej następującą treść:


<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@drawable/ryba">
</bitmap>

Kolejnym etapem będzie opisanie efektów specjalnych, czyli: obrotów, przesunięć,


przeskalowań rozmiarów, zaników. Zacznij od obrotu (rysunek 6.5). Znów przywołaj kre-
ator pliku XML, klikając prawym klawiszem myszki folder res i tym razem wybierając
zasób typu Tween Animation i główny tag tego zasobu o brzmieniu rotate (obrót).

Rysunek 6.5.
Kreator pomaga
w utworzeniu pliku XML
(w tym przypadku
o nazwie „obrot”)
definiującego efekt
obrotu

Kreator umieszcza plik obrot.xml w nowym folderze zasobów o nazwie anim.

W mojej wersji środowiska Eclipse niestety jeszcze nie działa kombinacja klawiszy
suflera Ctrl+Spacja. Licz się więc z koniecznością ręcznej edycji pliku, w tym przy-
padku o nazwie obrot.xml:
Rozdział 6.  Efekty specjalne 101

<?xml version="1.0" encoding="utf-8"?>


<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromDegrees="0"
android:toDegrees="720"
android:duration="1000"
android:pivotX="50%"
android:pivotY="50%">
</rotate>

Zasób ten definiuje obrót od kąta 0 stopni do kąta 720 stopni (czyli jakiś obiekt wizu-
alny — jeszcze nie wiadomo jaki — może zrobić dwa pełne obroty). Czas trwania ob-
rotu to 1000 milisekund, czyli 1 sekunda. Środek obrotu leży w centrum obracanego
komponentu (położenie środka zadaje się w procentach szerokości i wysokości kom-
ponentu).

Gdy już uruchomisz program, oczywiście wróć tutaj i wypróbuj inne wartości atry-
butów obrotu. Ale teraz — zdefiniowawszy rotację — od razu zdefiniuj pozostałe
efekty animacyjne.

Posługując się kreatorem, od razu określ i zapisz w pliku o nazwie skalowanie.xml prze-
kształcenie zmiany rozmiarów (główny tag o brzmieniu scale), potem przesunięcia
w pliku przesuw.xml (tag translate) i uzyskiwanie przezroczystości w pliku zanik.xml
(tag alpha). Wszystkie te pliki zostaną zapisane w folderze o nazwie anim (rysunek 6.6).

Rysunek 6.6.
Definiowanie różnych
efektów animacyjnych
102 Android. Podstawy tworzenia aplikacji

Treść pliku skalowanie.xml będzie wyglądać tak:


<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
android:fromXScale="1.0"
android:toXScale="2.0"
android:fromYScale="1.0"
android:toYScale="2.0"
android:pivotX="50%"
android:pivotY="50%"
android:duration="1000">
</scale>

Plik o nazwie przesuw.xml powinien mieć następującą treść:


<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromYDelta="0"
android:toYDelta="100%"
android:duration="1000">
</translate>

A oto treść pliku zanik.xml:


<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:fromAlpha="1.0"
android:toAlpha="0.0"
android:duration="1000">
</alpha>

Nie będę dokładnie omawiał czytelnych atrybutów, które pojawiły się w powyższych
plikach opisujących elementarne animacje (rysunek 6.7). Pamiętaj, że pivot oznacza
w tym przypadku środek, a duration to czas trwania animacji w milisekundach. Atry-
buty oznaczone przedrostkiem from opisują wartości startowe, przedrostkiem to —
końcowe.

Rysunek 6.7.
Przekształcenia zostały
opisane w odpowiednich
plikach XML (niektóre
niestety metodą
bezpośredniej edycji,
bo wmontowany
w Eclipse sufler nie zna
wszystkich atrybutów)
i za chwilę zostaną
wykorzystane do
animacji wyglądu
komponentów
Rozdział 6.  Efekty specjalne 103

Przygotowanie interfejsu użytkownika


Teraz przygotujesz interfejs aplikacji demonstrującej działanie zdefiniowanych wcze-
śniej przekształceń.

Twoja aplikacja będzie zawierać animowany element, w tym przypadku komponent


ImageView, oraz cztery przyciski mające zademonstrować działanie poszczególnych
przekształceń (rysunek 6.8).

Rysunek 6.8.
Aplikacja, na którą
składają się: animowany
element oraz cztery
przyciski

Przejdź do folderu layout i dwa razy kliknij znajdujący się tam plik, prawdopodobnie
o nazwie activity_main.xml (jest to domyślna nazwa nadawana przez kreator nowego
projektu). Zapewne pamiętasz, że dwukrotne kliknięcie jakiegoś zasobu otwiera go
w edytorze.

Na zakładce Image znajduje się komponent ImageView. Proponuję starannie umieścić


go w środku ekranu, tak aby pojawiające się linie prowadzące jasno dały znać, że lo-
kalizacją jest centrum (rysunek 6.9). Koniecznie musisz opracować właściwość src
(co wyświetlić) i przypisać jej wartość naszego pliku opisującego grafikę o nazwie
prawdopodobnie ryba.xml.

Potem umieść w okienku jeden z przycisków — np. pierwszy. Pozostałe przyklejaj do


niego, co jest zgodne z duchem RelativeLayout (komponenty doklejaj do siebie wza-
jemnie).
104 Android. Podstawy tworzenia aplikacji

Rysunek 6.9.
Komponent ImageView
znajduje się na zakładce
Images & Media

Oto treść pliku wyglądu o nazwie (jeśli jej nie zmieniłeś) activity_main.xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ImageView
android:id="@+id/imageView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:src="@drawable/ryba" />
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_marginLeft="28dp"
android:layout_marginTop="27dp"
android:onClick="obracaj"
android:text="Obrót" />
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/button1"
android:layout_alignBottom="@+id/button1"
android:layout_toRightOf="@+id/button1"
android:onClick="skaluj"
android:text="Skala" />
<Button
android:id="@+id/Button01"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/button2"
android:layout_alignBottom="@+id/button2"
android:layout_toRightOf="@+id/button2"
Rozdział 6.  Efekty specjalne 105

android:onClick="zanikaj"
android:text="Zanik" />
<Button
android:id="@+id/Button02"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/Button01"
android:layout_alignBottom="@+id/Button01"
android:layout_toRightOf="@+id/Button01"
android:onClick="przesuwaj"
android:text="Przesuw" />
</RelativeLayout>

Plik ten został utworzony techniką edycji wizualnej, czyli przez rozmieszczanie przy-
cisków i obrazka w polu ekranu urządzenia mobilnego. Ręcznie, ale też za pomocą
inspektora właściwości, należało wprowadzić napisy na przyciskach i nazwy metod
onClick. Następnie będziesz musiał zdefiniować te metody w pliku Javy.

Uruchomienie animacji
Przed chwilą należało wymyśleć nazwy metod onClick dowiązanych do zdarzeń „klik-
nięto przycisk”. Nadszedł moment na spisanie samych metod, czyli: na programowanie
w Javie.

Jeśli do pierwszego przycisku Obrót dowiązano metodę obracaj():


android:onClick="obracaj"

to w pliku Javy musi się znaleźć funkcja (w programowaniu obiektowym chętniej


mówi się: metoda) o następującym prototypie, czyli nagłówku:
public void obracaj( View v)
{
}

Jeśli takiej funkcji nie zdefiniujesz, program będzie działać, ale tylko do momentu
kliknięcia przycisku Obrót, kiedy to algorytm wykona skok do zapowiedzianej, ale
nieistniejącej funkcji i zakończy działanie komunikatem o błędzie.

Musisz zdefiniować wszystkie zadeklarowane funkcje, ale nie trzeba, żebyś od razu
spisywał ich ciała. Proponuję więc dopisać do pliku klasy głównej cztery puste, ale
prawidłowo skonstruowane funkcje do obsługi kliknięć czterech przycisków:
...
import android.view.View;
public class MainActivity extends Activity
{
...
public void obracaj( View v)
{
}
public void skaluj( View v)
106 Android. Podstawy tworzenia aplikacji

{
}
public void zanikaj( View v)
{
}
public void przesuwaj( View v)
{
}
}

W sekcji importów trzeba dopisać import pakietu klasy View.

Wewnątrz klasy głównej, poszerzającej biblioteczną klasę Activity, znalazły się cztery
nowe metody, jeszcze puste, ale z technicznego punktu widzenia w pełni wystarczają-
ce. Nazwy tych metod ściśle odpowiadają temu, co wpisałeś w pola atrybutów onClick
w pliku XML. Dzięki tej zgodności kliknięcie wybranego przycisku spowoduje wywo-
łanie odpowiedniej metody.

Napisz więc pierwszy algorytm metody obracaj():


...
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.*;
...
public void obracaj( View v)
{
Animation a = AnimationUtils.loadAnimation(this, R.anim.obrot);
ImageView iv = (ImageView)findViewById(R.id.imageView1);
iv.startAnimation(a);
}

Pojawienie się nowych nazw wymaga kolejnych importów — w przeciwnym razie


kompilator Javy nie rozpozna niektórych znaczeń.

W pierwszej linii ciała funkcji masz deklarację egzemplarza animacji opisanej w pliku
obrot.xml, znajdującym się w folderze anim. Najwyraźniej klasa Animation może czytać
pliki XML i na podstawie zawartej tam informacji przygotuje cały proces.

Potem wyszukaj po stronie Javy naszą rybkę do obracania. Zapewne pamiętasz, że


służy do tego niezwykle ważna funkcja findViewById() (znajdź komponent, mając jego
identyfikator, nadany mu w zasobach XML).

Jak wspominałem, funkcja findViewById() zwraca komponent bazowy View, nie zaś
potrzebny w tym przypadku ImageView, zatem konieczne jest rzutowanie ogólniejsze-
go typu View na konkretny typ ImageView:
(ImageView)findViewById(R.id.imageView1);

W efekcie otrzymujesz swoją rybkę i przechowujesz ją w zmiennej o nazwie iv (od


ImageView):
ImageView iv = (ImageView)findViewById(R.id.imageView1);
Rozdział 6.  Efekty specjalne 107

W ostatniej linii wskazujesz: rybka — start!:


iv.startAnimation(a);

Komponent — w tym przypadku ImageView o nazwie iv — uruchamia swoją we-


wnętrzną, publiczną funkcję startAnimation(), której argumentem jest utworzony
właśnie obiekt animacyjny a. Rozpoczyna się ruch (rysunek 6.10).

Rysunek 6.10.
Kliknięto przycisk
Obrót. Aby zmienić
parametry animacji,
trzeba udać się do
odpowiedniego pliku
XML w folderze anim
i poddać edycji atrybuty
animacji

Pozostałe funkcje przygotowujesz bardzo podobnie, uważając, aby czegoś nie pomylić:
public void skaluj( View v)
{
Animation a = AnimationUtils.loadAnimation(this, R.anim.skalowanie);
ImageView iv = (ImageView)findViewById(R.id.imageView1);
iv.startAnimation(a);
}
public void zanikaj( View v)
{
Animation a = AnimationUtils.loadAnimation(this, R.anim.zanik);
ImageView iv = (ImageView)findViewById(R.id.imageView1);
iv.startAnimation(a);
}
public void przesuwaj( View v)
{
Animation a = AnimationUtils.loadAnimation(this, R.anim.przesuw);
ImageView iv = (ImageView)findViewById(R.id.imageView1);
iv.startAnimation(a);
}

W każdej funkcji tworzysz obiekt animujący, wyszukujesz rybkę po jej identyfikatorze


i uruchamiasz jej animację.
108 Android. Podstawy tworzenia aplikacji

Animacja innych komponentów


Obiekty typu View (w tym oczywiście także poszerzające typ View czy — jak się czę-
sto mówi inaczej — potomkowie typu View) mają wspominaną poprzednio metodę
startAnimation(), która robi wszystko, co opisano w odpowiednim pliku XML.

Zastosuj więc animację do czterech przycisków. Żeby uniknąć definiowania nowej


animacji, jakoś ciekawie pasującej do przycisków, zastosuj już zdefiniowany obrót:
niech kliknięcie przycisku obraca przycisk, a także robi to, co dotychczas z rybką (rysu-
nek 6.11). Do każdej z czterech funkcji, już zawierających trzy linie animujące rybkę,
należy dopisać trzy linie animujące przycisk. Funkcja przypisana do przycisku Obrót
wyglądałaby mniej więcej tak:
public void obracaj( View v)
{
Animation obr = AnimationUtils.loadAnimation(this, R. anim.obrot);
Button b = (Button)findViewById(R.id.Button01);
b.startAnimation(obr);

Animation a = AnimationUtils.loadAnimation(this, R. anim.obrot);


ImageView iv = (ImageView)findViewById(R.id.imageView1);
iv.startAnimation(a);
}

Rysunek 6.11.
Animowac mozna kazdy
obiekt typu View lub
typu rozszerzającego
View. Po kliknięciu
przycisku przekształceniu
podlega rybka, ale także
obraca się sam przycisk

W algorytmach innych przekształceń byłyby takie same trzy linie, ale z odniesieniami
do następnych przycisków, nie do pierwszego Button01 (musisz sprawdzić w głównym
pliku wyglądu, jak nazywa się każdy z przycisków). To będzie działać.
Rozdział 6.  Efekty specjalne 109

Ale można napisać to wszystko lepiej. Animujesz komponent, który akurat klikasz.
Skoro tak, nie trzeba go wyszukiwać za pomocą czasochłonnej funkcji findViewById(),
bo sama funkcja Ci go podaje jako swój argument v:
public void obracaj( View v)

Pod zmienną v kryje się ten komponent, który kliknięto. Możesz więc wcześniejszy
kod napisać krócej, wykorzystując fakt, że komponentu, który kliknięto, nie trzeba
w tego rodzaju funkcji wyszukiwać:
public void obracaj( View v)
{
Animation obr = AnimationUtils.loadAnimation(this, R. anim.obrot);
v.startAnimation(obr);

Animation a = AnimationUtils.loadAnimation(this, R. anim.obrot);


ImageView iv = (ImageView)findViewById(R.id.imageView1);
iv.startAnimation(a);
}

Zaznaczone dwie linie należy dopisać w każdej z czterech funkcji. To sugeruje nam
kolejny malutki kroczek w doskonaleniu algorytmu: skoro musisz w kilku miejscach
dopisywać kilkakrotnie te same linie, zdefiniuj własną funkcję, zawierającą powtarzają-
ce się linie:
private void obracaj_przycisk( View v)
{
Animation obr = AnimationUtils.loadAnimation(this, R. anim.obrot);
v.startAnimation(obr);
}

Jest to funkcja prywatna, czyli dostępna tylko w klasie, w której została zdefiniowana
(zatem w Twojej klasie głównej). Funkcje prywatne to coś w rodzaju ukrytych przed
światem pomocników — jak nasza obracaj_przycisk().

Dla porządku warto przytoczyć ciała czterech funkcji — reakcji na kliknięcia i po-
mocniczej metody, obracającej ten przycisk, który akurat kliknięto:
...
public void obracaj( View v)
{
obracaj_przycisk( v);
Animation a = AnimationUtils.loadAnimation(this, R. anim.obrot);
ImageView iv = (ImageView)findViewById(R.id.imageView1);
iv.startAnimation(a);
}
public void skaluj( View v)
{
obracaj_przycisk( v);
Animation a = AnimationUtils.loadAnimation(this, R. anim.skalowanie);
ImageView iv = (ImageView)findViewById(R.id.imageView1);
iv.startAnimation(a);
}
110 Android. Podstawy tworzenia aplikacji

public void zanikaj( View v)


{
obracaj_przycisk( v);
Animation a = AnimationUtils.loadAnimation(this, R. anim.zanik);
ImageView iv = (ImageView)findViewById(R.id.imageView1);
iv.startAnimation(a);
}
public void przesuwaj( View v)
{
obracaj_przycisk( v);
Animation a = AnimationUtils.loadAnimation(this, R. anim.przesuw);
ImageView iv = (ImageView)findViewById(R.id.imageView1);
iv.startAnimation(a);
}
private void obracaj_przycisk( View v)
{
Animation obr = AnimationUtils.loadAnimation(this, R. anim.obrot);
v.startAnimation(obr);
}

Łączenie animacji
Być może chciałbyś połączyć przesuwanie z obracaniem, a może i z zanikaniem? Jak
tworzyć kompilacje wielu elementarnych przekształceń?

W edytorze wizualnym utwórz nowy przycisk, np. o takim zapisie w surowym XML:
<Button
android:id="@+id/button3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/button1"
android:layout_alignRight="@+id/Button02"
android:layout_below="@+id/button1"
android:onClick="stosuj_wszystko"
android:text="Wszystko razem..." />

Zwróć uwagę na najważniejszą linię:


android:onClick="stosuj_wszystko"

Ta linia oznacza, że będziesz musiał dopisać w Javie nową metodę — reakcję na


kliknięcie powyższego przycisku. Ale najpierw trzeba opisać animację, która będzie
syntezą kilku omówionych wcześniej efektów (rysunek 6.12).

Tajemnica łączenia efektów tkwi w nieco innym opracowaniu pliku XML, opisujące-
go przekształcenie. Jednak zasadnicze kroki, które musisz wykonać, są takie same jak
zwykle, gdy wprowadzasz do gry nowy zasób.
Rozdział 6.  Efekty specjalne 111

Rysunek 6.12.
Przycisk Wszystko
razem uaktywni
animację będącą
połączeniem
dotychczasowych
czterech efektów
specjalnych

Kliknij prawym klawiszem myszki folder anim i za pomocą kreatora utwórz nowy plik
XML, w tym przypadku o nazwie wszystko_razem.xml (rysunek 6.13). Plik ten będzie
zawierał opis animacji, która ma być syntezą czterech efektów animacyjnych. Tag
główny zasobu niech ma brzmienie set (zbiór).

Rysunek 6.13.
Tworzenie nowego pliku
XML zawierającego
opis animacji, która
będzie syntezą czterech
efektów animacyjnych
112 Android. Podstawy tworzenia aplikacji

Posługując się suflerem Ctrl+Spacja, następująco wyedytuj plik wszystko_razem.xml:


<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<rotate
android:fromDegrees="0"
android:toDegrees="720"
android:duration="1000"
android:pivotX="50%"
android:pivotY="50%"/>
<alpha
android:fromAlpha="1.0"
android:toAlpha="0.0"
android:duration="1000"/>
<translate
android:fromYDelta="0"
android:toYDelta="100%"
android:duration="1000"/>
<scale
android:fromXScale="1.0"
android:toXScale="2.0"
android:fromYScale="1.0"
android:toYScale="2.0"
android:pivotX="50%"
android:pivotY="50%"
android:duration="1000"/>
</set>

Jest to zbiór (tag główny — set w tłumaczeniu oznacza właśnie zbiór) efektów pro-
stych. Pozostało jeszcze tylko uzupełnić algorytm w Javie o nową metodę — reakcję
na kliknięcie przycisku Wszystko razem. Pamiętaj, że nazwa tej metody powinna brzmieć
stosuj_wszystko().

Oto uzupełnienie klasy głównej programu, spisanej w pliku MainActivity.java (jeśli


nie zmieniałeś nazw w kreatorze nowego projektu):
public void stosuj_wszystko( View v)
{
Animation a = AnimationUtils.loadAnimation(this, R.anim.wszystko_razem);
ImageView iv = (ImageView)findViewById(R.id.imageView1);
iv.startAnimation(a);
}

Tak jak zwykle: obiekt animacji tworzy się z zasobu XML, wskazanego jako argu-
ment w funkcji loadAnimation (odczytaj animację), należącej do klasy bibliotecznej
AnimationUtils (narzędzia do animacji). Potem uzyskujesz dostęp do rybki — będzie
ona reprezentowana przez zmienną o nazwie iv. Wreszcie rozpoczynasz animację
obiektu iv.

Posługując się plikiem XML z tagiem głównym set, możesz zbudować dowolną kom-
binację efektów specjalnych.
Rozdział 6.  Efekty specjalne 113

Animacja poklatkowa map bitowych


Teraz omówię inną, bardzo prostą animację, która polega na sekwencyjnym wyświetla-
niu wcześniej przygotowanych obrazków. Obrazki — jak klatki filmu w kinie — będą
pokazywane w określonych odstępach czasu i mogą złożyć się np. na animowany film.

Z tą technologią animacji jest jeden problem — dużym kłopotem jest przygotowanie


porządnych zasobów. Jeśli nie jesteś artystą — animatorem i nie chce Ci się rysować
poszczególnych klatek filmu, zajrzyj do projektu w pliku Rozdzial_6_b.zip. Ja też nie
jestem artystą, ale przygotowałem czteroklatkowy film. Każda klatka składa się z ob-
razu jednej literki. Do prób i testów taki film powinien wystarczyć.

Rozpocznij nowy projekt. Pracę należy rozpocząć od przygotowania klatek filmu. Jeśli
chcesz to naprawdę dobrze zrobić, przygotuj kilka klatek o nazwach np. a.png, b.png
itd. w trzech wersjach jakościowych i gdy kreator nowego projektu założy wszystkie
foldery, w folderach drawable umieść obrazy — klatki filmu. Całe zestawy obrazków po-
rozmieszczaj w folderach drawable-ldpi, drawable-mdpi i drawable-hdmi. Są to zesta-
wy przygotowane do wyświetlania na urządzeniach różnej jakości (rysunek 6.14).

Rysunek 6.14.
Przygotowanie klatek
filmu i rozmieszczenie
ich w różnych folderach
drawable

Po zgromadzeniu zasobów, czyli w omawianym przykładzie: klatek filmu, należy je


opisać w pliku XML. Kliknij prawym klawiszem myszki folder drawable i wykreuj plik
XML z głównym tagiem bitmap (rysunek 6.15).

Każdy zasób graficzny powinien być opisany w pliku XML. Oto plik o nazwie obraz1.xml,
opisujący pierwszą klatkę filmu, która omawianym przypadku ma nazwę a.png:
<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@drawable/a" />
114 Android. Podstawy tworzenia aplikacji

Rysunek 6.15.
Opisanie zasobów
w pliku XML z głównym
tagiem bitmap

Jak zwykle, edytuj ten plik za pomocą suflera Ctrl+Spacja. Sufler działa dobrze tylko
wtedy, gdy kursor ustawisz w tekście bardzo precyzyjnie. Sufler „rozumie” dialekt XML
— zasadnicze znaczenie dla jego działania ma np. to, czy stoi we wnętrzu tagu XML,
czy poza tym tagiem.

Reprezentacja XML klatki b jest umieszczona w pliku obraz2.xml i wygląda mniej


więcej tak:
<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@drawable/b" />

Plik obraz3.xml:
<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@drawable/c" />

Plik obraz4.xml:
<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@drawable/d" />

Pliki obraz1.xml, obraz2.xml itd. są bardzo podobne. Być może sięgniesz bezpośrednio na
dysk do folderów drawable i zechcesz produkować pliki w inny sposób, nie za pomo-
cą Eclipse (rysunek 6.16). Po „ręcznych” zmianach w obrazie plików należy poinfor-
mować Eclipse, że coś się zmieniło bez jego wiedzy. Służy do tego polecenie Refresh
(odśwież), które wywołasz, klikając prawym klawiszem myszki foldery projektu.
Rozdział 6.  Efekty specjalne 115

Rysunek 6.16.
Pliki wyprodukowane
bez pomocy Eclipse —
w takim przypadku
należy poinformować
Eclipse, że coś się
zmieniło (polecenie
Refresh wywołane
kliknięciem prawym
klawiszem myszki
folderów projektu)

Teraz zbuduj animację, czyli połącz klatki w jedną całość. Jak zwykle, kliknij prawym
klawiszem myszki folder drawable i z pomocą kreatora przygotuj nowy plik XML,
tym razem z tagiem głównym o brzmieniu animation-list (rysunek 6.17).

Rysunek 6.17.
Krecja zasobu XML
z tagiem głównym
animation_list
116 Android. Podstawy tworzenia aplikacji

Gdy kreator utworzy plik, wyedytuj jego treść w następujący, monotonny sposób —
plik animacja.xml:
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="true">
<item android:drawable="@drawable/obraz1" android:duration="200"/>
<item android:drawable="@drawable/obraz2" android:duration="200"/>
<item android:drawable="@drawable/obraz3" android:duration="200"/>
<item android:drawable="@drawable/obraz4" android:duration="200"/>
<item android:drawable="@drawable/obraz3" android:duration="200"/>
<item android:drawable="@drawable/obraz2" android:duration="200"/>
<item android:drawable="@drawable/obraz1" android:duration="200"/>
</animation-list>

Tag animation-list może (ale nie musi) mieć atrybut oneshot (jeden strzał), który
przybiera wartości true albo false i oznacza jednorazowe wyświetlenie filmu lub
zamknięcie go w pętli.

Tag animation-list zawiera zestaw tagów item, co oznacza w tym przypadku poje-
dynczą klatkę filmu. Każda klatka ma dwa atrybuty: zasób graficzny i czas jego wy-
świetlania, mierzony w milisekundach.

Kolejnym krokiem jest zbudowanie interfejsu użytkownika. Przejdź do folderu layout


i dwukrotnie kliknij znajdujący się tam plik opisujący wygląd aplikacji. Plik ten po-
winien mieć nazwę activity_main.xml (jeśli nie zmieniłeś jej wcześniej).

Niech aplikacja składa się z wyśrodkowanego obrazka i z przycisku z napisem Start.


Niech obrazek na opracowaną właściwość Background (tło). Jako tło obrazka wskaż
przygotowany poprzednio plik animacja.xml (rysunek 6.18).

Rysunek 6.18.
Interfejs do oglądania
filmu poklatkowego
składa się
z komponentu
ImageView
z właściwością
Background (tło)
ustawioną na animację
i z przycisku
z właściwością
onClick = "kliknieto"
(nazwa metody,
w której wykonany
zostanie start projekcji)
Rozdział 6.  Efekty specjalne 117

Oto proponowany plik wyglądu activity_main.xml po edycji:


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ImageView
android:id="@+id/imageView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:background="@drawable/animacja"/>
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/imageView1"
android:layout_alignParentTop="true"
android:layout_alignRight="@+id/imageView1"
android:layout_marginTop="20dp"
android:onClick="kliknieto"
android:text="Start" />
</RelativeLayout>

W tym pliku widzisz wycentrowany obrazek. Przycisk został ulokowany relatywnie


do obrazka — jego lewa i prawa krawędź są wyrównane do krawędzi obrazka. W ni-
niejszym tekście wygląda to na dosyć złożone, ale oczywiście edycja polegała na od-
powiedniej pracy myszką w edytorku wizualnym.

Co najważniejsze, obrazek ma opracowaną właściwość background (tło), a przycisk


właściwość onClick (co zrobić, gdy kliknięto).

W kolejnym etapie zaczyna się programowanie. Przejdź do edytora Javy, dwa razy
klikając plik MainActivity.java znajdujący się w folderze src (źródła w Javie).

Oto treść pliku MainActivity.java:


package pl.twojadomena.rozdzial_6_b;

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.View;
import android.graphics.drawable.AnimationDrawable;
import android.widget.ImageView;

public class MainActivity extends Activity


{
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
118 Android. Podstawy tworzenia aplikacji

public void kliknieto( View v)


{
ImageView img = (ImageView)findViewById(R.id.imageView1);
AnimationDrawable a = (AnimationDrawable)img.getBackground();
a.start();
}
}

W pliku tym doimportowano klasy, których nazwy pojawiają się w omawianym algo-
rytmie:
import android.view.View;
import android.graphics.drawable.AnimationDrawable;

oraz dodano metodę kliknieto(), zgłoszoną do obsługi kliknięcia przycisku.

W pierwszej linii metody kliknieto() w znany sposób znajdujesz komponent, którego


obraz będziesz animować. Jak zwykle funkcja findViewById() jest pośrednikiem między
zasobami XML a programem w Javie. W drugiej linii deklarujesz silnik wyświetlania
filmów. Silnik ten jest reprezentowany przez klasę o nazwie AnimationDrawable.

W ostatniej linii uruchamiasz film.

Warto wiedzieć, że Android ma wyjątkowo dobrze przygotowaną dokumentację onli-


ne (rysunek 6.19). Chcąc dowiedzieć się czegoś więcej o silniku AnimationDrawable,
wpisz w wyszukiwarkę Google frazy „android animationdrawable” (wielkość liter
dla Google jest nieistotna), wtedy niewątpliwie trafisz na opis klasy AnimationDrawa-
ble i wśród kilku publicznych jej metod, gdzieś obok metody start() znajdziesz
metodę stop(). Wypróbuj, jak działa start i stop filmu (rysunek 6.20). W tym celu:
1. W pliku animacji atrybut oneshoot ustaw na false — niech raz puszczony
film wyświetla się w kółko.
2. Rozbuduj interfejs o drugi przycisk z napisem Stop. Przypisz do jego
właściwości onClick nazwę nowej metody, która posłuży do zatrzymania
aplikacji.
3. W pliku Javy dopisz nową metodę o nazwie takiej, jaką przypisałeś
do atrybutu onClick nowego przycisku.

Oto algorytmy metod uruchamiającej i zatrzymującej projekcję (zwróć uwagę na ostatnie


linie):
public void kliknieto_start( View v)
{
ImageView img = (ImageView)findViewById(R.id.imageView1);
AnimationDrawable a = (AnimationDrawable)img.getBackground();
a.start();
}
public void kliknieto_stop( View v)
{
ImageView img = (ImageView)findViewById(R.id.imageView1);
AnimationDrawable a = (AnimationDrawable)img.getBackground();
a.stop();
}
Rozdział 6.  Efekty specjalne 119

Rysunek 6.19.
Dokumentacja online
Androida

Rysunek 6.20.
Interfejs programu
uzupełnionego
o zatrzymywanie filmu

Można to napisać lepiej: po co dwa razy wyszukiwać obiekt typu ImageView? Po co


dwa razy tworzyć silnik animacji?

Deklaracje dwóch zmiennych przenieś do sekcji prywatnej klasy. Do takich zmiennych


będą miały dostęp wszystkie metody naszej klasy głównej, w tym metody kliknieto_
start() i kliknieto_stop() połączone z przyciskami.
120 Android. Podstawy tworzenia aplikacji

Zmienne przeniesione do sekcji prywatnej klasy nadal wymagają zainicjowania. Ini-


cjowanie przeprowadzaj w metodzie onCreate(), tak ważnej, że wprowadzonej do
kodu bez żadnej zachęty z naszej strony.

Plik MainActivity.java po tych kosmetycznych poprawkach ma następującą treść:


...
public class MainActivity extends Activity
{
private ImageView img;
private AnimationDrawable a;

@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
img = (ImageView)findViewById(R.id.imageView1);
a = (AnimationDrawable)img.getBackground();
}

public void kliknieto_start( View v)


{
a.start();
}
public void kliknieto_stop( View v)
{
a.stop();
}
}

W powyższym kodzie deklarację zmiennych reprezentujących obrazek i silnik filmu


przeniosłeś do sekcji prywatnej klasy. Zmienne te inicjujesz w metodzie onCreate(),
która zawsze jest obecna w tej klasie i służy właśnie do inicjowania takich rzeczy.
Metody odpowiedzialne za uruchamianie i zatrzymanie projekcji operują na tym sa-
mym silniku i mają bardzo proste ciała.

Można to napisać jeszcze lepiej... Zauważ, że obrazek jest potrzebny tylko do utwo-
rzenia silnika animacji (w przeciwieństwie do silnika, który jest konieczny zarówno
w onCreate(), jak i w kliknieto_start() oraz w kliknieto_stop()). Spróbuj przenieść
deklarację obrazka do wnętrza metody onCreate().
Rozdział 7.
Własne
komponenty graficzne
W tym rozdziale powiększysz zbiór komponentów przeznaczonych do zabudowy ekra-
nu — takich jak: przyciski, napisy czy obrazki — o własne konstrukcje (rysunek 7.1).
Jak skonstruować własny komponent? Jak operować nim za pomocą znanych już pli-
ków XML? Jak oprogramować w Javie zachowanie się nowego komponentu? O tym
wszystkim przeczytasz w tym rozdziale.

Rysunek 7.1.
Komponenty wizualne
służą do zabudowy
ekranu
122 Android. Podstawy tworzenia aplikacji

Komponenty rozszerzają bazowy


superkomponent View
Projekt ten znajduje się w pliku Rozdzial_7_a.zip.

Już wspominałem, że każdy komponent wizualny, np. Button, rozszerza komponent


bazowy View:
class Button extends View
{
...
}

Ta linia oznacza, że jakaś nowa klasa (tutaj: Button), powstaje z klasy o nazwie View,
coś do niej dopisując (extends — rozszerza). Jest godne uwagi, że nowa klasa odziedzi-
czy wszystkie cechy już istniejącej klasy View. Z tego powodu mechanizm ten nazywa
się dziedziczeniem, a klasy View i Button utworzą tzw. hierarchię klas. Być może nawet
spotkasz w literaturze określenie, że View jest ojcem Buttona albo że Button jest po-
tomkiem Viewa.

Poszerzanie klas jest znakomitym wynalazkiem, bo nowego komponentu nie będziesz


musiał pisać od podstaw — wystarczy, że dopiszesz do niego jakieś nowe właściwo-
ści i metody.

Zacznij nowy projekt i kliknij prawym klawiszem myszki okolice pliku Javy z zamia-
rem napisania nowej klasy (rysunek 7.2). Plik ten prawdopodobnie ma nazwę MainAc-
tivity.java i znajduje się w folderze o nazwie src (source, czyli źródło, w znaczeniu:
plik źródłowy) i dalej w sekwencji podfolderów o nazwach zgodnych z nazwą Twojego
unikalnego pakietu.

Środowisko Eclipse dysponuje kreatorem nowej klasy, dlatego tworzenie nowej klasy
nie jest trudne. Niemniej zatrzymaj się tutaj na chwilę. W okienku kreatora nowej klasy
wpisz jej nazwę. Jeśli klasa ma rozszerzać inną, już istniejącą klasę, powinieneś wpi-
sać nazwę jej pakietu (na rysunku 7.3: android.view.View). Niżej zaznacz, że chcesz au-
tomatycznie otrzymać nagłówki konstruktorów i metod abstrakcyjnych (rysunek 7.3).

Kreator utworzył nowy plik o nazwie Kwadraty.java, zawierający nową klasę o nazwie
Kwadraty. Jeśli klasa jest publiczna, to w Javie plik i klasa muszą nazywać się tak samo
(rysunek 7.4).
Rozdział 7.  Własne komponenty graficzne 123

Rysunek 7.2.
Kliknij prawym
klawiszem myszki folder,
w którym są
przechowywane
algorytmy w języku
Java. Z menu wybierz
polecenie New i dalej
Class.

Rysunek 7.3.
Okienko kreatora
nowej klasy
124 Android. Podstawy tworzenia aplikacji

Rysunek 7.4.
Kreator utworzył nowy
plik o nazwie
Kwadraty.java,
zawierający nową klasę
o nazwie Kwadraty

Kreator powinien utworzyć plik Kwadraty.java o mniej więcej następującej treści:


package pl.twojadomena.rozdzial_7_a;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;

public class Kwadraty extends View


{
public Kwadraty(Context context)
{
super(context);
// TODO Auto-generated constructor stub
}
public Kwadraty(Context context, AttributeSet attrs)
{
super(context, attrs);
// TODO Auto-generated constructor stub
}
public Kwadraty(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
}
}

W pliku tym jest definicja nowej klasy Kwadraty, dziedziczącej po bibliotecznej klasie
View. Importy zostały dopisane przez kreator z powodu wystąpienia kilku niezrozu-
miałych napisów (Context, View, AttributeSet). Są to jakieś pomocnicze klasy — nie
ma po co do nich zaglądać, ufając, że kreator nowej klasy wie, co robi...

Nowa klasa Kwadraty zawiera trzy metody Kwadraty() różniące się listami swoich ar-
gumentów. To tzw. konstruktory klasy — funkcje, które do gry wchodzą zawsze jako
pierwsze.

Powinieneś zapamiętać następującą zasadę: jeśli w klasie jest metoda o nazwie iden-
tycznej z nazwą klasy, to jest ona tzw. konstruktorem.
Rozdział 7.  Własne komponenty graficzne 125

Ciała trzech konstruktorów są prawie puste, ale co oznaczają linie super? Te linie
wiążą się z dziedziczeniem czy, jak kto woli, z poszerzaniem jednej klasy (View) przez
inną (Kwadraty). Słowo kluczowe super oznacza wywołanie konstruktora klasy bazo-
wej. Metoda o nazwie super() to konstruktor klasy bazowej, w tym przypadku View:
public Kwadraty(Context context)
{
super(context);
// TODO - do zrobienia w przyszłości...
}

Konstruktor klasy potomnej przede wszystkim wywołuje konstruktora klasy bazowej.


Może też robić coś jeszcze w ramach poszerzania klasy bazowej i zachęcają do tego
wtrącone przez kreator komentarze (ToDo — coś jeszcze do zrobienia).

Tak napisana klasa Kwadraty w istocie niczym nie różni się od klasy View. Mówiąc
inaczej: formalnie poszerza ją, ale niczego do niej nie dodaje. Jest to nieciekawy przy-
padek jałowej definicji, niemniej powinieneś tutaj sprawdzić, że program poprawnie się
kompiluje i uruchamia.

Twoim celem jest grafika. Postaraj się zatem tak poszerzyć klasę View, aby ładnie wyglą-
dała. Musisz przyjrzeć się klasie bazowej View, znaleźć w niej metodę, która odpowiada
za grafikę, i napisać jej nową wersję (rysunek 7.5). Zgodnie z terminologią programowa-
nia obiektowego będzie to brzmiało tak: należy nadpisać metodę odpowiadającą za jakąś
czynność w klasie bazowej, zastępując ją lepszym egzemplarzem.

Rysunek 7.5. Jak poszerzyć klasę View, by na ekraniku pojawił się nowy komponent o ciekawej grafice?
126 Android. Podstawy tworzenia aplikacji

Stań kursorem w obrębie ciała klasy, ale poza jej metodami, i poproś suflera Ctrl+Spacja
o nową metodę. Gdy pojawi się lista z podpowiedziami, zacznij pisać wyraz „onDraw”
— w miarę przybywania liter w napisie liczba dostępnych wariantów będzie się zmniej-
szała (rysunek 7.6).

Rysunek 7.6.
Korzystanie z suflera
Ctrl+Spacja przy
wyborze nowej metody

Nadpisania wymaga metoda onDraw() klasy bazowej. Ta metoda w klasie bazowej


View jest wywoływana zawsze wtedy, gdy trzeba wyświetlić komponent na ekraniku,
i w oryginale nie robi nic. Nadpisanie metody rozumiane jest jako dostarczenie nowej
receptury grafiki.

Definicja klasy Kwadraty powinna się powiększyć o metodę onDraw():


@Override
protected void onDraw(Canvas canvas)
{
// TODO Auto-generated method stub
super.onDraw(canvas);
}

Nagłówek nowej funkcji musi dokładnie odpowiadać nagłówkowi oryginału znajdu-


jącemu się w klasie bazowej. Jeśli tego nie dopilnujesz, w programie pojawi się nowa
funkcja, tylko zbliżona do onDraw(), ale niezdolna do pełnego nadpisania oryginału.
Najlepiej więc uzyskiwać taki nagłówek za pomocą suflera Ctrl+Spacja, który prześledzi
kod klasy bazowej i bezbłędnie zaproponuje odpowiednie nagłówki.

Jedyna w tej funkcji fraza ze słowem kluczowym super nieco przypomina frazy w kon-
struktorach, jednak jej rola oraz sam zapis są inne. W konstruktorach słowo super ozna-
czało wywołanie konstruktora bazowego. Tam te linie musiały być spisane jako pierwsze,
bo tak się pisze w Javie konstruktory. Tutaj linia super oznacza wywołanie macierzy-
stej (bazowej) funkcji onDraw(), która przecież nazywa się tak samo w klasie Kwadraty
i w klasie View. Gdyby napisać:
@Override
protected void onDraw(Canvas canvas)
{
Rozdział 7.  Własne komponenty graficzne 127

onDraw(canvas);
}

kompilator z pewnością nie połapałby się, które onDraw() jest które. Słowo kluczowe
super oznacza tu: wywołaj metodę onDraw() z klasy bazowej. Słowo kluczowe super
— jeśli się pojawia — zawsze dotyczy klasy bazowej.

Bardzo często zauważysz, że metoda nadpisująca zawiera wywołanie swojej nadpi-


sywanej odpowiedniczki za pomocą słowa kluczowego super. Po co? Widocznie ta
nadpisywana odpowiedniczka coś ważnego jednak robi i nie można całkowicie pominąć
jej wywołania.

Pisząc metody Override (metody nadpisane), zawsze zwracaj uwagę na szczegóły — czy
po swoich własnych algorytmach powinieneś jeszcze dopuścić do głosu metodę bazową.

Niech nowa metoda onDraw() kreśli w środku komponentu niebieskie kółko o promie-
niu 100 jednostek (kółko w klasie Kwadraty? Jeśli chcesz, popraw ten błąd...):
@Override
protected void onDraw(Canvas canvas)
{
int szer = getMeasuredWidth();
int wys = getMeasuredHeight();
Paint p = new Paint();
p.setStyle(Paint.Style.FILL);
p.setColor( Color.BLUE);
canvas.drawCircle(szer/2, wys/2, 100, p);
super.onDraw(canvas);
}

W tej metodzie poznajesz rozmiary komponentu — funkcje getMeasure ....() należą


do bazowej klasy View i zwracają rozmiary.

Potem deklarujesz egzemplarz klasy Paint, określającej właściwości rysowania: kolory,


style, grubości itd. Egzemplarz nazywa się p i w kilku liniach ustawiasz go tak, aby
koło miało jednolity niebieski kolor.

Egzemplarz canvas klasy Canvas jest tu najważniejszy. Ta klasa zawiera wszystkie po-
lecenia rysowania, także rysowanie koła w środku komponentu o promieniu 100 jed-
nostek.

Pora sprawdzić, jak to wszystko działa. Jeśli Twoja klasa została prawidłowo napisana,
a w tym przypadku najważniejsze jest prawidłowe dziedziczenie po klasie bazowej View,
odpowiadająca jej ikonka powinna się pojawić na zakładce Custom & Library Views
(komponenty użytkownika) — rysunek 7.7. Być może będziesz musiał kliknąć wi-
doczny na dole okna przycisk Refresh (odśwież), jakby popędzając Eclipse do rozej-
rzenia się po zasobach i poszukania nowych elementów aplikacji. Przeciągnij ikonkę
swojej klasy na obszar ekranu.
128 Android. Podstawy tworzenia aplikacji

Rysunek 7.7.
Jeśli klasa została
prawidłowo napisana,
odpowiadająca jej
ikonka powinna pojawić
się na zakładce Custom
& Library Views

Prawidłowo napisana klasa, reprezentująca element zabudowy ekranu i dziedzicząca po


bazowej klasie View, powinna znaleźć się wśród bibliotecznych komponentów. Przecią-
gnij ją na obszar ekranika. Główny plik wyglądu, o nazwie prawdopodobnie activity_
main.xml, powinien mieć mniej więcej taką postać:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >

<pl.twojadomena.rozdzial_7_a.Kwadraty
android:id="@+id/kwadraty1"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
</RelativeLayout>

Jest to standardowy opis komponentu, takie frazy widziałeś już wielokrotnie. Tyle że
Twój nowy komponent ma trochę dziwną nazwę, kwalifikowaną nazwą całego pakietu:
<pl.twojadomena.rozdzial_7_a.Kwadraty
...

Zatem... potrafisz pisać własne komponenty! W pliku Javy umieszczasz rozszerzenie


klasy View z nadpisaną metodą onDraw(), realizującą to, co sobie wymyśliłeś. Nowy
potomek klasy View powinien być dostępny za pomocą edytorka wizualnego (być
może należy skorzystać z przycisku Refresh — odśwież paletę komponentów). Wy-
starczy umieścić ikonkę komponentu w polu ekranu i opracować kilka standardowych
właściwości, takich jak rozmiary czy położenie na ekranie (rysunek 7.8).
Rozdział 7.  Własne komponenty graficzne 129

Rysunek 7.8.
Do napisania
komponentu wystarczy
umieścić ikonkę
komponentu w polu
ekranu i opracować
kilka standardowych
właściwości, takich jak
rozmiary czy położenie
na ekranie

Spróbuj trochę pobawić się nowym komponentem. Niech komponent ma na swoich


granicach obwódkę, a jego wnętrze niech się składa z dziesięciu losowo rozmieszczonych
kółek w losowo wybranych kolorach (rysunek 7.9).

Rysunek 7.9.
Kółka
o losowo wyznaczonych
promieniach,
rozstawione w losowo
wybranych miejscach
i mające losowo
zdefiniowane kolory.
W każdym cyklu pętli
maszyna losująca
dostarcza sześciu liczb.
Trzy z nich służą do
zbudowania koloru,
pozostałe określają
położenie i wielkość
kółka. Tylko jakoś
przez przypadek wyszło,
że klasa nazywa się
Kwadraty... W wolnej
chwili wróć do
nadpisującej metody
onDraw() i zastąp kółka
kwadratami
130 Android. Podstawy tworzenia aplikacji

Jak zrealizować tak wyśrubowane żądanie? Oczywiście odpowiednio przygotować


metodę onDraw():
@Override
protected void onDraw(Canvas canvas)
{
int szer = getWidth();
int wys = getHeight();
Paint p = new Paint();
p.setAntiAlias(true);
p.setStyle(Paint.Style.FILL);
Random r = new Random();
for( int i = 0; i < 10; i++)
{
p.setARGB( 255, r.nextInt(256), r.nextInt(256), r.nextInt(256));
canvas.drawCircle( r.nextInt( szer), r.nextInt( wys), r.nextInt( 100), p);
}
p.setStyle(Paint.Style.STROKE);
p.setStrokeWidth( 5);
p.setColor( Color.YELLOW);
canvas.drawRect( 2, 2, szer-3, wys-3, p);
super.onDraw(canvas);
}

Najpierw odczytujesz rozmiary komponentu. Jest to obszar dostępny do rysowania.


Potem deklarujesz obiekt o nazwie p typu Paint (czyli: egzemplarz klasy Paint), który
posłuży do ustalania atrybutów kreślonych figur.

Już na samym początku masz niezwykły atrybut zwany antyaliasingiem, czyli wygła-
dzaniem. Koniecznie sprawdź, jak wygląda ekran urządzenia z tą linią, a jak bez niej:
p.setAntiAlias(true);

Potem rysujesz dziesięć kółek. Ustalasz styl rysowania na wypełnianie:


p.setStyle(Paint.Style.FILL);

a nie kreślenie tylko konturów, co też mógłbyś sprawdzić w wolnej chwili:


p.setStyle(Paint.Style.STROKE);

W zadaniu była mowa o losowaniu, zatem deklarujesz egzemplarz maszyny losującej:


Random r = new Random();

Dopisaniu tej linii do programu musi towarzyszyć dodanie importu odpowiedniego


pakietu:
import java.util.Random;

Samo rysowanie dziesięciu kółek realizuje pętla. W pętli ustalasz kolor rysowania i ry-
sujesz kolejne kółko. Widoczne tam frazy:
..., r.nextInt(256), ...

oznaczają pobranie kolejnej liczby z maszyny losującej, w omawianym przypadku: z za-


kresu od zera do 255 (czyli 256 możliwych do wylosowania liczb).
Rozdział 7.  Własne komponenty graficzne 131

Wykorzystując kółka, rozwiąż jeszcze jeden problem: przypisanie do nowego kom-


ponentu algorytmu do obsługi zdarzenia kliknieto (rysunek 7.10). Niech komponent
odświeży swój wygląd (albo może zrobi coś innego?) w momencie kliknięcia go.
Może to być dość efektowne — po kliknięciu powinny pojawić się zupełnie inne kół-
ka, bo nieprawdopodobieństwem jest oczekiwać od maszyny losującej Random, aby
dostarczyła dwa razy tych samych wartości liczbowych.

Rysunek 7.10.
Praca w edytorku
wizualnym: zaznaczenie
nowego komponentu
i w polu właściwości
OnClick wpisanie nazwy
metody (np. napis
kliknieto — oczywiście
bez polskich znaków,
bo nazwa metody jest
elementem programu
w Javie)

W edytorze wizualnym opracuj właściwość OnClick, wpisując tam nazwę metody. Po-
tem zdefiniuj tę metodę w klasie głównej, spisanej w pliku o nazwie prawdopodobnie
MainActivity.java:
package pl.twojadomena.rozdzial_7_a;

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.View;

public class MainActivity extends Activity


{
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void kliknieto(View v)
{
v.invalidate();
}
}

Metodę kliknieto() umieściłem jako ostatnią w klasie MainActivity (kolejność


metod nie ma znaczenia). Ta metoda zawiera wywołanie funkcji realizującej ponowne
odrysowanie grafiki komponentu. Wystąpienie zdarzenia OnClick z udziałem kompo-
nentu uruchomi metodę kliknieto(), a metoda ta uruchomi publiczną funkcję klasy
bazowej View, nakazującej odrysowanie grafiki. Samym odrysowaniem grafiki zajmie
się już Twoja nadpisana metoda onDraw().
132 Android. Podstawy tworzenia aplikacji

Poszerzanie klasy z nadpisywaniem jej wybranych metod to wielki chwyt programo-


wania obiektowego: oto masz jakąś klasę biblioteczną, nie wolno Ci zmieniać jej
ustroju, ale jeśli utworzysz jej potomka, wtedy możesz już coś poprawiać. Może nie
bezpośrednio w każdej linii kodu, ale za pomocą nadpisywania metod, co też jest wy-
starczająco skutecznym mechanizmem wprowadzania zmian.

Czy są metody, których nie wolno nadpisywać, bo prawdopodobnie coś można w ten
sposób popsuć w ustroju klasy? Tak — to metody finalne (słowo kluczowe final w linii
tytułowej metody). Nie spotkałeś się jeszcze z metodami final.

Czy są metody, które trzeba nadpisać, bo na poziomie klasy bazowej nie udało się ich
przygotować? Oczywiście — to tzw. metody abstrakcyjne, czyli nieistniejące. Też
jeszcze nie spotkałeś się z nimi.

Przegląd możliwości graficznych


Projekt ten znajduje się w pliku Rozdzial_7_b.zip.

Pora wykorzystać zdobyte umiejętności i przygotować komponent wizualny, który


posłuży do zbadania wybranych metod kreślących figury geometryczne. Po napisaniu
odpowiedniej klasy dziedziczącej po klasie View wielokrotnie użyjesz komponentu,
opisując w pliku XML jego pojawienia się na ekranie. Nie musisz się obawiać, że się
za bardzo napracujesz — jeden komponent powinien na Twoje żądanie pokazać albo kół-
ka, albo prostokąty, albo linie itd. (rysunek 7.11). Wystarczy, że napiszesz dostatecz-
nie uniwersalny komponent, wtedy będziesz mógł wielokrotnie użyć go w pliku XML
i za pomocą jednego narzędzia zademonstrować wiele możliwości biblioteki graficznej.

Rysunek 7.11.
Komponent jest ramką,
w lewej części
wyświetlane są jakieś
kształty (kółka,
prostokąty, linie itd.),
w prawej ich nazwy
Rozdział 7.  Własne komponenty graficzne 133

Zacznij nowy projekt. Gdy w folderze roboczym pojawi się nowy zestaw katalogów
i plików, tak jak w poprzednim projekcie kliknij prawym klawiszem myszki folder
przeznaczony na pliki Javy i przywołaj kreator nowej klasy.

W kreatorze nadaj nowej klasie nazwę (tutaj: Figura) i koniecznie określ, że ta klasa
poszerza klasę biblioteczną View. Jak już wiesz, klasa poszerzana to Superclass, a po
polsku mówi się raczej: klasa bazowa.

W kreatorze zaznacz też, żeby kreator dostarczył Ci gotowych nagłówków konstruk-


torów, które można będzie wydedukować z nagłówków konstruktorów klasy bazowej.
Na wszelki wypadek zażądaj też nagłówków metod nieistniejących, jednak koniecz-
nych do napisania, czyli metod abstrakcyjnych. Akurat w klasie View nie ma takich
metod, ale nie zaszkodzi być ostrożnym na zapas.

Niech nowa klasa będzie publiczna — wtedy kreator utworzy plik Figura.java zawie-
rający definicję klasy Figura (rysunek 7.12).

Rysunek 7.12.
Niech nowa klasa
nazywa się Figura
i niech koniecznie
rozszerza klasę View.
Jeśli jest to klasa
publiczna (takie
rozwiązanie proponuje
kreator nowej klasy),
jej plik także będzie
nazywać się Figura

Oto plik Figura.java tuż po półautomatycznym utworzeniu za pomocą kreatora nowej


klasy:
package pl.twojadomena.test_rozdzial_7_b;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;

public class Figura extends View


{
public Figura(Context context)
{
super(context);
}
public Figura(Context context, AttributeSet attrs)
{
134 Android. Podstawy tworzenia aplikacji

super(context, attrs);
}
public Figura(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
}
// Tu dopisz metodę onDraw.
}

Klasa bazowa View najwidoczniej zawiera trzy konstruktory i w klasie rozszerzającej


Figura kreator proponuje także definicje trzech konstruktorów. Tak naprawdę będzie
Ci potrzebny tylko środkowy konstruktor. Jest to konstruktor przystosowany do prze-
chwytywania wartości z plików XML. Nie będziesz z tego korzystać, ale środkowy
konstruktor jest niezbędny do kompilacji projektu. Pozostałe konstruktory też zostaw
w spokoju. Zapamiętaj, że w pierwszej linii każdego konstruktora musi się znajdować
wywołanie konstruktora klasy bazowej.

Teraz krok najważniejszy: opracowanie mechanizmu rysującego komponent na ekranie.


Zapewne pamiętasz, że za grafikę w klasie View odpowiada metoda onDraw(). W klasie
poszerzającej można nadpisać metodę bazową. Albo ręcznie dopisz do ustroju klasy
kolejną metodę, albo skorzystaj z suflera Ctrl+Spacja (kursor musi stać w odpowied-
nim miejscu tekstu!).

Oto proponowana metoda onDraw():


@Override
protected void onDraw(Canvas canvas)
{
int szer = getWidth();
int szer2 = szer/2;
int wys = getHeight();
Paint p = new Paint();
p.setAntiAlias(true);
p.setStyle(Paint.Style.FILL);
Random r = new Random();

CharSequence opis = getContentDescription();


for( int i = 0; i < 10; i++)
{
p.setARGB( 255, r.nextInt(256), r.nextInt(256), r.nextInt(256));
RectF rect = new RectF( r.nextInt( szer2), r.nextInt( wys), r.nextInt( szer2),
r.nextInt( wys));
if( opis.equals( "koło"))
{
canvas.drawCircle( r.nextInt( szer2), r.nextInt( wys), r.nextInt( 30), p);
}
else if( opis.equals( "elipsa"))
{
canvas.drawOval( rect, p);
}
else if( opis.equals( "prostokąt"))
{
canvas.drawRect( rect, p);
}
else if( opis.equals( "prostokąt okrągły"))
Rozdział 7.  Własne komponenty graficzne 135

{
canvas.drawRoundRect( rect, 5, 5, p);
}
else if( opis.equals( "łuk"))
{
canvas.drawArc( rect, r.nextInt( 360), r.nextInt( 360), false, p);
}
else if( opis.equals( "linia"))
{
canvas.drawLine( r.nextInt( szer2), r.nextInt( wys), r.nextInt( szer2),
r.nextInt( wys), p);
}
else if( opis.equals( "punkt"))
{
canvas.drawPoint( r.nextInt( szer2), r.nextInt( wys), p);
canvas.drawPoint( r.nextInt( szer2), r.nextInt( wys), p);
}
}
p.setTextSize( 20);
p.setTextAlign( Paint.Align.RIGHT);
p.setColor( Color.WHITE);
canvas.drawText( (String)opis, szer - 20, wys/2, p);

p.setStyle(Paint.Style.STROKE);
p.setStrokeWidth( 1);
canvas.drawRect( 0, 0, szer-1, wys-1, p);
super.onDraw(canvas);
}

Definicja tej metody jest podobna do tej z poprzedniego projektu, ale dłuższa. Najpierw
poznajesz ekranowe rozmiary komponentu i przechowujesz je w zmiennych szer i wys.
Wartość zmiennej szer2 jest równa połowie szerokości. Potem deklarujesz zmienną p
typu Paint — to egzemplarz klasy opisującej szczegóły rysowania: kolory, style,
grubości itp. Następnie wybierasz antyaliasing, czyli wygładzanie grafiki. Algorytm
rysowania z włączonym antyaliasingiem jest znacznie bardziej skomplikowany, ale za
to rysunek będzie wtedy zdecydowanie ładniejszy. Ustalasz styl malowania na pełny,
tzn. figury będą wypełnione farbą. Potem tworzysz egzemplarz maszyny losującej.
Wszystkie kolory, a także rozmiary i położenia poszczególnych figur geometrycznych
będą przypadkowe.

Kolejna linia wymaga obszerniejszego komentarza:


CharSequence opis = getContentDescription();

Metoda (pochodząca oczywiście z klasy bazowej View, bo nie Ty ją definiowałeś) służy


do przekazania wartości atrybutu android:contentDescription (opis zawartości) z pliku
XML do pliku Javy. W wyniku działania tego fragmentu programu wartość atrybutu
ustalona w pliku z zasobami znajdzie się w zmiennej o nazwie opis. Wrócę do tego
zagadnienia za chwilę, gdy po ukończeniu pracy nad klasą przejdziesz do edytorka
wizualnego i do plików XML.

Potem otwierasz pętlę wykonującą dziesięć obrotów. W każdym obrocie pętli losujesz
kolor dla obiektu p typu Paint. Wylosowania wymagają trzy amplitudy koloru, każda
136 Android. Podstawy tworzenia aplikacji

mieszcząca się w zakresie od 0 do 255. Losujesz też cztery liczby dla zmiennej o na-
zwie rect, która jest egzemplarzem klasy RectF. To opis prostokąta — seria czterech
liczb. Okazuje się, że większość figur w tej bibliotece oczekuje podania zakreślające-
go je prostokąta, ale nie wszystkie — np. dla kółka nie jest to konieczne. W każdym
razie od razu wylosuj rogi prostokąta.

Teraz następuje długa sekwencja instrukcji warunkowych, badających, jaki tekst mie-
ści się w zmiennej opis. Jeśli jest tam wyraz koło, kreślą się losowo rozmieszczone
koła o promieniu do 30 pikseli:
if( opis.equals( "koło"))
{
canvas.drawCircle( r.nextInt( szer2), r.nextInt( wys), r.nextInt( 30), p);
}

Jeśli w zmiennej opis znajduje się wyraz prostokąt, wykona się następujący kawałek
kodu:
else if( opis.equals( "prostokąt"))
{
canvas.drawRect( rect, p);
}

Tu już przydał się wylosowany prostokąt rect, ograniczający figurę.

Podsumowjąc: w tej długiej sekcji instrukcji warunkowych każda figura ma swój ka-
wałek kodu. Cztery przedostatnie instrukcje wypisują tekst na ekran — tym tekstem
jest opis figury. Cztery ostatnie obrysowują komponent cienką ramką.

Ta funkcja w istocie niewiele różni się od jej odpowiednika w poprzednim projekcie.


Jedyną nową i wartą uwagi sztuczką jest przekazanie do Javy wartości atrybutu zada-
nej w pliku XML:
opis = getContentDescription();

Spójrz więc, co się dzieje po stronie zasobów. Kliknij dwa razy główny plik wyglądu, aby
przywołać edytorek wizualny. Metodą bezpośredniej edycji zastąp tag RelativeLayout
tagiem ScrollView i tagiem LinearLayout. Dzięki temu aplikacja będzie „przewijana”
na ekraniku. Istnieje bowiem obawa, że figur będzie dużo i, być może, nie zmieszczą
się wszystkie razem na ekranie:
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#404040" >
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >

</LinearLayout>
</ScrollView>
Rozdział 7.  Własne komponenty graficzne 137

ScrollView wypełnia cały ekranik — świadczą o tym wartości match_parent przypi-


sane do jego rozmiarów. Z kolei cała szerokość ScrollViewa jest zapełniona rozkładem
liniowym pionowym, czyli jeden komponent będzie leżał pod drugim (rysunek 7.13).
Zauważ, że wysokość komponentu jest ustalona na wrap_content (otaczaj zawartość).
Jeśli komponentów będzie dużo, LinearLayout będzie długi, zauważy to ScrollView
i wyświetli pionowy suwak przewijania zawartości ekranu.

Rysunek 7.13. Nowy komponent powinien pojawić się w edytorze wizualnym. Nowe komponenty należy
osadzać w jakimś porządku (tutaj: w przesuwanym z góry na dół schemacie ScrollView) i każdemu z nich
trzeba przypisać wartość atrybutu w rodzaju android:contentDescription="koło", zgodnie z napisami
zdefiniowanymi w pliku Javy

Po tych czynnościach wstępnych, polegających na ustaleniu rozkładu komponentów


zabudowujących ekran, pora na dopisywanie komponentów Figura do głównego pliku
wyglądu. Czy nowy komponent jest już na zakładce Custom &Library Views? Jeśli
jeszcze go nie widać, kliknij znajdujący się tam przycisk Refresh (odśwież).

Przeciągaj na obszar ekranika komponent Figura, za każdym razem kontrolując jego


położenie w hierarchii innych obiektów, wyrównanie, ewentualnie marginesy. Z naj-
wyższą uwagą wpisuj wartości parametru android:contentDescription, bo parametr
ten odgrywa kluczową rolę po stronie Javy, w metodzie onDraw() (rysunek 7.14).

Po wizualnym wypełnieniu ekranu komponentami od razu przejdź do ręcznej edycji


pliku (służą do tego zakładki na dole okienka w edytorku wizualnym) i popraw ewentu-
alne niezręczności, a nawet usuń źle osadzony komponent, aby umieścić go lepiej.
138 Android. Podstawy tworzenia aplikacji

Rysunek 7.14. Komponenty należy przeciągać w pole ekranika albo, precyzyjniej, na odpowiednie
węzły struktury drzewiastej w prawym górnym rogu, jednocześnie opracowując właściwości
contentDescription, bo metoda onDraw() od razu przeczyta ich wartości i narysuje albo kółka,
albo linie, albo coś innego

Główny plik wyglądu, o nazwie prawdopodobnie activity_main.xml, powinien mieć


mniej więcej taką treść:
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#404040" >
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >

<pl.twojadomena.test_rozdzial_7_b.Figura
android:id="@+id/figura1"
android:layout_width="fill_parent"
android:layout_height="50dp"
android:layout_marginLeft="5dp"
android:layout_marginTop="5dp"
android:contentDescription="koło" />

<pl.twojadomena.test_rozdzial_7_b.Figura
android:id="@+id/figura2"
android:layout_width="fill_parent"
android:layout_height="50dp "
android:layout_marginLeft="5dp"
android:layout_marginTop="5dp"
android:contentDescription="prostokąt" />
Rozdział 7.  Własne komponenty graficzne 139

<pl.twojadomena.test_rozdzial_7_b.Figura
android:id="@+id/figura3"
android:layout_width="fill_parent"
android:layout_height="50dp"
android:layout_marginLeft="5dp"
android:layout_marginTop="5dp"
android:contentDescription="prostokąt okrągły" />
</LinearLayout>
</ScrollView>

Ten plik nie jest ukończony, bo zostało jeszcze kilka innych wartości contentDescription.
Ale przypomnij sobie, co pisałem o stylach. Podobieństwo definiowania kolejnych
komponentów Figura wręcz zaprasza do zbudowania czegoś w rodzaju podprogramu
XML — czyli właśnie stylu.

Kliknij dwa razy plik styles.xml, otwierając go do edycji (mógłbyś też wprowadzić do
gry nowy plik za pomocą kreatora plików XML, ale po co, skoro łatwiej dopisać się do
istniejącego). Proponuję, żebyś przeszedł do edycji tekstowej za pomocą zakładki na
dole edytorka i zdefiniował nowy styl za pomocą suflera Ctrl+Spacja. Styl musi mieć
nazwę (tutaj: styl) oraz kilka atrybutów i ich wartości (rysunek 7.15).

Rysunek 7.15. Edycja tekstowa pliku styles.xml i definiowanie nowego stylu za pomocą suflera
Ctrl+Spacja

Oto plik styles.xml, do którego dopisany został nowy, własny styl o nazwie styl:
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<style name="AppTheme" parent="android:Theme.Light" />
<style name="styl">
<item name="android:layout_width">fill_parent</item>
<item name="android:layout_height">50dp</item>
<item name="android:layout_marginLeft">5dp</item>
<item name="android:layout_marginRight">5dp</item>
140 Android. Podstawy tworzenia aplikacji

<item name="android:layout_marginTop">5dp</item>
<item name="android:background">#404040</item>
</style>
</resources>

Omawiany styl jest zbiorem definicji, które występują w każdym komponencie Figura.
Dzięki stylowi zapis głównego pliku wyglądu bardzo się skraca. Ponadto przy ewentual-
nych zmianach nie musisz nanosić ich w każdym komponencie — wystarczy, że coś po-
prawisz w pliku styles.xml. Styl jest niezwykle korzystnym rozwiązaniem, gdy masz
na ekranie dużo podobnych komponentów.

Główny plik wyglądu activity_main.xml robi się monotonny i krótki:


<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#404040" >
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<pl.twojadomena.test_rozdzial_7_b.Figura
android:id="@+id/figura1"
style="@style/styl"
android:contentDescription="koło" />
<pl.twojadomena.test_rozdzial_7_b.Figura
android:id="@+id/figura2"
style="@style/styl"
android:contentDescription="prostokąt" />
<pl.twojadomena.test_rozdzial_7_b.Figura
android:id="@+id/figura3"
style="@style/styl"
android:contentDescription="prostokąt okrągły" />
<pl.twojadomena.test_rozdzial_7_b.Figura
android:id="@+id/figura4"
style="@style/styl"
android:contentDescription="elipsa" />
<pl.twojadomena.test_rozdzial_7_b.Figura
android:id="@+id/figura5"
style="@style/styl"
android:contentDescription="łuk" />
<pl.twojadomena.test_rozdzial_7_b.Figura
android:id="@+id/figura6"
style="@style/styl"
android:contentDescription="linia" />
<pl.twojadomena.test_rozdzial_7_b.Figura
android:id="@+id/figura7"
style="@style/styl"
android:contentDescription="punkt" />
</LinearLayout>
</ScrollView>

Zagadnienie przekazywania do Javy wartości parametru określonej w pliku XML


jest dość złożone. Tutaj udało nam się stosunkowo łatwo określać wartości atrybutu
Rozdział 7.  Własne komponenty graficzne 141

contentDescription po stronie XML i odczytywać je za pomocą metody getContent


Description() po stronie Javy. Algorytm w Javie oczekiwał kilku wartości tego pa-
rametru i rozpoznawszy którąś z nich, kreślił kółka, prostokąty, linie itd. (rysunek 7.16).

Rysunek 7.16.
Program operuje
komponentem
wyświetlającym pewne
figury geometryczne
i wypisującym ich
nazwę. Ta nazwa
odgrywa ważną rolę
dodatkową — jest
parametrem
komponentu. Dzięki
nazwie komponent wie,
co ma narysować

Skoro napisy „koło” czy „prostokąt” są takie ważne w tej implementacji, należałoby
jeszcze trochę podnieść poziom elegancji aplikacji, definiując je w pliku strings.xml
(pamiętaj o suflerze Ctrl + spacja!):
<resources>
<string name="app_name">Test_Rozdzial_7_b</string>
<string name="hello_world">Hello world!</string>
<string name="menu_settings">Settings</string>
<string name="title_activity_main">Test Rozdział 7b.</string>
<string name="circle">koło</string>
<string name="rect">prostokąt</string>
<string name="roundrect">prostokąt okrągły</string>
<string name="oval">elipsa</string>
<string name="arc">łuk</string>
<string name="line">linia</string>
<string name="point">punkt</string>
</resources>

Wtedy w pliku wyglądu nie należy umieszczać bezpośrednich napisów, ale odwołania
do tekstów:
...
<pl.twojadomena.test_rozdzial_7_b.Figura
android:id="@+id/figura1"
style="@style/styl"
android:contentDescription="@string/circle" />
142 Android. Podstawy tworzenia aplikacji

<pl.twojadomena.test_rozdzial_7_b.Figura
android:id="@+id/figura2"
style="@style/styl"
android:contentDescription="@string/rect" />
...

Na zakończenie uwaga o naturze losowania i grafice komputerowej. Maszyny muszą


często wywoływać jakieś swoje funkcje onDraw(), w których są zgromadzone proce-
dury graficzne. Tego typu funkcje zazwyczaj są wywoływane przez system, tylko
niekiedy wywołuje je użytkownik. Wystarczy, aby urządzenie odświeżało wyświetlany
obraz.

Prawdopodobnie nie jest dobrze, gdy losowanie czegokolwiek odbywa się w funkcji
odpowiadającej za grafikę, bo za każdym razem otrzymuje się trochę inny obraz
(wylosowują się nieco inne wartości). Każde odświeżenie obrazu skutkuje pojawie-
niem się innego obrazu.

Jak zrobić, aby obraz przypadkowych figur był stabilny od początku do końca pracy
aplikacji? To dość proste. Wszystkie potrzebne liczby (jest ich tutaj sporo) należy
wylosować jeden raz, na początku czasu życia aplikacji. Najlepiej to zrobić w kon-
struktorze klasy, bo konstruktor jest funkcją, która wchodzi do gry tylko raz i zawsze
jako pierwsza:
public class Figura extends View
{
private liczby_losowe[100];
private void losuj_dane()
{
//tutaj losuj wszystkie liczby i przechowuj je np. w tablicy
}
public Figura(Context context)
{
super(context);
losuj_dane();
}
public Figura(Context context, AttributeSet attrs)
{
super(context, attrs);
losuj_dane();
}
public Figura(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
losuj_dane();
}
@Override
protected void onDraw(Canvas canvas)
{
//tutaj niczego już nie losuj - wykorzystaj liczby wcześniej wylosowane...
}
}
Rozdział 7.  Własne komponenty graficzne 143

Prywatna metoda losuj_dane() zajmuje się losowaniem kilkudziesięciu liczb. Warto


powołać taką metodę do życia, bo są aż trzy konstruktory, więc opisywanie w każdym
z nich procesu losowania byłoby marnowaniem czasu i miejsca.

Metoda losuj_dane() wpisuje losowe liczby do prywatnej tablicy liczby_losowe[].


Wprawdzie nic nie mówiłem o tablicach, ale nie przejmuj się brakami wiedzy technicz-
nej, poczuj tylko ideę tego algorytmu.

Metoda onDraw(), wywoływana często i nagle, niczego już nie losuje, jedynie pobiera
serię liczb z tablicy. Zawsze taką samą serię, przygotowaną jeden jedyny raz na począt-
ku pracy programu. Każdy generowany ekran jest taki sam. Program jest też dzięki
temu szybszy.
144 Android. Podstawy tworzenia aplikacji
Rozdział 8.
Mapy bitowe
Z mapami bitowymi mamy do czynienia od samego początku. Do tej pory były to gotowe
mapy bitowe, zgromadzone w folderach o nazwach drawable..., zazwyczaj przygoto-
wane w kilku rozmiarach i przeznaczone do wyświetlania na ekranach o różnej jakości.

Teraz przyjrzysz się mapom bitowym od strony programowania. Jak utworzyć mapę
bitową? Jak na niej rysować? Jak ją wyświetlić? Oto pytania, na które będziesz szukał
odpowiedzi.

Mapa bitowa zaczerpnięta


z zasobów aplikacji
Projekt ten znajduje się w pliku Rozdzial_8_a.zip.

Poznawanie świata map bitowych zacznij od przygotowania gotowego obrazka jako


zasobu graficznego. Nie będzie to nic nowego — zasobami graficznymi zajmowałeś
się już w rozdziałe trzecim.

W folderach drawable-mdpi, ...-ldpi i ...-hdmi (jak zapewne pamiętasz: przeznaczo-


nych na zasoby o różnej jakości) umieść jakąś mapkę bitową o boku kilkuset pikseli.
Może to być któraś mapka użyta we wcześniejszych projektach (rysunek 8.1). Będzie
to zasób graficzny przeznaczony do dalszych testów. Aby Eclipse zarejestrowało no-
wy zasób, powinieneś kliknąć prawym klawiszem myszki foldery projektu i wybrać
polecenie Refresh (odśwież).

Następnym krokiem niech będzie zbudowanie interfejsu użytkownika. Będziesz po-


trzebował trzech komponentów: przycisku Button do aktywowania operacji, którą za
chwilę będziesz implementować w Javie, pola tekstowego TextView do wypisywania in-
formacji o przebiegu całego procesu i obrazka ImageView do podglądu samej mapy
bitowej (rysunek 8.2). Przycisk i tekst umieść w miarę ciasno obok siebie, tym samym
oszczędzając miejsce na ekranie. Obrazek rozciągnij na pozostały obszar ekranu.
146 Android. Podstawy tworzenia aplikacji

Rysunek 8.1.
W folderach
drawable-... należy
umieścić mapkę bitową
w różnych rozmiarach

Rysunek 8.2.
Interfejs użytkownika
niech się składa
z przycisku, pola
tekstowego i obrazka

Główny plik wyglądu, umieszczony w folderze layout i prawdopodobnie noszący na-


zwę activity_main.xml niech ma mniej więcej taką treść:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:onClick="kliknij"
android:text="Button" />
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/button1"
Rozdział 8.  Mapy bitowe 147

android:layout_alignBottom="@+id/button1"
android:layout_marginLeft="20dp"
android:layout_toRightOf="@+id/button1"
android:text="TextView" />
<ImageView
android:id="@+id/imageView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true"
android:layout_below="@+id/button1"
android:layout_marginTop="20dp"
android:src="@drawable/ryba" />
</RelativeLayout>

Twoja treść nie musi dokładnie odpowiadać mojej — jest to plik uzyskany techniką
edycji wizualnej, podczas której o szczegółach decydują ruchy myszką. Zwróć tylko
uwagę na to, że przycisk ma przypisaną metodę do obsługi zdarzenia onClick:
android:onClick="kliknij"

Tę metodę za chwilę napiszesz w Javie. Metoda ta będzie głównym polem bitwy to-
czonej w niniejszym rozdziale.

Zwróć też uwagę, że obrazek wyświetla Twoją bitmapę (rysunek 8.3). Główny plik
Javy, opisujący działanie tzw. aktywności (Activity), znajdziesz w podfolderach src
(od wyrazu source — źródło, tutaj rozumiane jako pliki źródłowe). Widoczna tam
klasa rozszerza biblioteczną klasę Activity. Ta klasa nadpisuje dwie metody z klasy
bibliotecznej. Dodaj do klasy nową metodę do obsługi kliknięcia przycisku. Jej nazwa
musi być zgodna z nazwą właściwości onClick przycisku.
android:src="@drawable/ryba"

Rysunek 8.3.
Główny plik Javy
opisujący działanie
tzw. aktywności
(Activity) znajduje się
w podfolderach src
148 Android. Podstawy tworzenia aplikacji

Po tych czynnościach wstępnych przejdź do Javy. Na początek musisz spisać metodę


obsługującą kliknięcie przycisku. Jeśli tego nie zrobisz i nieopatrznie klikniesz przy-
cisk, program zakończy działanie, sygnalizując nieoczekiwany błąd. Skoro przy wła-
ściwości onClick przycisku wpisałeś słowo kliknij, nowa metoda powinna mieć na-
stępujący prototyp:
public void kliknij( View v)
{
}

Argumentem tej metody jest egzemplarz klasy View (jest to po prostu ten komponent,
który kliknięto). Dlaczego zatem View, a nie Button? Bo tak jest prościej — w każ-
dym komponencie znajduje się View (albo, mówiąc precyzyjniej, każdy komponent
rozszerza View). Traktowanie różnych klas w taki sposób, jakby były klasą bazową,
nazywa się polimorfizmem i bardzo upraszcza kod źródłowy. Cokolwiek klikniesz,
metoda do obsługi zdarzenia będzie miała taki sam prototyp!

Pracę w Javie zacząłeś od przygotowania metody kliknij(), a w kolejnym kroku zde-


finiuj zmienne, które pozwolą sięgać technikami programistycznymi zarówno do tek-
stu, jak i do obrazka.

Zadeklaruj dwie prywatne zmienne, reprezentujące pole tekstowe i obrazek. Prywatność


oznacza, że będziesz mógł z tych zmiennych korzystać tylko w obrębie klasy, w której
są zadeklarowane:
private ImageView obraz;
private TextView napis;

Tym zmiennym nadaj wartości w metodzie onCreate(), czyli na samym początku życia
aplikacji:
obraz = (ImageView) findViewById(R.id.imageView1);
napis = (TextView) findViewById(R.id.textView1);

Zapewne pamiętasz, że metoda findViewById() to recepta na znalezienie po stronie


Javy tego, co zostało zapisane w plikach XML:
...
android:id="@+id/imageView1"
...
android:id="@+id/textView1"
...

Dziwna fraza "@+id" oznacza: dodaj nowy identyfikator do listy, którą gdzieś przechowu-
jesz. Równie dziwna fraza "R.id." po stronie Javy oznacza: odszukaj na liście gdzieś
przechowywanych identyfikatorów obiekt o takim a takim brzmieniu identyfikatora.

Tak zadeklarowane i zainicjowane zmienne mogą być wykorzystane w każdej funkcji


należącej do klasy głównej. Spróbuj więc po kliknięciu przycisku odczytać rozmiary
obrazka i wypisać te dane w polu tekstowym (rysunek 8.4 i 8.5):
Rozdział 8.  Mapy bitowe 149

Rysunek 8.4.
Po kliknięciu przycisku
w polu tekstowym
pojawi się informacja
o rozmiarze komponentu
ImageView

Rysunek 8.5.
Po obróceniu
urządzenia o 90 stopni
(naciśnięcie klawisza
PageUp na klawiaturze
komputera) należy
kliknąć przycisk,
aby ponownie wykonać
metodę kliknij()
i wyświetlić rozmiar
komponentu

public void kliknij( View v)


{
int szer = obraz.getWidth();
int wys = obraz.getHeight();
napis.setText( szer + " x " + wys);
}
150 Android. Podstawy tworzenia aplikacji

Pojawiają się tu trzy nowe metody należące do bibliotecznych komponentów ImageView


i TextView. Ich nazwy są czytelne i nie wymagają komentarza. Jeśli chcesz poznać
więcej metod jakiegoś komponentu, wpisz w wyszukiwarkę nazwę komponentu, naj-
lepiej uzupełnioną wyrazem „Android” (czyli np.: „android imageview”).

Po tych czynnościach wstępnych zrealizuj cel tego projektu — zadeklaruj zmienną


umożliwiającą dostęp do grafiki za pośrednictwem Javy. W sekcji deklaracji zmien-
nych prywatnych (czyli dostępnych wyłącznie w tej klasie) dodaj nową zmienną typu
Bitmap:
private ImageView obraz;
private TextView napis;
private Bitmap bmp;

W metodzie onCreate() nadaj nowej zmiennej wartość:


bmp = BitmapFactory.decodeResource(getResources(), R.drawable.ryba);

Pojawia się tu nowa metoda decodeResource() (odszyfruj zasób), należąca do ważnej


klasy BitmapFactory (fabryka map bitowych). Fabryka map bitowych potrafi dostarczyć
mapę bitową — tak jak tutaj — z zasobów graficznych aplikacji, ale także z pliku na
karcie pamięci w telefonie czy z internetu.

Sprawdź, czy pod zmienną o nazwie bmp znajduje się mapa bitowa o konkretnych roz-
miarach. Uzupełnij metodę kliknieto() (która do tej pory wypisywała rozmiar kom-
ponentu — obrazka) o wyprowadzanie rozmiaru mapy bitowej (rysunek 8.6):
public void kliknij( View v)
{
napis.setText( obraz.getWidth() + " x " + obraz.getHeight() +
" / " + bmp.getWidth() + " x " + bmp.getHeight());
}

Rysunek 8.6.
Po kliknięciu przycisku
w polu tekstowym
pojawia się informacja
o rozmiarach
komponentu ImageView
i mapy bitowej.
W jednym z folderów
drawable znajduje się
obrazek ryby
o dokładnie takim
rozmiarze, jak widoczny
na ekraniku
Rozdział 8.  Mapy bitowe 151

To jednak jeszcze nie wszystko. Okazuje się, że mapa bitowa wprost z fabryki jest
chroniona przed zmianami — nie można na niej rysować. Dopiero wykonanie odpo-
wiedniej kopii tej mapy da nam swobodę działania:
Bitmap tmp = BitmapFactory.decodeResource(getResources(), R.drawable.ryba);
bmp = tmp.copy( Bitmap.Config.ARGB_8888, true);

Za pomocą fabryki map bitowych powstaje mapa — odpowiednik grafiki w folderach


drawable, na której jednak nie wolno rysować. W następnym kroku z tej mapy powstaje
mapa ostateczna. Opis pierwszego z dziwnych argumentów łatwo znajdziesz w wy-
szukiwarce — to mapa o ośmiobitowych głębiach składników koloru, czyli doskonała.
Drugi argument warunkuje to, o co w tym projekcie chodzi: będzie można rysować na
mapie, choć oryginał jest chroniony przed zmianami.

Oto cały plik źródłowy w języku Java:


package pl.twojadomena.rozdzial_8_a;
import android.os.Bundle;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.view.Menu;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
public class MainActivity extends Activity
{
private ImageView obraz;
private TextView napis;
private Bitmap bmp;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
obraz = (ImageView) findViewById(R.id.imageView1);
napis = (TextView) findViewById(R.id.textView1);
Bitmap tmp = BitmapFactory.decodeResource(getResources(), R.drawable.ryba);
bmp = tmp.copy( Bitmap.Config.ARGB_8888, true);
}
public void kliknij( View v)
{
int szer = obraz.getWidth();
int wys = obraz.getHeight();
napis.setText( szer + " x " + wys);
}
}
152 Android. Podstawy tworzenia aplikacji

Rysowanie na mapie bitowej


zaczerpniętej z zasobów aplikacji
Skoro umiesz już dostać się do mapy bitowej zdefiniowanej w zasobach aplikacji i wy-
świetlanej przez komponent ImageView, spróbuj coś na niej narysować. Podążając za
informacjami z poprzedniego rozdziału, musisz przede wszystkim znaleźć obiekt klasy
Canvas dla mapy bitowej, gdyż w systemie Android właśnie w Canvas znajduje się cały
aparat do rysowania.

Aby uniknąć bałaganu, wprowadź do gry nową, prywatną metodę, która będzie od-
powiedzialna za rysunek na kopii mapy bitowej, pobranej za pomocą fabryki z zaso-
bów aplikacji:
public void kliknij( View v)
{
rysuj();
// tutaj będzie wyświetlenie mapy bitowej przez ImageView
}
private void rysuj()
{
// rysowanie na mapie bitowej
}

Metoda rysuj() ma w programie wyłącznie znaczenie porządkowe.

Na początek otocz rybkę niebieską ramką (rysunek 8.7):


public void kliknij( View v)
{
rysuj();
obraz.setImageBitmap(bmp);
napis.setText( obraz.getWidth() + " x " + obraz.getHeight() +
" / " + bmp.getWidth() + " x " + bmp.getHeight());
}
private void rysuj()
{
int szer = bmp.getWidth(), wys = bmp.getHeight();
Canvas c = new Canvas(bmp);
Paint p = new Paint();
p.setColor(Color.BLUE);
p.setStyle(Paint.Style.STROKE);
c.drawRect(0, 0, szer-1, wys-1, p);
}

Na szczególną uwagę zasługują tutaj dwie linie. Pierwsza z nich to sposób pozyskania
aparatu Canvas dla mapy bitowej:
Canvas c = new Canvas(bmp);
Rozdział 8.  Mapy bitowe 153

Rysunek 8.7.
Rybka w ramce — niby
prosta rzecz. Jednak
należało najpierw
przeczytać mapę bitową
z zasobów aplikacji,
potem uczynić ją
podatną na zmiany,
następnie pozyskać
i wykorzystać obiekt
typu Canvas, wreszcie
narysować ramkę i na
koniec mapę bitową
wyświetlić na powrót
w komponencie
ImageView

Powyższa instrukcja jest jakby otwarciem bramy dla procesu jakiegokolwiek rysowa-
nia po mapie bitowej. Druga ważna linia to sposób na wyświetlenie zmian poczynionych
na mapie bitowej:
obraz.setImageBitmap(bmp);

Ta instrukcja jest zakończeniem rysowania. Te dwie linie realizują kluczowe zagad-


nienia postawione w tym podrozdziale — jak rysować na mapie bitowej i jak ten ry-
sunek wyświetlić za pomocą komponentu ImageView.

Warto uporządkować sytuację, przytaczając treść pliku MainActivity.java w obecnej,


rozwojowej postaci:
package pl.twojadomena.rozdzial_8_a;

import android.os.Bundle;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.Menu;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;

public class MainActivity extends Activity


{
154 Android. Podstawy tworzenia aplikacji

private ImageView obraz;


private TextView napis;
private Bitmap bmp;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

obraz = (ImageView) findViewById(R.id.imageView1);


napis = (TextView) findViewById(R.id.textView1);

Bitmap temp = BitmapFactory.decodeResource(getResources(),


R.drawable.ryba);
bmp=temp.copy(Bitmap.Config.ARGB_8888, true);
}

@Override
public boolean onCreateOptionsMenu(Menu menu)
{
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
public void kliknij( View v)
{
rysuj();
obraz.setImageBitmap(bmp);
napis.setText( obraz.getWidth() + " x " + obraz.getHeight() +
" / " + bmp.getWidth() + " x " + bmp.getHeight());
}
private void rysuj()
{
int szer = bmp.getWidth(), wys = bmp.getHeight();
Canvas c = new Canvas(bmp);
Paint p = new Paint();
p.setColor(Color.BLUE);
p.setStyle(Paint.Style.STROKE);
c.drawRect(0, 0, szer-1, wys-1, p);
}
}

W dalszej części rozdziału będziesz się zajmować już tylko funkcją rysuj(). Algo-
rytmy pozyskiwania mapy bitowej z zasobów aplikacji, kopiowania jej do innej mapy
i odświeżania widoku pozostaną bez zmian.

Przekształcenie mapy bitowej techniką


piksel po pikselu
Operacji graficznych możliwych do przeprowadzenia na mapie bitowej jest nieskoń-
czenie wiele — można rysować na rybce kółka, prostokąty, linie i wszystko to, co
jeszcze mieści biblioteka Canvas. Jako ćwiczenie wybierz operacje, które są podstawą
Rozdział 8.  Mapy bitowe 155

tzw. cyfrowego przetwarzania obrazu. Chodzi o klasę operacji, w których czytany jest
piksel, zmieniany jest jego kolor i wstawia się go na powrót do mapy bitowej.

Przeczytaj kolor każdego piksela, zamień między sobą dwie składowe koloru — np.
czerwoną i niebieską — i tak uzyskany nowy kolor wpisz do poszczególnych punk-
tów mapy.

Algorytm wymaga dwóch pętli, które przebiegną piksel po pikselu przez całą po-
wierzchnię obrazka. Każdy piksel ma jakiś kolor — ten kolor trzeba przeczytać i na-
stępnie rozłożyć na składowe r, g, b (takimi symbolami zazwyczaj oznacza się składowe:
czerwień, zieleń i błękit). Mając trzy składowe, w kolejnym kroku złóż je ponownie
w kolor, ale zamieniając czerwień z błękitem (rysunek 8.8).

Rysunek 8.8.
Efekt zamiany składowej
koloru r ze składową b
powoduje, że niebieskie
niebo staje się
czerwone. Może dlatego
spotyka się nazwę tego
przekształcenia The Day
After

Oto funkcja rysuj() realizująca zamianę składowych r i b koloru:


private void rysuj()
{
int szer = bmp.getWidth(), wys = bmp.getHeight();
int kolor, r, g, b;
Canvas c = new Canvas(bmp);
Paint p = new Paint();
for( int x = 0; x < szer; x ++)
{
for( int y = 0; y < wys; y ++)
{
kolor = bmp.getPixel(x, y);
r = Color.red( kolor);
g = Color.green(kolor);
b = Color.blue(kolor);
bmp.setPixel(x, y, Color.rgb( b, g, r));
}
}
156 Android. Podstawy tworzenia aplikacji

p.setColor(Color.WHITE);
p.setStyle(Paint.Style.STROKE);
c.drawRect(0, 0, szer-1, wys-1, p);
}

Wewnątrz pary pętli czytasz kolor piksela za pomocą metody getPixel(). Kolor ten
jest rozkładany na amplitudy barw r, g, b. W ostatniej linii we wnętrzu pętli masz
dwie instrukcje w jednej linii: utworzenie koloru za pomocą metody rgb() i wpisanie
go do obrazu za pomocą metody setPixel().

Algorytmów tej kategorii jest mnóstwo. Zastanów się twórczo, co zrobiłbyś z trzema
liczbami r, g, b przed ponownym złożeniem ich w kolor. Pamiętaj o jednym ważnym
ograniczeniu: każda z trzech liczb musi się mieścić w przedziale od 0 do 255. Wynika
to z rodzaju mapy bitowej, z jaką pracujesz:
bmp = tmp.copy( Bitmap.Config.ARGB_8888, true);

Te ósemki oznaczają po osiem bitów na każdy z kanałów barwnych, a — jak wiadomo


— osiem bitów to zakres liczbowy od zera do 255.

Oto kilka znanych pomysłów na przekształcenie r, g, b:


r = g = b = (r + g + b)/3; //obraz monochromatyczny

r = 255 - r; g = 255 - g; b = 255 - b; //obraz negatywowy

int prog = 120; //progowanie obrazu do bieli i czerni


if( r < prog || g < prog || b < prog)
r = g = b = 0;
else
r = g = b = 255;

Jeszcze inne pomysły ilustrują rysunki 8.9 i 8.10.

Uzyskanie nowej, czystej mapy bitowej


Projekt ten znajduje się w pliku Rozdzial_8_b.zip.

Do tej pory mapę bitową czytałeś z zasobów programu. Odpowiadał za to algorytm:


Bitmap tmp = BitmapFactory.decodeResource(getResources(), R.drawable.ryba);

uzupełniony linią zdejmowania zabezpieczeń przed rysowaniem po obrazie pobranym


z zasobów (tak naprawdę to nie jest żadne zdejmowanie zabezpieczeń — to po prostu
kopiowanie obrazu do zupełnie nowej mapy bitowej pozbawionej ochrony):
bmp = tmp.copy( Bitmap.Config.ARGB_8888, true);

Na obrazie bmp można było rysować, deklarując aparat Canvas:


Canvas c = new Canvas(bmp);
Rozdział 8.  Mapy bitowe 157

Rysunek 8.9.
Pomysł na
przekształcenie obrazu,
które można nazwać
„szybą do łazienki”.
Losuje się np. 1000
pikseli, pobiera ich
kolor i wykreśla na
obrazie kółko w kolorze
piksela o niewiekim
promieniu (np. 10
pikseli). Efektem jest
rozmycie obrazu

Rysunek 8.10.
A może zamiast kółek
dwie prostopadłe kreski
o jakiejś długości?
Jakiej? Losowej?

Teraz postąpisz trochę inaczej — nie będziesz czytać istniejącej mapy bitowej, a całko-
wicie programowo utworzysz nową. Mówiąc bardziej technicznie: poprosisz system
o miejsce w pamięci urządzenia i w tym miejscu będziesz przechowywać swoją grafikę.

Przygotuj nowy projekt, którego interfejs umożliwi utworzenie mapy bitowej, wy-
konanie na niej jakichś prostych operacji typu wykreślenie linii, kółek czy prostoką-
tów, a także czyszczenie mapy. Zaimplementuj linię tekstu, w której będziesz wypisywać
bieżące komunikaty. No i oczywiście umieść na ekranie pusty komponent typu ImageView,
dzięki któremu będzie możliwe obejrzenie Twojej mapy (rysunek 8.11).
158 Android. Podstawy tworzenia aplikacji

Rysunek 8.11.
Niech nowy projekt
zawiera kilka
przycisków, wśród
których znajduje się ten
najważniejszy —
uruchamiający algorytm
tworzenia mapy bitowej
w pamięci urządzenia

Jeśli nie zmieniałeś domyślnych ustawień, w aplikacji obowiązuje rozkład kompo-


nentów zwany RelativeLayout. W rozkładzie tym — jak zapewne pamiętasz — ko-
lejne komponenty są „dowiązywane” do poprzednich na zasadzie „leż pod tamtym”, „leż
obok tamtego” itp. Zabudowę ekranu w RelativeLayout robi się łatwo, jeśli układasz
komponenty w przemyślany sposób, według naturalnej kolejności. Poprawiając położenie
czy inną właściwość jakiegoś komponentu, upewnij się, że opracowujesz ten komponent,
o który rzeczywiście Ci chodzi (rysunek 8.12).

Rysunek 8.12. Zgodnie z ustawieniami domyślnymi w aplikacji obowiązuje rozkład komponentów


RelativeLayout
Rozdział 8.  Mapy bitowe 159

Główny plik wyglądu, znajdujący się w folderach res/layout i prawdopodobnie mający


nazwę activity_main.xml, powinien mieć mniej więcej taką oto treść:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<Button
android:id="@+id/button1"
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:onClick="kliknieto_kreuj"
android:text="Kreuj" />
<Button
android:id="@+id/button2"
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_toRightOf="@+id/button1"
android:onClick="kliknieto_linie"
android:text="Linie" />
<Button
android:id="@+id/button3"
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_toRightOf="@+id/button2"
android:onClick="kliknieto_elipsy"
android:text="Elipsy" />
<Button
android:id="@+id/button4"
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_toRightOf="@+id/button3"
android:onClick="kliknieto_prostokaty"
android:text="Prostokąty" />
<Button
android:id="@+id/button5"
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_toRightOf="@+id/button4"
android:onClick="kliknieto_czysc"
android:text="Czyść" />
<ImageView
android:id="@+id/imageView1"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_above="@+id/textView1"
160 Android. Podstawy tworzenia aplikacji

android:layout_alignParentLeft="true"
android:layout_below="@+id/button1" />
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true"
android:background="#0000A0"
android:text="Komunikat"
android:textColor="#FFFF00" />
</RelativeLayout>

Tę treść da się uzyskać techniką układania komponentów myszką. Jedynie treść napi-
sów oraz kilka kolorów wprowadziłem za pomocą inspektora właściwości bądź przez
bezpośrednią edycję pliku XML.

Omawiany interfejs implementuje pięć przycisków, zatem powinieneś przygotować


odpowiednie metody do obługi kliknięć tych przycisków. Te metody zostały już za-
deklarowane w powyższym pliku XML:
android:onClick="kliknieto_kreuj"
android:onClick="kliknieto_linie"
android:onClick="kliknieto_elipsy"
android:onClick="kliknieto_prostokaty"
android:onClick="kliknieto_czysc"

Przejdź zatem do głównego pliku Javy, jak zwykle znajdującego się w folderze src,
i przygotuj nagłówki tych metod:
public void kliknieto_kreuj( View v)
{
}
public void kliknieto_linie( View v)
{
}
public void kliknieto_elipsy( View v)
{
}
public void kliknieto_prostokaty( View v)
{
}
public void kliknieto_czysc( View v)
{
}

Metody powinny mieć takie same nazwy, jak zadeklarowano w pliku wyglądu XML
we właściwościach onClick, i dokładnie takie prototypy. Wtedy każda z metod wejdzie
do gry, gdy system Android wykryje kliknięcie odpowiedniego przycisku.

Każda z tych metod w jakiś sposób będzie się odwoływała do opracowywanej mapy
bitowej. Ponieważ wszystkie te metody należą do jednej i tej samej klasy, mapa bitowa
powinna być zadeklarowana w klasie jako prywatna. Z tych samych powodów zadekla-
ruj jako prywatne zmienne reprezentujące komponenty TextView i ImageView — każda
Rozdział 8.  Mapy bitowe 161

z powyższych metod zapewne zechce wyświetlić obrazek na ImageView i być może


wypisać jakiś komentarz w TextView.

Po tych zmianach program w Javie zaczyna przyjmować następujący kształt:


package pl.twojadomena.rozdzial_8_b;

//tutaj pomijam sekcję importów

public class MainActivity extends Activity


{
private ImageView obraz;
private TextView napis;
private Bitmap bmp;

@Override
public void onCreate( Bundle savedInstanceState)
{
super.onCreate( savedInstanceState);
setContentView( R.layout.activity_main);

obraz = (ImageView) findViewById(R.id.imageView1);


napis = (TextView) findViewById(R.id.textView1);
}
}
public void kliknieto_kreuj( View v)
{
}
// tutaj pozostałe metody kliknieto...

Zapewne dostrzegasz deklarację trzech wspomnianych zmiennych prywatnych oraz


znaną inicjalizację zmiennych obraz i napis za pomocą metody findViewById() (znajdź
komponent, znając jego identyfikator). Inicjalizację takich zmiennych najlepiej prze-
prowadzać we własnej wersji metody OnCreate, nadpisującej metodę OnCreate w klasie
bazowej Activity.

Prywatna bitmapa bmp ciągle pozostaje niezainicjowana. Inicjalizowanie — będące


kluczowym algorytmem tego podrozdziału, poświęconego dynamicznemu tworzeniu
obrazów — oczywiście powinno odbyć się w metodzie kliknieto_kreuj(), którą
właśnie do tego utworzono:
public void kliknieto_kreuj( View v)
{
int szer = obraz.getWidth();
int wys = obraz.getHeight();
bmp = Bitmap.createBitmap( szer, wys, Bitmap.Config.ARGB_8888);
napis.setText( "Kreacja mapy " + bmp.getWidth() + " x " + bmp.getHeight());
}

Chcesz utworzyć mapę bitową o rozmiarach odpowiadających komponentowi, który


będzie ją wyświetlać — czyli ImageView — reprezentowanemu przez zmienną obraz.
Poznaj więc te rozmiary za pomocą łatwych do znalezienia w wyszukiwarce interneto-
wej metod klasy ImageView i przejdź do linii zasadniczej dla kogoś, kto chce zajmować
się obrazami:
bmp = Bitmap.createBitmap( szer, wys, Bitmap.Config.ARGB_8888);
162 Android. Podstawy tworzenia aplikacji

Do prywatnej zmiennej bmp został przypisany obiekt — mapa bitowa o wskazanych


rozmiarach i pełnym kolorze, zwanym niekiedy TrueColor (jest to system, w którym
każdemu składnikowi barwy odpowiada 256 odcieni, czyli każdy składnik jest opisa-
ny liczbą 8-bitową o wartości od 0 do 255).

Szkic aplikacji jest gotowy, mapa bitowa też jest wykreowana — przed Tobą przyjemne
zadanie wypełnienia algorytmami pozostałych metod kliknieto...(). Oto propozycja
wypełnienia mapy odcinkami o losowych położeniach, długościach i kolorach:
public void kliknieto_linie( View v)
{
int ile = 200;
int marg = 4;
int x1, y1, x2, y2;
int szer = obraz.getWidth();
int wys = obraz.getHeight();

Random r = new Random();


Canvas c = new Canvas(bmp);
Paint p = new Paint();

for( int i = 0; i < ile; i ++)


{
p.setARGB( 255, r.nextInt( 256), r.nextInt( 256), r.nextInt( 256));
x1 = marg + r.nextInt( szer-2*marg);
y1 = marg + r.nextInt( wys-2*marg);
x2 = marg + r.nextInt( szer-2*marg);
y2 = marg + r.nextInt( wys-2*marg);
c.drawLine( x1, y1, x2, y2, p);
}
obraz.setImageBitmap(bmp);
napis.setText( "Linie");
}

Co jest tutaj ważne? Ta metoda czyta rozmiary mapy bitowej i uzyskuje dla niej
zmienną typu Canvas. Potem w pętli losuje siedem liczb (trzy amplitudy koloru r, g, b
i cztery współrzędne początku i końca każdego odcinka) i wykreśla kolorowy odcinek.
Metoda setARGB(), należąca do obiektu typu Paint, ustawia kolor kolejnego odcinka.
Metoda drawLine(), należąca do obiektu typu Canvas, kreśli kolejny odcinek. Proces
powtarza się 200 razy, za co odpowiada pętla for(...) (rysunek 8.13).

Na zakończenie mapa bitowa jest kierowana do komponentu obraz typu ImageView. Tekst
wyświetlany przez komponent napis typu TextView będzie zawierał słowo "Linie". Po-
zostałe metody rysujące inne kształty działają niemal identycznie.

Metoda czyszcząca ekran jest chyba jeszcze prostsza:


public void kliknieto_czysc( View v)
{
int marg = 4;
int szer = obraz.getWidth();
int wys = obraz.getHeight();
Rozdział 8.  Mapy bitowe 163

Rysunek 8.13.
Na mapę bitową zostało
rzuconych 200 odcinków.
Potem mapa została
wyświetlona
w komponencie
ImageView, a następnie
zmodyfikowano napis na
dole ekranu

Canvas c = new Canvas(bmp);


Paint p = new Paint();
p.setStyle(Paint.Style.STROKE); //rysuj kontury, nie wypełnienia
bmp.eraseColor( Color.BLACK);
p.setColor(Color.WHITE);
c.drawRect( marg, marg, szer - marg, wys - marg, p);
obraz.setImageBitmap(bmp);
napis.setText( "Czyszczenie mapy " + bmp.getWidth() + " x " + bmp.getHeight());
}

Z rzeczy nowych masz tutaj wywołanie metody eraseColor(), która służy do kasowania
dotychczasowej zawartości mapy bitowej. Metoda drawRect() wyświetla obwódkę.

Tak napisany program ma wadę — nieuważny użytkownik może kliknąć rysowanie


linii, zanim kliknie kreowanie mapy bitowej (rysunek 8.14)! Musisz koniecznie za-
blokować taką ścieżkę akcji użytkownika. Jako pierwszy musi być kliknięty przycisk
Kreuj! Blokowanie i udostępnianie pewnych elementów interfejsu — tutaj przycisków
— jest jedną z najważniejszych umiejętności projektowania interfejsu użytkownika.

Do blokowania komponentu wykorzystuje się właściwość Enabled — włączony.


Wszystkim przyciskom „rysującym” ustaw właściwość Enabled na false, czyli je za-
blokuj (rysunek 8.15).
164 Android. Podstawy tworzenia aplikacji

Rysunek 8.14.
Okropny błąd w logice
interfejsu użytkownika
— można kliknąć
rysowanie po mapie
bitowej, zanim zostanie
ona utworzona!

Rysunek 8.15. Tę samą właściwość można opracowywać hurtem dla kilku komponentów, wystarczy
zaznaczyć grupę przycisków rysujących, odszukać w inspektorze właściwość Enabled i wyłączyć ją

Wyłączenie właściwości Enabled wiąże się z pojawieniem się nowych linii w tekście
pliku XML:
<Button
android:id="@+id/button2"
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
Rozdział 8.  Mapy bitowe 165

android:layout_alignParentTop="true"
android:layout_toRightOf="@+id/button1"
android:enabled="false"
android:onClick="kliknieto_linie"
android:text="Linie" />

W tym momencie, po uruchomieniu aplikacji z liniami enabled = false, w opisach


wybranych przycisków zauważysz, że te przyciski są jakby szare i nie działają. Są nie-
aktywne, wyłączone. Nie dostarczają do systemu komunikatów, że je kliknięto. Od-
powiednie metody kliknieto...() nie działają. Reagowanie na kliknięcia przywrócisz
dopiero wtedy, gdy użytkownik najpierw kliknie pierwszy przycisk, czyli gdy wykreuje
mapę bitową (rysunek 8.16):
public void kliknieto_kreuj( View v)
{
int szer = obraz.getWidth();
int wys = obraz.getHeight();
bmp = Bitmap.createBitmap( szer, wys, Bitmap.Config.ARGB_8888);
napis.setText( "Kreacja mapy " + bmp.getWidth() + " x " + bmp.getHeight());
findViewById(R.id.button2).setEnabled( true);
findViewById(R.id.button3).setEnabled( true);
findViewById(R.id.button4).setEnabled( true);
findViewById(R.id.button5).setEnabled( true);
}

Rysunek 8.16.
Zablokowane są
wszystkie przyciski
z wyjątkiem pierwszego.
Dopiero po kliknięciu
pierwszego przycisku
pozostałe zaczynają
normalnie reagować
na akcje użytkownika.
Blokowanie
i odblokowywanie
przycisków, edytorków
i innych elementów
interfejsu to elementarz
tworzenia logiki
interfejsów aplikacji

Po wykonaniu algorytmu kreowania mapy bitowej i po modyfikacji tekstu u dołu


ekranika kolejno odszukujesz każdy z zablokowanych po stronie XML przycisków
i go odblokowujesz po stronie Javy!
166 Android. Podstawy tworzenia aplikacji

Bardzo ważne w programowaniu obiektowym jest zrozumienie roli zmiennych prywat-


nych, takich jak: bmp, obraz czy napis. Te zmienne bywają nazywane krwiobiegiem
klasy. Jedna metoda ustawia wartość zmiennej — np. w omawianym przypadku me-
toda kliknieto_kreuj() nadaje wartość zmiennej bmp. Z kolei inne metody, takie jak np.
kliknieto_linie(), czytają tę zmienną i też coś z nią robią — tutaj: wywołują metody
rysujące linie, kółka czy kwadraciki. Zmienna prywatna komunikuje ze sobą różne
metody klasy.
Rozdział 9.
Wątek w drugim planie
Procesy takie jak: długotrwałe obliczenia, rysowanie skomplikowanej grafiki, ściąganie
danych z internetu, powinny być wykonywane w tle, jakby na drugim planie. Te pro-
cesy, uruchomione w wątku głównym, zablokowałyby na dłużej komputer. Wyglądało-
by to tak, jakby urządzenie się zawiesiło. Opanowany użytkownik zacząłby się niepo-
koić dopiero po kilku minutach, nerwowy wpadłby w szał już po kilku sekundach.

Jeśli maszyna musi na dłużej się czymś zająć, użytkownik powinien być informowany
o bieżącej sytuacji. Nie możesz tak po prostu obciążyć procesora obliczeniami i na ten
czas zerwać wszelką komunikację z otoczeniem. Urządzenie powinno pokazywać ja-
kąś kręcącą się klepsydrę, może jakiś pasek postępu, jakiś uspokajający komunikat in-
formujący, jak dużą część zadania już wykonano, a ile pozostało jeszcze do zrobienia.

Algorytmy długie powinny być wykonywane w tle, czyli w innym wątku. Z tego roz-
działu nauczysz się, jak tworzyć wątek poboczny i jak umieszczać w nim zadanie do
wykonania. W tym czasie wątek główny nadal będzie aktywny — zegar będzie wy-
świetlać cyferki, będzie działać telefon, ekran i klawiatura nie będą martwe.

Klasa AsyncTask i rysowanie


w drugim planie
Ten projekt znajduje się w pliku Rozdzial_9_a.zip.

W poprzednim rozdziale zajmowałeś się rysowaniem po mapach bitowych. Teraz zre-


alizuj proces zarekomendowany jako praca domowa na rysunku 8.10 — zastępowanie
pikseli „ściegiem krzyżykowym”, czyli parą krzyżujących się odcinków.

Przygotuj projekt bardzo podobny do kilku poprzednich. Niech w tym projekcie znaj-
dzie się mapa bitowa, zawczasu umieszczona w folderach drawable..., komponent
ImageView do jej wyświetlania, przycisk Button do uruchamiania procesu (zastępowa-
nia pikseli krzyżykami) i pole TextView do wyświetlania informacji (rysunek 9.1).
168 Android. Podstawy tworzenia aplikacji

Rysunek 9.1. Przygotowanie nowego projektu

Kliknięcie przycisku Start uruchamia proces zastępowania pikseli krzyżykami, który


trwa długo. W czasie wykonywania tego zadania urządzenie mobilne nie daje znaku
życia — procesor zajął się długą pętlą z obliczeniami (rysunek 9.2).

Rysunek 9.2.
Długotrwały proces
zastępowania pikseli
krzyżykami powoduje,
że urządzenie mobilne,
jak się wydaje, przestało
działać

Główny plik wyglądu aplikacji, znajdujący się w folderze res/layout, powinien mieć
mniej więcej taką postać:
Rozdział 9.  Wątek w drugim planie 169

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#200020" >
<Button
android:id="@+id/button1"
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:onClick="kliknieto_start"
android:text="Start!" />
<ImageView
android:id="@+id/imageView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@+id/textView1"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true"
android:layout_below="@+id/button1"
android:src="@drawable/palmy" />
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true"
android:text="Komunikat ..."
android:textColor="#FFFF00" />
</RelativeLayout>

Jest tutaj rozkład RelativeLayout, zatem ważna będzie kolejność budowania interfejsu
i „dowiązywanie” bieżącego elementu do poprzednich, wcześniej rozłożonych na ekra-
niku. Zauważ sygnalizację metody kliknieto_start() w atrybutach przycisku —
trzeba będzie umieścić definicję tej metody w pliku Javy.

Główny plik Javy, znajdujący się w folderze src, powinien mieć postać omówioną
w poprzednim rozdziale, kiedy to czytałeś mapę bitową z zasobów graficznych apli-
kacji i poddawałeś ją dowolnym zmianom:
package pl.twojadomena.rozdzial_9_a;

import java.util.Random;
import android.os.Bundle;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.Menu;
import android.view.View;
import android.widget.Button;
170 Android. Podstawy tworzenia aplikacji

import android.widget.ImageView;
import android.widget.TextView;

public class MainActivity extends Activity


{
private Button przycisk;
private ImageView obraz;
private TextView napis;
private Bitmap bmp;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

przycisk = (Button) findViewById(R.id.button1);


obraz = (ImageView) findViewById(R.id.imageView1);
napis = (TextView) findViewById(R.id.textView1);

Bitmap temp = BitmapFactory.decodeResource(getResources(),


R.drawable.palmy);
bmp=temp.copy(Bitmap.Config.ARGB_8888, true);
}
public void kliknieto_start( View v)
{
przycisk.setEnabled( false);
rysuj();
obraz.setImageBitmap(bmp);
napis.setText( obraz.getWidth() + " x " + obraz.getHeight() +
" / " + bmp.getWidth() + " x " + bmp.getHeight());
przycisk.setEnabled( true);
}
private void rysuj ()
{
int ile = 5000;
int szer = bmp.getWidth(), wys = bmp.getHeight();
int x, y, kolor;
int d = 20;
Canvas c = new Canvas(bmp);
Paint p = new Paint();
p.setStyle(Paint.Style.STROKE);
Random rand = new Random();
for( int i = 0; i < ile; i ++)
{
x = rand.nextInt( szer);
y = rand.nextInt( wys);
kolor = bmp.getPixel(x, y);
p.setColor( kolor);
c.drawLine( x-d, y-d, x+d, y+d, p);
c.drawLine( x-d, y+d, x+d, y-d, p);
}
p.setColor(Color.WHITE);
p.setStyle(Paint.Style.STROKE);
c.drawRect(0, 0, szer-1, wys-1, p);
}
}
Rozdział 9.  Wątek w drugim planie 171

Klasa główna rozszerza klasę biblioteczną Activity i — zgodnie ze znaną Ci zasadą


— nadpisuje metodę onCreate(). W metodzie tej — także jak zwykle — definiujesz
wartości zmiennych reprezentujących elementy interfejsu: przycisk, obraz i napis. Te
elementy są zadeklarowane w klasie jako prywatne, zatem będą dostępne tylko dla
metod omawianej klasy. Te zmienne są wewnętrzną sprawą omawianej klasy.

W metodzie onCreate() czytasz też mapę bitową umieszczoną w zasobach aplikacji,


czyli w folderach drawable-...:
Bitmap temp = BitmapFactory.decodeResource(getResources(), R.drawable.palmy);
bmp=temp.copy(Bitmap.Config.ARGB_8888, true);

Czytanie to omówiłem w rozdziale 8. Ma ono dwa etapy — przeczytanie zasobu za po-


mocą metody decodeResource() należącej do klasy BitmapFactory (fabryka map bi-
towych) oraz wykonanie jej kopii. Po co kopia? Bo na oryginale grafiki, odczytanym
z zasobów aplikacji, nie wolno rysować. Java tworzy najpierw egzemplarz o nazwie
temp, potem kopiuje jego zawartość do prywatnego egzemplarza bmp, widzianego już
we wszystkich metodach omawianej klasy i podatnego na dalsze zmiany rysunku.

Być może niepokoi Cię szastanie zasobami pamięci — w pewnym momencie mamy
w grze dwie mapy bitowe, a z tym nigdy nie ma żartów, bo mapa bitowa na ogół jest
dużym obiektem. Cóż, należy liczyć na to, że Java szybko zauważy, że mapa bitowa
temp po wykonaniu kopii nie jest już potrzebna, i usunie ją z pamięci. Wewnętrzny
odśmiecacz Javy (tak potocznie nazywa się narzędzie do oczyszczania pamięci z już
niepotrzebnych obiektów, danych, zasobów) to jedna z najciekawszych właściwości
tego języka.

Metoda kliknieto_start() odpowiada za obsługę kliknięcia przycisku (ustalenia tego


dokonałeś w pliku wyglądu XML, opisując przycisk). Ta metoda na początek blokuje
przycisk, aby na sam koniec go odblokować:
public void kliknieto_start( View v)
{
przycisk.setEnabled( false);
...
przycisk.setEnabled( true);
}

Blokowanie nie jest konieczne, ale skoro po poprzednim rozdziale już wiesz, jak to
się robi, to zabroń użytkownikowi klikania przycisku, gdy poprzednie kliknięcie cią-
gle jest realizowane.

Tak jak w poprzednim rozdziale z wnętrza powyższej metody następuje wywołanie


pomocniczej metody rysuj(). Możesz nie definiować tej metody, umieszczając jej algo-
rytmy bezpośrednio w kliknieto_start(), ale wydaje mi się, że jest to po prostu brzyd-
kie rozwiązanie. Jeśli nowe metody, czytelnie wyodrębnione z kodu głównego i do-
brze nazwane, podnoszą przejrzystość aplikacji, należy z nich korzystać.

Po metodzie rysuj() następuje odświeżenie obrazka ImageView i napisu TextView.


172 Android. Podstawy tworzenia aplikacji

Metoda rysuj() ma pracować długo, co jest istotą tego rozdziału poświęconego im-
plementowaniu procesów długotrwałych jak np. przetwarzanie grafiki, albo niepewnych
co do rezultatu, jak np. czytanie czegoś z Internetu. W tej metodzie masz więc długą
pętlę, która losuje współrzędne piksela, czyta jego kolor i w kolorze tym wykreśla
haft krzyżykowy. Sam proces nie ma istotnego znaczenia, ważne jest tylko, aby trwał
denerwująco długo. Jeśli trwa zbyt krótko, wydłuż pętlę albo dociąż procesor czymś
ekstra — jakimiś kółkami, elipsami czy czymkolwiek, co potrafisz wymyślić i zaim-
plementować na mapie bitowej.

Taką realizację postawionego zadania można nazwać liniową — procesor urządzenia


po kolei wykonuje instrukcje opisane w Javie. Tymczasem należy zrobić to inaczej —
niech procesor długą pętlę z haftem krzyżykowym wykonuje w oddzielnym wątku,
jakby w tle, co jakiś czas informując użytkownika o postępach!

Istnieje gotowa klasa, która potrafi uruchomić zadanie (tutaj zadaniem tym będzie
metoda rysuj()) w oddzielnym wątku i informować wątek główny o postępach. Ta klasa
nazywa się AsyncTask, co należy przetłumaczyć jako „zadanie do wykonania asynchro-
nicznie”, czyli w tle, z jakąś własną, niezależną szybkością.

Być może domyślasz się, że teraz nastąpi znana sztuczka Javy. AsyncTask należy wyko-
rzystać jako klasę bazową i rozszerzyć ją, przy okazji nadpisując metodę odpowiada-
jącą za wykonywane zadanie. Rozszerzanie klas bazowych i — jakby przy okazji —
nadpisywanie wybranych metod to najważniejszy mechanizm Javy.

Poniższy kod ilustruje ogólną zasadę, ale jeszcze nie działa. Zaczekaj więc z wpro-
wadzaniem go do edytora Eclipse:
class Proces extends AsyncTask
{
@Override
protected Void doInBackground()
{
rysuj();
}
@Override
protected void onPostExecute()
{
obraz.setImageBitmap(bmp);
przycisk.setEnabled(true);
napis.setText( "Koniec!");
super.onPostExecute(result);
}
}

Klasa Proces dziedziczy po AsyncTask i nadpisuje metodę doInBackground() (pracuj


w tle). Wtedy w jakiś cudowny sposób metoda rysuj() wykona się jakby cząstką
uwagi procesora, bez blokowania wątku głównego. Po wykonaniu pracy w tle wykona
się metoda onPostExecute() (zrób na koniec). Tam umieścisz instrukcje odświeżania
obrazu i wyświetlania komunikatu, a na zakończenie powinieneś wywołać bazową,
oryginalną metodę onPostExecute().
Rozdział 9.  Wątek w drugim planie 173

W klasie bazowej AsyncTask tkwi jeszcze jedna tajemnica Javy. Otóż jest to klasa uogól-
niona, generyczna, nazywana też szablonem klas. To, czym się teraz zajmiesz, bywa na-
zywane metaprogramowaniem. To najmodniejszy nurt programowania w Javie i C++;
nurt na samym topie.

Klasy w Javie mogą zawierać nieokreślone typy:


class Uogolniona
{
private T1 a, b;
T2 jakas_metoda()
{
}
}

Jest tu jakiś skrawek kodu definiujący klasę w Javie. Tylko czym są napisy T1 i T2
(mogłoby być ich więcej albo mniej) w powyższej próbie zobrazowania sytuacji? Są to
nieznane typy, które możesz sobie dowolnie wybrać, a Java wstawi je w miejsca napisów
T1 i T2. Z powyższej klasy uogólnionej mógłbyś więc uzyskać klasę:
class Uogolniona
{
private int a, b;
String jakas_metoda()
{
}
}

T1 zastąpiono typem int, T2 typem String. Mógłbyś uzyskać wiele innych klas, zastę-
pując T1 i T2 jakimiś konkretnymi typami.

Poprawny zapis klasy uogólnionej powinien jasno wskazywać, które napisy są niezna-
nymi typami, i wygląda następująco:
class Uogolniona< T1, T2>
{
private T1 a, b;
T2 jakas_metoda()
{
}
}

A zapis jej konkretnej (już nie uogólnionej) wersji:


class Uogolniona< int, String>;

W powyższej linii należy widzieć normalną klasę, w której w miejsce napisu T1 wej-
dzie napis int, a w miejsce napisu T2 napis String. Jeśli będziesz chciał dogłębniej
studiować omawiane tu zagadnienia, poznasz klasy generyczne i ich niesamowite moż-
liwości, ale teraz wróć do praktyki.
174 Android. Podstawy tworzenia aplikacji

AsyncTask jest klasą uogólnioną, zdefiniowaną z trzema typami — parametrami, które


w każdej konkretnej sytuacji musisz określić. Poszerzanie klasy AsyncTask powinno
wyglądać następująco:
class Proces extends AsyncTask< T1, T2, T3>
{
...
}

W klasie tej powinieneś nadpisać dwie metody:


T3 doInBackground( T1... a)
{
return T2;
}
void onPostExecute( T2 a)
{
super.onPostExecute(result);
}

Zamiast napisów T1, T2, T3 muszą wejść konkretne typy. Zacznij od sytuacji, gdy trzy
typy (parametry) są typami pustymi (nie wolno ich po prostu pominąć). Wewnątrz
klasy głównej zdefiniuj nową klasę, rozszerzającą AsyncTask< Void, Void, Void>
i nadpisującą dwie kluczowe metody:
class Proces extends AsyncTask< Void, Void, Void>
{
@Override
protected Void doInBackground(Void... params)
{
// TODO Auto-generated method stub
return null;
}
@Override
protected void onPostExecute(Void result)
{
// TODO Auto-generated method stub
super.onPostExecute(result);
}
}

Poprawnie napisz nagłówek klasy, wejdź w obszar jej ciała i skorzystaj ze znakomitego
suflera Ctrl+Spacja. Metody do nadpisywania pojawią się automatycznie, samo Eclipse
zaznaczy miejsca, w które powinieneś wstawić swój kod. Na uwagę zasługuje to, że
te metody mają różne postaci dla różnych typów generujących konkretną klasę z klasy
uogólnionej. Na rysunku 9.3 sufler podpowiada prototypy metod, gdy klasa jest gene-
rowana typami < Void, Void, Void>.
Rozdział 9.  Wątek w drugim planie 175

Rysunek 9.3.
Korzystanie z suflera
Ctrl+Spacja przy
nadpisywaniu metod
(poszerzanie klasy
AsyncTask)

Oto kod w Javie z niewielkimi skrótami:


package pl.twojadomena.rozdzial_9_a;

import java.util.Random;
//reszta importów

public class MainActivity extends Activity


{
private Button przycisk;
private ImageView obraz;
private TextView napis;
private Bitmap bmp;

@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
przycisk = (Button) findViewById(R.id.button1);
//itd. ciało wcześniejszej metody onCreate()
}
public void kliknieto_start( View v)
{
Proces p = new Proces();
p.execute();
}
//--------------------------------------------------------------
// klasa wewnętrzna w klasie głównej.
class Proces extends AsyncTask<Void, Void, Void>
{
@Override
protected Void doInBackground(Void... arg)
{
int ile = 5000;
int szer = bmp.getWidth(), wys = bmp.getHeight();
176 Android. Podstawy tworzenia aplikacji

int x, y, kolor;
int d = 20;
//itd. ciało wcześniejszej metody rysuj()
return null;
}
@Override
protected void onPostExecute(Void result)
{
obraz.setImageBitmap(bmp);
przycisk.setEnabled(true);
napis.setText( "Koniec!");
super.onPostExecute(result);
}
}
}

Co tu się stało? We wnętrzu klasy głównej zdefiniowałeś nową klasę — Java umoż-
liwia takie rozwiązanie. Nowa klasa rozszerza klasę generyczną AsyncTask, utworzo-
ną z trzema typami pustymi Void. Sufler Ctrl+Spacja potrafi wyobrazić sobie ciało
takiej już konkretnej klasy i zaproponować garść metod do nadpisania, z których wy-
brałeś dwie najważniejsze: doInBackground() (pracuj w tle) i onPostExecute() (zrób
na koniec).

Nie omawiałem jeszcze sposobu, jak uruchomić cały proces pracy w tle. Spójrz do
wnętrza metody kliknieto_start(). Kiedyś w metodzie tej znajdował się cały algorytm
— kreślenie czegoś na mapie bitowej za pomocą długiej pętli i na koniec odświeżenie
obrazka. Teraz w metodzie tej tworzysz egzemplarz procesu i uruchamiasz go:
Proces p = new Proces();
p.execute();

Cała zmiana polega zatem na: utworzeniu nowej klasy, przeniesieniu męczącego al-
gorytmu rysowania do Twojej wersji doInBackground(), przeniesieniu odświeżeń
końcowych do Twojej wersji onPostExecute(), utworzeniu egzemplarza i uruchomie-
niu go. Męczący algorytm jest wywoływany za pośrednictwem rozszerzenia klasy
AsyncTask, nie zaś bezpośrednio.

Korzystając z suflera Ctrl+Spacja, być może dostrzegłeś następną, intrygującą meto-


dę do nadpisania: onPreExecute() (zrób przed startem). Zaimplementuj także ją.
Oczywiście jeśli nie chcesz wertować dokumentacji i szukać, jaki prototyp powinna
mieć metoda onPreExecute(), wejdź do wnętrza klasy Proces i uruchom sufler
Ctrl+Spacja.

Skoro wątek główny nie musi się zajmować uciążliwym rysowaniem, niech zajmie
się animacją informującą, że praca jest w toku. Umieść na środku obrazu któryś kom-
ponent ProgressBar, jego właściwość Visibility ustaw na invisible (niewidoczny).
Widocznym uczynisz go w onPreExecute(). Ponownie ukryjesz go w onPreExecute()
(rysunek 9.4).
Rozdział 9.  Wątek w drugim planie 177

Rysunek 9.4. Skoro wątek główny nie musi zajmować się uciążliwym rysowaniem, niech zajmie się
animacją informującą, że praca jest w toku

Oto program uzupełniony o komponent ProgressBar (wskaźnik postępu prac) i metodę


onPreExecute():
package pl.twojadomena.rozdzial_9_a;
import android.widget.ProgressBar;
//reszta importów
public class MainActivity extends Activity
{
...
private ProgressBar stan;

@Override
public void onCreate(Bundle savedInstanceState)
{
...
stan = (ProgressBar) findViewById(R.id.progressBar1);
}
public void kliknieto_start( View v)
{
Proces p = new Proces();
p.execute();
}
class Proces extends AsyncTask< Void, Void, Void>
{
@Override
protected Void doInBackground(Void... arg)
{
...
}
protected void onPreExecute()
{
stan.setVisibility( ProgressBar.VISIBLE);
...
178 Android. Podstawy tworzenia aplikacji

}
@Override
protected void onPostExecute(Void result)
{
stan.setVisibility( ProgressBar.INVISIBLE);
...
}
}
}

Klasa główna zawiera dwie metody, a także klasę wewnętrzną, rozszerzającą klasę
uogólnioną AsyncTask. Rozszerzenie polega na nadpisaniu trzech metod: głównej do-
InBackground() i pomocniczych onPreExecute() oraz onPostExecute(). W pierwszej
z metod pomocniczych uruchamiasz wirowanie wskaźnika ProgressBar (ściślej: czynisz
go widocznym), w drugiej go chowasz.

Długi proces rysowania krzyżyków odbywa się w tle (rysunek 9.5). W tym czasie
proces główny zajmuje się animowaniem komponentu ProgressBar i użytkownik wie,
że komputer się nie zawiesił, tylko jest czymś zajęty.

Rysunek 9.5.
Długi proces rysowania
krzyżyków odbywa się
w tle

Sytuacja jest taka: potrafisz coś zrobić przed uruchomieniem procesu w tle (np. od-
słonić wskaźnik ProgressBar) i po jego zakończeniu (np. odświeżyć obrazek, ukryć
ProgressBar). Jak jednak zrobić, żeby użytkownik dostawał jakiś sygnał wieokrotnie
w trakcie długiego procesu? Mogłaby to być np. informacja, w którym obrocie długiej
pętli rysującej krzyżyki aktualnie jesteś.
Rozdział 9.  Wątek w drugim planie 179

Jeśli chcesz informować użytkownika o postępie prac w trakcie działania klasy


AsyncTask, nadpisz jeszcze jedną metodę w klasie rozszerzającej klasę biblioteczną
AsyncTask:
protected void onProgressUpdate( Void... values)
{
// TODO Auto-generated method stub
super.onProgressUpdate(values);
}

Metodę onProgressUpdate() dodaj do swojego kodu za pomocą suflera Ctrl+Spacja,


stojąc kursorem we wnętrzu klasy Proces (bez suflera musiałbyś sprawdzić w doku-
mentacji, jaka jest jej postać przy generowaniu typami < Void, Void, Void>). Nazwę
metody należy przetłumaczyć jako „poinformuj, co robisz”. Umieścisz w niej kod od-
powiadający za dostarczanie informacji o stopniu zaawansowania zadania.

Metoda onProgressUpdate() to jeszcze nie wszystko. W głównej metodzie doInBack


ground() musisz tu i ówdzie wtrącić wywołanie metody publishProgress() (pokaż,
w jakiej fazie pracy jesteś). Metody publishProgress() i onProgressUpdate() współ-
pracują ze sobą.

Ale to ciągle nie wszystko. Aby wspomniane metody mogły się ze sobą komuniko-
wać, bieżący indeks pętli i liczbę jej obrotów przenieś do sekcji prywatnej klasy Pro-
ces (pamiętasz uwagę o tym, że elementy prywatne są „krwiobiegiem” klasy, zapew-
niając komunikację między jej metodami?).

Niech metoda publishProcess() będzie wywoływana w każdym obrocie pętli. Być


może to za często — praca w tle zapewne dodatkowo zostanie spowolniona zbyt czę-
stymi komunikatami o postępach.

Niech onProgressUpdate() pokazuje, który obrót pętli akurat mamy. Niech też co tysiąc
obrotów będzie pokazywany aktualny (jeszcze nieukończony) obrazek:
class Proces extends AsyncTask<Void, Void, Void>
{
private int nr, ile = 5000;
@Override
protected Void doInBackground( Void... arg)
{
...
for( nr = 0; i < ile; i ++)
{
...
publishProgress();
}
...
}
@Override
protected void onProgressUpdate( Void... values)
{
napis.setText( "Stan: " + nr + " / " + ile);
if( nr % 1000 == 0)
obraz.setImageBitmap(bmp);
180 Android. Podstawy tworzenia aplikacji

// TODO Auto-generated method stub


super.onProgressUpdate(values);
}
}

Dzięki przeniesieniu do prywatnej sekcji licznik pętli nr i jej zakres ile dane te służą
teraz dwóm metodom. Poprzednio te deklaracje znajdowały się w metodzie doInBack
ground() i tylko jej służyły. Teraz metoda doInBackground() ustawia wartości
zmiennej nr, zaś metoda onProgressUpdate() wypisuje te wartości do paska komunikatu
na dole ekranika (rysunek 9.6).

Rysunek 9.6.
Idealnie! Długotrwały proces
pracuje w tle i nie absorbuje
głównego wątku. W momencie
startu, w metodzie
onPreExecute() odsłania się
wirujący na środku
ProgressBar. W każdym
obrocie pętli następuje
wywołanie metody
publishProcess(),
czyli de facto metody
onProgressUpdate(), która
odświeża pasek komunikatu
na dole ekranika i odrysowuje
co tysięczną wersję rysunku.
Na zakończenie, w metodzie
onPostExecute(), chowa się
ProgressBar i ostatecznie
odświeża rysunek. Na pasku
komunikatu pojawia się napis
„Koniec”

Klasą AsyncTask musisz się posługiwać sprawnie i bez lęku. Każdy proces wymaga-
jący odrobiny czasu czy, co gorsza, nie wiadomo jak długiego czasu, powinien być
umieszczany w nadpisanej metodzie głównej klasy AsyncTask. Dodatkowo imple-
mentując metody onPreExecute(), onPostExecute(), a zwłaszcza onProgressUpdate()
wraz z wtrąconym wywołaniem publishProgress(), potrafisz poinformować użyt-
kownika, w jakim stanie jest jego urządzenie mobilne.

Przytoczę jeszcze raz kompletną, choć pustą definicję nadpisanej klasy AsyncTask,
konkretyzowanej trzema typami Void:
import android.os.AsyncTask;
...
class Proces extends AsyncTask< Void, Void, Void>
{
@Override
protected Void doInBackground( Void... arg)
{
...
Rozdział 9.  Wątek w drugim planie 181

publishProgress();
return null;
}
@Override
protected void onPreExecute()
{
...
super.onPreExecute();
}
@Override
protected void onPostExecute( Void result)
{
...
super.onPostExecute(result);
}
@Override
protected void onProgressUpdate( Void... values)
{
...
super.onProgressUpdate(values);
}
}

Taką postać nadpisywane metody będą miały tylko wtedy, gdy uogólniona klasa
AsyncTask zostanie konkretyzowana typami pustymi, które w Twojej praktyce są wy-
starczające:
AsyncTask< Void, Void, Void>

Pamiętaj też o zapewnieniu komunikacji między metodami. Głównie chodzi tu o naczel-


nego wykonawcę doInBackground() i informatora o stopniu zaawansowania inProgress
Update(). Komunikację zapewnią odpowiednie zmienne prywatne w klasie rozsze-
rzającej AsyncTask. W Twoim projekcie były to zmienne sterujące pętlą kreślącą
krzyżyki:
private int nr, ile = 5000;

Metoda doInBackground() podbijała stan zmiennej nr, metoda inProgressUpdate() stan


ten natychmiast wyświetlała w pasku komunikatu.

Ściąganie danych z internetu


Projekt ten znajduje się w pliku Rozdzial_9_b.zip.

Ściąganie danych z internetu jest czymś jeszcze gorszym niż długotrwałe procesy opi-
sywane powyżej. Może trwać sekundę, godzinę lub nieskończenie długo, jeśli wskaza-
łeś zły adres internetowy.

Operacja ściągania danych — poza szczególnymi sytuacjami — powinna być zawsze


przeprowadzana w tle. W tym czasie wątek główny powinien czymś zająć użytkownika
— np. może wyświetlać informację o zaawansowaniu procesu transmisji.
182 Android. Podstawy tworzenia aplikacji

Napiszesz program, który będzie ściągał grafikę z podanej lokalizacji internetowej


i wyświetlał ją na ekraniku. Rozpocznij więc nowy projekt, a interfejs aplikacji niech
będzie taki jak poprzednio: przycisk uruchamiający proces, obrazek do wyświetlenia
grafiki i tekst komunikatu (rysunek 9.7). Jeśli chcesz uspokoić nerwowego użytkow-
nika, możesz dodać też wirujące kółko wskaźnika ProgressBar.

Rysunek 9.7.
Celem zadania jest
program, który będzie
wyświetlał obrazek
z zaznaczoną aktualną
pozycją stacji
kosmicznej ISS.
Proces ściągania
rozpoczyna się
w momencie kliknięcia
przycisku. Ściąganie
(przynajmniej w mojej
lokalizacji) jest bardzo
szybkie i niemal
niezauważalne,
ale xw projekcie chodzi
o informowanie
użytkownika o stanie
procesu za pomocą
komunikatów w polu
tekstowym na dole
ekranu

Główny plik wyglądu, prawdopodobnie o nazwie activity_main.xml, ma następującą


zawartość:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#200020" >
<Button
android:id="@+id/button1"
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:onClick="kliknieto_sciagaj"
android:text="Ściągaj!" />
<ImageView
android:id="@+id/imageView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:src="@android:drawable/screen_background_dark" />
Rozdział 9.  Wątek w drugim planie 183

<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:text="Komunikat."
android:textColor="#FFFF00" />
<ProgressBar
android:id="@+id/progressBar1"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:visibility="invisible" />
</RelativeLayout>

Zwróć uwagę na wartość atrybutu onClick przycisku — za chwilę trzeba będzie zde-
finiować w Javie odpowiednią metodę. Zauważ też, że ProgressBar jest ukryty — od-
słonisz go na czas ściągania pliku z internetu.

Teraz kilka słów o zupełnie nowym w Twojej praktyce aspekcie Androida — tzw. ze-
zwoleniach (Permissions). Otóż aplikacja androidowa, przygotowana przez kreator
nowego projektu, ma bardzo ograniczony zakres działania, np. nie może sięgnąć do
internetu. Chodzi tutaj o koszty — są użytkownicy, którzy za połączenie z internetem
muszą płacić i zdecydowanie nie życzą sobie, żeby jakakolwiek aplikacja próbowała
ściągać z internetu jakieś obrazki.

Zezwolenia opisuje się w pliku AndroidManifest.xml (rysunek 9.8). Oto ten plik z już
dopisanym zezwoleniem na łączenie się z internetem:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="pl.twojadomena.rozdzial_9_b"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="15" />
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".MainActivity"
android:label="@string/title_activity_main" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
184 Android. Podstawy tworzenia aplikacji

Rysunek 9.8.
Plik — manifest
aplikacji trzeba
edytować za pomocą
specjalizowanych
edytorków różnych
sekcji (choć edycja
czysto tekstowa też jest
możliwa). Na zakładce
Permissions
(zezwolenia) należy
dodać nowe zezwolenie
i określić jego typ jako
INTERNET. Aplikacja
będzie mogła łączyć się
z internetem

Ciekawsze rzeczy dzieją się w pliku Javy. W standardowy sposób zorganizuj prywat-
ne zmienne, reprezentujące te elementy interfejsu, po które za chwilę będziesz sięgać
programowo. Zadeklaruj też adres internetowy, pod którym znajduje się obrazek (ry-
sunek 9.9), i bitmapę, do której obrazek zostanie wczytany:
//tutaj seria importów, automatycznie dopisywana przez Eclipse ...
public class MainActivity extends Activity
{
private String adres = "http://www.heavens-
above.com/orbitdisplay.aspx?icon=iss&width=300&height=300&satid=25544";
private Button przycisk;
private ImageView obraz;
private ProgressBar stan;
private TextView napis;
private Bitmap bmp;

@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

przycisk = (Button) findViewById(R.id.button1);


obraz = (ImageView) findViewById(R.id.imageView1);
stan = (ProgressBar) findViewById(R.id.progressBar1);
napis = (TextView) findViewById(R.id.textView1);
}
...
}
Rozdział 9.  Wątek w drugim planie 185

Rysunek 9.9.
Wklejanie
przytoczonego tutaj
adresu internetowego
pozwoli upewnić się,
że pod nim znajduje się
grafika. Można wpisać
inny adres bezpośrednio
prowadzący do jakiegoś
pliku z rozszerzeniem
png lub jpg

Następnym elementem jest metoda, której nazwę zadeklarowano w pliku wyglądu


XML jako algorytm — reakcja programu na kliknięcie przycisku:
public void kliknieto_sciagaj( View v)
{
OdczytObrazka oo = new OdczytObrazka();
oo.execute();
}

Podobnie jak w poprzedniej aplikacji znajduje się tutaj deklaracja egzemplarza klasy
OdczytObrazka (poniżej omawiam tę klasę) i wywołanie jej metody execute().

Klasa o nazwie OdczytObrazka zawiera nowy, ale łatwy algorytm odczytywania pliku
ze wskazanej lokalizacji internetowej i kierowania go do przygotowanego kompo-
nentu obraz typu ImageView. Ponieważ jednak ani na chwilę nie chcesz stracić władzy
nad urządzeniem mobilnym, ściąganie wszelkich danych z internetu uruchomisz w tle.
Dlatego przygotowywana klasa OdczytObrazka rozszerza klasę szablonową AsyncTask.

Proponuję — tak jak i poprzednio — najłatwiejszy wariant, w którym szablon


AsyncTask jest konkretyzowany typami pustymi Void:
class OdczytObrazka extends AsyncTask< Void, Void, Void>
{
...

Za pomocą suflera Ctrl+Spacja (albo przy użyciu dostępnej w internecie dokumentacji


klasy) nadpisz w jej ustroju trzy metody, których nazwy mogłyby brzmieć: doInBack
ground(), onPreExecute(), onPostExecute() (w wolnym tłumaczeniu „zadanie”, "przed
zadaniem" i „po zadaniu”):
186 Android. Podstawy tworzenia aplikacji

class OdczytObrazka extends AsyncTask<Void, Void, Void>


{
@Override
protected Void doInBackground( Void... arg0)
{
// TODO Auto-generated method stub
return null;
}
@Override
protected void onPreExecute()
{
// TODO Auto-generated method stub
super.onPreExecute();
}
@Override
protected void onPostExecute(Void result)
{
// TODO Auto-generated method stub
super.onPostExecute(result);
}
}

Metody te miałyby inne postaci (mówi się raczej: prototypy, a nie: postaci) przy kon-
kretyzacji innymi typami niż typ pusty.

Komunikację między tymi metodami niech zapewni zmienna:


private boolean sukces = true;

Jeśli główna metoda doInBackground() nie zdoła przeczytać obrazka, zmiennej sukces
nada wartość false. Będzie to sygnał dla metody kończącej onPostExecute(), żeby
wypisać komunikat nie o sukcesie, ale o porażce całego procesu.

Klasa OdczytObrazka jest klasą wewnętrzną w klasie głównej (jest zdefiniowana mię-
dzy jej klamrami {}), zatem ma dostęp do danych prywatnych w rodzaju: adres, bmp,
obraz czy napis.

Zatem do roboty. Oto kompletna definicja klasy wewnętrzej OdczytObrazka:


class OdczytObrazka extends AsyncTask<Void, Void, Void>
{
boolean sukces=true;
@Override
protected Void doInBackground( Void... arg0)
{
URL u;
InputStream is;
try
{
u = new URL( adres);
is = u.openStream();
Bitmap temp = BitmapFactory.decodeStream(is);
bmp=temp.copy(Bitmap.Config.ARGB_8888, true);
}
catch( Exception e)
{
Rozdział 9.  Wątek w drugim planie 187

sukces = false;
}
return null;
}
@Override
protected void onPreExecute()
{
stan.setVisibility( ProgressBar.VISIBLE);
przycisk.setEnabled(false);
napis.setText( "Pobieranie danych ...");
super.onPreExecute();
}
@Override
protected void onPostExecute(Void result)
{
if( sukces)
{
Canvas c = new Canvas( bmp);
Paint p = new Paint();
int szer = bmp.getWidth(), wys = bmp.getHeight();
p.setColor(Color.WHITE);
p.setStyle(Paint.Style.STROKE);
c.drawRect(0, 0, szer-1, wys-1, p);

obraz.setImageBitmap( bmp);
napis.setText( "Pobieranie zakończone. Obraz " + szer + " x " + wys);
}
else
{
napis.setText( "Błąd podczas pobierania danych.");
}
przycisk.setEnabled(true);
stan.setVisibility( ProgressBar.INVISIBLE);
super.onPostExecute(result);
}
}

Omówię wybrane momenty tego algorytmu. Za czytanie danych z internetu odpowia-


dają linie:
URL u;
InputStream is;
u = new URL( adres);
is = u.openStream();

Jednak strumień danych is jest trudny do analizowania — inaczej taka analiza wy-
gląda, jeśli dane są tekstem, inaczej, jeśli jest to (tak jak w omawianym przypadku)
binarna zawartość obrazka. Na szczęście istnieje garść gotowych algorytmów, potra-
fiących czytać dane ze strumieni, a ten potrzebny znajduje się w znakomitej, już tro-
chę Ci znanej fabryce map bitowych:
Bitmap temp = BitmapFactory.decodeStream(is);

Myślę, że nazwa metody czytającej strumień, w którym lecą do nas gdzieś ze świata
binarne dane graficzne, jest aż nadto czytelna.
188 Android. Podstawy tworzenia aplikacji

To nie wszystko. W Javie zawsze musisz być przygotowany na możliwość wystąpie-


nia błędów. Jeśli tylko błąd — zwany tutaj wyjątkiem — może się pojawić, powinieneś
przygotować algorytmy do obsługi wyjątków. Robi się to następująco:
try
{
u = new URL( adres);
is = u.openStream();
Bitmap temp = BitmapFactory.decodeStream(is);
bmp=temp.copy(Bitmap.Config.ARGB_8888, true);
}
catch( Exception e)
{
sukces = false;
}

Niebezpieczny ciąg instrukcji (często jest to jedna instrukcja, czasem bywają ich tysiące)
należy wpisać w sekcji try, co oznacza: „próbuj”. Jeśli coś pójdzie nie tak, program
przeskoczy do sekcji catch( Exception e), co oznacza: „łap błąd e”. Jeśli wszystko
będzie OK, sekcja catch zostanie pominięta.

Zauważ zatem, że jeśli w sekcji catch ustawisz omówioną powyżej flagę sukces =
false, będzie to sygnał dla metody kończącej onPostExecute() do wypisania odpo-
wiedniego komunikatu.

Metoda onPreExecute() w zasadzie nie różni się od tej z poprzedniego projektu. Ak-
tywuje widoczność okrągłego wskaźnika ProgressBar, chroni przycisk przed dalszy-
mi kliknięciami na czas ściągania z internetu i wypisuje stosowny komunikat o roz-
poczęciu ściągania danych.

Metoda onPostExecute() zachowuje się różnie w zależności, czy nastąpił sukces, czy
porażka. W obu przypadkach wypisuje odpowiedni komunikat, ale jeszcze doryso-
wuje ramkę dookoła obrazka, co możesz śmiało pominąć, jeśli grafika wydaje Ci się
wystarczająco ładna. Wreszcie wyświetla mapę bitową w komponencie obraz typu
ImageView. Na zakończenie ukrywa wskaźnik ProgressBar i przywraca przyciskowi
zdolność reagowania na kliknięcia.

Z rysowaniem ramki jest jednak pewien kłopot. Ponieważ grafika z internetu ma róż-
ne właściwości, głębię kolorów, strukturę pikseli, przed rysowaniem dobrze jest ją
przerobić na tzw. format fotograficzny. Dlatego po przeczytaniu mapy bitowej ko-
piujesz ją z flagą oznaczającą ośmiobitowe amplitudy kolorów R, G, B. Podsumowując:
z dowolnej jakości obrazu otrzymujesz obraz klasy fotograficznej:
Bitmap temp = BitmapFactory.decodeStream(is);
bmp=temp.copy(Bitmap.Config.ARGB_8888, true);

Gdyby pominąć rysowanie ramki (szczegółowiej: definiowanie egzemplarza aparatu


graficznego Canvas), powyższe dwie linie mógłbyś zastąpić jedną:
bmp = BitmapFactory.decodeStream(is);
Rozdział 9.  Wątek w drugim planie 189

Rysunek 9.10.
Obraz stacji kosmicznej
ISS na stronach
www.heavens-
above.com rzeczywiście
przesuwa się nad
obrazem Ziemi —
wystarczy kliknąć
przycisk co kilka minut
i porównać grafiki.
Wieczorami niekiedy
udaje się zobaczyć
bardzo jasny punkt,
szybko przesuwający się
po niebie — to wielkie
baterie słoneczne
odbijają promienie
Słońca w kierunku Ziemi
190 Android. Podstawy tworzenia aplikacji
Rozdział 10.
Więcej ekranów
dla aplikacji
W tym rozdziale opracujesz aplikację, która będzie operowała kilkoma ekranami. Na
ekranie głównym umieścisz kilka przycisków umożliwiających wybór kolejnych
ekranów. Oprzesz się na umiejętnościach zdobytych w poprzednim rozdziale i na ko-
lejnych ekranach umieścisz kilka wariantów ściągania i obrazowania ciekawych da-
nych z internetu. Niech to będzie aktualny obraz położenia stacji kosmicznej ISS na
tle Ziemi, obraz aktualnej fazy Księżyca i obraz Słońca z bieżącym rozkładem plam
na jego powierzchni (rysunek 10.1). Wszystkie dane znajdziesz w internecie. Oczywi-
ście mogłyby to być zupełnie inne dane, np. jakieś obrazki z lwem, tygrysem i kotem,
ale dlaczego przy okazji aplikacji wieloekranowej nie przyjrzeć się astronomii.

Rysunek 10.1.
Aplikacja składa się
z czterech ekranów.
Ekran główny daje
możliwość przejścia
do trzech ekranów
poświęconych
wizualizacji różnych
danych pobieranych
z internetu
192 Android. Podstawy tworzenia aplikacji

Problem nie należy do trudnych. Oprócz jednego pliku Javy będziesz miał kilka, każ-
dy z jedną klasą publiczną. Podobnie z plikiem wyglądu: będzie ich też kilka, każdy
opisze wygląd kolejnego ekranu. W pliku AndroidManifest.xml w odpowiedni sposób
zdefiniujesz wzajemną grę tych plików.

Materiał tego rozdziału można nazwać trzema aplikacjami w jednej, z niewielkim do-
datkiem czegoś nowego.

Projekt ten znajduje się w pliku Rozdzial_10_a.zip.

Okno główne
Rozpocznij nowy projekt. Okno główne zabuduj serią przycisków i krótkich opisów.
Niech każdy przycisk ma opracowany atrybut onClick — nazwę metody, która wej-
dzie do gry po wykryciu kliknięcia (rysunek 10.2). Jeśli znajdziesz więcej interesują-
cych grafik związanych z wybranym tematem — dodaj więcej przycisków. Z pewno-
ścią Twoi nauczyciele docenią ten trud, gdy prześlesz im na telefon swoją aplikację.

Rysunek 10.2. RelativeLayout wymaga przemyślenia kolejności rozmieszczania elementów.


Do każdego przycisku należy przypisać metodę obsługującą kliknięcie

Oto proponowana treść głównego pliku wyglądu:


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#200000" >
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
Rozdział 10.  Więcej ekranów dla aplikacji 193

android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:onClick="kliknieto_1"
android:text="Stacja ISS" />
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/button1"
android:layout_alignBottom="@+id/button1"
android:layout_toRightOf="@+id/button1"
android:text="Pozycja stacji kosmicznej ISS"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="#FFFF00" />
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@+id/button1"
android:layout_toLeftOf="@+id/textView1"
android:onClick="kliknieto_2"
android:text="Księżyc" />
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/button2"
android:layout_alignBottom="@+id/button2"
android:layout_alignLeft="@+id/textView1"
android:text="Fazy Księżyca"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="#FFFF00" />
<Button
android:id="@+id/button3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignRight="@+id/button2"
android:layout_below="@+id/button2"
android:onClick="kliknieto_3"
android:text="Słońce" />
<TextView
android:id="@+id/textView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/button3"
android:layout_alignBottom="@+id/button3"
android:layout_toRightOf="@+id/button3"
android:text="Plamy na Słońcu"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="#FFFF00" />
</RelativeLayout>
194 Android. Podstawy tworzenia aplikacji

Jeśli wygląd ekranu głównego aplikacji jest zadowalający, przejdź do Javy i zaimple-
mentuj trzy metody kliknieto...():
package pl.twojadomena.rozdzial_10_1;
import android.os.Bundle;
import android.view.View;
import android.app.Activity;
import android.content.Intent;
public class MainActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
//ISS
public void kliknieto_1( View v)
{
}
//Księżyc
public void kliknieto_2( View v)
{
}
//Słońce
public void kliknieto_3( View v)
{
}
}

W każdej z metod za chwilę umieścisz po dwie linie otwierające nowe okna — ekrany.
Najpierw jednak opracuj te okna. Zacznij od tego, co jest najłatwiejsze, czyli od wy-
glądu opisywanego w plikach XML. Kliknij prawym klawiszem myszki folder layout
(wygląd) i wybierz opcję New/Android XML File (rysunek 10.3).

Za pomocą kreatora utwórz nowy plik XML. Typem zasobu niech będzie Layout. Nazwę
pliku dobierz starannie, by potem nie pogubić się w natłoku plików. Tagiem głównym
niech będzie RelativeLayout (rysunek 10.4).

Skoro kreator nowego projektu nadaje plikowi wyglądu nazwę w rodzaju activity_
main.xml, proponuję, abyś plik wyglądu okna poświęconego stacji ISS nazwał activity_
iss.xml. Niech plik ten zawiera komponent ImageView do wyświetlania grafiki, okrągły
wskaźnik postępu ProgressBar do uspokajania użytkownika, gdy grafika będzie zbyt dłu-
go ładować się z internetu, przycisk Button z napisem Powrót i pole TextView do wypro-
wadzania komunikatów. W podobny sposób wykreuj pliki wyglądu activity_ksiezyc.xml
i activity_slonce.xml (rysunek 10.5).
Rozdział 10.  Więcej ekranów dla aplikacji 195

Rysunek 10.3.
Każde okienko będzie
miało własny plik
wyglądu XML i własny
plik Javy

Rysunek 10.4.
Tworzenie nowego
pliku: wybór nazwy,
typu zasobu i tagu
głównego
196 Android. Podstawy tworzenia aplikacji

Rysunek 10.5.
Cztery okienka i cztery
pliki wyglądów każdego
z nich. Jeśli okienka
wyglądają identycznie,
do ich utworzenia
można użyć jednego
i tego samego pliku
wyglądu

Czy można to zrobić lepiej? Przede wszystkim zauważ, że wszystkie trzy pliki wyglądu
okienek z grafiką są, a przynajmniej mogą być, identyczne. Każde z tych okienek za-
wiera obrazek, przycisk, napis i wskaźnik postępu. Oczywiście każde można zbudo-
wać inaczej, indywidualnie, ale można też zamiast wyglądów trzech okienek opracować
tylko jeden i umieścić go w jednym pliku XML. Jest to jednak rzadki przypadek i aby
podkreślić symetrię pracy nad każdym z okienek, proponuję dla każdego cierpliwie
generować komplet plików. Możesz jakoś różnicować wyglądy — okno Słońca być
może powinno mieć żółty odcień, a Księżyca srebrny?

Plik wyglądu okienka powinien mieć mniej więcej następującą strukturę:


<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#400000" >
<ImageView
android:id="@+id/imageView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:src="@android:drawable/dark_header" />
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:text="Komunikat"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="#FFFF00" />
<ProgressBar
android:id="@+id/progressBar1"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/imageView1"
Rozdział 10.  Więcej ekranów dla aplikacji 197

android:layout_centerHorizontal="true"
android:visibility="visible" />
<Button
android:id="@+id/button1"
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@+id/textView1"
android:layout_alignParentLeft="true"
android:onClick="kliknieto_powrot"
android:text="Powrót" />
</RelativeLayout>

Zauważ drobną różnicę w komponencie ProgressBar w stosunku do poprzedniego roz-


działu — jest on od razu widoczny, podczas gdy w rozdziale 9 oczekiwał na kliknięcie
przycisku Ściągaj. Tutaj ściąganie rozpocznie się automatycznie, gdy tylko użytkownik
wejdzie do wybranego okienka, zatem wskaźnika ProgressBar nie ma sensu wstępnie
ukrywać. Zauważ też, że przycisk ma przypisaną metodę, którą trzeba będzie zdefi-
niować w pliku Javy.

Teraz przygotuj trzy pliki Javy, każdy opisujący jedną publiczną klasę reprezentującą
okienko (rysunek 10.6). Jak zwykle, pomoże Ci kreator, a dodatkowo wzoruj się na
zawartości plików, z którymi pracowałeś do tej pory.

Rysunek 10.6. Po kliknięciu prawym przyciskiem myszki nazwy pakietu zawierającego algorytmy
w Javie należy wybrać operację New/Class

Skoro Eclipse plik Javy nazywa MainActivity.java, proponuję, byś swoje pliki na-
zwał: IssActivity.java, KsiezycActivity.java, SlonceActivity.java. Nie jest to konieczne,
ale porządek w plikach powinien być (rysunek 10.7). Cierpliwie wygeneruj wszystkie
trzy pliki z trzema klasami Javy opisującymi trzy okienka.
198 Android. Podstawy tworzenia aplikacji

Rysunek 10.7.
Dobieranie nazwy nowej
klasy powinno być
staranne, aby nie
pogubić się w gąszczu
plików. Koniecznie
należy zaznaczyć,
że klasa rozszerza
biblioteczną klasę
Activity. Dobrze jest
zawsze zaznaczać, żeby
kreator tworzył metody
abstrakcyjne, czyli te,
które i tak trzeba
napisać, bo w klasie
bazowej ich brakuje

Typowy plik Javy ma trywialną strukturę, którą za chwilę wzbogacisz:


package pl.twojadomena.rozdzial_10_1;

import android.app.Activity;

public class KsiezycActivity extends Activity


{
}

Kreator nie napracował się za dużo. Mógł dodać choćby metodę onCreate(), która
jest niezwykle użyteczna, bo służy do inicjalizacji ustroju klasy. Pamiętaj o suflerze
Ctrl+Spacja i przypomnij sobie, że wielokrotnie już opracowywana metoda onCre-
ate() ma następującą postać:
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}

Zacznij pisać nazwę metody, obserwując podpowiedzi, wreszcie wybierz właściwą.


Wnętrze metody skopiuj z wcześniejszych projektów, ale uaktualnij odwołanie do
nowego pliku XML opisującego wygląd ekranu (rysunek 10.8).
Rozdział 10.  Więcej ekranów dla aplikacji 199

Rysunek 10.8.
By napisać nazwę
metody, najpierw
należy stanąć kursorem
w odpowiednim
kontekście (tutaj:
wewnątrz klamer
ograniczających ciało
klasy) i przywołać sufler
klawiszami Ctrl+Spacja

Metoda onCreate(), np. w pliku IssActivity.java, powinna mieć następującą postać:


@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_iss);
}

gdzie napis activity_iss oznacza nazwę pliku wyglądu z folderu res/layout.

Oprócz metody onCreate() do każdej z trzech klas trzeba też dodać metodę wykazaną
we właściwości onClick przycisku:
public void kliknieto_powrot( View v)
{
finish();
}

Ta metoda zostanie uruchomiona, gdy system wykryje kliknięcie przycisku. Metoda


ta zawiera wywołanie metody kończącej życie ekranu. Metoda finish() znajduje się
w klasie bazowej Activity.

To jest dobry moment, aby wrócić do okna głównego, które zawiera trzy przyciski, i do
wnętrz metod kliknieto_...(), aby wpisać tam algorytmy otwierania innych aktywności:
//ISS
public void kliknieto_1( View v)
{
Intent iss = new Intent( this, IssActivity.class);
startActivity( iss);
}
//Księżyc
public void kliknieto_2( View v)
{
200 Android. Podstawy tworzenia aplikacji

Intent ksi = new Intent( this, KsiezycActivity.class);


startActivity( ksi);
}
//Słońce
public void kliknieto_3( View v)
{
Intent slo = new Intent( this, SlonceActivity.class);
startActivity( slo);
}

Pojawia się tutaj nowa klasa Intent (zamiar zrobienia czegoś, w omawianym przypadku:
pokazania Słońca, Księżyca itd.). Klasa Intent jest w Androidzie ważna — tutaj wy-
korzystujesz ją jako klucz do otwarcia własnego okienka, ale w systemie są gotowe
intencje — zamiary edytowania tekstu, telefonowania, odbierania rozmowy.

Ostatnią z rzeczy koniecznych do zrobienia jest wykazanie w pliku AndroidManifest.xml


trzech dodatkowych okien (poprawniej będzie mówić: aktywności) i nadanie im od-
powiednich uprawnień (rysunek 10.9). Podobnie jak w poprzednim rozdziale, musisz
zezwolić aplikacji na sięganie do internetu (rysunek 10.10).

Rysunek 10.9.
Trzy dodatkowe
aktywności (klasy
rozszerzające klasę
Activity) należy
zgłosić w pliku
AndroidManifest.xml.
Wystarczy dwa razy
kliknąć nazwę tego
pliku i ostrożnie
wypełnić
odpowiednie pola
w specjalizowanym
edytorze

Ostatecznie plik manifestu powinien mieć następującą postać:


<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="pl.twojadomena.rozdzial_10_1"
android:versionCode="1"
android:versionName="1.0" >

<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="15" />
<uses-permission android:name="android.permission.INTERNET"/>
Rozdział 10.  Więcej ekranów dla aplikacji 201

Rysunek 10.10.
Należy zezwolić
aplikacji na sięganie
do internetu

<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
android:label="@string/title_activity_main" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".IssActivity"
android:label="@string/title_activity_main" >
</activity>
<activity
android:name=".KsiezycActivity"
android:label="@string/title_activity_main">
</activity>
<activity
android:name=".SlonceActivity"
android:label="@string/title_activity_main">
</activity>
</application>
</manifest>

W pliku manifestu możesz zatytułować każdą aktywność. Odpowiednie teksty najlepiej


zdefiniuj w pliku strings.xml. Dwa razy kliknij ten plik i otwórz go do edycji w specjali-
zowanym edytorze. Za pomocą zakładki na dole edytora możesz też przejść do zwykłej
edycji tekstowej, która w tym przypadku jest bardzo prosta (rysunek 10.11).
202 Android. Podstawy tworzenia aplikacji

Rysunek 10.11. W pliku manifestu można zatytułować każdą aktywność. Odpowiednie teksty najlepiej
jest definiować w pliku strings.xml

Przygotowanie pliku manifestu, plików wyglądu XML, przygotowanie plików Javy


i uzupełnienie każdego z nich o metody onCreate() i kliknieto_powrot()kończy część
niezbędną do zrealizowania programu. Program powinien działać, choć niczego jeszcze
nie będzie wyświetlać (rysunek 10.12).

Rysunek 10.12.
Program jeszcze
nie wyświetla danych
astronomicznych,
ale nawigacja między
ekranami powinna
działać poprawnie

Pora na oprogramowanie każdego z trzech ekranów, tak aby wyświetlały one wybrane,
znalezione w internecie materiały. Każda z aktywności wygląda i działa identycznie,
zmienia się jedynie adres internetowy ściąganej grafiki (rysunek 10.13).
Rozdział 10.  Więcej ekranów dla aplikacji 203

Rysunek 10.13.
Ta aplikacja jest
napisana niezręcznie
— trzy ekrany niemal
niczym się nie różnią,
zawierają niemal
identyczny kod w XML
i w Javie. Tutaj chodziło
jednak
o zademonstrowanie,
jak z wnętrza jednego
okna (jednej
aktywności)
uruchamiać drugie

Ponieważ zagadnieniom ściągania i wyświetlania grafiki był poświęcony rozdział 9,


przytoczę tylko klasę zajmującą się obrazem Księżyca:
package pl.twojadomena.rozdzial_10_1;

import java.io.InputStream;
import java.net.URL;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;

public class KsiezycActivity extends Activity


{
private String adres = "http://api.usno.navy.mil/imagery/moon.png";
private Button przycisk;
private ImageView obraz;
private ProgressBar stan;
private TextView napis;
private Bitmap bmp;

@Override
protected void onCreate(Bundle savedInstanceState)
204 Android. Podstawy tworzenia aplikacji

{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_ksiezyc);

przycisk = (Button) findViewById(R.id.button1);


obraz = (ImageView) findViewById(R.id.imageView1);
napis = (TextView) findViewById(R.id.textView1);
stan = (ProgressBar) findViewById(R.id.progressBar1);

OdczytObrazka oo = new OdczytObrazka();


oo.execute();
}
public void kliknieto_powrot( View v)
{
finish();
}
//----------------------------------------------------------------
class OdczytObrazka extends AsyncTask<Void, Void, Void>
{
boolean sukces=true;
@Override
protected Void doInBackground( Void... arg0)
{
URL u;
InputStream is;
try
{
u = new URL( adres);
is = u.openStream();
Bitmap temp = BitmapFactory.decodeStream(is);
bmp=temp.copy(Bitmap.Config.ARGB_8888, true);
}
catch( Exception e)
{
sukces = false;
}
return null;
}
@Override
protected void onPreExecute()
{
stan.setVisibility( ProgressBar.VISIBLE);
przycisk.setEnabled(false);
napis.setText( "Pobieranie danych ...");
super.onPreExecute();
}
@Override
protected void onPostExecute(Void result)
{
if( sukces)
{
Canvas c = new Canvas( bmp);
Paint p = new Paint();
int szer = bmp.getWidth(), wys = bmp.getHeight();
p.setColor(Color.WHITE);
p.setStyle(Paint.Style.STROKE);
c.drawRect(0, 0, szer-1, wys-1, p);
Rozdział 10.  Więcej ekranów dla aplikacji 205

obraz.setImageBitmap( bmp);
napis.setText( "Pobieranie danych zakończone.");
}
else
{
napis.setText( "Błąd podczas pobierania danych.");
}
przycisk.setEnabled(true);
stan.setVisibility( ProgressBar.INVISIBLE);
super.onPostExecute(result);
}
}
}

Pozostałe dwie aktywności są bardzo podobne, różnią się adresem internetowym.

Proces odczytu mapy bitowej musi się teraz zacząć automatycznie. W interfejsie użyt-
kownika nawet nie ma przycisku Ściągnij!, jak to było w poprzednim rozdziale. W związ-
ku z tym uruchomienie procesu ściągania przeniosłeś do metody onCreate():
protected void onCreate(Bundle savedInstanceState)
{
...
OdczytObrazka oo = new OdczytObrazka();
oo.execute();
}

Klasa OdczytObrazka, której egzemplarz tutaj deklarujesz, rozszerza klasę AsyncTask


przeznaczoną do uruchamiania algorytmów w drugim planie (w oddzielnym wątku). Tak
postępuje się zawsze, gdy proces może trwać długo.

Klasa AsyncTask jest szablonem konkretyzowanym, tutaj: trzema typami Void (czyli
pustymi, jednak ich nazwy trzeba przytaczać). To najprostszy przypadek użycia klasy
AsyncTask.

W klasie OdczytObrazka nadpisujesz trzy metody, których nazwy w języku polskim


brzmiałyby zanim_zrobisz, rób i gdy_zrobiłeś. Przyjrzyj się uważnie, co się dzieje
w każdej z tych metod. Gdybyś miał kłopoty ze zrozumieniem, powinieneś wrócić do
poprzedniego rozdziału.

Ta sama aplikacja napisana lepiej,


bo krócej
Projekt ten znajduje się w pliku Rozdzial_10_b.zip.

Poprzednio napisana aplikacja niepotrzebnie operowała trzema niemal identycznymi


aktywnościami. Była za długa. Teraz zrobisz to inaczej.
206 Android. Podstawy tworzenia aplikacji

Główną aktywność (to ten ekran z trzema przyciskami) w zasadzie pozostawisz bez
zmian. Pewne różnice pojawią się w momencie definiowania zamiaru obejrzenia Księży-
ca, Słońca czy czegokolwiek innego — jakoś trzeba będzie poinformować uruchamianą
jedną aktywność, co ma wyświetlać. Trzy aktywności ściągające dane z internetu zastąpi
jedna, uniwersalna (rysunek 10.14).

Rysunek 10.14.
Aplikacja
„odchudza się”

Problemem jest, jak poinformować aktywność, co ma wyświetlać. Jak wygląda komu-


nikacja między aktywnością główną a aktywnością wtórną, wywoływaną z głównej?

Oto główny plik MainActivity.java:


package pl.twojadomena.rozdzial_10_b;

import android.os.Bundle;
import android.view.View;
import android.app.Activity;
import android.content.Intent;

public class MainActivity extends Activity


{
private String adres_iss = "http://www.heavens-
above.com/orbitdisplay.aspx?icon=iss&width=300&height=300&satid=25544";
private String adres_ksi = "http://api.usno.navy.mil/imagery/moon.png";
private String adres_slo =
"http://sdo.gsfc.nasa.gov/assets/img/latest/latest_512_4500.jpg";

@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
//ISS
public void kliknieto_1( View v)
{
Intent zamiar = new Intent( this, DwaActivity.class);
zamiar.putExtra( "adres", adres_iss);
startActivity( zamiar);
}
//Księżyc
Rozdział 10.  Więcej ekranów dla aplikacji 207

public void kliknieto_2( View v)


{
Intent zamiar = new Intent( this, DwaActivity.class);
zamiar.putExtra( "adres", adres_ksi);
startActivity( zamiar);
}
//Słońce
public void kliknieto_3( View v)
{
Intent zamiar = new Intent( this, DwaActivity.class);
zamiar.putExtra( "adres", adres_slo);
startActivity( zamiar);
}
}

W części prywatnej powyższej klasy deklarujesz trzy zmienne reprezentujące trzy adresy
internetowe. Odpowiedni adres należy przekazać do aktywności wtórnej, która musi teraz
być przygotowana do wyświetlenia danych o Księżycu, Słońcu, ISS itd. Jak to zrobić?
Spójrz np. na algorytm kliknięcia przycisku odpowiadający za stację kosmiczną ISS.
Pojawiła się tam nowa linia:
zamiar.putExtra( "adres", adres_iss);

Metoda putExtra() przesyła do aktywności zmienną adres_iss. Napis adres to identy-


fikator przekazanej zmiennej. Po stronie odbiorczej będziesz musiał użyć identycznego
napisu.

Spójrz zatem w aktywność wtórną, tutaj nazwaną DwaActivity i spisaną w pliku


DwaActivity.java, która powinna odebrać zmienną adres_iss reprezentowaną przez
identyfikator adres:
package pl.twojadomena.rozdzial_10_b;

//sekcja importów

public class DwaActivity extends Activity


{
private String adres;
...
@Override
protected void onCreate(Bundle savedInstanceState)
{
...
Bundle b = getIntent().getExtras();
adres = b.getString( "adres");
OdczytObrazka oo = new OdczytObrazka();
oo.execute();
}
...
}

Algorytm ten — tutaj mocno skrócony — zawiera dwie zmiany. Po pierwsze, prywatna
zmienna adres nie ma przypisanej wartości (poprzednio była adresem internetowym):
private String adres;
208 Android. Podstawy tworzenia aplikacji

Po drugie, w metodzie onCreate() następuje przechwycenie parametru:


Bundle b = getIntent().getExtras();
adres = b.getString( "adres");

Widoczny tu napis adres (ten w cudzysłowie) musi być identyczny z brzmieniem identy-
fikatora po stronie nadawczej. Zatem zmienna adres (ta bez cudzysłowu) w tym mo-
mencie ma już wartość.

Reszta algorytmu jest identyczna i nie ma sensu go przytaczać po raz trzeci.

Nowa klasa Bundle służy do przekazywania albo przechowywania różnych parame-


trów. Oprócz metody getString() ma metody w rodzaju getInt(), getDouble() itd.
Polskie tłumaczenie nazwy klasy (tobołek) dobrze oddaje jej rolę w Androidzie.

Zatem do tobołka dorzuć jeszcze różne tytuły okienek, tak aby okienko ze Słońcem
mogło różnić się od okienka z Księżycem.

Po stronie nadawczej algorytm będzie mniej więcej taki:


public void kliknieto_1( View v)
{
Intent zamiar = new Intent( this, DwaActivity.class);
zamiar.putExtra( "adres", adres_iss);
zamiar.putExtra( "tytul", "Pozycja stacji ISS");
//tu przekaż do aktywności wtórnej inne parametry
startActivity( zamiar);
}

Po stronie odbiorczej:
@Override
protected void onCreate(Bundle savedInstanceState)
{
Bundle b = getIntent().getExtras();
adres = b.getString( "adres");
String tyt = b.getString( "tytul");
setTitle( tyt);
}

Po stronie odbiorczej należy pamiętać identyfikator przekazywanej wartości i jej typ


— tutaj: String — aby do odczytu użyć metody getString(), a nie np. getInt().

Ta aplikacja składa się z dwóch aktywności: głównej i pobocznej. Aktywność poboczna


odbiera sygnały od aktywności głównej za pomocą tobołka Bundle, dołączonego do
zamiaru Intent. Dzięki temu jedna aktywność poboczna potrafi wyświetlić i Księżyc,
i Słońce. Jest to przykład aktywności sterowanej parametrami podawanymi przez ak-
tywność nadrzędną.

Program wymaga dwóch drobnych modyfikacji w pliku Manifest.xml: zezwolenia


(Permission) na dostęp do internetu i zgłoszenia drugiej, pobocznej aktywności (aktyw-
ność główną zgłosił kreator nowego projektu na samym początku pracy):
Rozdział 10.  Więcej ekranów dla aplikacji 209

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="pl.twojadomena.rozdzial_10_b"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="15" />
<uses-permission android:name="android.permission.INTERNET"/>

<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".MainActivity"
android:label="@string/title_activity_main" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".DwaActivity" >
</activity>
</application>
</manifest>
210 Android. Podstawy tworzenia aplikacji
Słowniczek
ADT Plugin for Eclipse lub Eclipse Plugin
Android Development Tool — narzędzie ściągane i uruchamiane wewnątrz środowiska
Eclipse. Służy do podłączenia Androida do Eclipse.

Android SDK
Software Development Kit — środowisko do pracy programistycznej w Androidzie; nasze
główne pole bitwy.

Android SDK Manager


Aplikacja dostępna po zainstalowaniu Androida SDK, służy do instalowania lub usuwania
różnych składników oprogramowania, głównie wersji systemu na poszczególne urzą-
dzenia mobilne.

AVD
Android Virtual Device — imitacja urządzenia mobilnego na ekranie peceta.

Dalvik
Oprogramowanie zwane maszyną wirtualną, stanowiące interfejs pomiędzy różno-
rodnymi urządzeniami mobilnymi a jednolitym ich obrazem z punktu widzenia pro-
gramisty. Tę ideę zaczerpnięto z Javy, która też wymaga maszyny wirtualnej działają-
cej na sprzęcie. Dzięki takiemu podejściu programista widzi jakby jedno uniwersalne
urządzenie, nie zaś ich tysiące. Kłopot polega na konieczności przygotowania maszyny
wirtualnej na każde nowe urządzenie, ale tym zajmują się producenci telefonów, table-
tów, lodówek, pojazdów marsjańskich...

dp
Umowna jednostka rozmiaru ekranowego, uniwersalny, niezależny od gęstości ekranu
piksel.
212 Android. Podstawy tworzenia aplikacji

IDE
Integrated Developer Environment — zintegrowane środowisko programistyczne.
Firma Google zaleca postawić na Eclipse — bezpłatne elastyczne środowisko okienko-
we, do którego można podłączyć chyba wszystkie narzędzia programistyczne, także
Android SDK. Samo podłączenie realizuje wtyczka ADT Plugin for Eclipse.

Java SE Development Kit lub JDK


Java Standard Edition — podstawowy pakiet oprogramowania Java dla programistów.
Praca w Androidzie wymaga wcześniejszego zainstalowania Java SE DK. Brak Javy wy-
kryje instalator środowiska Android SDK. Spośród różnych wariantów Javy SE instaluj
właściwą dla swojego systemu operacyjnego (prawdopodobnie Windows), zwracając
uwagę na wersje 32- i 64-bitowe.

Workspace
Termin określający w edytorze Eclipse miejsce na dysku (folder), w którym są przecho-
wywane Twoje projekty. Każdy projekt jest dość skomplikowanym systemem plików,
których nie wolno pochopnie modyfikować. Można mieć kilka folderów Workspace
i przełączać się między nimi, używając polecenia z menu File/Switch Workspace.
Skorowidz
A błąd, 188 E
błąd w logice interfejsu, 164
AbsoluteLayout, 32, 34 budowanie interfejsu, 169 Eclipse, 9
ADT, Android Development Eclipse Plugin, 211
Tools, 12 edytor Eclipse, 9
akcja, Patrz metoda
C edytowanie pliku, 30 , 67, 201
aktywności ściągające dane, czas trwania animacji, 102 efekt, 97
206 czyszczenie ekranu, 162 rozmycia obrazu, 157
aktywność zamiany składowej koloru,
poboczna, 208 155
wtórna, 207 D zanikania, 102
algorytm efekty
Dalvik, 9, 211
klasy publicznej, 81 animacyjne, 98
definiowanie
rysowania, 135 efektów animacyjnych, 101 specjalne, 100
Android SDK, 9, 211 egzemplarza aparatu egzemplarz procesu, 176
Android SDK Manager, 13, 211 graficznego, 188 ekran, 191
animacja, 98 klasy w klasie, 176 HVGA, 41
łączenie efektów, 110 przycisku, 79, 84 QVGA, 41
poklatkowa, 113 stylów, 87 WVGA, 41
przycisku, 108 tła, 66 element
antyaliasing, 130, 135 długotrwały proces, 168 resource, 92
aparat graficzny, 188 dodawanie shape, 89
aplikacja z ekranami, 191 identyfikatora, 148 elementy interfejsu, 25
atrybut zasobu XML, 49 emulator
angle, 68 dokumentacja online Androida, aplikacji, 18
contentDescription, 46 119 o ekranach różnej jakości,
gravity, 50 dostęp 41
tileMode, 51 do internetu, 208
atrybuty XML, 48 do zmiennych, 119 F
AVD, Android Virtual Device, dp, device independent pixel,
14, 41, 211 64, 211 fabryka map bitowych, 150, 187
drzewo plików projektu, 98 film, 113, 116
dynamiczne tworzenie obrazów, folder, Patrz katalog
B 161 format fotograficzny, 188
bitmapy prywatne, 161 działanie aktywności, 147 funkcja, Patrz metoda
blokowanie dziedziczenie, 125 funkcje prywatne, 109
komponentu, 163
przycisku, 165, 171
214 Android. Podstawy tworzenia aplikacji

G MainActivity, 81
OdczytObrazka, 186, 205
nowej aplikacji, 50
zasobu XML, 48, 100, 115
głębia koloru, 151 Paint, 127 kształty, 66
gra w kółko i krzyżyk, 87 Proces, 172
graficzne zasoby aplikacji, 39 RectF, 136
View, 106, 122, 134
L
klasy
I bazowe, 127, 133
layout, 22, 58
AbsoluteLayout, 32, 34
IDE, 11, 212 implementacja, LinearLayout, 24, 26
identyfikator, 148 implements, 82 RelativeLayout, 70, 90,
implementacja klasy, 82 konstruktor, 124 158
importowanie pakietu, 81, 130 kreator, 122 TableLayout, 27, 58
informacja o rozmiarach nazwa, 124, 198 LinearLayout, 24, 26
komponentu, 150 rozszerzanie, extends, 82, linia, 68
inicjowanie 132, 171, 174 lista stanów, 53
bitmapy, 161 tworzenie, 122 lokalizacja
zmiennej, 120 klatki filmu, 113 oprogramowania, 12
instalator Android SDK, 10 klawisze Ctrl+Spacja, 68, 92, pliku graficznego, 37
instalowanie 99, 198 losowanie
Android SDK, 9 kolejność jednokrotne, 142
IDE Eclipse, 9, 12 budowania interfejsu, 169 liczb, 131
Javy, 9 rozmieszczania elementów, pikseli, 157
interfejs 192
do oglądania filmu, 116 kolor, 61, 65
użytkownika, 103, 146 kolor piksela, 156 Ł
komponent łączenie
Button, 59, 145
J ImageView, 35, 103, 145,
animacji, 110
klatek, 115
194
Java, 9
ProgressBar, 177
JDK, 212
jednostka dp, 64
RelativeLayout, 91 M
TableRow, 63
TextView, 71, 145 manifest, 200
K View, 122 mapy bitowe, 43, 48, 145
inicjalizowanie, 161
komponenty
katalog blokowanie, 163 operacje graficzne, 154
bin, 17 graficzne, 121 podgląd, 145
drawable, 39, 49 informacja o rozmiarach, 150 przekształcenie, 154
layout, 22, 63 nazywanie, 71 rysowanie, 152
res, 17–21, 39 położenie, 137, Patrz także maszyna
src, 17 rozkład losująca Random, 131
values, 20, 62 tworzenie, 121 wirtualna Dalvik, 9
klasa wizualne, 121 metoda
Activity, 106, 147, 171 własne, 121 data_i_godzina(), 84
Animation, 106 wybieranie, 31 decodeResource(), 150
AnimationDrawable, 118 konfigurowanie Eclipse, 11–13 doInBackground(), 180
AsyncTask, 167, 173 konstruktor klasy, 124 drawLine(), 162
BitmapFactory, 150 bazowej, 125 drawRect(), 163
Bundle, 208 potomnej, 125 eraseColor(), 163
Calendar, 85 kółka, 129 execute(), 185
Canvas, 127, 152 kółko i krzyżyk, 87 findViewById(), 86, 106,
Figura, 133 kreator 148, 161
ImageView, 161 klasy, 122 finish(), 81
Intent, 200 nowego projektu, 78, 194 getContentDescription(), 141
Skorowidz 215

getInstance(), 85 obrazek, 145 pozyskiwanie mapy bitowej,


getPixel(), 156 obrót, 101, 109 154
getString(), 208 obrót ekranu, 96 proces, 167
kliknieto(), 79, 95 obsługa proces ściągania, 182
kliknieto_kreuj(), 161 kliknięć, 105, 147, 192 projekcja filmu, 118
kliknieto_start(), 169 wyjątków, 188 prostokąt, 68
kliknij(), 148 zdarzenia onClick, 95 prototyp metody, 148, 174
losuj_dane(), 143 oczyszczanie pamięci, 171 przechwytywanie parametru,
obracaj(), 106 odczytywanie 208
onCreate(), 120, 171, 199 obrazka, 186 przekształcanie mapy bitowej,
onDraw(), 126, 130, 134 pliku, 185 154
onPostExecute(), 188 odśmiecacz Javy, 171 przesunięcie, 102, 110
onPreExecute(), 176, 188 odświeżanie obrazka, 171 przesuwanie ekranu, 58–61
onProgressUpdate(), 179 okno przezroczystość, 101
putExtra(), 207 emulatora aplikacji, 18 przycisk, 64
rgb(), 156 kreatora klasy, 123 „Koniec”, 77
rysuj(), 152, 155 okrągły wskaźnik postępu, 194 Button, 32, 194
setARGB(), 162 określanie wartości atrybutu, 140 Obrót, 107
setText(), 86 opisywanie przyciski
start(), 118 efektów specjalnych, 100 animowanie, 98
startAnimation(), 107 zasobów, 114 blokowanie, 165, 171
stop(), 118 oprogramowanie ekranów, 202 definiowanie, 79, 84
stosuj_wszystko(), 112 osadzanie elementów, 83 rozmieszczanie, 93
super(), 125 wstawianie, 59
metody przypisywanie kolorów, 65
abstrakcyjne, 198
P
finalne, 132
nadpisane, 127
pakiet R
Eclipse Classic, 11
prywatne, 109 Javy, 10 RelativeLayout, 70, 90, 158
piksel, 155 rozkład
N plik AbsoluteLayout, 32, 34
activity_main xml, 58 LinearLayout, 24, 26
nadawanie AndroidManifest xml, 183, RelativeLayout, 70, 90, 158
nazwy plikowi xml, 92 200 TableLayout, 27, 58
wartości wspólnej, 64 Figura.java, 133 rozłożenie swobodne, 32
nadpisywanie metody, 125 MainActivity.java, 79, 117, rozmiar
narzędzia Android SDK, 10 153, 206 komponentu, 149
nawigacja między ekranami, strings xml, 20, 25, 34 obrazka, 43
202 pliki rozmieszczanie
nazwa class, 17 przycisków, 93
klasy, 124, 198 wyglądu, 28, 79, 91, 104, elementów, 192
metody, 124, 198 117, 138, 146, 159, 182, rozmycie obrazu, 157
pliku, 124 196 rozszerzanie
nazywanie podgląd mapy bitowej, 145 funkcjonalności, 82
komponentów, 71 podpowiedź, 68, 92, 99, 198 klasy, 82, 132, 171, 174
pliku XML, 99 pole tekstowe, 69, 86 rysowanie
polecenie Refresh, 114, 145 na ekranie, 134
polimorfizm, 96, 148 na mapie bitowej, 152, 153
O położenie komponentu, 137 ramki, 188
obiekty ułożone postfiks w drugim planie, 167
obok siebie, 24 hdpi, 40, 43 w tle, 178
w oczkach sieci, 27 ldpi, 39 rzutowanie, 95
obraz klasy fotograficznej, 188 mdpi, 40
xhdpi, 40, 43
216 Android. Podstawy tworzenia aplikacji

S klasy, 122
komponentów, 121
Style, 92
Visibility, 176
ScrollView, 57 mapy bitowej, 157 Workspace, 19, 212
SDK Location, 12 nazwy, 49 wskaźnik postępu, 177, 194
Shapes, 66 pierwszej aplikacji, 15, 19 wstawianie
silnik animacji, 119 pliku, 62, 195 fraz XML, 67
skalowanie, 102 pliku XML, 99 przycisku, 59
składowe koloru, 155 wirtualnego urządzenia, 41 wierszy, 63
słowo kluczowe zasobu XML, 54 wybór
extends, 82 typ komponentu, 31
final, 132 Void, 180 nazwy rysunku, 36
implements, 82 nieokreślony, 173 zasobu, 45
super, 125, 127 typy wyświetlaczy, 41 wygląd
stan urządzenia, 180 aplikacji, 19, 22, 57
struktura katalogów drawable, ekranu głównego, 194
39
U tabelaryczny, 58
strumień danych, 187 układanie obiektów, 24, 27 wygładzanie grafiki, 130, 135
sufler, 68, 92, 99, 198 uruchamianie wyjątek, 80, 188
suwak przewijania, 60, 137 animacji, 105 wyświetlacze, 41
aplikacji, 47 wyświetlanie
daty, 82
Ś procesu w tle, 176, 178
projekcji, 118 grafiki, 194, 203
ściąganie danych z internetu, urządzenia AVD, 46 obrazka, 161
181 urządzenie zmian, 153
środowisko programistyczne, 9 QVGA, 42
WVGA800, 42 Z
ustalanie atrybutów figur, 130
T ustawienia kursora, 67 zakładka Images & Media, 45
TableLayout, 27, 58 zamiana składowej koloru, 155
zanikanie, 102
tag W zarządca systemów Android, 13
animation-list, 115
bitmap, 113 wątek zasoby
LinearLayout, 136 główny, 167 graficzne, 39, 113, 145
ScrollView, 58, 136 poboczny, 167 XML, 55
tło wersja systemu Android, 14 zdarzenie onClick, 95, 147
aplikacji, 66, 89 wirujące kółko, 182 zegar, 82
gradientowe, 90 wklejanie adresu internetowego, zezwolenie na dostęp do
TrueColor, 162 185 internetu, 208
tworzenie właściwość zmienne
AVD, 15 Background, 65, 66 prywatne, 166
efektów, 97 contentDescription, 138 typu Bitmap, 150
ekranów, 191 Enabled, 163
ikony, 17, 36 src, 103

You might also like