Professional Documents
Culture Documents
Stasiewicz A. - Android. Podstawy Tworzenia Aplikacji
Stasiewicz A. - Android. Podstawy Tworzenia Aplikacji
Stasiewicz A. - Android. Podstawy Tworzenia Aplikacji
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
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.
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.
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
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ę.
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.
Rysunek 1.2.
By zainstalować zestaw
narzędzi Android SDK,
konieczne jest
zainstalowanie
pakietu Javy
Rozdział 1. Instalowanie środowiska programistycznego 11
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.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
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
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
Rysunek 1.12.
Tworzenie głównej
ikony aplikacji
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
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.
Rysunek 2.1. W katalogu res znajdują się podkatalogi drawable, do których warto zajrzeć
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
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.
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.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).
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" />
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
Rysunek 2.11.
Przykładowy wygląd
TableLayout
</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ń.
Z kolei atrybut:
android:paddingRight="5dp"
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"
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>
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ć.
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
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.
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.
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.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
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ć.
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
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
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).
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
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.
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.
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
Ż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>
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">
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)
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
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"
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.
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>
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.
Rysunek 4.2.
Przeciąganie
komponentu Button
w poszczególne wiersze
tabeli
60 Android. Podstawy tworzenia aplikacji
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
Kolory
Projekt ten znajduje się w pliku Rozdzial_4_b.zip.
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
</resources>
Ż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
</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
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>
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...
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>
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.
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
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
<gradient android:startColor="#0000FF"
android:endColor="#000040"
android:angle="90"/>
<stroke android:width="10dp"
android:color="#FFFFFF"/>
</shape>
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.
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
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
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.
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>
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”.
Rysunek 5.2.
Uzupełnianie (w pliku
wyglądu) definicji
przycisku o nazwę
metody, która zostanie
użyta, gdy ktoś kliknie
przycisk
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ę
import android.view.View;
setContentView(R.layout.activity_main);
}
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
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.
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
Pierwszy tekst jest ustawiony pod przyciskiem, ale także z marginesem. Drugi tekst
jest ustawiony pod pierwszym tekstem, także z marginesem.
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
Metoda kliknieto() ma identyczną postać jak poprzednio — nie będę jej omawiać.
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
Warto zapamiętać: niezwykle ważną rolę gra mechanizm komunikacji na linii zasoby
XML — Java.
XML: android:id="@+id/textView1"
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
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
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.
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
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.
</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>
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>
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;
Rysunek 5.17. Do publicznej klasy Javy należy dopisać metodę zgłoszoną do obsługi zdarzenia onClick
(kliknięto przycisk)
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
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!
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.
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
Rysunek 6.3.
Nazywanie nowego
pliku XML oraz
wybieranie głównego
tagu zasobu — w tym
wypadku bitmapa
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)
Rysunek 6.5.
Kreator pomaga
w utworzeniu pliku XML
(w tym przypadku
o nazwie „obrot”)
definiującego efekt
obrotu
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
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
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
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.
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 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)
{
}
}
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.
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.
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);
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);
}
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);
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
Łą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..." />
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
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().
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
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
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.
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.
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
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).
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;
W pliku tym doimportowano klasy, których nazwy pojawiają się w omawianym algo-
rytmie:
import android.view.View;
import android.graphics.drawable.AnimationDrawable;
Rysunek 6.19.
Dokumentacja online
Androida
Rysunek 6.20.
Interfejs programu
uzupełnionego
o zatrzymywanie filmu
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
img = (ImageView)findViewById(R.id.imageView1);
a = (AnimationDrawable)img.getBackground();
}
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
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.
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
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...
}
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
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.
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);
}
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
<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
...
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
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
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);
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), ...
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;
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.
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.
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
super(context, attrs);
}
public Figura(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
}
// Tu dopisz metodę onDraw.
}
{
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.
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);
}
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ą.
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
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
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
<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.
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" />
...
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
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.
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
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
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!
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);
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.
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
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);
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
}
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);
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;
@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.
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
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);
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
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.
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
@Override
public void onCreate( Bundle savedInstanceState)
{
super.onCreate( savedInstanceState);
setContentView( R.layout.activity_main);
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();
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.
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
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ę.
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" />
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
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.
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.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;
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.
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.
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.
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);
}
}
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.
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()
{
}
}
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
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)
import java.util.Random;
//reszta importów
@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.
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
@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
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 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
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>
Ś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.
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
<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);
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
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.
Metody te miałyby inne postaci (mówi się raczej: prototypy, a nie: postaci) przy kon-
kretyzacji innymi typami niż typ pusty.
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.
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);
}
}
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
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);
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.
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ę.
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?
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>
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
import android.app.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);
}
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
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();
}
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
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.
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
<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>
Rysunek 10.11. W pliku manifestu można zatytułować każdą aktywność. Odpowiednie teksty najlepiej
jest definiować w pliku strings.xml
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
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;
@Override
protected void onCreate(Bundle savedInstanceState)
204 Android. Podstawy tworzenia aplikacji
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_ksiezyc);
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);
}
}
}
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 AsyncTask jest szablonem konkretyzowanym, tutaj: trzema typami Void (czyli
pustymi, jednak ich nazwy trzeba przytaczać). To najprostszy przypadek użycia klasy
AsyncTask.
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ę”
import android.os.Bundle;
import android.view.View;
import android.app.Activity;
import android.content.Intent;
@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
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);
//sekcja importów
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
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ść.
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 odbiorczej:
@Override
protected void onCreate(Bundle savedInstanceState)
{
Bundle b = getIntent().getExtras();
adres = b.getString( "adres");
String tyt = b.getString( "tytul");
setTitle( tyt);
}
<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.
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.
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
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