Professional Documents
Culture Documents
h
w
|
8
N
w ±
I
A8
X
k
n
Æ k
k
n
n
Naszym Przyjaciołom i naszej Rodzinie
— dziękujemy Wam bardzo za miłość i wsparcie
O autorach
iths
David Griff
Dawn Griffiths
Dawn Griffiths zaczynała jako matematyk David Griffiths zaczął programować w wieku 12 lat,
na jednym z czołowych brytyjskich uniwersytetów, kiedy obejrzał film dokumentalny poświęcony
gdzie ukończyła studia matematyczne z wyróżnieniem. pracom Seymoura Paperta. W wieku 15 lat napisał
Później rozpoczęła karierę w branży produkcji implementację języka LOGO opracowanego przez
oprogramowania i dysponuje już dwudziestoletnim Paperta. Po zakończeniu studiów matematycznych
doświadczeniem w branży IT. na uniwersytecie zaczął pisać kod przeznaczony
dla komputerów oraz artykuły w czasopismach dla
Przed napisaniem książki Android. Programowanie ludzi. Pracował jako instruktor zwinnych metod
aplikacji. Rusz głową! Dawn napisała trzy inne książki programowania, programista, parkingowy, ale nie
z serii Head First (Head First Statistics — polskie w takiej kolejności. Potrafi programować w ponad
wydanie: Head First. Statystyka. Edycja polska, Head dziesięciu językach i pisać prozę tylko w jednym,
First 2D Geometry oraz Head First C — polskie a kiedy nie pisze, ani nie zajmuje się doradztwem,
wydanie: C. Rusz głową!) i brała udział w pracach spędza większość czasu ze swoją uroczą żoną
nad wieloma innymi z tej serii. — i współautorką tej książki — Dawn.
Kiedy nie pracuje nad żadną z książek z serii Rusz Przed napisaniem książki Android. Programowanie
głową!, doskonali się w tai-chi, biega, robi koronki aplikacji. Rusz głową! David napisał trzy inne książki
i gotuje. Uwielbia także podróżować i spędzać czas z tej serii: Head First Rails (polskie wydanie: Head First
ze swoim mężem Davidem. Ruby on Rails. Edycja polska), Head First Programming
oraz Head First C (polskie wydanie: C. Rusz głową!).
iv
Spis treści
W
Wprowadzenie
Twój mózg jest nastawiony na Androida. Jesteś tu po to, by się czegoś nauczyć, natomiast
Twój mózg robi Ci przysługę, upewniając się, że to, czego się nauczyłeś, szybko wyleci
z pamięci. Twój mózg myśli sobie: „Lepiej zostawić miejsce na coś ważnego, na przykład: których
dzikich zwierząt lepiej unikać albo czy jeżdżenie nago na snowboardzie to dobry pomysł”.
A zatem, w jaki sposób możesz skłonić swój mózg, by myślał, że Twoje życie zależy
od umiejętności pisania aplikacji na Androida?
v
Spis treści
Zaczynamy
1
Skok na głęboką wodę
Android błyskawicznie podbił świat.
Każdy chce mieć smartfon lub tablet, a urządzenia z Androidem są niezwykle popularne.
W tej książce nauczymy Cię, jak pisać własne aplikacje, a zaczniemy od pokazania procesu
przygotowania bardzo prostej aplikacji i uruchomienia jej na wirtualnym urządzeniu z Androidem.
W trakcie tych prac poznasz także kilka podstawowych komponentów wszystkich aplikacji
na Androida, takich jak aktywności i układy. Jedyną rzeczą, której będziesz do tego
potrzebować, jest znajomość Javy, choć wcale nie musisz być w niej mistrzem…
2
3
5
6
8
12
SDK
oid
13
dr
An 14
15
Android Studio utworzy pełną strukturę katalogów aplikacji 16
Przydatne pliki projektu 17
Edycja kodu z użyciem edytorów Android Studio 18
Uruchamianie aplikacji w emulatorze Androida 23
Tworzenie wirtualnego urządzenia z Androidem 24
Uruchomienie aplikacji w emulatorze 27
Postępy możesz obserwować w konsoli 28
Jazda próbna 29
Ale co się właściwie stało? 30
Usprawnianie aplikacji 31
Czym jest układ? 32
Plik activity_main.xml zawiera dwa elementy 33
Plik układu zawiera odwołanie do łańcucha, a nie sam łańcuch znaków 34
Zajrzyjmy do pliku strings.xml 35
Weź swoją aplikację na jazdę próbną 37
Twój przybornik do Androida 38
<Layout>
</Layout>
<Układ>
Aktywność
vi Układ
Urządzenie </Układ>
Spis treści
2
Aplikacje, które coś robią
Większość aplikacji musi w jakiś sposób reagować na poczynania
użytkowników.
Z tego rozdziału dowiesz się, co zrobić, aby Twoje aplikacje były nieco bardziej interaktywne.
Przekonasz się, jak zmusić aplikację, by coś zrobiła w odpowiedzi na działania użytkownika,
oraz jak sprawić, by aktywności i układy porozumiewały się ze sobą jak starzy kumple.
Przy okazji pokażemy Ci nieco dokładniej, jak naprawdę działa Android poznasz plik R,
czyli ukryty klejnot, który spaja pozostałe elementy aplikacji.
<Layout> 40
42
</Layout> <resources>
43
Układ </resources> 44
45
strings.xml
48
49
Stosuj zasoby łańcuchowe, a nie łańcuchy podawane w kodzie 50
Zmiana układu i zastosowanie w nim zasobów łańcuchowych 51
Aktywność Weź swoją aplikację na jazdę próbną 52
Dodanie wartości do komponentu Spinner 53
Dodanie do komponentu Spinner odwołania do string-array 54
Jazda próbna komponentu Spinner 54
Musimy zadbać o to, by przycisk coś robił 55
Niech przycisk wywołuje metodę 56
BeerExpert
Jak wygląda kod aktywności? 57
Dodaj do aktywności metodę onClickFindBeer() 58
Metoda onClickFindBeer() musi coś robić 59
Dysponując obiektem View, można odwoływać się do jego metod 60
Aktualizacja kodu aktywności 61
Pierwsza wersja aktywności 63
Jazda próbna — test modyfikacji 65
Tworzenie własnej klasy Javy 66
Dodaj do aktywności wywołanie metody naszej klasy,
aby była wyświetlana FAKTYCZNA porada 67
Kod aktywności, wersja 2 69
Co się dzieje podczas wykonywania tego kodu? 70
Jazda próbna — test aplikacji 71
Twój przybornik do Androida 72
vii
Spis treści
3
Jakie są Twoje intencje?
Większość aplikacji potrzebuje więcej niż jednej aktywności.
Dotychczas mieliśmy do czynienia z aplikacjami składającymi się tylko z jednej aktywności. Kiedy
jednak sprawy się komplikują, jedna aktywność zwyczajnie nie wystarczy. Dlatego w tym rozdziale
pokażemy Ci, jak tworzyć aplikacje składające się z wielu aktywności i jak nasze aplikacje
mogą porozumiewać się z innymi, wykorzystując w tym celu intencje. Pokażemy także, jak można
używać intencji, by wykraczać poza granice naszych aplikacji, i jak wykorzystywać aktywności
należące do innych aplikacji dostępnych w urządzeniu do wykonywania akcji. To wszystko
zapewni nam znacznie większe możliwości.
74
75
75
Intencja
78
80
83
Do: InnaAktywnosc
84
85
86
87
Metoda putExtra() zapisuje w intencji dodatkowe informacje 88
Aktualizacja kodu aktywności CreateMessageActivity 91
Zastosowanie informacji przekazanych w intencji w klasie
ReceiveMessageActivity 92
Co się dzieje, gdy użytkownik kliknie przycisk Wyślij wiadomość? 93
Jazda próbna aplikacji 94
Jak działają aplikacje na Androida? 95
Co się dzieje podczas działania kodu? 99
Jak Android korzysta z filtrów intencji? 102
Hej, Musisz uruchomić aplikację na PRAWDZIWYM urządzeniu 105
użytkowniku! Każda Jazda próbna aplikacji 107
z tych aktywności może
Zmień kod, aby wyświetlać okno dialogowe 111
wysyłać wiadomości. Której
z nich chcesz użyć? Jazda próbna aplikacji 112
Twój przybornik do Androida 114
CreateMessageActivity
viii Android
Użytkownik
Spis treści
4
Była sobie aktywność
Aktywności stanowią podstawę wszystkich aplikacji na Androida.
Wiesz już, jak tworzyć aktywności i jak sprawić, by jedna aktywność uruchomiła drugą, używając
do tego celu intencji. Ale co tak naprawdę dzieje się za kulisami? W tym rozdziale nieco
dokładniej poznamy cykl życia aktywności. Co się dzieje, kiedy aktywność jest tworzona
i usuwana? Jakie metody są wywoływane, gdy aktywność jest wyświetlana i pojawia się na
Aktywność ekranie, a jakie gdy aktywność traci miejsce wprowadzania i jest ukrywana? W jaki sposób
uruchomiona można zapisywać i odtwarzać stan aktywności?
116
118
onCreate()
119
122
123
onStart() Obiekty Handler umożliwiają planowanie wykonania kodu 124
Pełny kod metody runTimer() 125
Kompletny kod aktywności StopwatchActivity 126
onResume() Obrót ekranu zmienia konfigurację urządzenia 132
Od narodzin do śmierci: stany aktywności 133
Cykl życia aktywności: od utworzenia do usunięcia 134
W jaki sposób radzić sobie ze zmianami konfiguracji? 136
Aktywność onRestart()
Co się stanie po uruchomieniu aplikacji? 139
działająca Tworzenie i usuwanie to nie cały cykl życia aktywności 142
Cykl życia aktywności: widzialny czas życia 143
Zaktualizowany kod aktywności StopwatchActivity 147
Co się dzieje podczas działania aplikacji? 148
onPause()
Jazda próbna aplikacji 149
A co się dzieje, jeśli aplikacja jest tylko częściowo widoczna? 150
Cykl życia aktywności: życie na pierwszym planie 151
onStop()
Zatrzymanie stopera w razie wstrzymania aktywności 154
Kompletny kod aktywności 157
Wygodny przewodnik po metodach cyklu życia aktywności 161
onDestroy() Twój przybornik do Androida 162
Aktywność
usunięta
ix
Spis treści
Interfejs użytkownika
5
Podziwiaj widoki
Nie masz innego wyjścia, musisz tworzyć szałowe układy.
Jeśli chcesz pisać aplikacje, których inni będą używać, musisz zadbać o to, by wyglądały one
dokładnie tak, jak sobie tego życzysz. Zagadnienie tworzenia układów potraktowaliśmy
dotychczas bardzo powierzchownie, najwyższy zatem czas, by przyjrzeć mu się dokładniej.
W tym rozdziale pokażemy Ci różne typy układów, które można tworzyć, i zabierzemy Cię na
wycieczkę po najważniejszych komponentach GUI i sposobach ich stosowania. Pod koniec tego
rozdziału przekonasz się, że choć wszystkie te układy i komponenty wyglądają nieco inaczej,
to jednak mają ze sobą więcej wspólnego, niż można by przypuszczać.
Pole
Przycisk tekstowe
x View View
Spis treści
6
Zorganizuj się
Chcesz wiedzieć, jaki jest najlepszy sposób na określenie struktury aplikacji?
Znasz już podstawowe elementy konstrukcyjne używane do tworzenia aplikacji, więc teraz nadszedł
czas, żebyś się lepiej zorganizował. W tym rozdziale pokażemy Ci, jak możesz przekształcić
zbiór pomysłów w niesamowitą aplikację. Zobaczysz, że listy danych mogą stać się kluczowym
elementem projektu aplikacji i że łączenie ich może prowadzić do powstania aplikacji łatwej
w użyciu i zapewniającej ogromne możliwości. Przy okazji zapoznasz się z obiektami
nasłuchującymi i adapterami, dzięki którym Twoja aplikacja stanie się bardziej dynamiczna.
228
Ekran
początkowy
229
z listą opcji
230
231
Menu zawierające 232
wszystko, co 233
można u nas Struktura aplikacji dla kafeterii Coffeina 234
zjeść Układ aktywności głównego poziomu składa się z obrazka i listy 238
Kompletny kod układu aktywności głównego poziomu 240
Szczegółowe Zapewnianie reakcji ListView na kliknięcia za pomocą
informacje obiektu nasłuchującego 241
o każdym Kompletny kod aktywności TopLevelActivity 243
napoju Jak utworzyć aktywność listy? 249
Łączenie widoków list z tablicami za pomocą adaptera ArrayAdapter 251
Dodanie adaptera ArrayAdapter do aktywności DrinkCategoryActivity 252
Co się stanie po wykonaniu kodu? 253
Jak obsługiwaliśmy kliknięcia w aktywności TopLevelActivity? 256
Kompletny kod aktywności DrinkCategoryActivity 258
Aktywność szczegółów wyświetla informacje o jednym rekordzie 259
Wypełnienie widoków danymi 261
Kod aktywności DrinkActivity 263
Jazda próbna aplikacji 266
Twój przybornik do Androida 268
Utworzymy adapter ArrayAdapter, To jest
To jest nasz widok listy. nasza tablica.
aby powiązać widok listy z naszą tablicą.
xi
Spis treści
Fragmenty
7
Zadbaj o modularyzację
Wiesz już, jak tworzyć aplikacje, które działają tak samo niezależnie od tego,
na jakim urządzeniu zostały uruchomione…
…ale co zrobić w przypadku, kiedy akurat chcesz, by aplikacja wyglądała i działała inaczej
w zależności od tego, czy zostanie uruchomiona na telefonie, czy na tablecie? W tym rozdziale
pokażemy Ci, co zrobić, aby aplikacja wybierała układ, który najlepiej pasuje do wielkości
ekranu urządzenia. Oprócz tego przedstawimy fragmenty, czyli modularne komponenty
kodu, które mogą być wielokrotnie używane przez różne aktywności.
273
275
276
278
282
283
284
286
290
292
294
295
A zatem fragment 301
będzie zawierał jedną listę. 302
Kiedy chcieliśmy użyć aktywności 303
zawierającej pojedynczą listę,
305
zastosowaliśmy aktywność typu
ListActivity. Zastanawiam się, czy 307
przypadkiem nie istnieje jakiś typ 309
fragmentu stanowiący odpowiednik 315
tej klasy.
319
321
322
xii
Spis treści
Fragmenty zagnieżdżone
8
Zadbaj o potomstwo
Wiesz już, że stosowanie fragmentów w aktywnościach pozwala na
wielokrotne wykorzystywanie kodu i zwiększa elastyczność aplikacji.
W tym rozdziale mamy zamiar pokazać Ci, jak zagnieżdżać fragmenty w innych
fragmentach. Dowiesz się, jak używać menedżera fragmentów podrzędnych,
by poskromić niesforne transakcje. Ponadto dowiesz się, dlaczego tak ważna jest
znajomość różnic między aktywnościami i fragmentami.
326
332
335
Metoda getFragmentManager() tworzy transakcje
na poziomie aktywności 340
Zagnieżdżone fragmenty wymagają zagnieżdżonych transakcji 341
Kompletny kod fragmentu WorkoutDetailFragment 343
Jazda próbna aplikacji 344
Dlaczego kliknięcie przycisku powoduje awarię aplikacji? 345
Przyjrzyjmy się kodowi układu StopwatchFragment 346
Zaimplementuj we fragmencie interfejs OnClickListener 349
Powiązanie obiektu nasłuchującego OnClickListener z przyciskami 351
Kod fragmentu StopwatchFragment 352
Jazda próbna aplikacji 354
Kod fragmentu WorkoutDetailFragment 358
Jazda próbna aplikacji 359
Twój przybornik do Androida 364
Aktywność Fragment
xiii
Spis treści
Paski akcji
9
Na skróty
Każdy lubi chodzić na skróty.
Z tego rozdziału dowiesz się, jak korzystając z pasków akcji, wzbogacić aplikację o możliwość chodzenia
na skróty. Pokażemy Ci, jak uruchamiać inne aplikacje za pomocą elementów akcji dodawanych do
pasków akcji, jak udostępniać treści innym aplikacjom, używając dostawcy akcji współdzielenia, oraz
jak poruszać się w górę hierarchii aplikacji za pomocą przycisku W górę umieszczonego na pasku
akcji. Dowiesz się też, jak można nadawać tworzonym aplikacjom spójny wygląd i sposób działania,
korzystając z motywów, i poznasz pakiet biblioteki wsparcia systemu Android.
366
tępniania
Właśnie tak wygląda akcja udos 367
jej
wyświetlona na pasku akcji. Po aplikacji,
kliknięciu wyświetlana jest lista 368
ci.
którym możemy udostępnić treś 369
370
371
372
373
374
375
376
377
378
379
382
383
384
386
388
389
391
API 21? 392
Idealnie pasuje. 393
394
395
values-v21
<xml> Name: AppTheme
</xml>
Parent: Theme.Material.Light
Android styles.xml
xiv
Spis treści
Szuflady nawigacyjne
10
Z miejsca na miejsce
Aplikacje są nieporównanie lepsze, gdy można się po nich łatwo poruszać.
W tym rozdziale przedstawimy Ci szufladę nawigacyjną wysuwany panel, który jest
wyświetlany na ekranie po przesunięciu palcem lub kliknięciu ikony umieszczonej na pasku akcji.
Pokażemy Ci, jak można wyświetlać w niej listę odnośników umożliwiających przechodzenie
do kluczowych węzłów aplikacji. Oprócz tego przekonasz się, że przełączanie fragmentów
pozwala łatwo docierać do tych węzłów i je szybko wyświetlać.
398
399
400
401
402
403
404
405
406
407
412
413
414
Stosowanie ActionBarDrawerToggle 417
Modyfikowanie elementów paska akcji w trakcie działania aplikacji 418
Zaktualizowany kod aktywności MainActivity 419
Włączenie możliwości otwierania i zamykania szuflady nawigacyjnej 420
Synchronizacja stanu przycisku ActionBarDrawerToggle 421
Zaktualizowany kod aktywności MainActivity 422
Obsługa zmian konfiguracji 425
Reagowanie na zmiany stosu cofnięć 426
Dodawanie znaczników do fragmentów 427
Kompletny kod aktywności MainActivity 429
Zawartość ekranu jest
wyświetlana w układzie Jazda testowa aplikacji 435
FrameLayout. Chcemy, by Twój przybornik do Androida 436
wypełniała ona cały dostępny
obszar ekranu. W tym przykładzie
jest ona częściowo przesłonięta
przez szufladę nawigacyjną.
xv
Spis treści
11
Odpal bazę danych
Jeśli rejestrujesz najlepsze wyniki lub przesyłane komunikaty,
to Twoja aplikacja będzie musiała przechowywać dane.
A w Androidzie dane są zazwyczaj bezpiecznie i trwale przechowywane w bazach danych
SQLite. W tym rozdziale pokażemy Ci, jak utworzyć bazę danych, dodawać do niej tabele,
wypełnić ją wstępnie danymi, a wszystko to za pomocą pomocnika SQLite. Dowiesz się też,
w jaki sposób można bezproblemowo przeprowadzać aktualizacje struktury bazy danych
i jak w razie konieczności wycofania zmian wrócić do jej wcześniejszych wersji.
xvi
Spis treści
12
Nawiązywanie połączenia z bazą danych
Jak łączysz swoje aplikacje z bazami danych SQLite?
Dotychczas dowiedziałeś się, jak tworzyć bazy danych, używając pomocnika SQLite. Kolejnym
krokiem będzie uzyskanie dostępu do tych baz danych w aktywnościach. Z tego rozdziału dowiesz
się, jak tworzyć kursory, by pobierać dane z bazy danych, jak poruszać się po kursorach oraz
jak pobierać z nich dane. Oprócz tego dowiesz się, jak używać adapterów kursorów, by łączyć
kursory z widokami list. A na koniec przekonasz się, że pisanie wydajnego kodu wielowątkowego
korzystającego z klasy AsyncTask może zagwarantować wysoką wydajność działania aplikacji.
474
onPreExecute 478
479
481
488
doInBackground 489
490
Dodanie ulubionych napojów do aktywności DrinkActivity 508
Kod aktywności DrinkActivity 513
Nowy kod aktywności głównego poziomu 518
onProgressUpdate Zmodyfikowany kod aktywności TopLevelActivity 524
Metoda onPreExecute() 531
Metoda doInBackground() 532
Metoda onProgressUpdate() 533
onPostExecute Metoda onPostExecute() 534
Klasa AsyncTask 535
Kod aktywności DrinkActivity 537
Twój przybornik do Androida 540
Kursor
Widok ListView Adapter CursorAdapter
xvii
Spis treści
Usługi
13
Do usług
Są operacje, które będziemy chcieli realizować niezależnie od tego,
która aplikacja jest widoczna na ekranie.
Na przykład kiedy rozpoczniesz odtwarzanie pliku w aplikacji muzycznej, to najprawdopodobniej
będziesz oczekiwać, że będzie ona kontynuowała odtwarzanie nawet wówczas, gdy przejdziesz
do innej aplikacji. Z tego rozdziału dowiesz się, jak używać usług, by radzić sobie w sytuacjach
takich jak ta. Przy okazji nauczysz się korzystać z kilku wbudowanych usług systemu Android.
Dowiesz się, jak za pomocą usługi powiadomień sprawić, by użytkownik zawsze był dobrze
poinformowany, i jak poznać aktualne położenie geograficzne, korzystając z usługi lokalizacji.
543
545
546
547
554
557
559
561
562
570
573
575
577
578
580
582
586
Tworzenie obiektu ServiceConnection 587
Powiązanie z usługą należy utworzyć podczas uruchamiania aktywności 588
<Layout>
Wyświetlanie przebytego dystansu 589
Kompletny kod aktywności MainActivity 590
</Layout>
Twój przybornik do Androida 595
activity_main.xml
getDistance() Systemowa
usługa
lokalizacyjna
1.11
MainActivity.java OdometerService.java
xviii
Liczba przebytych kilometrów.
Spis treści
Material Design
14
W materialistycznym świecie
W API poziomu 21 Google wprowadził Material Design.
Z tego rozdziału dowiesz się, czym jest Material Design i jak sprawić, by nasze aplikacje do
niego pasowały. Zaczniemy od przedstawienia widoków kart (ang. card views), których możemy
wielokrotnie używać w całej aplikacji, zapewniając jej spójny wygląd i sposób obsługi. Następnie
przedstawimy widok RecyclerView elastycznego przyjaciela widoków list. Przy okazji dowiesz
się także, jak tworzyć własne adaptery i jak całkowicie zmienić wygląd widoku RecyclerView,
używając zaledwie dwóch wierszy kodu.
598
600
603
604
606
607
608
609
610
611
612
613
Kod klasy PizzaMaterialFragment 614
Do rozmieszczania zawartości RecyclerView używa menedżera układu 615
Określanie menedżera układu 616
Kompletny kod klasy PizzaMaterialFragment 617
Zastosowanie fragmentu PizzaMaterialFragment
w aktywności MainActivity 618
Co się stanie po uruchomieniu kodu? 619
Utworzenie aktywności PizzaDetailActivity 627
Co musi robić aktywność PizzaDetailActivity? 628
Aktualizacja pliku AndroidManifest.xml 628
Kod pliku PizzaDetailActivity.java 629
Obsługa kliknięć w widoku RecyclerView 631
Dodanie interfejsu do adaptera 634
Implementacja interfejsu Listener
we fragmencie PizzaMaterialFragment 636
Umieszczenie treści na samym początku 639
Kompletny kod pliku układu fragment_top.xml 644
Kompletny kod klasy TopFragment 645
Twój przybornik do Androida 647
xix
Spis treści
ART
A
Środowisko uruchomieniowe Androida
Aplikacje na Androida muszą działać na urządzeniach wyposażonych
w słabe procesory i bardzo małą pamięć.
Aplikacje pisane w Javie mogą potrzebować sporo pamięci, a ponieważ działają wewnątrz własnej
wirtualnej maszyny Javy (JVM), ich uruchomienie na komputerze o niewielkiej mocy obliczeniowej
może trwać dosyć długo. Android rozwiązuje ten problem, nie używając JVM do uruchamiania
aplikacji. Zamiast JVM używa całkowicie odmiennej wirtualnej maszyny, nazywanej środowiskiem
uruchomieniowym Androida (ang. Android Runtime, w skrócie ART). W tym dodatku zobaczysz,
jak to się dzieje, że ART umożliwia dobre działanie aplikacji napisanych w Javie nawet na małych
komputerach o niewielkiej mocy obliczeniowej.
ADB
B
Android Debug Bridge
W tej książce skoncentrowaliśmy się na zaspokajaniu wszystkich potrzeb
związanych z pisaniem aplikacji na Androida z wykorzystaniem IDE.
Zdarzają się jednak sytuacje, w których zastosowanie narzędzi obsługiwanych z poziomu wiersza
poleceń jest po prostu przydatne. Mamy tu na myśli na przykład przypadki, gdy Android Studio nie
jest w stanie zauważyć urządzenia z Androidem, choć my wiemy, że ono istnieje. W tym rozdziale
przedstawimy Android Debug Bridge (w skrócie ADB) obsługiwany z poziomu wiersza
poleceń program narzędziowy, którego można używać do komunikacji z emulatorem
lub z rzeczywistymi urządzeniami zaopatrzonymi w Androida.
xx Urządzenie
Spis treści
Emulator
C
Emulator Androida
Czy miałeś kiedyś wrażenie, że cały swój czas spędzasz, czekając na emulator?
Nie ma najmniejszych wątpliwości co do tego, że emulator Androida jest bardzo przydatny. Dzięki
niemu możemy się przekonać, jak nasza aplikacja będzie działała na urządzeniach innych niż te,
do których mamy fizyczny dostęp. Niekiedy jednak można odnieść wrażenie, że emulator działa…
wolno. W tym dodatku wyjaśnimy, dlaczego tak się dzieje. Ale to nie wszystko, damy Ci bowiem
także kilka wskazówek, jak przyspieszyć jego działanie.
Pozostałości
D
Dziesięć najważniejszych zagadnień (których nie opisaliśmy)
Nawet po tym wszystkim, co opisaliśmy w tej książce, wciąż pozostaje
wiele innych interesujących zagadnień.
Jest jeszcze kilka dodatkowych spraw, o których musisz się dowiedzieć. Czulibyśmy się nie
w porządku, gdybyśmy je pominęli, a jednocześnie chcieliśmy oddać w Twoje ręce książkę,
którą dasz radę podnieść bez intensywnego treningu na siłowni. Dlatego zanim odłożysz
tę książkę, przeczytaj kilka dodatkowych zagadnień opisanych w tym dodatku.
S
674
xxi
Jak korzystać z tej książki
Wprowadzenie
Nie mogę uwierzyć,
że zamieścili to w książce
o programowaniu aplikacji
na Androida!
odpowiemy na palące
W tej części książki orzy umieścili
pytanie: „Dl acz ego aut
e niezwykłe rzeczy?”.
w książce te wszystki
xxiv Wprowadzenie
Wprowadzenie
Jest tylko jeden, drobny problem. Twój mózg stara się Ci pomóc.
Próbuje zapewnić, aby te ewidentncie nieważne informacje nie zajęły
cennych zasobów w Twojej głowie. Zasobów, które powinny zostać
wykorzystane na zapamiętanie naprawdę ważnych rzeczy. Takich jak
tygrysy. Takich jak zagrożenie, jakie niesie ze sobą pożar. Takich jak to,
że nie powinieneś był publikować na Facebooku tych zdjęć z imprezy.
Co gorsze, nie ma żadnego sposobu, aby powiedzieć mózgowi:
„Hej, mózgu mój, dziękuję ci bardzo, ale niezależnie od tego, jak nudna
jest tak książka i jak nieznaczne są emocje, których aktualnie doznaję,
to jednak naprawdę chcę zapamiętać wszystkie te informacje”.
xxvi Wprowadzenie
Wprowadzenie
Oto co MY zrobiliśmy
Użyliśmy rysunków, ponieważ Twój mózg zwraca większą uwagę na obrazy niż na tekst. Jeśli chodzi
o mózg, to faktycznie jeden obraz jest wart tysiąc słów. W sytuacjach, gdy mieliśmy do czynienia zarówno
z tekstem, jak i z rysunekiem, umieściliśmy tekst na rysunku, mózg bowiem działa bardziej efektywnie,
kiedy tekst jest na czymś, co opisuje, niż kiedy jest umieszczony w innym miejscu i stanowi część
większego fragmentu tekstu.
Użyliśmy pomysłów i rysunków w nieoczekiwany sposób, ponieważ Twój mózg pragnie nowości, a poza
tym staraliśmy się zawrzeć w nich chociaż trochę emocji, gdyż mózg jest skonstruowany tak, że zwraca
uwagę na biochemię związaną z emocjami. Prawdopodobieństwo zapamiętania informacji jest większe,
jeśli sprawia ona, że coś czujemy, nawet jeśli to uczucie nie jest niczym więcej jak lekkim rozbawieniem,
zaskoczeniem lub zainteresowaniem.
Użyliśmy bezpośrednich zwrotów i przekazaliśmy treści w stylu konwersacyjnym, gdyż mózg zwraca
większą uwagę, jeśli sądzi, że prowadzisz rozmowę, niż wtedy, kiedy jesteś jedynie biernym słuchaczem
prezentacji. Mózg działa w ten sposób nawet wówczas, gdy czytasz zapis rozmowy.
Zamieściliśmy w książce wiele ćwiczeń, ponieważ mózg uczy się i pamięta więcej, gdy coś robi, niż gdy
o czymś czyta. Poza tym podane ćwiczenia stanowią wyzwania, choć nie są przesadnie trudne, bo właśnie
takie preferuje większość osób.
Zastosowaliśmy wiele stylów nauczania, gdyż Ty możesz preferować instrukcje opisujące krok po kroku
sposób postępowania, ktoś inny może woleć analizowanie zagadnienia opisanego ogólnie, a jeszcze inna
osoba może chcieć przejrzeć przykładowy fragment kodu. Niezależnie jednak od ulubionego sposobu
nauki każdy skorzysta na tym, że te same informacje będą przedstawiane kilkukrotnie na różne sposoby.
Podaliśmy informacje przeznaczone dla obu półkul Twojego mózgu, gdyż im bardziej mózg będzie
zaangażowany, tym większe będzie prawdopodobieństwo nauczenia się i zapamiętania podawanych
informacji i tym dłużej możesz koncentrować się na nauce. Ponieważ angażowanie jednej półkuli mózgu
często oznacza, że druga może odpocząć, zatem będziesz mógł uczyć się bardziej produktywnie przez
dłuższy okres czasu.
Dodatkowo zamieściliśmy opowiadania i ćwiczenia prezentujące więcej niż jeden punkt widzenia,
ponieważ mózg uczy się łatwiej, gdy jest zmuszony do przetwarzania i podawania własnej opinii.
Postawiliśmy przed Tobą wyzwania, zarówno poprzez podawanie ćwiczeń, jak i stawianie pytań, na które
nie zawsze można odpowiedzieć w prosty sposób, a to dlatego, że mózg uczy się i pamięta, gdy musi
popracować nad czymś. Pomyśl sam: nie można zdobyć dobrej kondycji, obserwując ćwiczenia w telewizji.
Ale dołożyliśmy wszelkich starań, aby zapewnić, że gdy pracujesz, to robisz dokładnie to, co trzeba.
Aby ani jeden dendryt nie musiał przetwarzać trudnego przykładu ani analizować tekstu zbyt lapidarnego
lub napisanego niezrozumiałym żargonem.
1 Zwolnij — im więcej rozumiesz, tym mniej musisz 6 Pij wodę, dużo wody
zapamiętać Mózg pracuje najlepiej, gdy dostarcza mu się dużo płynów.
Nie ograniczaj się jedynie do czytania. Przerwij na chwilę Odwodnienie (do którego może dojść nawet, zanim
lekturę i pomyśl. Kiedy znajdziesz w tekście pytanie, nie poczujesz pragnienie) obniża zdolność percepcji.
zaglądaj od razu na stronę z odpowiedzią. Wyobraź sobie,
że ktoś faktycznie zadaje Ci pytanie. Im bardziej zmusisz 7 Posłuchaj swojego mózgu
swój mózg do myślenia, tym większa będzie szansa, że się Uważaj, kiedy Twój mózg staje się przeciążony. Jeśli
czegoś nauczysz i coś zapamiętasz. spostrzeżesz, że zaczynasz czytać pobieżnie i zapominać,
o czym przeczytałeś przed chwilą, to najwyższy czas, żeby
2 Rób ćwiczenia, notuj sobie zrobić przerwę. Po przekroczeniu pewnego punktu
Zamieściliśmy je w książce, ale gdybyśmy zrobili je nie będziesz się uczył szybciej, „wciskając” do głowy
za Ciebie, to niczym nie różniłoby się to od sytuacji, więcej informacji, co gorsze, może to zaszkodzić całemu
w której ktoś za Ciebie wykonywałby ćwiczenia fizyczne. procesowi nauki.
I nie ograniczaj się jedynie do czytania ćwiczeń. Używaj
ołówka. Można znaleźć wiele dowodów na to, że fizyczna 8 Poczuj coś!
aktywność podczas nauki może poprawić jej wyniki. Twój mózg musi wiedzieć, że to, czego się uczysz, jest
ważne. Z zaangażowaniem śledź zamieszczane w tekście
3 Czytaj fragmenty oznaczone jako „Nie istnieją opowiadania. Nadawaj własne tytuły zdjęciom. Zalewanie
głupie pytania” się łzami ze śmiechu po przeczytaniu głupiego dowcipu
Chodzi tu o wszystkie fragmenty umieszczone z boku i tak jest lepsze od braku jakiejkolwiek reakcji.
tekstu. Nie są to fragmenty opcjonalne — stanowią one
część podstawowego tekstu książki! Nie pomijaj ich. 9 Pisz jak najwięcej kodu
Istnieje tylko jeden sposób, by nauczyć się programowania
4 Niech lektura tej książki będzie ostatnią rzeczą, jaką aplikacji na Androida: pisanie kodu, a im będzie go
robisz przed pójściem spać — a przynajmniej ostatnią więcej, tym lepiej. I właśnie to będziesz robić podczas
rzeczą stanowiącą wyzwanie intelektualne lektury tej książki. Pisanie programów jest umiejętnością,
Pewne elementy procesu uczenia się (a w szczególności a jedynym sposobem jej nabycia jest ciągła praktyka.
przenoszenie informacji do pamięci długoterminowej) Mamy zamiar dać Ci wiele okazji do tego: w każdym
następują po odłożeniu książki. Mózg potrzebuje trochę rozdziale zamieściliśmy ćwiczenia stawiające przed Tobą
czasu dla siebie i musi dodatkowo przetworzyć dostarczone problemy, które możesz rozwiązać. Nie pomijaj ich —
informacje. Jeśli podczas tego koniecznego na wykonanie podczas rozwiązywania ćwiczeń możesz się bardzo wiele
dodatkowego przetwarzania czasu zmusisz go do innej nauczyć. Zamieściliśmy także rozwiązania wszystkich
działalności, to część z przyswojonych informacji może ćwiczeń — śmiało zerknij na rozwiązanie, jeśli utkniesz!
zostać utracona. (Łatwo jest utknąć na jakiejś drobnostce). Staraj się
jednak rozwiązać problem, zanim zajrzysz do rozwiązania.
5 Mów o zdobywanych informacjach — głośno I koniecznie, zanim przejdziesz do dalszej części książki,
Mówienie aktywuje odmienne obszary mózgu. Jeśli postaraj się uruchomić programy, nad którymi pracujesz.
próbujesz coś zrozumieć lub zwiększyć szansę na
zapamiętanie informacji na dłużej, powtarzaj to na głos.
Jeszcze lepiej — staraj się to na głos komuś wytłumaczyć.
W ten sposób nauczysz się szybciej, a oprócz tego będziesz
mógł odkryć kwestie, o których nie wiedziałeś podczas
czytania tekstu książki. jesteś tutaj xxix
Jak korzystać z tej książki
Przeczytaj to
Ta książka jest doznaniem poznawczym, a nie podręcznikiem. Celowo usunęliśmy z niej
wszystko, co mogłoby Ci przeszkadzać w uczeniu się i poznawaniu materiału zamieszczonego
w danym miejscu książki. W przypadku pierwszej lektury tej książki należy zacząć od samego
początku, gdyż jej dalsze fragmenty bazują na wiedzy, którą musisz zdobyć wcześniej.
Możesz nam wierzyć lub nie, ale nawet jeśli nigdy wcześniej nie zdarzyło Ci się napisać
żadnej aplikacji na Androida, to możesz wskoczyć na głęboką wodę i zacząć pisać własne
aplikacje. Przy okazji poznasz trochę Android Studio — oficjalne IDE do pisania aplikacji
na Androida.
Podczas lektury niniejszej książki napiszesz kilka różnych aplikacji. Niektóre z nich są
bardzo małe, co pozwoli Ci skoncentrować się na konkretnym aspekcie programowania na
Androida. Inne z kolei są większe, dzięki czemu przekonasz się, jak ich różne komponenty
pasują do siebie i ze sobą współpracują. W książce nie dokończymy wszystkich fragmentów
każdej z tych aplikacji, możesz to jednak zrobić samodzielnie. To wszystko zalicza się do
doznania poznawczego. Kody źródłowe aplikacji przedstawionych w tej książce można
pobrać z serwera FTP wydawnictwa Helion: ftp://ftp.helion.pl/przyklady/andrrg.zip
Ćwiczenia i aktywności nie są jedynie dodatkami, są one elementem treści tej książki.
Niektóre z nich mają wspomóc Twoją pamięć, inne — ułatwić Ci zrozumienie, a jeszcze inne
— pomóc w wykorzystaniu nabytej wiedzy i umiejętności. Nie pomijaj tych ćwiczeń!
xxx Wprowadzenie
Wprowadzenie
Jedną z cech, która wyróżnia książki z serii Rusz głową!, jest to, że nam naprawdę zależy, żebyś
wszystko zrozumiał. Chcemy także, byś kończąc lekturę tej książki, pamiętał wszystko, co w niej
przeczytałeś. W przypadku większości książek informacyjnych i encyklopedycznych przyswojenie
i zapamiętanie informacji nie jest celem, ale w tej książce chodzi o naukę, dlatego znajdziesz
w niej wiele pojęć, które pojawiają się kilka razy.
Do niektórych ćwiczeń w ogóle nie można podać jednej dobrej odpowiedzi, w innych przypadkach
to doświadczenie, które zdobywasz, rozwiązując te ćwiczenia, ma dać Ci możliwość określenia, czy
i kiedy podana odpowiedź będzie poprawna. W niektórych ćwiczeniach z tej serii znajdziesz także
podpowiedzi, które ułatwią Ci znalezienie rozwiązania.
Edward
Edward Yue Shung Wong pasjonował się programowaniem od czasu, gdy w 2006
roku napisał pierwszy wiersz kodu w języku Haskell. Obecnie pracuje w sercu
londyńskiego City nad sterowanym zdarzeniami przetwarzaniem danych handlowych.
Edward z radością dzieli się swoją pasją programistyczną z londyńską społecznością
programistów Javy w ramach London Java Community i Software Craftsmanship
Community. Jeśli akurat przebywa z dala od klawiatury, to można go znaleźć na
boisku do piłki nożnej, gdzie czuje się w swoim żywiole, lub obejrzeć na YouTubie
(@arkangelofkaos), jak gra w gry komputerowe.
xxxii Wprowadzenie
Wprowadzenie
Podziękowania
Dla naszej redaktorki
Pisanie książki z serii Head First jest jak jazda kolejką górską, a prace nad
tą akurat książką nie były pod tym względem wyjątkiem. Ta książka w ogóle
nie ujrzałaby światła dziennego, gdyby nie uprzejmość i wsparcie naszych
przyjaciół i rodziny. Specjalne podziękowania przesyłamy: Andy’emu P,
Steve’owi, Jacqui, Angeli, Paulowi B, Mamie, Tacie, Carlowi, Robowi
i Lorraine.
2 Rozdział 1.
Zaczynamy
jesteś tutaj 3
Czynności
4 Zmodyfikować aplikację.
I na koniec wprowadzimy kilka zmian w aplikacji
stworzonej w kroku 2.
Nie istnieją
głupie pytania
P: Czy wszystkie aplikacja na P: W jakim stopniu muszę znać P: Czy muszę znać biblioteki Swing
Androida są pisane w Javie? Javę, by pisać aplikacje na Androida? i AWT?
O: Aplikacje na Androida można pisać O: Powinieneś dobrze znać Javę SE. Jeśli O: Android nie używa ani biblioteki
także w innych językach, lecz prawda nie czujesz się pewnie, to radzimy sięgnąć Swing, ani biblioteki AWT, dlatego nie
jest taka, że przeważająca większość najpierw po książkę Java. Rusz głową! przejmuj się, jeśli nie masz doświadczeń
programistów używa Javy. napisaną przez Kathy Sierrę i Berta Batesa. w pisaniu w Javie aplikacji o graficznym
interfejsie użytkownika.
4 Rozdział 1.
Zaczynamy
Jesteś tutaj.
¨
Środowisko programistyczne Przygotowanie środowiska
¨ Stworzenie aplikacji
¨ Uruchomienie aplikacji
Java jest najpopularniejszym językiem używanym do pisania aplikacji na Androida. ¨ Modyfikacja aplikacji
Jednak urządzenia działające pod tym systemem nie wykonują plików .class ani .jar.
Zamiast tego, aby poprawić szybkość i wydajność działania, urządzenia te korzystają
z własnego, zoptymalizowanego formatu zapisu skompilowanego kodu. A to oznacza,
że do pisania aplikacji na Androida nie można używać zwyczajnych środowisk do
pisania kodu w Javie — konieczne są bowiem specjalne narzędzia do konwersji
skompilowanego kodu do formatu używanego przez platformę Android, do
zainstalowania ich na urządzeniu oraz debugowania kodu po uruchomieniu aplikacji.
Android SDK
Android SDK (Software Development Kit) to zestaw narzędzi i bibliotek niezbędnych
do pisania aplikacji na Androida. W jego skład wchodzą:
Platforma SDK
Istnieje jedna taka platforma Dokumentacja
dla każdej wersji Androida. By móc korzystać
z najnowszej dokumentacji
bez połączenia z internetem.
Narzędzia SDK
Narzędzia do debugowania Wsparcie dla Androida
i testowania oraz inne przydatne Dodatkowe API, które nie
programy.
SDK wchodzą w skład standardowej
id
ro
platformy.
d
An
Aplikacje przykładowe Płatności Google Play
Jeśli do zrozumienia niektórych API Pozwalają na zintegrowanie
potrzebujesz kodu praktycznych z aplikacją mechanizmów
To tylko kilka do rozliczeń finansowych.
przykładów, to mogą Ci w tym najważniejszych
pomóc aplikacje przykładowe. elementów.
¨
Zainstaluj Javę Przygotowanie środowiska
¨ Stworzenie aplikacji
¨ Uruchomienie aplikacji
Android Studio jest środowiskiem programistycznym służącym do pisania ¨ Modyfikacja aplikacji
aplikacji w języku Java, musisz się zatem upewnić, czy na swoim komputerze masz
zainstalowaną odpowiednią wersję Javy.
http://www.oracle.com/technetwork/java/javase/downloads/index.html
https://developer.android.com/sdk/installing/index.html?pkg=studio
W tej książce nie zamieszczamy instrukcji
Na tej samej stronie zostały także opublikowane informacje dotyczące instalacji instalacji Android Studio, gdyż mogłoby
Android Studio. Postępuj zgodnie z nimi, aby zainstalować IDE na swoim się okazać, że bardzo szybko staną się one
nieaktualne. Postępuj zgodnie z opisem
komputerze. Kiedy to już zrobisz, uruchom Android Studio i postępuj zgodnie instalacji zamieszczonym na stronie,
z instrukcjami, by zainstalować najnowsze narzędzia SDK i biblioteki. a na pewno bez problemu się uda.
Kiedy wszystko będzie już gotowe, zobaczysz ekran powitalny Android Studio.
Teraz będziesz już gotów, by rozpocząć tworzenie swojej pierwszej aplikacji
na Androida.
To jest ekran
powitalny
Android Studio.
Prezentuje on
zestaw opcji
odpowiadający
czynnościom,
jakie można
wykonać.
6 Rozdział 1.
Zaczynamy
Nie istnieją
głupie pytania
P: Napisaliście, że do pisania aplikacji P: A czy można pisać aplikacje na P: Większość aplikacji jest
na Androida będziemy używać Android Android bez używania żadnego IDE? budowanych przy użyciu gradle?
Studio. Czy to konieczne? Napisaliście chyba wcześniej,
O: Tak, można, lecz wymaga to znacznie że bardzo wielu programistów
O: Precyzyjnie rzecz ujmując, Android więcej pracy. Większość aplikacji na używa Android Studio?
Studio nie jest niezbędne do pisania aplikacji Androida jest obecnie przygotowywanych
na Androida. Będziesz potrzebować przy użyciu programu do budowania O: Android Studio udostępnia graficzny
dowolnego narzędzia umożliwiającego o nazwie gradle. Projekty gradle można interfejs do obsługi gradle, jak również wiele
pisanie i kompilację kodu Javy oraz paru tworzyć i budować, używając dowolnego innych narzędzi służących do tworzenia
innych specjalistycznych narzędzi służących edytora tekstów i wiersza poleceń. układów, odczytu dzienników oraz
do konwersji skompilowanego kodu do debugowania.
postaci, w której urządzenia z Androidem P: Narzędzie do budowania?
będą go w stanie wykonywać. Czy gradle to coś takiego jak ANT?
Ta aplikacja jest
To jest nazwa bardzo prosta, ale jak
aplikacji. na Twoją pierwszą
aplikację na Androida
w zupełności
wystarczy.
To jest przykładowy
napis, który Android
Studio samo dodało
do aplikacji.
jesteś tutaj 7
Utworzenie projektu Ten etap prac już zakończyłeś,
więc oznaczyliśmy go ptaszkiem.
8 Rozdział 1.
Zaczynamy
Kreator utworzył
nazwę pakietu na
podstawie nazwy
aplikacji i nazwy
firmowej domeny.
jesteś tutaj 9
Poziom API
Teraz musisz określić, którego poziomu API platformy Android będzie używać
tworzona aplikacja. Numer poziomu API rośnie wraz z każdą nową wersją
Androida. Jeśli nie chcesz, by aplikacja działała wyłącznie na najnowszych
urządzeniach, to wybierz jeden z wcześniejszych API.
Na następnej stronie znajdziesz .
W tym przykładzie wybierzemy API poziomu 15, co oznacza, że aplikacja będzie więcej informacji o poziomach API
w stanie działać na przeważającej większości urządzeń. Co więcej, utworzymy
wyłącznie aplikację w wersji przeznaczonej na telefony i tablety, dlatego nie
zaznaczaj żadnych innych pól wyboru widocznych w tym oknie kreatora.
Minimalna wymagana
wersja SDK to najniższa
wersja obsługiwana
przez aplikację. Twoja
aplikacja będzie działać
na urządzeniach z API
wybranego lub wyższego
poziomu, natomiast na
urządzeniach z API
niższego poziomu nie uda
się uruchomić aplikacji.
10 Rozdział 1.
Zaczynamy
Wszystkie wersje Androida mają swój numer i nazwę kodową. Numer wersji dokładnie określa numer
platformy Android (na przykład 5.0), natomiast nazwa kodowa to nieco bardziej „przyjazna” nazwa, która może
obejmować kilka kolejnych wersji Androida (tak jest na przykład w przypadku wersji Jelly Bean). Poziom API
odnosi się do wersji API używanych przez aplikacje. Na przykład Androidowi 5.0 odpowiada poziom API 21.
Pisząc aplikacje na Andorida, naprawdę trzeba poważnie zastanowić się nad tym, z jakimi wersjami systemu
nasza aplikacja ma być zgodna. Jeśli określimy, że będzie ona zgodna tylko z najnowszą wersją SDK, to może się
okazać, że nie będzie w stanie działać na wielu urządzeniach. Udziały procentowe urządzeń z poszczególnymi
wersjami Androida można znaleźć na stronie https://developer.android.com/about/dashboards/index.html.
1
Wszystkie te nazwy odpowiadają nazwom słodyczy: ice cream sandwich to lodowa kanapka, jelly bean to żelki w kształcie
fasolek, a lollipop to lizak — przyp. tłum.
jesteś tutaj 11
15 000 metrów
Aktywność (ang. activity) jest pojedynczą, precyzyjnie zdefiniowaną czynnością, Układy definiują,
którą użytkownik może wykonywać. Można zatem utworzyć aktywność pozwalającą
na redagowanie e-maila, robienie zdjęcia czy też odnajdywanie kontaktu. Aktywności
jak będzie
są zazwyczaj skojarzone z jednym ekranem, a ich kod jest pisany w Javie. wyglądał interfejs
Układ (ang. layout) opisuje wygląd ekranu. Układy są tworzone w formie zwyczajnych
plików XML i informują system o tym, w jaki sposób na ekranie są rozmieszane
użytkownika.
poszczególne elementy interfejsu użytkownika.
Przyjrzyjmy się nieco dokładniej, jak aktywności i układy współpracują ze sobą, Aktywności
by utworzyć interfejs użytkownika aplikacji:
definiują akcje.
1 Urządzenie uruchamia
1
aplikację i tworzy obiekt
aktywności.
<Układ>
2
2 Obiekt aktywności
określa układ. </Układ>
<Układ>
Aktywność
3 Aktywność nakazuje
Urządzenie </Układ> Układ
urządzeniu wyświetlenie 3
układu na ekranie.
4 Użytkownik prowadzi
interakcje z układem
wyświetlonym na ekranie.
5 Aktywność odpowiada na
te interakcje, wykonując 4
odpowiedni kod aplikacji. 5
6 Aktywność aktualizuje
treści w układzie…
12 Rozdział 1.
Zaczynamy
Dostępnych jest
kilkanaście innyc także
rodzajów aktywn h
oś
które można wybr ci,
tym razem upew ać, ale
czy został zaznacnij się,
szablon Blank Ac zony
tivity.
jesteś tutaj 13
Konfiguracja aktywności
14 Rozdział 1.
Zaczynamy
jesteś tutaj 15
Struktura katalogów
Eksplorator plików prezentuje wszystkie aktualnie otwarte projekty. Aby rozwinąć lub
zwinąć katalog, wystarczy kliknąć strzałkę wyświetloną z lewej strony ikony katalogu.
Tutaj wybierz
Struktura katalogów projektu
opcję Project,
aby wyświetlić zawiera pliki różnych typów
pliki i katalogi
wchodzące Jeśli przejrzysz zawartość projektu, to zauważysz,
w skład projektu. że kreator utworzył wiele katalogów, a w nich pliki
wielu różnych typów:
To jest
nazwa Pliki źródłowe Java i XML
projektu. To wygenerowane przez kreator pliki aktywności
i układów.
Pliki konfiguracyjne
Pliki konfiguracyjne informują Androida
o zawartości aplikacji oraz o to tym, jak powinna
ona działać.
jesteś tutaj 17
Edytory
Edytor kodu
Większość plików jest
wyświetlana w edytorze kodu.
Edytor kodu przypomina
zwyczajny edytor tekstów,
lecz dysponuje kilkoma
dodatkowymi możliwościami,
takimi jak kolorowanie
kodu i sprawdzanie jego
poprawności syntaktycznej.
Edytor projektu
W przypadku edycji
układu mamy do
dyspozycji dodatkową
opcję. Zamiast
edytować bezpośrednio
kod XML możemy
skorzystać z edytora
projektu. Pozwala on To różne sposoby
przeciągać do układu prezentacji
zawartości tego
graficzne komponenty samego pliku:
interfejsu użytkownika pierwszy widok
Te przyciski przedstawia jego
i rozmieszczać je pozwalają kod, a drugi
w wybrany sposób. Edytor wybrać edytor, pokazuje wygląd
którego chcemy układu.
kodu i edytor projektu używać.
zapewniają dwa różne
sposoby prezentacji tego
samego pliku, zatem
w każdej chwili można się
pomiędzy nimi przełączać.
18 Rozdział 1.
?
Zaczynamy
DO CZEGO SŁUŻĘ?
activity_main.xml
<RelativeLayout xmlns:android=”http://schemas.android.com/apk/res/android”
xmlns:tools=”http://schemas.android.com/tools”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:paddingLeft=”16dp”
android:paddingRight=”16dp”
android:paddingTop=”16dp”
android:paddingBottom=”16dp”
tools:context=”.MainActivity”>
<TextView
android:text=”@string/hello_world”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content” />
</RelativeLayout>
jesteś tutaj 19
?
Rozwiązanie
DO CZEGO SŁUŻĘ?
ROZWIĄZANIE
Poniżej przedstawiamy kod źródłowy pliku układu wygenerowanego przez
Android Studio. Wiemy, że jeszcze nigdy nie widziałeś takiego pliku na oczy,
ale przekonajmy się, czy potrafisz dopasować opisy umieszczone u dołu strony
z odpowiednimi wierszami kodu. Aby Ci ułatwić, podaliśmy jedną odpowiedź.
activity_main.xml
<RelativeLayout xmlns:android=”http://schemas.android.com/apk/res/android”
xmlns:tools=”http://schemas.android.com/tools”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:paddingLeft=”16dp”
android:paddingRight=”16dp”
android:paddingTop=”16dp”
android:paddingBottom=”16dp”
tools:context=”.MainActivity”>
<TextView
android:text=”@string/hello_world”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content” />
</RelativeLayout>
20 Rozdział 1.
?
Zaczynamy
DO CZEGO SŁUŻĘ?
MainActivity.java
package com.hfad.mojapierwszaapka;
import android.os.Bundle;
import android.app.Activity;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
jesteś tutaj 21
?
Kolejne rozwiązanie
DO CZEGO SŁUŻĘ?
ROZWIĄZANIE
A teraz przekonajmy się, czy będziesz w stanie podobnie dopasować opisy do
kodu aktywności. To jest kod przykładowy, a nie kod wygenerowany przez
Android Studio podczas tworzenia projektu. A zatem dopasuj opisy do
odpowiednich wierszy kodu.
MainActivity.java
package com.hfad.mojapierwszaapka;
import android.os.Bundle;
import android.app.Activity;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
22 Rozdział 1.
Zaczynamy
Dotychczas przekonałeś się jedynie, jak wygenerowana aplikacja będzie ¨ Uruchomienie aplikacji
¨ Modyfikacja aplikacji
wyglądała w Android Studio, i dowiedziałeś nieco, z czego się ona składa.
Ale zapewne tym, co naprawdę chciałbyś zobaczyć, jest ta aplikacja
w działaniu, prawda?
jesteś tutaj 23
Tworzenie AVD
My chcielibyśmy się
przekonać, jak nasza
nowa aplikacja wygląda
na telefonie Nexus 4.
A zatem najpierw na
liście Category zaznacz Kiedy wybierzesz urządzenie,
tutaj zostaną wyświetlone
opcję Phone, a potem szczegółowe informacje o nim.
z listy obok wybierz
opcję Nexus 4. Następnie
kliknij przycisk Next.
24 Rozdział 1.
Zaczynamy
Kolejną czynnością, jaką musisz wykonać, jest wybór obrazu systemu. Obraz systemu
udostępnia zainstalowaną wersję systemu operacyjnego Android. W ten sposób możesz
określić, która wersja systemu ma działać na tworzonym urządzeniu wirtualnym i jaki
ma być typ jego procesora (ARM lub x86).
Musisz wybrać obraz systemu odpowiadający poziomowi API, który będzie zgodny
z tworzoną aplikacją. Na przykład jeśli tworząc aplikację, określiłeś, że minimalnym
poziomem API jest poziom 15, to tworząc AVD, wybierz obraz systemu obsługujący
co najmniej API poziomu 15. W tym przykładzie wybierzemy obraz systemu dla API
poziomu 21. A zatem wybierz opcję Lollipop 21 armeabi-v7a z wartością Android 5.0.1
lub Default w kolumnie Target. Kiedy to zrobisz, kliknij przycisk Next.
Jeśli te obrazy
systemów
nie będą
zainstalowane,
to AVD Manager
umożliwi ich
pobranie.
jesteś tutaj 25
Sprawdzenie konfiguracji
To są wszystkie
opcje, które wybrałeś
na kilku poprzednich
stronach.
Teraz AVD Manager utworzy urządzenie wirtualne, a kiedy skończy, wyświetli je na liście.
Możesz już zamknąć okno AVD Managera.
26 Rozdział 1.
Zaczynamy
Biblioteki Zasoby
Emulator
Emulator
Poniżej zamieściliśmy zawartość konsoli wyświetloną podczas wykonanej Android Studio uruchamia emulator,
a w nim AVD symulujące Nexusa 4
przez nas próby wykonania aplikacji: — to, które przed chwilą utworzyłeś.
28 Rozdział 1.
Zaczynamy
¨ Przygotowanie środowiska
¨ Stworzenie aplikacji
Jazda próbna ¨ Uruchomienie aplikacji
¨ Modyfikacja aplikacji
Sprawdźmy zatem, co widać na ekranie podczas uruchamiania aplikacji.
jesteś tutaj 29
Co się stało?
Nie istnieją
głupie pytania
P: Wspominaliście, że podczas tworzenia pliku APK kody P: To wszystko wygląda na bardzo skomplikowane. Nie
źródłowe Javy zostają skompilowane do postaci kodów można po prostu użyć zwyczajnej wirtualnej maszyny Javy?
bajtowych i dodane do pliku APK. Przypuszczam,
że chodziło Wam o to, że zostają one skompilowane O: Środowisko ART jest w stanie konwertować kody DEX na kod
do kodów bajtowych Javy, prawda? maszynowy, wykonywany bezpośrednio przez procesor urządzenia
z Androidem. To sprawia, że aplikacja działa znacznie szybciej
O: Owszem, zostają, ale to nie wszystko. Na Androidzie sytuacja i zużywa znacznie mniej baterii.
wygląda nieco inaczej.
Kluczowa różnica polega na tym, że na Androidzie kod nie jest P : Czy wirtualna maszyna Javy naprawdę powoduje
wykonywany w standardowej wirtualnej maszynie Javy Java aż tak duże narzuty?
VM. Zamiast tego działa on w środowisku uruchomieniowym
Androida (ART), a na starszych urządzeniach w poprzedniku
O: Tak. Ponieważ na Androidzie każda aplikacja działa
w odrębnym procesie. Oznacza to, że gdyby była używana
ART, środowisku uruchomieniowym noszącym nazwę Dalvik.
standardowa wirtualna maszyna Javy, to urządzenia z Androidem
Oznacza to, że piszemy kod w Javie, kompilujemy go do postaci
musiałyby mieć znacznie więcej pamięci.
plików .class, używając kompilatora Javy, a następnie pliki klasowe
zostają zapisane w pliku w formacie DEX, który zajmuje mniej
miejsca i jest bardziej wydajny od zwyczajnych kodów bajtowych.
P: Czy za każdym razem, gdy tworzę nową aplikację,
muszę także tworzyć nowe AVD?
Środowisko uruchomieniowe wykonuje te kody DEX. Więcej
informacji na ten temat można znaleźć w Dodatku A. O: Nie. Po utworzeniu AVD możesz go używać do uruchamiania
wszystkich tworzonych aplikacji. Czasami można jednak utworzyć
wiele urządzeń wirtualnych, aby testować aplikacje w różnych
środowiskach. Na przykład można utworzyć wirtualny tablet, aby
przekonać się, jak aplikacja będzie wyglądać i działać na większych
urządzeniach.
30 Rozdział 1.
Zaczynamy
Usprawnianie aplikacji ¨
¨
Przygotowanie środowiska
Stworzenie aplikacji
Nasza aktywność
określa, co
aplikacja robi
Aktywność kontroluje działanie aplikacji i w jaki sposób
prowadzi interakcję
Android Studio utworzyło aktywność o nazwie z użytkownikiem.
MainActivity.java. Ta aktywność określa, co aplikacja
robi i jak ma reagować na poczynania użytkownika. MainActivity.java
activity_main.xml
Teraz chcielibyśmy zmienić wygląd aplikacji, a konkretnie — treść
tekstu wyświetlanego na ekranie. Oznacza to, że będziemy musieli
zmodyfikować komponent odpowiadający za wygląd aplikacji.
Musimy zatem nieco dokładniej przyjrzeć się układowi.
jesteś tutaj 31
Układ
Edytor projektu
W Android Studio pliki układów można
przeglądać i edytować na dwa sposoby:
używając edytora kodu lub edytora projektu.
Edytor kodu
W przypadku wybrania edytora kodu
Edytor kodu
jest w nim wyświetlana zawartość pliku
activity_main.xml. Przyjrzymy się jej
teraz nieco dokładniej.
<RelativeLayout xmlns:android=”http://schemas.android.com/apk/res/android”
xmlns:tools=”http://schemas.android.com/tools” To jest pełna ścieżka
android:layout_width=”match_parent” dostępu do pliku
activity_main.xml.
android:layout_height=”match_parent” Mojapierwszaapka
... Tutaj Android Studio
ay nt
t>.
res
To
<TextView
android:text=”@string/hello_world” To jest element <TextView>
zagnieżdżony wewnątrz layout
android:layout_width=”wrap_content” elementu <RelativeLayout>. <xml>
</xml>
android:layout_height=”wrap_content” /> activity_main.xml
</RelativeLayout>
Powyższy kod składa się z dwóch elementów. Android Studio czasami wyświetla
Pierwszy to <RelativeLayout>. Informuje on system, wartość odwołania zamiast
że położenie elementów układu ma być określane w sposób Obejrzyj to! rzeczywistego kodu.
względny. Elementu <RelativeLayout> można użyć
na przykład do wyśrodkowania elementów w układzie, Na przykład zamiast faktycznego kodu ”@string/
wyświetlenia ich przy dolnej krawędzi ekranu bądź określenia hello_world” Adroid Studio może wyświetlić
położenia jednego elementu względem innego. tekst ”Hello world!”. Każde takie podstawienie
powinno zostać wyróżnione w edytorze kodu
Drugim elementem naszego układu jest <TextView>. podświetleniem, a jego kliknięcie lub umieszczenie
Służy on do wyświetlania tekstu na ekranie. Jak widać, na nim wskaźnika myszy powinno spowodować
element <TextView> został umieszczony wewnątrz elementu pokazanie faktycznego kodu.
<RelativeLayout>, a w naszym przypadku służy on do
wyświetlania przykładowego tekstu „Hello world!”.
<TextView
Element android:text=”@string/hello_world” Co możesz
TextView powiedzieć o tym
opisuje tekst
android:layout_width=”wrap_content” wierszu kodu?
wyświetlany android:layout_height=”wrap_content” />
w układzie.
jesteś tutaj 33
Łańcuchy znaków
To trochę skomplikowane.
Dlaczego w pliku activity_main.xml nie
można po prostu podać wyświetlanego
tekstu? Czy tak nie byłoby prościej?
Załóżmy, że napisałeś aplikację i stała się ona wielkim hitem w lokalnym, krajowym sklepie
Google Play. Ale przecież nie chcesz się ograniczać tylko do jednego kraju lub języka
— chcesz zapewne, by Twoja aplikacja była dostępna globalnie, w wielu językach.
Umieszczenie używanych łańcuchów znaków w pliku strings.xml sprawia, że radzenie sobie z takimi
zagadnieniami staje się znacznie prostsze. Zamiast zmieniać podane na stałe wartości w wielu
miejscach kodu i w plikach układu wystarczy zastąpić plik strings.xml jego wielojęzyczną wersją.
Oprócz tego stosowanie pliku strings.xml jako centralnego źródła wartości tekstowych znacząco
ułatwia wprowadzanie globalnych zmian w tekstach używanych w aplikacji. Jeśli szef zażąda
skorygowania tekstów wyświetlanych w aplikacji ze względu na zmianę nazwy firmy, wystarczy
wprowadzić poprawki w pliku strings.xml.
34 Rozdział 1.
Zaczynamy
Jak widać, w pliku znajduje się wiersz kodu wyglądający dokładnie jak to, czego szukamy.
Opisuje on zasób łańcuchowy o nazwie hello_world i wartości ”Hello world!”:
jesteś tutaj 35
Pod lupą
<string name=”nazwa_łańcucha”>wartość_łańcucha</string>
W pliku układu można odwołać się do konkretnego łańcucha znaków, używając zapisu
o postaci:
36 Rozdział 1.
Zaczynamy
To jest
zaktualizowana
wersja aplikacji
uruchomiona
w emulatorze.
Nie istnieją
głupie pytania
P: Czy umieszczanie wartości łańcuchowych w plikach P: A skąd aplikacja będzie wiedziała, którego z nich użyć?
zasobów, takich jak strings.xml, jest absolutnie
konieczne? O: Umieść swój domyślny plik z polskimi zasobami łańcuchowymi
w standardowym miejscu katalogu app/src/main/res/values.
O: Nie ma takiego obowiązku, jednak Android wyświetla A plik z tekstami angielskimi w katalogu app/src/main/res/values-
komunikat ostrzegawczy, jeśli wartości tekstowe zostaną podane en. Jeśli w urządzeniu został wybrany język angielski, to Android
na stałe w kodzie. Początkowo można sądzić, że to sporo zastosuje pliki zasobów z katalogu app/src/main/res/values-en.
zachodu, lecz takie rozwiązanie znacznie ułatwia inne prace, takie Jeśli w urządzeniu został wybrany jakikolwiek inny język, system
jak lokalizowanie aplikacji. Poza tym łatwiej jest już od samego użyje plików zasobów z katalogu app/src/main/res/values.
początku używać zasobów łańcuchowych niż dodawać je później.
P: Kod układu, który wygenerowało moje Android
P: W jaki sposób umieszczenie łańcuchów znaków Studio, wygląda nieco inaczej niż ten przedstawiony
w osobnym pliku może pomóc w lokalizacji aplikacji? w książce. Czy mam się tym martwić?
O: Załóżmy, że chcesz, by Twoja aplikacja domyślnie wyświetlała O: Kod XML generowanego układu może się nieco różnić
tekstu w języku polskim i w języku angielskim, jeśli w telefonie w zależności od używanej wersji Android Studio. Nie musisz się
został wybrany inny język niż polski. W takim przypadku zamiast jednak tym przejmować, gdyż zaczynając od zaraz i tak będziesz
na stałe umieszczać teksty w różnych językach w aplikacji, mógłbyś się uczył tworzyć własne układy, dlatego i tak zastąpisz układ
utworzyć jeden plik z tekstami polskimi i drugi z angielskimi. wygenerowany przez Android Studio.
jesteś tutaj 37
Przybornik
CELNE SPOSTRZEŻENIA
Kolejne wersje Androida są identyfikowane na Plik AndroidManifest.xml zawiera informacje
podstawie numeru wersji, poziomu API oraz o samej aplikacji. Jest on przechowywany
nazwy kodowej. w katalogu app/src/main.
Android Studio jest specjalną wersją InelliJ IDEA, AVD to skrót od angielskich słów Android
dostosowaną do współpracy z Android Software Virtual Device, oznaczający wirtualne urządzenie
Development Kit (Android SDK) i z systemem z Androidem. Takie wirtualne urządzenia są
budowy gradle. wykonywane w emulatorze Androida i udają
urządzenia fizyczne.
Typowa aplikacja na Androida składa się
z aktywności, układów oraz plików zasobów. Plik APK to plik pakietu aplikacji. Przypomina on
plik JAR, przy czym zawiera aplikację na Androida
Układy opisują, jak aplikacja ma wyglądać. Są one
jej kody bajtowe, biblioteki i zasoby. Instalacja
przechowywane w katalogu app/src/main/res/
aplikacji na urządzeniu z Androidem polega na
layout.
zainstalowaniu pliku APK.
Aktywności opisują, co aplikacja robi i w jaki Aplikacje na Androida działają w niezależnych
sposób prowadzi interakcję z użytkownikiem.
procesach wykonywanych przez środowisko
Pisane przez nas aktywności są przechowywane
uruchomieniowe Androida (ART).
w katalogu app/src/main/java.
RelativeLayout określa rozmieszczenie
Plik strings.xml zawiera pary nazwa – wartość.
elementów w układzie w sposób względny.
Pozwala on zapewnić separację pomiędzy
wartościami tekstowymi oraz układami Element TextView służy do wyświetlania
i aktywnościami, jak również ułatwia tekstów.
lokalizowanie aplikacji.
38 Rozdział 1.
2. Tworzenie interaktywnych aplikacji
40 Rozdział 2.
Tworzenie interaktywnych aplikacji
1 Utworzyć projekt.
Tworzymy zupełnie nową aplikację, musisz więc utworzyć
nowy projekt. Podobnie jak w poprzednim rozdziale, będziesz
potrzebować prostego układu i aktywności.
2 Zaktualizować układ.
Po wygenerowaniu aplikacji musisz zaktualizować jej układ
i dodać do niego wszystkie niezbędne komponenty GUI.
jesteś tutaj 41
Utworzenie projektu
¨ Utworzenie projektu
Utworzenie projektu ¨ Aktualizacja układu
¨ Połączenie aktywności
Zabierzmy się zatem za pisanie nowej aplikacji (czynności, jakie należy w tym celu ¨ Implementacja logiki
wykonać, są bardzo podobne do tych, które wykonaliśmy w poprzednim rozdziale):
Otwórz Android Studio i na ekranie powitalnym kliknij opcję Start a new Android Studio project.
Spowoduje to uruchomienie kreatora przedstawionego w rozdziale 1.
Kiedy zostaniesz poproszony o podanie nazwy aplikacji, wpisz Doradca piwny, co automatycznie
spowoduje zmianę nazwy pakietu na com.hfad.doradcapiwny.
Chcemy, by nasza aplikacja działała na większości telefonów i tabletów, dlatego na liście Minimum
SDK wybierz API poziomu 15 i upewnij się, że będzie zaznaczone pole wyboru Phone and Tablet.
Oznacza to, że każdy telefon lub tablet, na którym będziemy chcieli uruchomić aplikację, będzie
musiał mieć zainstalowane API przynajmniej poziomu 15. Kryterium to spełnia przeważająca
większość aktualnie używanych urządzeń z Androidem.
Jako domyślną aktywność wybierz szablon Blank Activity (pusta aktywność). Nadaj jej nazwę
FindBeerActivity, a plik układu nazwij activity_find_beer. Zaakceptuj domyślne wartości w polach
Title i Menu Resource Name, gdyż nie będziemy ich na razie potrzebować.
wszystkie te
Kreator przeprowadzi Cię przez im rozdziale.
czynności, podo bnie jak w popr zedn
j nazw ę Dora dca piwny,
Tworzonej aplikacji nada m API
określ, że jej minimalnym poziome y aktywności,
będzie 15, a następnie podaj nazw ity_find_beer.
activ
FindBeerActivity, i pliku układu,
42 Rozdział 2.
Tworzenie interaktywnych aplikacji
Podobnie jak w poprzednim rozdziale, także teraz kreator utworzył domyślny layout
układ, który zawiera element <TextView> prezentujący tekst „Hello world!”. <xml>
</xml>
Poniżej przedstawiliśmy kod tego układu:
activity_find_beer.xml
<TextView
android:text=”@string/hello_world”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content” />
jesteś tutaj 43
Edytor projektu
¨ Utworzenie projektu
Dodawanie komponentów w edytorze projektu ¨ Aktualizacja układu
¨ Połączenie aktywności
Komponenty graficznego interfejsu użytkownika można dodawać do układów na dwa ¨ Implementacja logiki
sposoby: bezpośrednio w kodzie XML bądź w edytorze projektu. Zacznijmy od dodania
przycisku w edytorze projektu.
Z lewej strony edytora projektu jest wyświetlona paleta zawierająca komponenty GUI, które
można przeciągać do układu. Jeśli przyjrzysz się sekcji Widgets, zauważysz w niej na pewno
komponent przycisku — Button. Kliknij go, a następnie przeciągnij do edytora projektu.
To jest paleta
komponentów.
W naszej
wersji aplikacji
A to jest przycisk umieściliśmy
— komponent Button. przycisk
Przeciągnij go do układu. pod tekstem
„Hello world!”.
<Button
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”New Button”
android:id=”@+id/button”
android:layout_below=”@+id/textView”
android:layout_alignLeft=”@+id/textView” />
android:text android.widget.TextView
setText(CharSequence,
Ta właściwość określa, jaki tekst powinien zostać wyświetlony w komponencie. TextView.BufferType)
W przypadku komponentu Button będzie to tekst wyświetlany na przycisku:
...
android:text=”New button”
¨ Utworzenie projektu
Dokładniejszy przegląd kodu układu ¨ Aktualizacja układu
¨ Połączenie aktywności
Przyjrzyjmy się nieco dokładniej kodowi układu i podzielmy go na poszczególne elementy, ¨ Implementacja logiki
abyś mógł lepiej zrozumieć, jak on działa (nie przejmuj się, jeśli Twój kod wygląda nieco
inaczej, po prostu przeanalizuj wraz z nami kod podany poniżej):
To jest <TextView
widok
tekstowy. android:text=”@string/hello_world” DoradcaPiwny
android:layout_width=”wrap_content”
android:layout_height=”wrap_content” app/src/main
android:id=”@+id/textView” />
res
To jest <Button
przycisk.
android:layout_width=”wrap_content” layout
android:layout_height=”wrap_content” <xml>
</xml>
android:text=”New Button”
activity_find_beer.xml
android:id=”@+id/button”
android:layout_below=”@+id/textView”
android:layout_alignLeft=”@+id/textView” />
Element RelativeLayout
ślania
Pierwszym elementem w kodzie układu jest <RelativeLayout>. Element Istnieją także inne sposoby okre
a kom pone ntów graf iczn ego
położeni
ten informuje system, że położenie poszczególnych komponentów GUI interfejsu użytkownika. Poznasz
je
ma być wyznaczane względem innych komponentów. Można go zatem w dalszej części książki.
użyć na przykład, aby wyświetlić jeden komponent z lewej strony innego
bądź aby wyrównać komponenty w określony sposób.
46 Rozdział 2.
Tworzenie interaktywnych aplikacji
Element TextView
Pierwszym elementem umieszczonym wewnątrz <RelativeLayout> Zastosowanie układu
jest <TextView>:
... względnego oznacza,
<TextView że położenie
android:text=”@string/hello_world”
android:layout_width=”wrap_content” komponentów GUI
android:layout_height=”wrap_content”
będzie określane
android:id=”@+id/textView” />
... względem innych
Jak widać, w kodzie tego elementu nie zostały podane żadne właściwości elementów układu.
określające, w którym miejscu układu powinien on zostać wyświetlony, dlatego
domyślnie Android wyświetli go w lewym górnym rogu ekranu. Zwróć też
uwagę, że komponent ten ma identyfikator o wartości textView. Przekonasz się,
dlaczego to takie ważne, gdy przyjrzymy się następnemu elementowi.
Element Button
Ostatnim elementem umieszczonym w układzie <RelativeLayout>
jest przycisk — element <Button>: Widok tekstowy jest domyślnie
wyświetlany w lewym górnym
... rogu ekranu.
<Button
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”New Button”
android:id=”@+id/button”
android:layout_below=”@+id/textView”
android:layout_alignLeft=”@+id/textView” />
...
Dodając przycisk do układu, umieściliśmy go w taki sposób, by był widoczny Przycisk ma zostać wyświetlony
poniżej widoku tekstowego,
poniżej tekstu, a jego lewa krawędź znajdowała się bezpośrednio poniżej lewej a ich lewe krawędzie mają być
krawędzi widoku tekstowego. Oznacza to, że położenie przycisku zostało wyrównane.
określone względem położenia widoku tekstowego i ten fakt jest wyraźnie
odzwierciedlony w kodzie XML układu:
android:layout_below=”@+id/textView”
android:layout_alignLeft=”@+id/textView”
DoradcaPiwny
Zmiany w kodzie XML układu…
app/src/main
Przekonałeś się już, że zmiany wprowadzane w widoku projektu są
natychmiast uwzględniane w kodzie XML układu. To samo dotyczy
res
odwrotnej sytuacji: wszelkie zmiany wprowadzane w kodzie XML
układu są pokazywane w widoku projektu.
layout
Sprawdźmy, czy faktycznie tak się dzieje. Zastąp zawartość swojego <xml>
</xml>
pliku activity_find_beer.xml kodem przedstawionym poniżej:
activity_find_beer.xml
<RelativeLayout xmlns:android=”http://schemas.android.com/apk/res/android”
xmlns:tools=”http://schemas.android.com/tools”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:paddingBottom=”16dp”
android:paddingLeft=”16dp”
android:paddingRight=”16dp”
android:paddingTop=”16dp”
tools:context=”.FindBeerActivity” >
Spinner to
stosowana <Spinner
w Androidzie android:id=”@+id/color” Ten element wyświetla
nazwa na rozwijaną w układzie komponent
android:layout_width=”wrap_content” Spinner, czyli rozwijaną
listę konkretnych
wartości. Pozwala android:layout_height=”wrap_content” listę wartości.
on wybrać jedną android:layout_alignParentTop=”true”
z dostępnych opcji.
android:layout_centerHorizontal=”true”
android:layout_marginTop=”37dp” />
<Button
android:id=”@+id/find_beer”
Umieść przycisk android:layout_width=”wrap_content”
poniżej rozwijanej android:layout_height=”wrap_content”
listy i wyrównaj go android:layout_alignLeft=”@+id/color”
do lewej krawędzi
listy. android:layout_below=”@+id/color”
android:text=”Button” />
<TextView
android:id=”@+id/brands”
android:layout_width=”wrap_content”
Umieść widok android:layout_height=”wrap_content”
Zrób to sam!
tekstowy poniżej
przycisku android:layout_alignLeft=”@+id/find_beer”
i wyrównaj go android:layout_below=”@+id/find_beer”
do lewej krawędzi
przycisku. android:layout_marginTop=”18dp”
android:text=”TextView” /> Zastąp zawartość swojego
pliku activity_find_beer.xml
</RelativeLayout>
przedstawionym tu kodem XML.
48 Rozdział 2.
Tworzenie interaktywnych aplikacji
jesteś tutaj 49
Dodanie łańcuchów znaków
¨ Utworzenie projektu
Stosuj zasoby łańcuchowe, ¨ Aktualizacja układu
a nie łańcuchy podawane w kodzie ¨ Połączenie aktywności
¨ Implementacja logiki
Jest jeszcze jedna rzecz, którą musimy zmienić przed próbą uruchomienia
tej aplikacji. Aktualnie zarówno tekst umieszczony na przycisku, jak i tekst
prezentowany w widoku tekstowym są podane na stałe, bezpośrednio
we właściwości text. Jak już wspominaliśmy w rozdziale 1., takie teksty
warto zamienić na zasoby łańcuchowe, definiowane w pliku strings.xml.
Choć nie jest to właściwie obowiązkowe, to jednak takie postępowanie jest
dobrym nawykiem, który warto sobie wyrobić. Stosowanie plików zasobów
łańcuchowych do gromadzenia statycznych tekstów ułatwia późniejsze
tworzenie innych wersji językowych aplikacji, a jeśli kiedyś pojawi się
konieczność wprowadzenia zmian w wyświetlanych tekstach, będzie to
można zrobić w jednym miejscu.
</resources>
DoradcaPiwny
50 Rozdział 2.
Tworzenie interaktywnych aplikacji
...
<Spinner
android:id=”@+id/color” DoradcaPiwny
android:layout_width=”wrap_content”
app/src/main
android:layout_height=”wrap_content”
android:layout_alignParentTop=”true”
res
android:layout_centerHorizontal=”true”
android:layout_marginTop=”37dp” /> layout
<xml>
</xml>
<Button activity_find_beer.xml
android:id=”@+id/find_beer”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_alignLeft=”@+id/color”
android:layout_below=”@+id/color” Dzięki temu na przycisku
zostanie wyświetlona
android:text=”@string/find_beer” /> zawartość zasobu
łańcuchowego find_beer.
<TextView
android:id=”@+id/brands”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_alignLeft=”@+id/find_beer”
android:layout_below=”@+id/find_beer”
Dzięki temu w widoku tekstowym zostanie
android:layout_marginTop=”18dp” wyświetlona zawartość zasobu brands.
Choć obecnie jest on pusty, to jednak
android:text=”@string/brands” /> jeśli w przyszłości podamy w nim jakiś inny
łańcuch znaków, zostanie on wyświetlony.
...
jesteś tutaj 51
Jazda próbna
¨ Utworzenie projektu
Weź swoją aplikację na jazdę próbną ¨ Aktualizacja układu
¨ Połączenie aktywności
Wciąż mamy jeszcze sporo do zrobienia w naszej aplikacji, sprawdźmy ¨ Implementacja logiki
jednak, jak ona wygląda w swojej aktualnej postaci. Zapisz wszystkie
wprowadzone zmiany, a następnie wybierz opcję Run ‘app’ z menu Run.
Kiedy zostaniesz o to poproszony, wybierz opcję uruchomienia emulatora.
strings.xml widoku tekstowego?
Aktywność
52 Rozdział 2.
Tworzenie interaktywnych aplikacji
¨ Utworzenie projektu
Dodanie wartości do komponentu Spinner ¨ Aktualizacja układu
¨ Połączenie aktywności
Obecnie nasz układ zawiera rozwijaną listę, lecz jeszcze jest ona pusta. Za każdym ¨ Implementacja logiki
razem, gdy używamy komponentu tego typu, musimy zadbać o to, by zawierał on
listę wartości, gdyż tylko w takim przypadku użytkownik będzie mógł coś wybrać.
Zasoby to
Listę wartości do komponentu Spinner można przekazać mniej więcej w taki różnego rodzaju
sam sposób, w jaki określa się tekst wyświetlany w widoku tekstowym: przy użyciu
zasobów. Dotychczas używaliśmy pliku strings.xml wyłącznie do definiowania używane przez
pojedynczych wartości łańcuchowych. Tym razem musimy natomiast określić aplikację dane
tablicę takich łańcuchów i odwołać się do niej w komponencie.
i pliki, które
Tworzenie zasobu tablicowego nie są kodem.
przypomina dodawanie łańcuchów znaków
Jak już wiemy, zasób łańcuchowy można dodać do pliku strings.xml,
używając następującego kodu XML:
<string name=”nazwa_łańcucha”>wartość_łańcucha</string>
jesteś tutaj 53
Jazda próbna
”@string/nazwa_łańcucha”
Domyślnie
w komponencie
wyświetlana Kliknij Kiedy
jest pierwsza komponent, klikniesz
wartość. aby rozwinąć wartość,
listę wartości. zostanie
ona
wybrana.
54 Rozdział 2.
Tworzenie interaktywnych aplikacji
¨ Utworzenie projektu
Musimy zadbać o to, by przycisk coś robił ¨ Aktualizacja układu
¨ Połączenie aktywności
Jak na razie dodaliśmy do układu komponenty GUI i wypełniliśmy rozwijaną listę, używając ¨ Implementacja logiki
tablicy łańcuchów znaków. Kolejnym zadaniem, którym musimy się zająć, jest zapewnienie,
by po kliknięciu przycisku aplikacja odpowiednio zareagowała na wartość wybraną z listy.
Chcemy, by nasza aplikacja działała mniej więcej w sposób opisany poniżej:
2
1
<Layout>
</Layout> 3
5
Układ Aktywność
Urządzenie
getBrands("bursztynowe")
"Jack Amber"
"Red Moose"
4
BeerExpert
jesteś tutaj 55
Atrybut onClick
...
<Button
DoradcaPiwny
android:id=”@+id/find_beer”
android:layout_width=”wrap_content” app/src/main
android:layout_height=”wrap_content”
android:layout_alignLeft=”@+id/color” res
android:layout_below=”@+id/color”
android:text=”@string/find_beer” layout
<xml>
android:onClick=”onClickFindBeer” /> </xml>
package com.hfad.doradcapiwny;
jest
public class FindBeerActivity extends Activity { To jest metoda onCreate(), która go
wywoływana w momencie pierwsze
tworzenia aktywności.
@Override
protected void onCreate(Bundle savedInstanceState) { Metoda setContentView() informuje
super.onCreate(savedInstanceState); system o tym, którego układu używa
dana aktywność. W tym przypadku
setContentView(R.layout.activity_find_beer); jest to układ activity_find_beer.
}
}
Powyższy kod to wszystko, czego potrzeba do utworzenia prostej aktywności. Jak widać,
aktywność jest klasą, która dziedziczy po klasie android.app.Activity i implementuje
metodę onCreate().
Jeśli metoda nie będzie miała powyższej postaci, to nie zostanie wywołana, Jeśli chcesz, aby
gdy użytkownik dotknie przycisku. Stanie się tak dlatego, że za kulisami
Android będzie poszukiwał publicznej metody zwracającej wartość typu metoda odpowiadała
void, której nazwa odpowiada wartości podanej w kodzie XML układu.
na kliknięcia przycisku,
Z pozoru można przypuszczać, że parametr View tej metody jest musi być metodą
niepotrzebny, niemniej jednak jego istnienie jest uzasadnione. Otóż
parametr ten odwołuje się do komponentu GUI, który doprowadził do publiczną, zwracać
wywołania metody (w naszym przypadku komponentem tym jest przycisk).
Jak już wspominaliśmy, elementy graficznego interfejsu użytkownika, takie
wartość typu void
jak przyciski lub widoki tekstowe, są obiektami klasy View. i mieć jeden parametr
Zmodyfikujmy zatem kod naszej aktywności. Dodaj do niej metodę typu View.
onClickFindBeer():
FIndBeerActivity.java
<Layout>
onClickFindBeer()
</Layout>
FindBeerActivity.java
activity_find_beer.xml
58 Rozdział 2.
Tworzenie interaktywnych aplikacji
jesteś tutaj 59
Metody klasy View
W ten sposób uzyskamy obiekt typu Spinner i będziemy mogli korzystać z jego metod.
Poniższy przykład pokazuje, jak można pobrać aktualnie wybraną wartość komponentu
i skonwertować ją na łańcuch znaków:
color.getSelectedItem()
60 Rozdział 2.
Tworzenie interaktywnych aplikacji
Magnesiki aktywności
Ktoś napisał nową wersję metody onClickFindBeer(), którą możesz dodać
do swojej aktywności, używając do tego celu magnesików przyczepionych
na lodówce. Niestety tajemnicza kuchenna trąba powietrzna oderwała kilka
magnesików. Czy potrafisz przyczepić je z powrotem we właściwych miejscach?
setText
findViewById R.id.color
TextView color Nie musisz
używać
R.view.brands wszystkich
R.id.brands magnesików.
(TextView) findView
Button R.view.color
findView View findViewById
(Spinner)
getSelectedItem()
beerType
jesteś tutaj 61
Magnesiki — rozwiązanie
findView
Button
R.view.color
findView
62 Rozdział 2.
Tworzenie interaktywnych aplikacji
package com.hfad.doradcapiwny;
DoradcaPiwny
import android.os.Bundle;
import android.app.Activity; app/src/main
import android.view.View;
java
import android.widget.Spinner; Używamy tych
dodatkowych klas.
import android.widget.TextView;
com.hfad.doradcapiwny
@Override
protected void onCreate(Bundle savedInstanceState) { Tej metody nie musimy zmieniać.
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_find_beer);
}
jesteś tutaj 63
Co się dzieje?
1 Użytkownik wybiera rodzaj piwa z rozwijanej listy i klika przycisk Odszukaj piwo!.
Powoduje to wywołanie metody public void onClickFindBeer(View) zdefiniowanej w aktywności.
Układ określa, którą metodę aktywności należy wywołać w reakcji na kliknięcie przycisku przy użyciu
właściwości android:onClick elementu <Button>.
<Layout>
</Layout>
Układ FindBeerActivity
komponent Spinner
FindBeerActivity
komponent TextView
bursztynowe
FindBeerActivity komponent
Spinner
4 Aktywność określa wartość właściwości text komponentu TextView tak, by odpowiadała ona
aktualnie wybranemu rodzajowi piwa.
"bursztynowe"
64 Rozdział 2.
Tworzenie interaktywnych aplikacji
piwa jest
Wybrany rodzaj doku
wyświetlany w wi
tekstowym.
Nie istnieją
głupie pytania
P : Dodałem nowy łańcuch znaków do pliku strings.xml, P: W tym przypadku? A nie zawsze?
ale nie mogę go znaleźć w pliku R.java. Dlaczego go tam
nie ma? O: Rozwijanych list można używać do bardziej złożonych rzeczy
niż wyświetlanie tekstów. Na przykład można na nich wyświetlać
O: Android Studio generuje plik R.java w momencie zapisywania ikonę obok tekstu. Fakt, że metoda getSelectedItem() zwraca
zmian wprowadzonych w projekcie. Jeśli dodałeś zasoby, lecz nie wynik typu Object, zapewnia większą elastyczność.
widzisz ich w pliku R.java, to sprawdź, czy zmiany zostały zapisane.
Plik R.java jest także aktualizowany w momencie budowania P: Czy nazwa metody onClickFindBeer ma znaczenie?
aplikacji. Aplikacja jest budowana przed jej uruchomieniem,
a zatem także uruchomienie aplikacji powoduje zaktualizowanie
O: Znaczenie ma tylko to, by nazwa metody w kodzie aktywności
odpowiadała nazwie podanej w atrybucie onClick przycisku
pliku R.java.
w kodzie układu.
P: Wygląda na to, że wartości wyświetlane w rozwijanej P: Dlaczego zastąpiliśmy kod aktywności wygenerowany
liście są statyczne, gdyż są to łańcuchy podane w zasobie
przez Android Studio?
string-array. Czy można te wartości modyfikować
programowo? O: Zintegrowane środowiska programistyczne, takie jak Android
O: Owszem, można, choć jest to bardziej złożone niż użycie Studio, zawierają wiele funkcji i narzędzi umożliwiających
programistom oszczędzanie czasu. Generują za nas dużo kodu, co
wartości statycznych. W dalszej części książki pokażemy,
w jaki sposób można uzyskać pełną kontrolę nad wartościami czasami jest bardzo użyteczne. Uważamy jednak, że podczas nauki
wyświetlanymi w komponentach, w tym także na rozwijanych nowego języka lub nowego obszaru zagadnień programistycznych,
listach. takich jak tworzenie aplikacji na Androida, lepiej jest poznawać
podstawy języka, a nie kod generowany przez IDE. Dzięki temu
P: Jaki jest typ obiektu zwracanego przez metodę można lepiej zrozumieć kod, którego następnie będzie można
używać niezależnie od stosowanego IDE.
getSelectedItem()?
jesteś tutaj 65
Klasa BeerExpert
Zgodnie z tym, co sygnalizowaliśmy już na samym początku rozdziału, nasza </Layout> <resources>
66 Rozdział 2.
Tworzenie interaktywnych aplikacji
Zaostrz ołówek
Rozszerz kod aktywności tak, by wywoływał metodę getBrands()
klasy BeerExpert i wyświetlał wyniki w widoku tekstowym.
package com.hfad.doradcapiwny;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.View;
import android.widget.Spinner;
import android.widget.TextView;
import java.util.List; Ten wiersz kodu dodaliśmy za Ciebie.
jesteś tutaj 67
Rozwiązanie zadania
Zaostrz ołówek
Rozwiązanie Rozszerz kod aktywności tak, by wywoływał metodę getBrands()
klasy BeerExpert i wyświetlał wyniki w widoku tekstowym.
package com.hfad.doradcapiwny;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.View;
import android.widget.Spinner;
import android.widget.TextView;
import java.util.List;
// wyświetlenie wyników
Wyświetla wyniki
brands.setText(brandsFormatted); w widoku tekstowym.
}
} wymaga jedynie zwyczajnego
Zastosowanie klasy BeerExpert nie musisz się przejmować,
sane go w Javi e, dlate go
kodu napi lądać nieco inaczej niż nasze.
jeśli Twoje rozw iąza nie będz ie wyg
68 Rozdział 2.
Tworzenie interaktywnych aplikacji
package com.hfad.doradcapiwny;
DoradcaPiwny
import android.os.Bundle;
import android.app.Activity; app/src/main
import android.view.Menu;
import android.view.View; java
import android.widget.Spinner;
import android.widget.TextView; com.hfad.doradcapiwny
import java.util.List; Używamy tej dodatkowej klasy.
FIndBeerActivity.java
public class FindBeerActivity extends Activity {
private BeerExpert expert = new BeerExpert();
jesteś tutaj 69
Co się dzieje?
<Layout>
getBrands("bursztynowe")
"Jack Amber"
FindBeerActivity "Red Moose" BeerExpert
"Jack Amber
Red Moose"
FindBeerActivity komponent
TextView
70 Rozdział 2.
Tworzenie interaktywnych aplikacji
jesteś tutaj 71
Przybornik
Opanowałeś już rozdział 2. i dodałeś j
Pełny kod przykładowe
do swojego przybornika z narzędziami aplikacji prezen tow ane j w tym
umiejętności tworzenia interaktywnych rozdziale mo żes z pob rać
aplikacji na Androida. nictwa
z serwera FTP wydaw
.helion .pl/
Helion: ftp://ftp
przyklady/an drrg.z ip
CELNE SPOSTRZEŻENIA
Element Button pozwala dodawać przyciski.
Element Spinner umożliwia dodawanie pola, którego kliknięcie powoduje wyświetlenie listy wartości.
Wszystkie komponenty GUI (graficznego interfejsu użytkownika) są rodzajami widoków.
Wszystkie one dziedziczą po klasie View.
Tablicę łańcuchów znaków można dodawać, używając następującego zapisu:
<string-array name=”tablica”>
<item>łańcuch1</item>
...
</string-array>
Aby kliknięcie przycisku wywołało metodę, należy dodać do jego kodu w układzie następujący atrybut:
android:onClick=”metodaClick”
72 Rozdział 2.
3. Wiele aktywności i intencji
74 Rozdział 3.
Wiele aktywności i intencji
<Layout> <Layout>
</Layout> </Layout>
activity_create_message.xml activity_receive_message.xml
Teskt wpisany w aktywności
CreateMessageActivity
zostaje przekazany
do aktywności
1 ReceiveMessageActivity.
jesteś tutaj 75
Aktualizacja układu
<Button android:id=”@+id/send”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
Zastąp android:layout_alignParentLeft=”true”
element
<TextView> android:layout_alignParentTop=”true”
wygenerowany
przez Android android:layout_marginLeft=”36dp”
Studio android:layout_marginTop=”21dp”
elementami Kliknięcie przycisku spowoduje wywołanie
<Button> android:onClick=”onSendMessage” metody onSendMessage() zdefiniowanej
i <EditText>. w aktywności.
android:text=”@string/send” />
To jest zasób łańcuchowy.
<EditText android:id=”@+id/message”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content” Element <EditText> definiuje
android:layout_alignLeft=”@+id/send” pole tekstowe służące
android:layout_below=”@+id/send” do wpisywania danych.
android:layout_marginTop=”18dp”
Dziedziczy on po tej samej
android:ems=”10” >
</EditText> klasie View co wszystkie inne
Ten atrybut określa, jak szerokie powinno
być pole <EditText>. W tym przypadku
komponenty GUI, które
</RelativeLayout> jego wielkość powinna wystarczyć do
zapisania 10 wielkich liter „M”.
poznaliśmy do tej pory.
76 Rozdział 3.
Wiele aktywności i intencji
import android.view.View;
¨ Utworzenie pierwszej aktywności
Utworzenie drugiej aktywności i układu ¨ Utworzenie drugiej aktywności
¨ Wywołanie drugiej aktywności
Android Studio udostępnia kreator umożliwiający dodawanie do aplikacji ¨ Przekazanie danych
kolejnych aktywności i układów. Stanowi on uproszczoną wersję kreatora nowych
aplikacji i możemy go używać za każdym razem, gdy chcemy dodać do aplikacji
nową aktywność.
Aby utworzyć nową aktywność, wybierz z menu głównego Android Studio opcję
File/New/Activity, a następnie wybierz opcję Blank Activity. W efekcie na ekranie
zostanie wyświetlone okno, w którym będziesz mógł podać dane dotyczące tworzonej
aktywności.
Tworząc nowe aktywności i układy, należy określić ich nazwy. W tym przypadku
aktywności nadaj nazwę ReceiveMessageActivity, a układowi — activity_receive_
message. Upewnij się, czy oba pliki znajdą się w pakiecie com.hfad.komunikator.
W pozostałych polach zaakceptuj wartości domyślne, a następnie kliknij przycisk Finish.
W pozostałych
polach zaakceptuj
wartości
domyślne, gdyż
interesuje nas
utworzenie nowej
aktywności
i nowego układu.
Większość kodu
wygenerowanego
przez Android
Studio usuniemy
i zastąpimy
własnym.
78 Rozdział 3.
Wiele aktywności i intencji
<Layout> <Layout>
</Layout> </Layout>
activity_create_message.xml activity_receive_message.xml
CreateMessageActivity.java RecieveMessageActivity.java
jesteś tutaj 79
AndroidManifest.xml
¨ Utworzenie pierwszej aktywności
Przedstawiamy plik manifestu aplikacji ¨ Utworzenie drugiej aktywności
na Androida ¨ Wywołanie drugiej aktywności
¨ Przekazanie danych
Każda aplikacja na Androida musi zawierać plik o nazwie AndroidManifest.xml. Można
go znaleźć w katalogu projektu, w podkatalogu app/src/main. Plik ten zawiera kluczowe
informacje dotyczące aplikacji, takie jak aktywności, z których się składa, używane Komunikator
biblioteki oraz wiele innych deklaracji. Android generuje dla nas ten plik w momencie
tworzenia aplikacji. Jeśli przypomnisz sobie ustawienia wybierane podczas tworzenia app/src/main
aplikacji, to niektóre fragmenty kodu pliku manifestu będą wyglądać podobnie. <xml>
Plik AndoridManifest.xml </xml>
Poniżej przedstawiliśmy naszą wersję pliku AndroidManiest.xml: można znaleźć w tym AndroidManifest.xml
katalogu.
<?xml version=”1.0” encoding=”utf-8”?>
<manifest xmlns:android=”http://schemas.android.com/apk/res/android”
package=”com.hfad.komunikator” > To jest podana przez
nas nazwa pakietu.
Jeśli
piszesz
<application Android Studio dodało do
naszej aplikacji domyślną ikonę.
Obejrzyj to!aplikacje
android:allowBackup=”true” Przyjrzymy się jej dokładniej na Androida, nie
android:icon=”@mipmap/ic_launcher” w dalszej części książki. używając żadnego
zintegrowanego
android:label=”@string/app_name” Motyw graficzny określa środowiska
android:theme=”@style/AppTheme” > wygląd aplikacji. Także o tych
zagadnieniach dowiesz się programistycznego,
więcej w dalszej części książki. to musisz utworzyć
ten plik ręcznie.
<activity
android:name=”.CreateMessageActivity”
To jest nasza android:label=”@string/app_name” > Ten wiersz kodu
pierwsza określa, że to jest
aktywność, <intent-filter> główna aktywność
Create aplikacji.
<action android:name=”android.intent.action.MAIN” />
Message
Activity. <category android:name=”android.intent.category.LAUNCHER” />
</intent-filter>
</activity> Z kolei ten wiersz informuje,
że aktywności można użyć
do uruchomienia aplikacji.
<activity
To jest
nasza druga android:name=”.ReceiveMessageActivity”
aktywność,
Receive android:label=”@string/title_activity_receive_message” >
Message
Activity. </activity>
80 Rozdział 3.
Wiele aktywności i intencji
Obejrzyj to!
Poniższy wiersz kodu jest obowiązkowy i służy do określenia nazwy klasy aktywności: aktywność
została
android:name=”nazwa_klasy_aktywności” automatycznie
zadeklarowana, gdyż
Przy czym nazwa_klasy_aktywności to, jak łatwo się domyślić, nazwa klasy dodaliśmy ją, używając
aktywności poprzedzona znakiem kropki (.). W naszym przypadku będzie to zatem kreatora Android Studio.
.ReceiveMessageActivity. Nazwa klasy jest poprzedzona znakiem kropki, gdyż
Android łączy nazwę klasy z nazwą pakietu, uzyskując w ten sposób pełną nazwę klasy. W przypadku samodzielnego
dodawania dodatkowych
Ten wiersz jest opcjonalny i służy do określenia przyjaznej dla użytkownika nazwy aktywności będziemy
aktywności: musieli ręcznie wprowadzać
odpowiednie zmiany
android:label=”@string/etykieta_aktywności”
w pliku manifestu —
Ta nazwa jest wyświetlana na samej górze ekranu w czasie, gdy dana aktywność jest AndroidManifest.xml. Także
wykonywana. Jeśli atrybut nie zostanie określony, Android będzie w tym miejscu w przypadku stosowania innego
wyświetlał nazwę aplikacji. IDE informacje o dodatkowych
aktywnościach mogą nie być
Deklaracja aktywności może zawierać także inne właściwości, takie jak uprawnienia automatycznie dodawane do
niezbędne do wykonania aktywności, oraz informację, czy dana aktywność może być pliku manifestu.
używana przez aktywności należące do innych aplikacji.
jesteś tutaj 81
intencje
¨ Utworzenie pierwszej aktywności
Intencja jest rodzajem komunikatu ¨ Utworzenie drugiej aktywności
¨ Wywołanie drugiej aktywności
Dotychczas stworzyliśmy aplikację z dwiema aktywnościami, z których każda używa ¨ Przekazanie danych
własnego układu. W momencie uruchamiania aplikacji zostanie wykonana pierwsza
aktywność — CreateMessageActivity. Naszym kolejnym zadaniem jest zadbanie
o to, by po kliknięciu przycisku Wyślij wiadomość aktywność CreateMessageActivity Aktywność uruchamia się,
wywołała drugą aktywność — ReceiveMessageActivity.
tworząc intencję i używając
Zawsze, gdy chcemy uruchomić jedną aktywność z poziomu innej aktywności, musimy
użyć w tym celu intencji. Można ją sobie wyobrażać jako „intencję zrobienia czegoś”. jej w wywołaniu metody
Intencja to rodzaj komunikatu pozwalającego powiązać ze sobą niezależne obiekty
(takie jak aktywności) w trakcie działania aplikacji. Jeśli jedna aktywność chce startActivity().
uruchomić drugą, robi to, przesyłając do systemu Android odpowiednią intencję.
W efekcie system uruchomi aktywność i przekaże do niej tę intencję.
Intencja określa aktywność,
Do utworzenia i przesłania intencji wystarczy jeden lub dwa wiersze kodu. W pierwszej do której chcemy ją przesłać.
Informację tę można by porównać
kolejności należy utworzyć intencję, używając poniższego wiersza kodu: z adresem umieszczonym na
Intent intent = new Intent(this, KlasaDocelowa.class); kopercie.
Pierwszy parametr tego wywołania informuje system, z jakiego obiektu pochodzi Intencja
intencja, i jak widać, aby odwołać się do bieżącej aktywności, można posłużyć
się referencją this. Drugim parametrem jest nazwa klasy aktywności, do której
intencja jest skierowana.
Do: InnaAktywnosc
Po utworzeniu intencji można ją przekazać do systemu Android w następujący
sposób:
Wywołanie metody startActivity()
startActivity(intent); uruchamia aktywność określoną w intencji.
Kiedy Android odbierze intencję, sprawdza, czy wszystko jest w porządku, po czym
uruchamia aktywność. Jeśli aktywności nie uda się odnaleźć, zgłaszany jest wyjątek
ActivityNotFoundException.
Niech no sprawdzę.
„Mój drogi Tak, wydaje się, że wszystko jest
Androidzie, czy mogłabym w porządku. Powiem Aktywności 2,
Cię prosić, żebyś kazał Aktywności 2 żeby się brała do pracy.
Oo… komunikat.
wziąć się do roboty? Szczerze oddana, Chyba zacznę działać.
Twoja stara kumpela, Aktywność 1”.
Intencja Intencja
82 Rozdział 3.
Wiele aktywności i intencji
package com.hfad.komunikator;
Musimy zaimportować
klasę Intent, android.
import android.app.Activity; content.Intent,
gdyż będziemy jej
import android.content.Intent; używać w metodzie
import android.os.Bundle; onSendMessage().
import android.view.View;
Komunikator
jesteś tutaj 83
Co się dzieje?
¨ Utworzenie pierwszej aktywności
Co się dzieje po uruchomieniu aplikacji? ¨ Utworzenie drugiej aktywności
¨ Wywołanie drugiej aktywności
Zanim weźmiemy naszą nową aplikację na jazdę próbną, ¨ Przekazanie danych
jeszcze raz przeanalizujmy, jak ona będzie działać:
<Layout>
onSendMessage()
2 Użytkownik klika przycisk.
W odpowiedzi na kliknięcie zostaje
wywołana metoda onSendMessage()
aktywności CreateMessageActivity.
CreateMessageActivity
Urządzenie
onSendMessage()
Intencja
3 Metoda onSendMessage() prosi
system o uruchomienie aktywności CreateMessageActivity
ReceiveMessageActivity, Do: Receive
używając do tego intencji. Message
Android upewnia się, że intencja Activity
jest prawidłowa, po czym nakazuje
uruchomienie aktywności Intencja
ReceiveMessageActivity. Android
Do: ReceiveMessageActivity
ReceiveMessageActivity
84 Rozdział 3.
Wiele aktywności i intencji
Android
Urządzenie
ReceiveMessageActivity
<Layout>
</Layout>
activity_receive_message
Wpisz komunikat
i kliknij
przycisk Wyślij
wiadomość.
jesteś tutaj 85
Przekazanie tekstu
Trzeba wprowadzić kilka zmian w układzie. Musimy dodać do elementu <TextView> identyfikator
”message”, żebyśmy mogli odwoływać się do niego w kodzie aktywności; oprócz tego musimy zapobiec
wyświetlaniu tekstu „Hello world!”. Jak powinien wyglądać zmodyfikowany układ? Spróbuj go przygotować
Ćwiczenie samodzielnie, zanim spojrzysz na następną stronę.
86 Rozdział 3.
Wiele aktywności i intencji
android:layout_width=”wrap_content”
android:layout_height=”wrap_content” />
</RelativeLayout>
Nie istnieją
głupie pytania
Zamiast usuwać poniższy wiersz kodu:
P: Czy muszę używać intencji? Czy nie mogę
android:text=”@string/hello_world” utworzyć instancji drugiej aktywności w kodzie
pierwszej?
można także zmodyfikować plik strings.xml tak, by zasób
hello_world zawierał pusty łańcuch znaków. W tym O: To jest dobre pytanie, jednak nie to nie jest
przykładzie zdecydowaliśmy się tego nie robić, gdyż jedynym androidowy sposób uruchamiania aktywności. Jednym
tekstem, który kiedykolwiek będziemy chcieli wyświetlać z powodów jest to, że przekazując do systemu intencje,
w widoku tekstowym, jest komunikat przesłany z aktywności Android będzie wiedział, w jakiej kolejności były
CreateMessageActivity. wykonywane poszczególne aktywności. A to z kolei
oznacza, że w razie kliknięcia przycisku Wstecz na
Skoro poradziliśmy sobie z układami, możemy zająć się urządzeniu Android będzie dokładnie wiedział,
kodem aktywności. gdzie ma wrócić.
jesteś tutaj 87
Ekstra, ekstra
EditText
EditText
import putExtra
jesteś tutaj 89
Rozwiązanie zagadkowego basenu
package com.hfad.komunikator;
Zagadkowy basen. Rozwiązanie
import android.os.Bundle;
Twoim zadaniem jest powyciąganie
import android.app.Activity;
Musisz z basenu fragmentów kodu
import android.content.Intent; zaimportować i wstawienie ich w odpowiednie
import android.view.View; klasę EditText. puste miejsca pliku
import android.widget.EditText
............................................. CreateMessageActivity.java.
Żadnego fragmentu kodu
nie można użyć więcej niż
raz, lecz nie wszystkie fragmenty
public class CreateMessageActivity extends Activity { będą potrzebne. Twoim celem jest
@Override skompletowanie aktywności, która
będzie pobierać tekst z widoku
protected void onCreate(Bundle savedInstanceState) {
tekstowego i zapisywać go w intencji.
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_create_message);
}
Te fragmenty kodu
nie były potrzebne.
putExtraString
90 Rozdział 3.
Wiele aktywności i intencji
package com.hfad.komunikator;
Komunikator
import android.os.Bundle;
import android.app.Activity; app/src/main
import android.content.Intent;
Musisz zaimportować klasę EditText,
import android.view.View; android.widget.EditText, gdyż jest java
import android.widget.EditText; ona używana w kodzie aktywności.
com.hfad.komunikator
Te wiersze pobierają
// Metoda onSendMessage() jest wywoływana po kliknięciu przycisku tekst zapisany
w komponencie EditText.
public void onSendMessage(View view) {
EditText messageView = (EditText)findViewById(R.id.message);
String messageText = messageView.getText().toString();
Intent intent = new Intent(this, ReceiveMessageActivity.class);
intent.putExtra(ReceiveMessageActivity.EXTRA_MESSAGE, messageText);
startActivity(intent);
} Ten fragment kodu tworzy intencję,
następnie dodaje do niej tekst. Nazwę
} To wywołanie informacji zapisywanej w intencji
uruchamia aktywność określamy przy użyciu stałej, dzięki
ReceiveMessageActivity, czemu możemy mieć pewność, że w obu
używając w tym celu intencji. aktywnościach, CreateMessageActivity
i ReceiveMessageActivity, będzie używany
ten sam łańcuch znaków. Tę stałą dodamy
do klasy ReceiveMessageActivity na
następnej stronie.
Teraz, kiedy aktywność CreateMessageActivity zapisuje już
dodatkowe informacje w intencji, musimy zająć się ich pobraniem
i wyświetleniem.
jesteś tutaj 91
Metoda getStringExtra()
import android.app.Activity;
app/src/main
import android.content.Intent;
Musimy
import android.os.Bundle; zaimportować klasy
Intent i TextView. java
import android.widget.TextView;
com.hfad.komunikator
public class ReceiveMessageActivity extends Activity {
public static final String EXTRA_MESSAGE = ”message”;
ReceiveMessage
Activity.java
@Override To nazwa wartości, którą będziemy przekazywali w intencji.
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); Te dwa wiersze pobierają
intencję, a następnie używają
setContentView(R.layout.activity_receive_message); metody getStringExtra(),
by odczytać przekazany
Intent intent = getIntent(); w niej łańcuch znaków.
String messageText = intent.getStringExtra(EXTRA_MESSAGE);
TextView messageView = (TextView)findViewById(R.id.message);
messageView.setText(messageText);
}
} Ten wiersz wyświetla łańcuch znaków w widoku tekstowym.
92 Rozdział 3.
Wiele aktywności i intencji
3 Po uruchomieniu aktywność
ReceiveMessageActivity określa,
że używa układu activity_receive_ CreateMessageActivity
message.xml, i to właśnie on jest
wyświetlany na ekranie urządzenia. Cześć!
Aktywność aktualizuje także układ,
wyświetlając w nim tekst przekazany
w intencji. Urządzenie
ReceiveMessageActivity
<Layout>
</Layout>
activity_receive_message
jesteś tutaj 93
Jazda próbna
Okazuje się, że ze względu na sposób, w jaki zaprojektowano
Androida, nie jest to aż tak trudne, jak się wydaje.
Czy pamiętasz, jak na początku rozdziału napisaliśmy, że zadania to
sekwencje połączonych aktywności? No więc okazuje się, że nasze
możliwości nie ograniczają się do stosowania aktywności należących do
naszej aplikacji. Można wykraczać poza granice własnej i używać aktywności
należących do innych aplikacji.
94 Rozdział 3.
Wiele aktywności i intencji
jesteś tutaj 95
Stosowanie akcji
Kolejną rzeczą, której się nauczymy, będzie tworzenie intencji zwracających zbiór aktywności,
których można używać w standardowy sposób — na przykład do wysyłania wiadomości.
96 Rozdział 3.
Wiele aktywności i intencji
Gdyby istniały jeszcze jakieś inne informacje, które chcielibyśmy dodać do intencji,
to moglibyśmy to zrobić, używając kolejnych wywołań metody putExtra().
Na przykład poniższy kod pozwala określić temat wysyłanej wiadomości: Jeśli dana aplikacja nie
umożliwia określania tematu
intent.putExtra(Intent.EXTRA_SUBJECT, subject); wiadomości, to zostanie on
zignorowany. Jednak wszystkie
gdzie subject jest tekstem określającym temat wiadomości. aplikacje, które wiedzą, jak
używać tematu, zastosują go.
jesteś tutaj 97
Zastosowanie akcji
package com.hfad.komunikator;
Komunikator
import android.os.Bundle;
import android.app.Activity; app/src/main
import android.content.Intent;
java
import android.view.View;
import android.widget.EditText; com.hfad.komunikator
98 Rozdział 3.
Wiele aktywności i intencji
A… Intencja niejawna.
Muszę zatem znaleźć wszystkie
2 Android dowiaduje się, że intencję może
aktywności, które są w stanie
przekazać wyłącznie do aktywności, które
obsługiwać akcję ACTION_SEND,
potrafią wykonywać akcję ACTION_SEND
i operują na danych typu text/plain.
operują na danych typu text/plain
Sprawdza zatem wszystkie aktywności,
i należą do kategorii DEFAULT.
wyszukując spośród nich te, do których
może wysłać intencję.
Jeśli system nie znajdzie aktywności, które mogą
obsłużyć intencję, zostanie zgłoszony wyjątek
ActivityNotFoundException.
CreateMessageActivity Android
CreateMessageActivity
3 Jeśli tylko jedna aktywność jest
w stanie obsługiwać przekazaną intencję, Intencja
to Android uruchomi ją i przekaże
do niej intencję.
Do: Aktywność Android
messageText:”Cześć!”
Aktywność
jesteś tutaj 99
Co się dzieje?
Hej, użytkowniku!
Każda z tych aktywności
4 Jeśli więcej niż jedna może wysyłać wiadomości.
aktywność może odebrać Której z nich chcesz
intencję, Android użyć?
wyświetli okno dialogowe
pozwalające wybrać
jedną z nich i poprosi CreateMessageActivity
użytkownika o dokonanie
wyboru.
Android
Użytkownik
5 Kiedy użytkownik
wybierze aktywność,
której chce użyć,
Android uruchamia ją CreateMessageActivity
i przekazuje do niej
intencję. Intencja
Aktywność wyświetli
Android
dodatkowy tekst
przekazany w intencji Do: Wybrana aktywność
Użytkownik
jako treść wysyłanej messageText: „Cześć!”
wiadomości. Wybrana
aktywność
100 Rozdział 3.
Wiele aktywności i intencji
Filtr intencji (ang. intent filter) określa typy intencji, które mogą być obsługiwane
przez dany komponent. Na przykład poniższy fragment kodu dotyczy aktywności,
która potrafi obsługiwać akcje ACTION_SEND. Co więcej, aktywność ta obsługuje dane
typu MIME text/plain lub obrazy:
Ten wiersz informuje Androida,
że aktywność jest w stanie
<activity android:name=”ShareActivity”> obsługiwać akcję ACTION_SEND.
<intent-filter>
<action android:name=”android.intent.action.SEND”/> Ten filtr intencji musi
określać kategorię
<category android:name=”android.intent.category.DEFAULT”/> o wartości DEFAULT,
<data android:mimeType=”text/plain”/> gdyż w przeciwnym
razie nie będzie mógł
<data android:mimeType=”image/*”/> To są typy danych, obsługiwać intencji
które aktywność niejawnych.
</intent-filter>
potrafi obsługiwać.
</activity>
W pierwszej kolejności Android bierze pod uwagę filtry określające kategorię typu
android.intent.category.DEFAULT:
<intent-filter>
<category android:name=”android.intent.category.DEFAULT”/>
...
</intent-filter>
Intencje, które nie określają tej kategorii, zostaną pominięte, gdyż nie mogą one obsługiwać
intencji niejawnych.
Oprócz tego, jeśli w intencji
Następnie Android porównuje intencje z filtrami intencji, uwzględniając przy tym została określona kategoria,
podaną w nich akcję i obsługiwany typ MIME. Na przykład jeśli intencja określa akcję to Android sprawdzi także
kategorię podaną w filtrze
Intent.ACTION_SEND, gdyż została utworzona w poniższy sposób: intencji. To rozwiązanie jest
jednak stosowane raczej
Intent intent = new Intent(Intent.ACTION_SEND); sporadycznie, dlatego nie
przedstawimy go tutaj.
to Android uwzględni wyłącznie aktywności, których filtry intencji określają akcję
android.intent.action.SEND, używając następującego kodu:
<intent-filter>
<action android:name=”android.intent.action.SEND”/>
...
</intent-filter>
intent.setType(”text/plain”);
to Android uwzględni tylko te aktywności, które potrafią obsługiwać konkretny typ danych:
<intent-filter>
<data android:mimeType=”text/plain”/>
...
</intent-filter>
Jeśli w intencji nie został określony typ MIME, to system postara się go wywnioskować
na podstawie danych zapisanych w intencji.
Kiedy Android zakończy porównywanie intencji z filtrami intencji komponentów, będzie wiedział,
ile z nich udało mu się dopasować. Jeżeli uda mu się odnaleźć tylko jedno dopasowanie, to uruchomi
odpowiedni komponent (w naszym przypadku będzie to aktywność) i przekaże do niego intencję. Jeżeli
natomiast takich dopasowań będzie więcej, to Android poprosi użytkownika o wybranie jednego z nich.
102 Rozdział 3.
Wiele aktywności i intencji
<activity android:name=”SendActivity”>
<intent-filter>
<action android:name=”android.intent.action.SEND”/>
<category android:name=”android.intent.category.DEFAULT”/>
<data android:mimeType=”*/*”/>
</intent-filter>
</activity>
<activity android:name=”SendActivity”>
<intent-filter>
<action android:name=”android.intent.action.SEND”/>
<category android:name=”android.intent.category.MAIN”/>
<data android:mimeType=”text/plain”/>
</intent-filter>
</activity>
<activity android:name=”SendActivity”>
<intent-filter>
<action android:name=”android.intent.action.SENDTO”/>
<category android:name=”android.intent.category.MAIN”/>
<category android:name=”android.intent.category.DEFAULT”/>
<data android:mimeType=”text/plain”/>
</intent-filter>
</activity>
104 Rozdział 3.
Wiele aktywności i intencji
http://developer.android.com/tools/extras/oem-usb.html
http://developer.android.com/tools/device.html#setting-up
Pierwszym z wyświetlonych
urządzeń jest emulator.
To jest nasze
fizyczne urządzenie.
106 Rozdział 3.
Wiele aktywności i intencji
startActivity(chosenIntent);
Na kilku kolejnych stronach nieco dokładniej przyjrzymy się temu, co się dzieje
w momencie wywołania metody createChooser().
108 Rozdział 3.
Wiele aktywności i intencji
Widzę, że muszę
2 Android sprawdza, które aktywności
utworzyć okno dialogowe
mogą obsłużyć tę intencję, wyboru aktywności, które
sprawdzając ich filtry intencji. obsługują akcję SEND i operują
na danych typu text/plain.
Porównywane są akcja, typ danych
oraz kategoria, obsługiwane przez
poszczególne aktywności.
CreateMessageActivity Android
Dzięki za intencję,
Androidzie. A czy możesz
teraz uruchomić tę
5 Aktywność prosi
intencję?
system o uruchomienie
aktywności określonej
Intencja
w intencji.
Do: WybranaAktywność
message:”Cześć!”
CreateMessageActivity Android
CreateMessageActivity
6 Android uruchamia
aktywność określoną
w intencji i przekazuje
do niej tę intencję.
Intencja
110 Rozdział 3.
Wiele aktywności i intencji
Komunikator
Zaktualizuj plik strings.xml…
Chcemy, by okno dialogowe do wyboru aktywności miało tytuł Wysyłanie app/src/main
wiadomości…, a zatem do pliku strings.xml dodaj zasób łańcuchowy o nazwie
chooser i wartości Wysyłanie wiadomości... (nie zapomnij także res
o zapisaniu zmienionego pliku).
... values
<string name=”chooser”>Wysyłanie wiadomości...</string> <xml>
</xml>
... strings.xml
...
java
// Metoda onSendMessage() jest wywoływana po kliknięciu przycisku
public void onSendMessage(View view) {
com.hfad.komunikator
EditText messageView = (EditText)findViewById(R.id.message);
String messageText = messageView.getText().toString();
Intent intent = new Intent(Intent.ACTION_SEND); CreateMessage
intent.setType(”text/plain”); Activity.java
intent.putExtra(Intent.EXTRA_TEXT, messageText); Pobranie tytułu okna.
String chooserTitle = getString(R.string.chooser);
Intent chosenIntent = Intent.createChooser(intent, chooserTitle);
startActivity(intent);
startActivity(chosenIntent); Uruchomienie aktywności Wyświetlenie okna dialogowego
} wybranej przez użytkownika. wyboru aktywności.
...
Metoda getString() służy do pobierania wartości zasobów łańcuchowych.
Ma ona jeden parametr określający identyfikator zasobu (w naszym przypadku
jest to R.string.chooser):
Jeśli zajrzysz do pliku R.java, znajdziesz
getString(R.string.chooser); chooser w klasie wewnętrznej o nazwie string.
Teraz, kiedy już zaktualizowaliśmy aplikację, zobaczmy, jak w działaniu wygląda nasze
nowe okno dialogowe wyboru aktywności.
jesteś tutaj 111
Jazda próbna
112 Rozdział 3.
Wiele aktywności i intencji
Nie istnieją
głupie pytania
ale mo żes z
w tym rozdzi
Opanowałeś już rozdział 3. i dodałeś pobrać z serwe ra FTP
do swojego przybornika z narzędziami wydawnictwa Helion:
klady/
umiejętności tworzenia aplikacji zawierających ftp://ftp.helion.pl/przy
wiele aktywności i stosowania intencji. andrrg.zip
CELNE SPOSTRZEŻENIA
Zadanie to co najmniej dwie aktywności połączone w sekwencję.
Element <EditText> definiuje pole służące do wpisywania tekstów. Klasa EditText dziedziczy po klasie View.
W Android Studio nowe aktywności można dodawać, wybierając z menu opcję File/New.../Activity.
Każdej tworzonej aktywności musi odpowiadać wpis w pliku AndroidManifest.xml.
Intencje to komunikaty, których komponenty systemu Android używają do wzajemnej komunikacji.
Intencja jawna w jawny sposób określa komponent, do którego jest skierowana. Takie intencje można tworzyć,
używając wywołania o postaci Intent intent = new Intent(this, KlasaDocelowa.class);.
Aby uruchomić aktywność, wystarczy użyć wywołania startActivity(intent). Jeśli nie uda się znaleźć
odpowiednich aktywności, to takie wywołanie zgłosi wyjątek ActivityNotFoundException.
Metoda putExtra() pozwala na dodawanie do intencji dodatkowych informacji.
Intencję, która doprowadziła do uruchomienia aktywności, można pobrać przy użyciu metody getIntent().
Grupa metod get*Extra() pozwala pobierać dodatkowe informacje zapisane w intencji metoda
getStringExtra() pobiera łańcuchy znaków, getIntExtra() pobiera liczby całkowite, i tak dalej.
Akcja aktywności określa standardową czynność, którą dana aktywność potrafi wykonywać. Aby wysłać wiadomość,
należy użyć akcji ACTION_SEND.
W celu utworzenia intencji niejawnej umożliwiającej wykonanie określonej akcji należy użyć wywołania Intent intent
= new Intent(akcja);.
Metoda setType() pozwala określić typ danych zapisanych w intencji.
Android wyznacza intencje na podstawie nazwy komponentu, akcji, typu danych oraz kategorii, określonych w intencji.
Wszystkie te informacje są porównywane z zawartością filtrów intencji zapisanych w pliku AndroidManifest.xml, którym
dysponuje każda aplikacja na Androida. Aby aktywność mogła odbierać i obsługiwać intencje niejawne, musi mieć
kategorię o wartości DEFAULT.
Metoda createChooser() pozwala przesłonić domyślne systemowe okno dialogowe wyboru aktywności. Metoda ta
pozwala określić tytuł i nie zapewnia użytkownikowi możliwości ustawienia aktywności domyślnej. Jeśli żadna aktywność
nie jest w stanie obsłużyć intencji, to metoda wyświetli stosowny komunikat. Metoda createChooser() zwraca obiekt
klasy Intent.
Wartość zasobu łańcuchowego można pobrać, używając wywołania getString(R.string.nazwalancucha);.
114 Rozdział 3.
4. Cykl życia aktywności
Aplikacja
Aktywność
Aktywność
Aktywność Aktywność
Aktywność
Aktywność
Aktywność Aktywność
Aktywność
116 Rozdział 4.
Cykl życia aktywności
Aplikacja 1 Aplikacja 2
startActivity()
Intencja
Aktywność Aktywność
Intencja
Aktywność Aktywność
Android
Aktywność Aktywność
onCreate()
Aktywność
Android
Niemniej jednak cały czas nie wiemy jeszcze wielu rzeczy na temat sposobu
działania aktywności. Jak długo istnieją aktywności? Co się dzieje, kiedy
aktywność znika z ekranu? Czy wciąż działa? Czy wciąż zajmuje miejsce
w pamięci? I co się dzieje, gdy działanie aplikacji zostaje przerwane przez
nadchodzącą rozmowę telefoniczną? Chcielibyśmy mieć możliwość kontrolowania
działania aktywności w całym zakresie różnych sytuacji. Ale jak to zrobić?
Aplikacja stopera
W tym rozdziale przyjrzymy się dokładniej tajnikom działania aktywności,
często spotykanym przyczynom problemów z aplikacjami oraz sposobom
rozwiązywania tych problemów za pomocą metod cyklu życia aktywności.
Metody te mamy zamiar poznać, pisząc prostą aplikację — Stoper.
To jest liczba
sekund.
Kiedy klikniesz
przycisk Start, liczba
sekund zaczyna być
inkrementowana.
Kiedy klikniesz
przycisk Stop, liczba
sekund przestaje być
inkrementowana.
118 Rozdział 4.
Cykl życia aktywności
<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” Stoper
android:paddingBottom=”16dp”
android:paddingLeft=”16dp” app/src/main
android:paddingRight=”16dp”
res
android:paddingTop=”16dp”
tools:context=”.StopwatchActivity” > layout
<xml>
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_alignParentTop=”true”
android:layout_centerHorizontal=”true”
android:layout_marginTop=”0dp”
android:text=””
android:textAppearance=”?android:attr/textAppearanceLarge”
android:textSize=”92sp” /> Te atrybuty sprawią, że czas
wyświetlany przez nasz stoper
będzie ładny i duży.
<Button
android:id=”@+id/start_button”
Ten kod dotyczy przycisku
android:layout_width=”wrap_content” Start. Jego kliknięcie
android:layout_height=”wrap_content” powoduje wywołanie
metody onClickStart().
android:layout_below=”@+id/time_view”
android:layout_centerHorizontal=”true”
android:layout_marginTop=”32dp”
android:onClick=”onClickStart” Dalsza część
kodu układu
android:text=”@string/start” /> znajduje się
na następnej
stronie.
android:text=”@string/stop” />
strings.xml
Układ jest gotowy! Teraz zajmijmy się aktywnością.
120 Rozdział 4.
Cykl życia aktywności
package com.hfad.stoper;
import android.os.Bundle;
Stoper
import android.app.Activity;
import android.view.View;
app/src/main
Metoda runTimer()
Kolejną rzeczą, którą musimy napisać, jest metoda runTimer(). Metoda ta
pobierze referencję do komponentu TextView umieszczonego w układzie
aplikacji, sformatuje liczbę sekund do postaci łańcucha znaków prezentującego
liczbę godzin, minut i sekund, a następnie wyświetli ten łańcuch znaków
w układzie. Oprócz tego, jeśli wartość zmiennej running będzie wynosiła true,
to metoda runTimer() dodatkowo inkrementuje wartość zmiennej seconds.
Poniżej przedstawiliśmy kod tej metody:
Pobranie referencji do komponentu
private void runTimer() { TextView.
final TextView timeView = (TextView)findViewById(R.id.time_view);
...
int hours = seconds/3600;
int minutes = (seconds%3600)/60; Ten fragment formatuje liczbę
W tych
miejscach int secs = seconds%60; sekund do postaci godzin,
pominęliśmy minut i sekund. To zwyczajny
krótkie String time = String.format(”%d:%02d:%02d”, kod napisany w Javie.
fragmenty hours, minutes, secs);
kodu.
Przedstawimy timeView.setText(time);
je na Ta instrukcja wyświetla tekst w komponencie TextView.
następnej if (running) {
stronie. Jeśli zmienna running ma
seconds++;
wartość true, to ta instrukcja
} inkrementuje wartość zmiennej
seconds.
...
}
Metoda post()
Metoda post() przekazuje kod, który ma zostać wykonany najszybciej jak to
możliwe (czyli zazwyczaj niemal natychmiast). Metoda ta ma jeden parametr
— obiekt typu Runnable. W Androidowie obiekty Runnable odpowiadają
obiektom tego samego typu stosowanym w zwyczajnych programach pisanych
w Javie — reprezentują zadanie, które należy wykonać. Kod, który chcemy
wykonać, należy umieścić w metodzie run() obiektu Runnable, a obiekt
Handler zadba o to, by został on wykonany możliwie jak najszybciej.
Oto, jak wygląda implementacja takiego rozwiązania:
Metoda postDelayed()
Metoda postDelayed() działa bardzo podobnie do metody post(), z tym,
że używamy jej do przesyłania kodu, który ma zostać wykonany w przyszłości.
Metoda postDelayed() ma dwa parametry: obiekt Runnable oraz liczbę typu
long. Obiekt Runnale, w swojej metodzie run(), zawiera kod, który należy
wykonać, natomiast liczba określa o ile milisekund chcemy opóźnić jego
wykonanie. Kod zostanie wykonany możliwie jak najszybciej po upłynięciu
czasu określonego przez opóźnienie. Poniżej przedstawiliśmy przykład
zastosowania tej metody:
124 Rozdział 4.
Cykl życia aktywności
package com.hfad.stoper;
Stoper
import android.os.Bundle;
import android.os.Handler; app/src/main
import android.app.Activity;
java
import android.view.View;
import android.widget.TextView; com.hfad.stoper
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_stopwatch);
runTimer();
Do aktualizacji stopera używamy innej
} metody. Wywołujemy ją w momencie
tworzenia aktywności.
126 Rozdział 4.
Cykl życia aktywności
Urządzenie
Użytkownik
<xml>
</xml>
AndroidManifest.xml Android
3 Android sprawdza, czy jest uruchomiony inny proces tej samej aplikacji,
a jeśli takiego nie ma, to tworzy nowy proces.
Następnie system tworzy nowy obiekt aktywności — w naszym przypadku będzie to
obiekt aktywności StopwatchActivity.
Proces 1
Aplikacja 1
Android
128 Rozdział 4.
Cykl życia aktywności
runTimer() <xml>
</xml>
StopwatchActivity Układ
seconds=0
StopwatchActivity
Urządzenie running=false
Nie istnieją
głupie pytania
P: Dlaczego Android uruchamia aplikację w odrębnym P : Czy nie można by napisać w metodzie onCreate()
procesie? pętli, która aktualizowałaby stoper?
O: Ze względów bezpieczeństwa i dla zapewnienia stabilności. O: Nie. Wykonywanie metody onCreate() musi się zakończyć
Uniemożliwia to aplikacji uzyskanie dostępu do danych innych przed wyświetleniem ekranu aplikacji. Umieszczenie w niej
aplikacji. Oprócz tego oznacza to, że jeśli aplikacja ulegnie awarii, nieskończonej pętli uniemożliwiłoby zatem wyświetlenie układu.
nie będzie to miało zgubnych konsekwencji dla innych aplikacji.
P: Metoda runTimer() wygląda na naprawdę złożoną.
P: Do czego służy metoda onCreate()? Nie prościej by Czy naprawdę trzeba wykonywać te wszystkie operacje?
O: Android musi przygotować środowisko na potrzeby gdy będziemy musieli zaplanować wykonanie kodu, używane
aktywności już po jej utworzeniu. Kiedy aktywność jest już gotowa, rozwiązanie będzie wyglądało podobnie do tego z metody
Android wywołuje jej metodę onCreate(). To właśnie dlatego runTimer().
kod przygotowujący ekran aplikacji jest wykonywany w metodzie
onCreate(), a nie w konstruktorze aktywności.
130 Rozdział 4.
Cykl życia aktywności
Co się stało?
A więc co się stało, że po obróceniu urządzenia w aplikacji wystąpił błąd?
seconds=12
StopwatchActivity
Urządzenie running=true
Urządzenie
132 Rozdział 4.
Cykl życia aktywności
134 Rozdział 4.
Cykl życia aktywności
Istnieją dwa rozwiązania: można nakazać, żeby system pominął ponowne uruchamianie
aktywności, albo można zapisać stan aktywności w taki sposób, by nowa aktywność mogła
go odtworzyć. Teraz przyjrzymy się obu tym rozwiązaniom nieco dokładniej.
android:configChanges=”zmiana_konfiguracji”
Stoper
gdzie zmiana_konfiguracji jest typem zmiany konfiguracji urządzenia.
app/src/main
W naszym przypadku chcemy, by system pomijał zmiany wielkości i orientacji <xml>
ekranu, dlatego musimy dodać do pliku AndroidManifest.xml następujący fragment </xml>
kodu: AndroidManifest.xml
Jeśli Android rozpozna zmianę konfiguracji, której typ został podany w atrybucie,
to zamiast standardowego odtwarzania aktywności zostanie wywołana metoda
onConfigurationChanged(Configuration):
Tę metodę można przesłonić, aby obsługiwać zmiany konfiguracji, o ile pojawi się
taka konieczność.
136 Rozdział 4.
Cykl życia aktywności
gdzie bundle jest nazwą zmiennej typu Bundle, * określa typ zapisywanej
wartości, a nazwa i wartość to odpowiednio nazwa i wartość zapisywanej
danej. Na przykład aby zapisać w obiekcie Bundle wartość typu int
Aktywność
przechowywanej w zmiennej seconds, należałoby użyć następującego usunięta
wywołania:
bundle.putInt(”seconds”, seconds);
W jednym obiekcie typu Bundle można zapisać wiele takich par danych.
@Override app/src/main
public void onSaveInstanceState(Bundle savedInstanceState)
{ java
savedInstanceState.putInt(”seconds”, seconds);
savedInstanceState.putBoolean(”running”, running); com.hfad.stoper
Te wywołania
} zapisują wartości
zmiennych seconds Stopwatch
Skoro już zapisaliśmy wartość naszych zmiennych w obiekcie Bundle, i running w obiekcie
Activity.java
Bundle.
możemy z nich skorzystać w metodzie onCreate().
bundle.get*(”nazwa”);
138 Rozdział 4.
Cykl życia aktywności
seconds=8
StopwatchActivity
Urządzenie running=true
Zaraz mnie
usuną, muszę
was zapisać…
seconds=8
StopwatchActivity
Urządzenie
running=true
bundle
“seconds”=8
“running”=true
Urządzenie
seconds=0
StopwatchActivity
bundle
running=false
“seconds”=8
“running”=true
seconds=8
StopwatchActivity
Urządzenie
running=true
bundle
“seconds”=8
“running”=true
140 Rozdział 4.
Cykl życia aktywności
Nie istnieją
głupie pytania
P: Dlaczego Android chce P: Dlaczego Android nie zapisuje P: Czy Bundle to jakiś rodzaj mapy
odtwarzać aktywność tylko dlatego, automatycznie wartości wszystkich zaimplementowanej w Javie?
że obróciliśmy ekran? zmiennych? Dlaczego muszę ten kod
pisać ręcznie? O: Nie, ale ta klasa została
O: Metoda onCreate() jest normalnie zaprojektowana, by działać podobnie do
używana do przygotowywania ekranu. Jeśli O: Może się zdarzyć, że nie będziesz klasy java.util.Map. Obiekty klasy
jej kod jest zależny od konfiguracji ekranu (na chciał zapisywać wartości wszystkich Bundle mają dodatkowe możliwości,
przykład jeśli używamy odmiennych układów zmiennych. Na przykład możesz używać którymi nie dysponują mapy, na przykład
dla urządzeń działających w orientacji zmiennej zawierającej bieżącą szerokość można je przekazywać pomiędzy
pionowej i poziomej), to będziemy chcieli, ekranu. Zapewne nie chciałbyś odtwarzać procesami. To naprawdę bardzo przydatna
by metoda onCreate() była wywoływana wartości tej zmiennej podczas ponownego możliwość, gdyż pozwala Androidowi mieć
po każdej zmianie konfiguracji. Co więcej, wywołania metody onCreate(). dostęp do bieżącego stanu aktywności.
możemy także chcieć zmieniać teksty
wyświetlane w interfejsie użytkownika
aplikacji, kiedy użytkownik zmieni wybrane
ustawienia lokalne.
Na przykład załóżmy, że w trakcie pomiaru czasu za pomocą naszej aplikacji Nawet jeśli wcale nie chcesz,
by aplikacja stopera działała
stopera zadzwoni telefon. W takim przypadku, pomimo tego, że stoper w taki sposób, to jest to doskonały
nie będzie widoczny, wciąż będzie działał. A co zrobić, gdybyśmy chcieli, pretekst do przedstawienia kolejnych
metod cyklu życia aktywności.
aby niewidoczny stoper był zatrzymywany i wznawiał pomiar czasu po jego
ponownym wyświetleniu?
142 Rozdział 4.
Cykl życia aktywności
Gdy przesłaniamy metody cyklu życia aktywności, bardzo ważne jest, by w ich
kodzie w pierwszej kolejności wywołać przesłanianą metodę klasy bazowej
— w naszym przypadku będzie to metoda onStop():
super.onStop();
W przypadku przesłaniania
Istnieje kilka ważnych powodów, by to robić. Po pierwsze, musimy się
upewnić, że aktywność wykona wszystkie czynności zaimplementowane
metod cyklu życia
w metodzie klasy bazowej. Po drugie, Android nigdy nam nie wybaczy aktywności koniecznie
pominięcia tej czynności — jeśli nie wywołamy metody klasy bazowej,
Android zgłosi wyjątek. musimy wywoływać
W przypadku naszej aplikacji chcemy, by wywołanie metody onStop() metody klasy bazowej.
spowodowało zatrzymanie stopera. W tym celu wystarczy, że przypiszemy
zmiennej running wartość false: Jeśli tego nie zrobimy,
@Override Android zgłosi wyjątek.
protected void onStop() {
super.onStop();
running = false;
}
A zatem nasz stoper już się zatrzymuje, gdy aktywność staje się niewidoczna.
Naszym kolejnym zadaniem jest uruchomienie stopera po ponownym
wyświetleniu aktywności.
144 Rozdział 4.
Cykl życia aktywności
Zaostrz ołówek
Teraz Twoja kolej. Zmień kod aktywności w taki sposób, że jeśli przed
wywołaniem metody onStop() stoper działał, to zacznie on działać
także po ponownym wyświetleniu aktywności.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_stopwatch);
if (savedInstanceState != null) {
seconds = savedInstanceState.getInt(”seconds”);
running = savedInstanceState.getBoolean(”running”);
}
runTimer();
z
To jest pierwsza część kodu aktywności. Musis
} zaimplementować metodę onStart() i wprow
adzić
.
drobne modyfikacje w kodzie innych metod
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
savedInstanceState.putInt(”seconds”, seconds);
savedInstanceState.putBoolean(”running”, running);
savedInstanceState.putBoolean(”wasRunning”, wasRunning);
}
@Override
protected void onStop() {
super.onStop();
running = false;
}
Zaostrz ołówek
Teraz Twoja kolej. Zmień kod aktywności w taki sposób, że jeśli przed
Rozwiązanie wywołaniem metody onStop() stoper działał, to zacznie on działać
także po ponownym wyświetleniu aktywności.
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
savedInstanceState.putInt(”seconds”, seconds);
savedInstanceState.putBoolean(”running”, running);
savedInstanceState.putBoolean(“wasRunning”, wasRunning); Zapisujemy stan zmiennej
} wasRunning.
@Override
protected void onStop() {
super.onStop(); Rejestrujemy, czy w momencie wywoływania
wasRunning = running; metody onStop() stoper działał, czy nie.
running = false;
}
@Override
protected void onStart() {
super.onStart();
To jest implementacja metody
if (wasRunning) { onStart(). Jeśli stoper działał,
to niech dalej działa.
running = true;
}
}
146 Rozdział 4.
Cykl życia aktywności
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
savedInstanceState.putInt(”seconds”, seconds);
savedInstanceState.putBoolean(”running”, running);
savedInstanceState.putBoolean(“wasRunning”, wasRunning); Tu zapisujemy stan
} zmiennej wasRunning.
@Override
protected void onStop() {
super.onStop(); Ta instrukcja zapisuje, czy w momencie
wasRunning = running; wywoływania metody onStop() stoper działał,
czy nie.
running = false;
}
@Override
protected void onStart() {
super.onStart();
To jest implementacja metody
if (wasRunning) { onStart(). Jeśli wcześniej
running = true; stoper działał, to tu go
ponownie uruchamiamy.
}
}
...
jesteś tutaj 147
Co się dzieje?
seconds=16
Stopwatch running=true
Activity
Urządzenie
wasRunning=false
seconds=16
Aktywność wciąż W metodzie onStop()
istnieje, choć nie jest zmiennej running
widoczna. zostaje przypisana
wartość false.
Stopwatch running=false
Activity
Urządzenie
wasRunning=true
seconds=16
W metodzie onStart()
zmiennej running
running=true przypisywana jest
Stopwatch wartość true.
Activity
Urządzenie
wasRunning=true
148 Rozdział 4.
Cykl życia aktywności
Włączyliśmy stoper,
a następnie przeszliśmy
do ekranu głównego
urządzenia.
Na czas, w którym
aplikacja była
niewidoczna, stoper
został zatrzymany. Po ponownym wyświetleniu
aplikacji stoper wznowił
pomiar czasu.
Nie istnieją
głupie pytania
P: Czy zamiast tych dwóch metod moglibyśmy P: A czym różni się ta sytuacja?
zastosować metodę onRestart()?
O: W przypadku obrócenia urządzenia dotychczasowa
O : Metoda onRestart() jest używana w sytuacjach, w których aktywność jest usuwana, a na jej miejsce jest tworzona nowa.
chcemy wykonać jakiś kod, gdy aplikacja zostaje wyświetlona po Gdybyśmy zatem umieścili kod w metodzie onRestart(), to nie
jej wcześniejszym ukryciu. Nie jest ona natomiast wywoływana zostałby on wykonany po ponownym utworzeniu aktywności.
podczas wyświetlania aktywności po raz pierwszy. W naszym Metoda onStart() jest wywoływana w obu tych przypadkach.
przypadku chcieliśmy, by aplikacja cały czas działała według
założeń, nawet po obróceniu urządzenia.
Kiedy aktywność jest widoczna, lecz nie dysponuje miejscem wprowadzania, jej działanie zostaje
wstrzymane. Może się to stać, gdy aktywność zostanie przesłonięta przez inną, która nie zajmuje całego
ekranu albo jest przezroczysta. Aktywność wyświetlona na wierzchu będzie dysponować miejscem
wprowadzania, lecz ta pod nią wciąż będzie widoczna, choć jej działanie zostanie wstrzymane.
Nasza aktywność
stopera wciąż
jest widoczna,
lecz została
częściowo
przesłonięta
i nie dysponuje
miejscem
wprowadzania.
Aktywność zmienia stan
na wstrzymaną, gdy
nie dysponuje miejscem
wprowadzania, lecz
cały czas jest widoczna
dla użytkownika. Taka
To jest aktywność aktywność cały czas żyje
należąca do
innej aplikacji, i zachowuje wszystkie
wyświetlona na
naszym stoperze. informacje o swoim
stanie.
Istnieją dwie metody cyklu życia aktywności, które są związane ze wstrzymywaniem jej działania
i jego wznawianiem: onPause() i onResume(). Metoda onPause() jest wywoływana, gdy
aktywność jest widoczna, lecz inna aktywność dysponuje miejscem wprowadzania. Z kolei metoda
onResume() jest wywoływana bezpośrednio przed momentem, gdy aktywność ponownie zacznie
prowadzić interakcję z użytkownikiem. Jeśli aplikacja musi w jakiś sposób reagować na wstrzymanie
i wznowienie działania aktywności, konieczne będzie zaimplementowanie tych dwóch metod.
150 Rozdział 4.
Cykl życia aktywności
Początkowa aktywność
1 Użytkownik uruchamia aktywność.
Zostają wywołane metody onCreate(), onStart()
Aktywność oraz onResume() cyklu życia aktywności.
uruchomiona
2 Nasza aktywność jest przesłaniana przez inną.
onCreate() Zostaje wywołana metoda onPause() aktywności.
Aktywność
Aktywność uruchomiona
usunięta
onCreate()
3
onStart() 4
152 Rozdział 4.
Cykl życia aktywności
Aktywność
Aktywności mogą przechodzić uruchomiona
bezpośrednio od wywołania
onStart() do wywołania
onStop(), pomijając przy tym
onCreate()
wywołania metod onPause()
i onResume().
@Override
protected void onResume() {
super.onResume();
Zrób to sam!
if (wasRunning) {
running = true;
}
} W swoim kodzie zastąp
metody onStop() i onStart()
Przekonajmy się teraz, co się stanie po uruchomieniu aplikacji. przedstawionymi obok
metodami onPause() i onResume().
154 Rozdział 4.
Cykl życia aktywności
seconds=15
running=true
Stopwatch
Activity
Urządzenie
wasRunning=false
seconds=15
Aktywność zostaje
wstrzymana, gdyż W metodzie
jest widoczna, ale onPause() zmiennej
nie na pierwszym running zostaje
running=false przypisana wartość
planie. Stopwatch false.
Activity
Urządzenie
wasRunning=true
seconds=15
W metodzie onResume()
zmiennej running zostaje
running=true przypisana wartość true.
Stopwatch
Activity
Urządzenie
wasRunning=true
Nie istnieją
głupie pytania
P: Niektóre z metod cyklu życia aktywności Metody onCreate() i onStart() zawsze będą wywoływane
w odpowiednim czasie, a to oznacza, że nasza aplikacja może
nie zawsze są wywoływane. Czy to nie może być
zadbać o to, by na początku jej działania wszystko z nią było
przyczyną dziwacznego działania aplikacji?
w porządku. A to jest znaczenie ważniejsze.
O: W niektórych okolicznościach Android może pominąć Najważniejsze jest jednak, by dobrze zrozumieć, które
wywołania metod onStop() i onPause(). Zazwyczaj metody cyklu życia aplikacji są wywoływane w różnych
zawierają one kod wykonujący operacje porządkowe. okolicznościach.
156 Rozdział 4.
Cykl życia aktywności
package com.hfad.stoper;
Stoper
import android.os.Bundle;
import android.os.Handler;
app/src/main
import android.app.Activity;
import android.view.View;
java
import android.widget.TextView;
com.hfad.stoper
public class StopwatchActivity extends Activity {
// Liczba sekund wyświetlana przez stoper
private int seconds = 0; Stopwatch
Zmiennych seconds, running i wasRunning Activity.java
// Czy stoper działa? używamy odpowiednio do przechowywania liczby
private boolean running; zmierzonych sekund, określenia, czy stoper działa,
i określenia, czy stoper działał w momencie
private boolean wasRunning; wstrzymywania aktywności.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); Ten fragment kodu odtwarza
setContentView(R.layout.activity_stopwatch); stan stopera w razie usunięcia
i ponownego utworzenia
if (savedInstanceState != null) { aktywności.
seconds = savedInstanceState.getInt(”seconds”);
running = savedInstanceState.getBoolean(”running”);
wasRunning = savedInstanceState.getBoolean(”wasRunning”);
}
runTimer();
}
158 Rozdział 4.
Cykl życia aktywności
160 Rozdział 4.
Cykl życia aktywności
onCreate() Gdy aktywność jest tworzona po raz pierwszy. Można jej używać onStart()
do normalnej, statycznej inicjalizacji, takiej jak tworzenie widoków.
Metoda udostępnia także obiekt Bundle, zapewniający dostęp do
zapisanego wcześniej stanu aktywności.
onStart() Gdy aktywność staje się widoczna. Po niej jest wywoływana metoda onResume()
onResume(), jeśli aktywność ma zostać wyświetlona na pierwszym lub onStop()
planie, lub metoda onStop(), jeśli aktywność ma zostać ukryta.
onPause() Gdy aktywność nie jest już wyświetlana na pierwszym planie, onResume()
ponieważ ma zostać wznowione działanie innej aktywności. lub onStop()
Następna aktywność zostanie uruchomiona dopiero po wykonaniu
tej metody, zatem musi ona działać bardzo szybko. Jeśli aktywność
ma być ponownie wyświetlona na pierwszym planie, to w następnej
kolejności wywoływana jest metoda onResume(), jeśli natomiast
aktywność jest ukrywana, to kolejną wywoływaną metodą będzie
onStop().
onStop() Gdy aktywność nie jest już widoczna. Przyczyną może być onRestart()
przesłonięcie danej aktywności przez inną bądź też planowane lub onDestroy()
usunięcie aktywności. Jeśli aktywność ma zostać ponownie
wyświetlona, to po tej metodzie zostanie wywołana metoda
onRestart(), jeśli natomiast aktywność ma zostać usunięta,
to następną wywołaną metodą będzie onDestroy().
onDestroy() Gdy aktywność ma zostać usunięta lub gdy kończy działanie. żadna
ale mo żes z
w tym rozdzi
Opanowałeś już rozdział 4. i dodałeś pobrać z ser we ra FT P
do swojego przybornika z narzędziami wydawnictwa Helion:
klady/
znajomość metod tworzących cykl życia ftp://ftp.helion.pl/przy
aktywności. andrrg.zip
Aktywność
CELNE SPOSTRZEŻENIA uruchomiona
Każda aplikacja domyślnie działa we własnym procesie.
Tylko główny wątek aplikacji może aktualizować jej
interfejs użytkownika. onCreate()
Do planowania wykonania kodu lub przekazywania
go do innego wątku można używać obiektów klasy
Handler. onStart()
Zmiana konfiguracji urządzenia skutkuje usunięciem
i ponownym utworzeniem aktywności.
Aktywność dziedziczy metody cyklu życia po klasie onResume()
Activity. W razie przesłaniania którejkolwiek z tych
metod należy wywołać przesłanianą metodę klasy
bazowej.
Metoda onSaveInstanceState(Bundle) pozwala Aktywność onRestart()
zapisać stan aktywności przed jej usunięciem. Tego działająca
samego obiektu Bundle można użyć w metodzie
onCreate() do odtworzenia stanu aktywności.
Do zapisywania wartości w obiekcie Bundle służą
onPause()
metody bundle.put*(”nazwa”, wartosc).
Wartości te można następnie odczytywać, używając
metod bundle.get*(”nazwa”).
Metody onCreate() i onDestroy() obsługują onStop()
odpowiednio narodziny i śmierć aktywności.
Metody onRestart(), onStart() oraz onStop()
są związane ze zmianami widzialności aktywności. onDestroy()
Metody onResume() i onPause() są związane
z uzyskiwaniem i traceniem przez aktywność miejsca
wprowadzania.
Aktywność
usunięta
162 Rozdział 4.
5. Interfejs użytkownika
Podziwiaj widoki
Zapamiętaj: layout_row=”18”
i layout_column=”56”.
A nie: „za tym białym”.
W tym rozdziale pokażemy kilka innych układów, których możesz używać, tworząc
aplikacje, oraz parę dodatkowych komponentów GUI, dzięki którym Twoje
aplikacje staną się bardziej interaktywne. Zacznijmy od układów.
164 Rozdział 5.
Interfejs użytkownika
LinearLayout
Układ liniowy umieszcza poszczególne widoki
jeden obok drugiego, w pionie lub w poziomie. Jeśli
układ rozmieszcza widoki w pionie, to utworzą one
pojedynczą kolumnę. Jeśli natomiast układ rozmieszcza
widoki w poziomie, to utworzą one pojedynczy wiersz.
¨ RelativeLayout
Układ RelativeLayout rozmieszcza widoki ¨ LinearLayout
w sposób względny ¨ GridLayout
Jak już wiesz, układ względy pozwala określać położenie widoków względem układu
lub względem innych widoków w nim umieszczonych.
Układ względny definiuje się przy użyciu elementu <RelativeLayout>, takiego jak ten
przedstawiony poniżej:
166 Rozdział 5.
Interfejs użytkownika
¨ RelativeLayout
Dodawanie wypełnienia ¨ LinearLayout
¨ GridLayout
Jeśli chcesz, aby wokół układu pozostało troszkę wolnego miejsca,
możesz zastosować atrybuty padding. Pozwalają one określać,
ile miejsca chcemy pozostawić pomiędzy daną krawędzią układu
a jego elementem nadrzędnym. Poniżej pokazaliśmy, w jaki sposób
można nakazać zastosowanie wypełnienia o szerokości 16dp wokół
wszystkich krawędzi układu: paddingTop
<RelativeLayout ...
android:paddingBottom=”16dp” paddingLeft
android:paddingLeft=”16dp”
android:paddingRight=”16dp” Dodajemy wypełnienie Układ
android:paddingTop=”16dp”> o wielkości 16 dp.
...
</RelativeLayout>
<resources>
<dimen name=”activity_horizontal_margin”>16dp</dimen> Układ odczyta wartości
wypełnień z pliku zasobów
<dimen name=”activity_vertical_margin”>16dp</dimen> wymiarów.
</resources>
¨ RelativeLayout
Rozmieszczanie widoków względem układu nadrzędnego ¨ LinearLayout
¨ GridLayout
W przypadku stosowania układu względnego konieczne jest określenie położenia danego
widoku względem innego widoku należącego do układu bądź względem elementu nadrzędnego.
Elementem nadrzędnym widoku jest układ, w którym dany widok jest umieszczony.
Jeśli chcemy, by widok zawsze był umieszczony w konkretnym miejscu ekranu, niezależnie
od jego wielkości i orientacji, to jego położenie powinniśmy określać względem elementu
nadrzędnego. Na przykład poniżej pokazaliśmy, w jaki sposób można zapewnić, aby przycisk
zawsze był wyświetlony w prawym górnym rogu układu:
android:layout_alignParentTop=”true”
android:layout_alignParentRight=”true”
168 Rozdział 5.
Interfejs użytkownika
¨ RelativeLayout
Atrybuty do umiejscawiania widoków ¨ LinearLayout
względem układu nadrzędnego ¨ GridLayout
android:atrybut=”true”
Atrybut Działanie
¨ RelativeLayout
Rozmieszczanie widoków względem innych widoków ¨ LinearLayout
¨ GridLayout
Oprócz rozmieszania widoków względem układu nadrzędnego można je także
rozmieszczać względem innych widoków. To rozwiązanie doskonale sprawdza się,
gdy chcemy, by grupa widoków była wyrównana w taki sam sposób, niezależnie
od wielkości i orientacji ekranu.
Aby rozmieścić jeden widok względem innego, w widoku stanowiącym punkt odniesienia
należy określić jego identyfikator, używając w tym celu atrybutu android:id:
android:id=”@+id/button_click_me”
Składnia ”@+id” nakazuje, by Android dodał identyfikator do pliku R.java jako zasób.
Jeśli w tym wyrażeniu pominiemy znak +, to Android nie doda identyfikatora jako
zasobu, a podczas próby skompilowania aplikacji wystąpią błędy.
Poniżej pokazaliśmy, w jaki sposób można utworzyć dwa przyciski, z których pierwszy
jest umieszczony pośrodku układu, a drugi — poniżej pierwszego:
tu
Używamy tego przycisku jako punka
odniesienia do określenia położeni
imy
<RelativeLayout ... > drugiego przycisku, dlatego mus
określić jego identyfikator.
<Button
android:id=”@+id/button_click_me”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_centerInParent=”true”
android:text=”@string/click_me” />
<Button Drugi przycisk ma być
umieszczony poniżej
android:layout_width=”wrap_content” pierwszego, tak by lewe
android:layout_height=”wrap_content” krawędzie obu przycisków
były wyrównane.
android:layout_alignLeft=”@+id/button_click_me”
android:layout_below=”@+id/button_click_me”
android:text=”@string/new_button_text” />
</RelativeLayout>
android:layout_alignLeft=”@+id/button_click_me”
android:layout_below=”@+id/button_click_me”
170 Rozdział 5.
Interfejs użytkownika
¨ RelativeLayout
Atrybuty do rozmieszczania widoków ¨ LinearLayout
względem innych widoków ¨ GridLayout
android:atrybut=”@+id/identyfikator_widoku”
Twój widok jest
umieszczony powyżej.
Atrybut Działanie
android:layout_above Umieszcza widok powyżej widoku stanowiącego
punkt odniesienia.
Widok stanowiący
punkt odniesienia.
android:layout_below Umieszcza widok poniżej widoku stanowiącego
punkt odniesienia.
Twój widok jest
umieszczony poniżej.
Wyrównuje
android:layout_alignLeft Wyrównuje lewą krawędź widoku z lewą krawędzią lewe
widoku stanowiącego punktu odniesienia. krawędzie
widoków.
Wyrównuje
android:layout_alignRight Wyrównuje prawą krawędź widoku z prawą krawędzią prawe
widoku stanowiącego punktu odniesienia. krawędzie
widoków.
¨ RelativeLayout
Stosowanie marginesów do oddalania widoków od siebie ¨ LinearLayout
¨ GridLayout
Jeżeli zastosujesz przedstawione wyżej atrybuty do rozmieszczania widoków, układ nie będzie
pozostawiał pomiędzy nimi zbyt dużo wolnego miejsca. Aby powiększyć odstępy między
widokami, należy dodać do widoku jeden lub kilka marginesów.
<Button
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_alignLeft=”@+id/button_click_me”
android:layout_below=”@+id/button_click_me”
android:layout_marginTop=”50dp” Dodanie marginesu
powyżej dolnego 50dp
android:text=”@string/button_below” /> przycisku spowodowało
pojawienie się odstępu
</RelativeLayout> między przyciskami.
android:atrybut=”10dp”
Atrybut Działanie
android:layout_marginTop Dodaje dodatkowy obszar nad widokiem.
172 Rozdział 5.
Interfejs użytkownika
¨ RelativeLayout
RelativeLayout — podsumowanie ¨ LinearLayout
¨ GridLayout
Zanim przejdziemy do następnego typu układów, podsumujmy jeszcze, w jaki sposób są
tworzone układy względne.
<RelativeLayout xmlns:android=”http://schemas.android.com/apk/res/android”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:paddingBottom=”16dp”
android:paddingLeft=”16dp”
android:paddingRight=”16dp”
android:paddingTop=”16dp”...>
...
</RelativeLayout>
android:layout_marginTop=”5dp”
android:layout_marginBottom=”5dp”
android:layout_marginLeft=”5dp”
android:layout_marginRight=”5dp”
¨ RelativeLayout
Układ LinearLayout wyświetla widoki ¨ LinearLayout
w jednym wierszu lub kolumnie ¨ GridLayout
Układ liniowy wyświetla widoki jeden obok drugiego, umieszczając je bądź to w pionie,
bądź w poziomie. W przypadku rozmieszczania elementów w pionie utworzą one
pojedynczą kolumnę, natomiast w przypadku wyświetlania ich w poziomie — jeden wiersz.
<LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
android:layout_width=”match_parent” To są te same atrybuty, których
Aby utworzyć układ używaliśmy już w układzie
android:layout_height=”match_parent” względnym.
liniowy, musisz
użyć elementu android:orientation=”vertical” Widoki mają być rozmieszczane
<LinearLayout>.
...> w pionie.
...
</LinearLayout>
android:orientation=”vertical”
Widoki będą rozmieszczane w poziomie, jeśli zastosujemy atrybut o postaci: Układ liniowy o orientacji
poziomej.
android:orientation=”horizontal”
Układ
liniowy
o orientacji
pionowej.
W przypadku orientacji
pionowej poszczególne
widoki tworzą jedną kolumnę.
174 Rozdział 5.
Interfejs użytkownika
¨ RelativeLayout
Układ liniowy wyświetla widoki w kolejności ¨ LinearLayout
ich występowania w kodzie XML ¨ GridLayout
<Button
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”@string/click_me” />
</LinearLayout>
¨ RelativeLayout
Zmieńmy nieco prosty układ liniowy ¨ LinearLayout
¨ GridLayout
Na pierwszy rzut oka układ liniowy może się wydawać bardzo prosty i mało elastyczny.
W końcu jedyne, co potrafi, to organizowanie widoków w ściśle określonej kolejności.
Aby jednak zyskać nieco więcej elastyczności, możemy modyfikować wygląd układu,
korzystając z jego dodatkowych atrybutów. Aby pokazać te dodatkowe możliwości,
spróbujmy nieco zmodyfikować bardzo prosty układ liniowy.
176 Rozdział 5.
Interfejs użytkownika
¨ RelativeLayout
Początkowa wersja naszego układu liniowego ¨ LinearLayout
¨ GridLayout
Nasz układ liniowy składa się z dwóch pól tekstowych i przycisku. Przycisk ma etykietę
o treści Wyślij, natomiast pola tekstowe zawierają odpowiednio następujące teksty
podpowiedzi: Do i Treść wiadomości.
Tekst podpowiedzi pola tekstowego to napis, który jest wyświetlany w polu, gdy jest
ono puste. Ma on stanowić dla użytkownika podpowiedź sugerującą, co należy w pisać
w danym polu. Definiuje się go przy użyciu atrybutu android:hint:
<LinearLayout 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:paddingBottom=”16dp”
android:paddingLeft=”16dp”
android:paddingRight=”16dp”
android:paddingTop=”16dp”
android:orientation=”vertical”
tools:context=”.MainActivity” >
Szerokość pól tekstowych
jest równa szerokości układu
<EditText nadrzędnego.
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
Atrybut android:hint określa tekst podpowiedz
sugerujący, co użytkownik powinien wpisa i,
android:hint=”@string/to” /> ć
w danym polu.
Wartości
tych zasobów <EditText
łańcuchowych
zostały android:layout_width=”match_parent”
standardowo
zdefiniowane
android:layout_height=”wrap_content”
w pliku android:hint=”@string/message” />
strings.xml.
<Button
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”@string/send” />
</LinearLayout>
¨ RelativeLayout
Rozciągaaaaamy widok, zwiększając jego wagę ¨ LinearLayout
¨ GridLayout
Wysokość wszystkich widoków w naszym przykładowym układzie jest możliwie
jak najmniejsza — zajmują one w pionie tylko tyle miejsca, ile jest niezbędne
do wyświetlenia ich zawartości. My chcielibyśmy natomiast, by pole przeznaczone
do podania treści widomości zajęło całą wysokość układu dostępną po
wyświetleniu pozostałych widoków.
W tym celu musimy dodać temu polu tekstowemu nieco wagi (ang. weight).
Dodając widokowi wagę, informujemy, że ma on zająć nieco więcej miejsca
w układzie.
android:layout_weight=”liczba”
178 Rozdział 5.
Interfejs użytkownika
¨ RelativeLayout
Dodawanie wagi do widoków ¨ LinearLayout
¨ GridLayout
Chcemy, by pole tekstowe do podawania treści wiadomości zajmowało jak najwięcej
miejsca w układzie. W tym celu dodamy do niego atrybut layout_weight i przypiszemy
mu wartość 1. Ponieważ będzie on jedynym widokiem, w którym określimy wagę,
zostanie on rozciągnięty w pionie i zajmie całą dostępną wysokość układu pozostałą
po wyświetleniu reszty widoków. Poniżej przedstawiliśmy zmodyfikowany kod układu:
<EditText
To jest jedyny
widok w układzie, android:layout_width=”match_parent” Wysokość tego widoku zostanie określona
w którym android:layout_height=”0dp” przez układ liniowy na postawie wartości
określiliśmy wagę. atrybutu layout_weight. Przypisanie atrybutowi
Zostanie on zatem android:layout_weight=”1” layout_height wartości 0 dp jest bardziej
rozciągnięty i zajmie efektywne niż użycie wartości „wrap_content”.
cały wolny obszar android:hint=”@string/message” />
układu, który nie
został zużyty przez
pozostałe widoki. <Button
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”@string/send” />
</LinearLayout>
Przypisanie polu tekstowemu do podania treści wiadomości wagi 1 oznacza, Domyślnie tekst
że ma ono zająć cały dostępny obszar ekranu, który nie został zajęty przez podpowiedzi, Treść
inne widoki. Dzieje się tak dlatego, że do żadnego innego widoku nie wiadomości, jest
wyświetlany w połowie
dodaliśmy atrybutu layout_weight. wysokości pola tekstowego.
Zajmiemy się nim już
niebawem.
Pole do podania treści
wiadomości ma wagę 1.
Ponieważ jest to jedyny widok
układu, w którym została
określona waga, zostanie
ono rozciągnięte i zajmie
cały dostępny obszar układu
pozostały po wyświetleniu
reszty widoków.
¨ RelativeLayout
Dodawanie wagi do większej liczby widoków ¨ LinearLayout
¨ GridLayout
W naszym przykładzie atrybut layout_weight dodaliśmy tylko do jednego widoku.
A co by się stało, gdyby takich widoków było więcej?
<EditText
android:layout_width=”match_parent”
android:layout_height=”0dp”
android:layout_weight=”2”
android:hint=”@string/message” />
...
Pole Do ma wagę 1, zatem
</LinearLayout> zostanie mu przydzielona
1/3 pozostałego miejsca
w układzie.
180 Rozdział 5.
Interfejs użytkownika
¨ RelativeLayout
Wykorzystanie ciężkości do określania ¨ LinearLayout
miejsca wyświetlania tekstu ¨ GridLayout
android:gravity=”top”
A zatem dodamy teraz atrybut android:gravity do pola tekstowego Treść
wiadomości, tak by prezentowany w nim tekst podpowiedzi był wyświetlony
przy jego górnej krawędzi:
<LinearLayout ... >
...
<EditText
android:layout_width=”match_parent”
android:layout_height=”0dp”
android:layout_weight=”1” Tekst wyświetlany w polu tekstowym
android:gravity=”top” będzie widoczny u góry tego pola.
android:hint=”@string/message” />
...
</LinearLayout>
¨ RelativeLayout
Stosowanie atrybutu android:gravity ¨ LinearLayout
— lista wartości ¨ GridLayout
android:gravity=”wartosc”
Wartość Działanie
182 Rozdział 5.
Interfejs użytkownika
¨ RelativeLayout
Przesunięcie przycisku w prawo ¨ LinearLayout
za pomocą atrybutu layout_gravity ¨ GridLayout
android:layout_gravity=”right”
Atrybut android:layout_alignRight można stosować
wyłącznie w układach względnych
android:layout_gravity=”right”
¨ RelativeLayout
Inne wartości, których można używać ¨ LinearLayout
w atrybucie android:layout_gravity ¨ GridLayout
android:layout_gravity=”wartosc”
Wartość Działanie
top, bottom, left, right Umieszcza widok u góry, u dołu, z lewej bądź z prawej strony
pojemnika, czyli obszaru, w którym się on znajduje.
184 Rozdział 5.
Interfejs użytkownika
¨ RelativeLayout
Kompletny układ liniowy ¨ LinearLayout
¨ GridLayout
Oto kompletny kod naszego układu liniowego:
<LinearLayout 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:paddingBottom=”16dp”
android:paddingLeft=”16dp”
android:paddingRight=”16dp”
android:paddingTop=”16dp”
android:orientation=”vertical”
tools:context=”.MainActivity” >
<EditText
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
android:hint=”@string/to” />
<EditText
android:layout_width=”match_parent”
android:layout_height=”0dp”
android:layout_weight=”1”
android:gravity=”top”
android:hint=”@string/message” />
Zawartość pola do
<Button wprowadzania treści
wiadomości będzie
android:layout_width=”wrap_content” wyświetlana na samej górze
android:layout_height=”wrap_content” tego widoku. Dzięki temu
będziemy mieć naprawdę
android:layout_gravity=”right” dużo miejsca do wpisywania.
android:text=”@string/send” />
</LinearLayout>
¨ RelativeLayout
LinearLayout — podsumowanie ¨ LinearLayout
¨ GridLayout
Poniżej zamieściliśmy podsumowanie informacji o tworzeniu układów liniowych.
<LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:orientation=”vertical”
...>
...
</LinearLayout>
android:layout_weight=”1”
186 Rozdział 5.
Interfejs użytkownika
Zaostrz ołówek
Poniżej zamieściliśmy kod XML układu aplikacji Doradca piwny, którą
napisaliśmy w rozdziale 2. Zmień go na układ liniowy, którego wygląd
pokazaliśmy u dołu strony.
<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:paddingBottom=”16dp”
android:paddingLeft=”16dp”
android:paddingRight=”16dp”
android:paddingTop=”16dp”
tools:context=”.FindBeerActivity” >
<Spinner
android:id=”@+id/color”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_alignParentTop=”true”
android:layout_centerHorizontal=”true”
android:layout_marginTop=”37dp”
android:entries=”@array/beer_colors” />
<Button
android:id=”@+id/find_beer”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_alignLeft=”@+id/color”
android:layout_below=”@+id/color”
android:text=”@string/find_beer”
android:onClick=”onClickFindBeer” />
<TextView
android:id=”@+id/brands”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_alignLeft=”@+id/find_beer”
android:layout_below=”@+id/find_beer”
android:layout_marginTop=”18dp”
najbardziej
android:text=”@string/brands” /> Ten układ nie wygra nagrody dla
czy potrafisz
stylowego układu, ale ciekawe,
</RelativeLayout> kod XML tak,
zmodyfikować przedstawiony tu
by uzyskać taki sam wygląd.
Zaostrz ołówek
Rozwiązanie Poniżej zamieściliśmy kod XML układu aplikacji Doradca piwny, którą
napisaliśmy w rozdziale 2. Zmień go na układ liniowy, którego wygląd
pokazaliśmy u dołu strony.
<RelativeLinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
xmlns:tools=”http://schemas.android.com/tools”
Zmień układ android:layout_width=”match_parent”
na liniowy.
android:layout_height=”match_parent”
android:paddingBottom=”16dp” Układ liniowy musi zawierać atrybut
android:paddingLeft=”16dp” android:orientation. Użyj wartości
android:paddingRight=”16dp” „horizontal”, aby wyświetlać widoki
jeden obok drugiego w poziomie.
android:paddingTop=”16dp”
android:orientation=”horizontal”
tools:context=”.FindBeerActivity” >
<Spinner
android:id=”@+id/color”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_alignParentTop=”true” Te wiersze nie będą nam już potrzebne.
android:layout_centerHorizontal=”true”
android:layout_marginTop=”37dp”
android:entries=”@array/beer_colors” />
<Button
android:id=”@+id/find_beer”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_alignLeft=”@+id/color” Te wiersze nie będą nam już potr
zebne.
android:layout_below=”@+id/color”
android:text=”@string/find_beer”
android:onClick=”onClickFindBeer” />
<TextView
android:id=”@+id/brands”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_alignLeft=”@+id/find_beer” zebne.
Te wiersze nie będą nam już potr
android:layout_below=”@+id/find_beer”
Zmień układ android:layout_marginTop=”18dp”
na liniowy.
android:text=”@string/brands” />
</RelativeLinearLayout>
188 Rozdział 5.
Interfejs użytkownika
¨ RelativeLayout
Układ GridLayout wyświetla widoki w siatce ¨ LinearLayout
¨ GridLayout
Układ GridLayout dzieli ekran na siatkę składającą się z wierszy i kolumn
i pozwala umieszczać widoki we wskazanych komórkach:
Każdy Układ GridLayout
z tych wymaga stosowania
obszarów
jest
Obejrzyj to! API poziomu 14
komórką. lub wyższego.
<GridLayout xmlns:android=”http://schemas.android.com/apk/res/android”
android:layout_width=”match_parent” To są te same atrybuty, których
używaliśmy w układach innych
Tu należy użyć android:layout_height=”match_parent” typów.
znaczników
<GridLayout>. android:columnCount=”2” Ten atrybut określa, ile kolumn
ma mieć siatka (w tym przypadk
... > u
będą dwie kolumny).
...
</GridLayout>
Liczba kolumn, z których ma się składać siatka, jest określana w następujący sposób:
android:columnCount=”liczba”
gdzie liczba określa liczbę kolumn. W podobny sposób możemy określić liczbę
wierszy siatki:
android:rowCount=”liczba”
<TextView
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”@string/textview” />
<Button
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”@string/click_me” />
<EditText
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:hint=”@string/edit” />
</GridLayout>
190 Rozdział 5.
Interfejs użytkownika
1. kolumna 2. kolumna
<GridLayout 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:paddingBottom=”16dp”
android:paddingLeft=”16dp”
android:paddingRight=”16dp”
android:paddingTop=”16dp”
android:columnCount=”2”
tools:context=”.MainActivity” >
</GridLayout>
Skoro zdefiniowaliśmy podstawę układu, możemy zacząć dodawać do niego poszczególne widoki.
192 Rozdział 5.
Interfejs użytkownika
<GridLayout...>
<TextView W układach siatki można używać
android:layout_width=”wrap_content”
atrybutów android:gravity
android:layout_height=”wrap_content”
android:text=”@string/to” /> i android:layout_gravity.
<EditText
android:layout_width=”wrap_content”
Atrybutu layout_gravity możemy używać
android:layout_height=”wrap_content” także w układach siatki. W tym układzie
zastosowaliśmy wartośc fill_horizontal, gdyż
android:layout_gravity=”fill_horizontal” chcemy, by pole tekstowe zajmowało całą
android:hint=”@string/to_hint” /> szerokość dostępnego obszaru.
</GridLayout>
<TextView
...
Kolumna 0 Kolumna 1
android:layout_row=”0”
android:layout_column=”0”
android:text=”@string/to” />
Wiersz 0 Do Wpisz adres e-mail
<EditText
...
android:layout_row=”0”
android:layout_column=”1”
android:hint=”@string/to_hint” />
</GridLayout>
jesteś tutaj 193
Scalanie komórek
android:layout_columnSpan=”liczba”
gdzie liczba określa, ile kolumn dany widok ma zajmować. Liczba zajmowanych kolumn: 2
W naszym przypadku zastosujemy atrybut o następującej postaci:
android:layout_columnSpan=”2”
<GridLayout...>
<TextView... />
To są widoki dodane na poprzedniej stronie do wiersza 0.
<EditText.../>
<EditText
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_gravity=”fill” Chcemy, aby widok zajął cały dostępny obszar
i aby umieszczony w nim tekst był wyświetlany
android:gravity=”top” u góry pola.
android:layout_row=”1”
android:layout_column=”0” Widok zaczyna się w kolumnie 0 i obejmuje dwie kolumny.
android:layout_columnSpan=”2”
android:hint=”@string/message” />
</GridLayout>
194 Rozdział 5.
Interfejs użytkownika
Kolumna 0 Kolumna 2
Wiersz 2 Wyślij
Magnesiki układowe
Napisaliśmy kod, który służy do wyśrodkowania przycisku Wyślij w trzecim
wierszu układu siatki, ale nagły podmuch strącił niektóre magnesiki na ziemię.
Czy możesz odtworzyć kod, używając przedstawionych poniżej magnesików?
<GridLayout...>
<TextView... />
To są widoki, które dodaliśmy już wcześniej.
<EditText.../>
<EditText.../>
<Button
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
fill_horizontal
android:layout_row=...............
"0"
android:layout_column=............... "2"
"0"
android:layout_gravity=...............
"1"
"1"
android:layout_columnSpan=...............
"2"
android:text=”@string/send” />
center_horizontal
</GridLayout>
<TextView
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_row=”0”
android:layout_column=”0”
android:text=”@string/to” />
<EditText
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_gravity=”fill_horizontal”
android:layout_row=”0”
android:layout_column=”1”
android:hint=”@string/to_hint” />
<EditText
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_gravity=”fill”
android:gravity=”top”
android:layout_row=”1”
android:layout_column=”0”
android:layout_columnSpan=”2”
android:hint=”@string/message” />
<Button
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_row=”2” Przycisk zajmuje dwie kolumny,
android:layout_column=”0” przy czym zaczyna się w pierwszej
kolumnie drugiego wiersza układu.
android:layout_gravity=”center_horizontal”
android:layout_columnSpan=”2”
android:text=”@string/send” />
</GridLayout>
196 Rozdział 5.
Interfejs użytkownika
<GridLayout xmlns:android=”http://schemas.android.com/apk/res/android”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:columnCount=”2”
... >
...
</GridLayout>
android:layout_row=”0”
android:layout_column=”0”
android:layout_columnSpan=”2”
Bądź układem 3
Trzy spośród pięciu układów pokazanych
na tej stronie zostały utworzone przez
układy przedstawione na następnej
stronie. Twoim zadaniem
jest dopasowanie każdego
z trzech układów do ekranów
reprezentujących ich wygląd.
198 Rozdział 5.
Interfejs użytkownika
A <GridLayout 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:columnCount=”3”
tools:context=”.MainActivity” >
<Button
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_gravity=”fill”
android:layout_columnSpan=”3”
android:text=”@string/hello” />
</GridLayout>
B <GridLayout 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:columnCount=”2”
tools:context=”.MainActivity” >
<Button
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_gravity=”fill”
android:layout_columnSpan=”2”
android:text=”@string/hello” />
<Button
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”@string/hi” />
</GridLayout>
C <GridLayout 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:columnCount=”2”
tools:context=”.MainActivity” >
<Button
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_row=”0”
android:layout_column=”0”
android:layout_columnSpan=”2”
android:text=”@string/hello” />
<Button
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_row=”1”
android:layout_column=”0”
android:text=”@string/hi” />
</GridLayout>
1
A <GridLayout 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:columnCount=”3”
tools:context=”.MainActivity” >
<Button
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_gravity=”fill”
android:layout_columnSpan=”3” Ten układ
android:text=”@string/hello” /> zawiera tylko
jeden przycisk
</GridLayout> zajmujący cały
obszar ekranu.
3 B <GridLayout 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:columnCount=”2”
tools:context=”.MainActivity” >
<Button
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_gravity=”fill” Ten przycisk
android:layout_columnSpan=”2” wypełnia prawie
android:text=”@string/hello” /> cały ekran,
zostawiając
<Button jedynie u dołu
android:layout_width=”wrap_content” nieco miejsca na
drugi przycisk.
android:layout_height=”wrap_content”
android:text=”@string/hi” />
</GridLayout>
200 Rozdział 5.
Interfejs użytkownika
4 C <GridLayout 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:columnCount=”2”
tools:context=”.MainActivity” >
<Button
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
Choć przycisk
android:layout_row=”0” zajmuje dwie
android:layout_column=”0” kolumny, to
android:layout_columnSpan=”2” jednak nie
android:text=”@string/hello” /> kazaliśmy go
rozciągnąć
na całą tę
<Button szerokość.
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_row=”1”
android:layout_column=”0”
android:text=”@string/hi” />
</GridLayout>
android.widget.TextView android.widget.Spinner
... ...
android.widget.EditText android.widget.Button
... ...
202 Rozdział 5.
Interfejs użytkownika
<RelativeLayout xmlns:android=”http://schemas.android.com/apk/res/android”
... >
Układ względny
<Button
W tym przykładzie ViewGroup
pominęliśmy duże android:id=”@+id/send”
fragmenty kodu
XML. Kluczowe ... />
znaczenie mają tu Przycisk
widoki umieszczone <EditText
w grupie widoków. View View
android:id=”@+id/message”
Pole tekstowe
... />
</RelativeLayout>
Podczas tworzenia aplikacji kod XML układu jest za kulisami przekształcany na obiekt
ViewGroup, który zawiera drzewo obiektów View. W powyższym przykładzie przycisk
zostanie przekształcony na obiekt Button, a pole tekstowe — na obiekt EditText.
Button i TextView to dwie klasy pochodne klasy View.
<Layout>
Układ względny
</Layout>
ViewGroup
layout.xml
Pole tekstowe
Przycisk
View View
204 Rozdział 5.
Interfejs użytkownika
Zabawy z widokami
Przyjrzyjmy się najpopularniejszym komponentom GUI. Znasz już kilka z nich, lecz
mimo to przedstawimy je jeszcze raz. Nie będziemy tu prezentować pełnego API
każdego z tych komponentów, a jedynie ich najważniejsze elementy, dzięki którym
będziesz mógł zacząć ich używać.
Widok tekstowy
android.view.View
Widok tekstowy, komponent
...
TextView, służy do wyświetlania
fragmentów tekstu na ekranie.
android.widget.TextView
Definiowanie w kodzie XML ...
Ten typ komponentów definiuje się w kodzie XML za pomocą elementu
<TextView>. Do określenia tekstu wyświetlanego w komponencie używany jest
atrybut android:text, przy czym sam tekst jest zazwyczaj definiowany jako zasób
łańcuchowy:
<TextView
android:id=”@+id/text_view”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”@string/text” />
android:textSize=”14sp”
Wielkość tekstu określa się za pomocą pikseli niezależnych od skali (sp). Piksele
niezależne od skali uwzględniają, czy użytkownik chce używać dużych czcionek,
czy nie. Tekst o wielkości 14sp będzie większy na urządzeniach skonfigurowanych
tak, by używały dużych czcionek, niż na tych, na których mają być używane małe
czcionki.
android:inputType=”number”
Definiując pole, można zastosować w nim jednocześnie wiele typów danych — wystarczy rozdzielić je
znakiem pionowej kreski (|). Na przykład aby wpisywane zdania zaczynały się od wielkiej litery,
a tekst był automatycznie poprawiany, należałoby użyć atrybutu o następującej postaci:
android:inputType=”textCapSentences|textAutoCorrect”
206 Rozdział 5.
Interfejs użytkownika
Przycisk
Przyciski są zazwyczaj stosowane po to, by aplikacja mogła
wykonać jakąś operację.
android.view.View
Definiowanie w kodzie XML ...
W kodzie XML układów przyciski definiuje się za pomocą elementu <Button>.
Tekst wyświetlany na przycisku jest określany w atrybucie android:text:
<Button android.widget.TextView
android:id=”@+id/button”
...
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”@string/button_text” />
android.widget.Button
...
Stosowanie w kodzie aktywności
Aby przycisk zareagował na kliknięcie, w kodzie XML układu należy dodać
atrybut android:onClick i podać w nim nazwę metody zdefiniowanej w kodzie
aktywności, którą należy wywołać:
android:onClick=”onButtonClicked”
onButtonClicked()
<Layout>
</Layout>
Aktywność
Układ
Przycisk przełącznika
Przycisk przełącznika może się znajdować w jednym z dwóch dostępnych stanów,
wybranym przez kliknięcie. android.view.View
...
Tak wygląda przycisk
przełącznika, kiedy Kiedy klikniesz
jest wyłączony. przycisk,
zostanie on
włączony.
android.widget.TextView
<ToggleButton ...
android:id=”@+id/toggle_button”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content” android.widget.
android:textOn=”@string/on” CompoundButton
android:textOff=”@string/off” /> ...
208 Rozdział 5.
Interfejs użytkownika
Przełącznik
Przełącznik to rodzaj małego suwaka, który działa bardzo podobnie
do przycisku omawianego wcześniej przełącznika.
Przełączniki
wymagają API
Obejrzyj to!
Tak wygląda A tak wygląda
przełącznik, kiedy przełącznik, poziomu 14
jest wyłączony. gdy jest lub wyższego.
włączony.
Jeśli planujesz stosowanie
przełączników w swojej aplikacji,
upewnij się, czy będzie ona używać
Definiowanie w kodzie XML wersji SDK obsługującej API
Przełączniki definiuje się w kodzie XML za pomocą elementów <Switch>. poziomu 14 lub wyższego.
Korzystając z atrybutów android:textOn i android:textOff, można
określać teksty wyświetlane przez przełącznik w zależności od jego stanu.
android.view.View
<Switch ...
android:id=”@+id/switch_view”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android.widget.TextView
android:textOn=”@string/on”
android:textOff=”@string/off” /> ...
android:onClick=”onSwitchClicked” android.widget.
CompoundButton
Metoda zdefiniowana w kodzie aktywności może mieć następującą postać: ...
/** Metoda wywoływana po kliknięciu przełącznika */
public void onToggleClicked(View view) {
// Czy przełącznik jest włączony? android.widget.Switch
boolean on = ((Switch) view).isChecked(); ...
Pola wyboru
Pola wyboru umożliwiają prezentowanie użytkownikom grupy opcji. android.view.View
Użytkownicy mogą następnie wybierać z tej grupy dowolne opcje.
...
Każde pole wyboru można zaznaczyć niezależnie od pozostałych.
android.widget.Button
...
<CheckBox android:id=”@+id/checkbox_milk”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content” android.widget.CheckBox
android:text=”@string/milk” /> ...
<CheckBox android:id=”@+id/checkbox_sugar”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”@string/sugar” />
210 Rozdział 5.
Interfejs użytkownika
<CheckBox android:id=”@+id/checkbox_milk”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”@string/milk”
android:onClick=”onCheckboxClicked”/>
W tym przypadku metoda onCheckboxClicked()
<CheckBox android:id=”@+id/checkbox_sugar” zostanie wywołana niezależnie od tego,
które pole wyboru zostało kliknięte.
android:layout_width=”wrap_content” W razie potrzeby w każdym polu wyboru
można zastosować inną metodę.
android:layout_height=”wrap_content”
android:text=”@string/sugar”
android:onClick=”onCheckboxClicked”/>
Przyciski opcji
Także ten rodzaj przycisków pozwala wyświetlać grupy opcji. Jednak w ich
przypadku użytkownik może w danej chwili wybrać tylko jedną opcję z grupy. android.view.View
...
Zastosuj przyciski
opcji, aby ograniczyć
możliwości użytkownika
do wyboru tylko jednej
spośród dostępnych opcji.
android.widget.TextView
Definiowanie w kodzie XML ...
Definiowanie przycisków opcji należy zacząć od dodania specjalnego typu grupy widoków:
<RadioGroup>. Wewnątrz elementu <RadioGroup> można następnie definiować
poszczególne przyciski opcji, używając w tym celu elementów <RadioButton>: android.widget.Button
212 Rozdział 5.
Interfejs użytkownika
Lista rozwijana
Jak już wiesz, komponent Spinner tworzy rozwijaną listę wartości, spośród których android.view.View
użytkownik może wybrać jedną.
...
Listy rozwijanej
używaliśmy android.view.ViewGroup
w rozdziale 2. ...
android.widget.
AdapterView
Definiowanie w kodzie XML ...
<string-array name=”spinner_values”>
<item>jasne</item>
<item>bursztynowe</item>
<item>brązowe</item>
<item>ciemne</item>
</string-array>
214 Rozdział 5.
Interfejs użytkownika
Widoki obrazów
Jak sama nazwa wskazuje, ten typ widoków służy do wyświetlania obrazów. android.view.View
...
android-ldpi Ekrany o niskiej gęstości, około 120 dpi. W zależności od tego, jaka wersja
Android Studio jest używana, IDE
android-mdpi Ekrany o średniej gęstości, około 160 dpi. może automatycznie utworzyć dla
nas niektóre z tych katalogów.
android-hdpi Ekrany o wysokiej gęstości, około 240 dpi.
android-xhdpi Ekrany o bardzo wysokiej gęstości, około 320 dpi.
android-xxhdpi Ekrany o bardzo, bardzo wysokiej gęstości, około 480 dpi.
android-xxxhdpi Ekrany o ultrawysokiej gęstości, około 640 dpi.
Jeżeli plik obrazu zostanie dodany tylko do jednego katalogu, to Android będzie go
używał zawsze — na wszystkich urządzeniach. Najczęściej do tego celu używany jest
katalog drawable.
<ImageView
android:layout_width=”200dp”
android:layout_height=”100dp”
android:src=”@drawable/starbuzz_logo”
android:contentDescription=”@string/starbuzz_logo” />
216 Rozdział 5.
Interfejs użytkownika
<Button
android:layout_width=”wrap_content” ej
Wyświetla logo Androida po praw
android:layout_height=”wrap_content” stronie tekstu na przycisku.
android:drawableRight=”@drawable/android”
android:text=”@string/click_me” />
<Button
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:drawableLeft=”@drawable/android”
android:text=”@string/click_me” />
<Button
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:drawableBottom=”@drawable/android”
android:text=”@string/click_me” />
<Button
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:drawableTop=”@drawable/android”
android:text=”@string/click_me” />
Przyciski z obrazami
Przyciski tego typu przypominają normalne przyciski, z tym,
że zamiast tekstu są na nich wyświetlane wyłącznie obrazy.
<ImageButton android.widget.ImageView
android:id=”@+id/button” ...
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:src=”@drawable/button_icon />
android.widget.
ImageButton
Stosowanie w kodzie aktywności
...
Aby przycisk ImageButton odpowiadał na kliknięcia, do jego kodu XML
należy dodać atrybut android:onClick i podać w nim nazwę metody
zdefiniowanej w kodzie aktywności: Klasa ImageButton
rozszerza klasę ImagView,
android:onClick=”onButtonClicked” a nie Button. Czy to Cię
zaskoczyło?
Tę metodę można zdefiniować w kodzie aktywności w następujący sposób:
onButtonClicked()
<Layout>
</Layout>
Aktywność
Układ
218 Rozdział 5.
Interfejs użytkownika
Widoki przewijane
Jeśli do układu dodamy bardzo dużo widoków, to na urządzeniach
z niewielkimi ekranami możemy mieć pewien problem — większość
układów nie udostępnia pasków przewijania pozwalających na przewijanie
ich zawartości. Na przykład jeśli dodamy do układu liniowego kilka dużych
przycisków, to zapewne nie będziemy w stanie zobaczyć ich wszystkich.
<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” Przenieś te atrybuty z początko
wego układu
do elementu <ScrollView>, gdyż
tools:context=”.MainActivity” > to
obecnie głównym elementem pliku on jest
.
<LinearLayout
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:paddingBottom=”16dp”
android:paddingLeft=”16dp”
android:paddingRight=”16dp”
android:paddingTop=”16dp”
android:orientation=”vertical” >
Umieszczenie układu wewnątrz
... elementu <ScrollView>
</LinearLayout> spowodowało dodanie fajnego,
pionowego paska przewijania.
</ScrollView> Teraz użytkownik może już
wyświetlić wszystkie widoki.
Krótkie komunikaty
Jest jeszcze jeden widżet, który chcielibyśmy przedstawić w tym rozdziale, java.lang.Object
a służy on do wyświetlania tak zwanych tostów (ang. toast). Tosty to zwyczajne,
...
krótkie komunikaty tekstowe wyświetlane na ekranie.
Domyślnie komunikaty są
wyświetlane u dołu ekranu.
220 Rozdział 5.
Interfejs użytkownika
Ćwiczenie
Pewnie nie będzie Ci się chciało
pisać kodu układu tutaj, ale może io?
Stud
poeksperymentujesz w Android
Ćwiczenie
Rozwiązanie
<GridLayout xmlns:android=”http://schemas.android.com/apk/res/android”
xmlns:tools=”http://schemas.android.com/tools”
My zastosowaliśmy układ siatki,
,
android:layout_width=”match_parent” ale nic nie stoi na przeszkodzie
by użyć układu względnego.
android:layout_height=”match_parent”
android:paddingBottom=”16dp”
android:paddingLeft=”16dp”
android:paddingRight=”16dp”
android:paddingTop=”16dp”
android:columnCount=”2” Nasz układ ma dwie kolumny.
tools:context=”.MainActivity” >
<TextView
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
ujący obie kolumny
android:layout_row=”0” Na górze wyświetlamy tekst zajm
pierwsze go wier sza ukła du.
android:layout_column=”0”
android:layout_columnSpan=”2”
android:text=”@string/message” /> Wszystkie widoki wymagają
łańcuchów znaków, dodanych
do pliku zasobów strings.xml.
<TextView
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_row=”1” W pierwszej kolumnie drugiego
wiersza umieściliśmy etykietę
android:layout_column=”0” Temperatura.
android:text=”@string/temp” />
222 Rozdział 5.
Interfejs użytkownika
dać użytkownikowi
Zastosowaliśmy przycisk przełącznika, by
gorąca czy zimna;
możliwość określenia, czy herbata ma być
<ToggleButton umieś ciliśm y go w wiers zu 1 i kolum nie 1.
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_row=”1”
android:layout_column=”1”
android:textOn=”@string/hot”
android:textOff=”@string/cold” />
<CheckBox android:id=”@+id/checkbox_milk”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_row=”2”
android:layout_column=”0”
android:text=”@string/milk” />
<CheckBox android:id=”@+id/checkbox_sugar”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_row=”3”
android:layout_column=”0”
android:text=”@string/sugar” />
<GridLayout...>
<TextView... />
<EditText.../>
<EditText.../>
<Button
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_row=...............
android:layout_column=...............
android:layout_gravity=...............
android:layout_columnSpan=...............
224 Rozdział 5.
e
Interfejs użytkownika
Rozdział 5.
Opanowałeś już rozdział 5. i dodałeś
do swojego przybornika z narzędziami
znajomość widoków i grup widoków.
CELNE SPOSTRZEŻENIA
Zorganizuj się
Rany! Mam tyle pomysłów…
Tylko czy uda mi się je zmienić
w najpopularniejszą
aplikację roku?
łowe
Szczegó żdym
cje o ka
informa
napoju
Lista wszystkich
naszych lokali Adresy i godz
iny
otwarcia
Menu zawierając wszystkich na
e szych
wszystko, co lokali
można u nas zjeś
ć
Lista oferowanych
napojów
Ekran początkowy
e z listą opcji
Szczegółow
je o każdej
informac
nu
pozycji me
228 Rozdział 6.
Widoki list i adaptery
Lista wszystkich
Aktywności kategorii Menu zawierające naszych lokali
Aktywności kategorii prezentują dane należące do wszystko, co
konkretnej kategorii i często przybierają postać list. można u nas Lista
Aktywności tego typu zazwyczaj zapewniają użytkownikom zjeść oferowanych
możliwość przejścia do kolejnej aktywności — szczegółów/
napojów
edycji. Przykładem aktywności kategorii może być lista
wszystkich napojów oferowanych przez kafeterię Coffeina.
Zastanów się, jaką aplikację chcesz napisać. Jakie aktywności powinna zawierać?
Podziel je na aktywności poziomu głównego, kategorii oraz szczegółów/edycji.
Ćwiczenie
Nawigowanie po aktywnościach
Podczas organizowania pomysłów i klasyfikowaniu ich na aktywności
poziomu głównego, kategorii oraz szczegółów/edycji możemy na podstawie
tej klasyfikacji od razu określić, w jaki sposób będzie można poruszać się
po projektowanej aplikacji. Ogólnie rzecz biorąc, chcemy, by użytkownik
przechodził od aktywności poziomu głównego przez aktywności kategorii
do aktywności szczegółów/edycji.
Aktywności kategorii
Lista Menu zawierające Lista wszystkich
znajdują się pomiędzy oferowanych wszystko, co naszych lokali
aktywnościami poziomu napojów można u nas zjeść
głównego i aktywnościami
szczegółów/edycji
Użytkownicy będą przechodzili od
aktywności poziomu głównego do
aktywności kategorii. W złożonych
aplikacjach może występować kilka
poziomów kategorii i podkategorii.
232 Rozdział 6.
Widoki list i adaptery
Ta aktywność wyświetla
szczegółowe informacje
dotyczące konkretnego
napoju.
Kiedy użytkownik
kliknie wybrany
napój, zostaną
Użytkownik klika wyświetlone
element Napoje, informacje o tym
co powoduje napoju.
wyświetlenie listy
napojów.
<Layout>
<Layout>
</Layout>
</Layout>
Ale dlaczego ta
aktywność nie Drink.java activity_drink.xml
activity_top_level.xml wymaga definiowania 5
układu? Dowiesz się
3
tego już niebawem.
1
2 4
234 Rozdział 6.
Widoki list i adaptery
package com.hfad.coffeina;
Każdy Drink ma nazwę i opis oraz
identyfikator zasobu graficznego. zawiera
public class Drink { Te identyfikatory
zasobów określają zdjęcia napojów,
private String name; do projektu na następnej stronie. które dodamy
private String description;
private int imageResourceId;
drinks to tablica trzech obiektów klasy Drink.
// drinks to tablica obiektów klasy Drink
public static final Drink[] drinks = {
To są zdjęcia new Drink(”Latte”, ”Czarne espresso z gorącym mlekiem i mleczną pianką.”,
napojów. R.drawable.latte),
Zaraz się nimi new Drink(”Cappuccino”, ”Czarne espresso z dużą ilością spienionego mleka.”,
zajmiemy. R.drawable.cappuccino),
new Drink(”Espresso”, ”Czarna kawa ze świeżo mielonych ziaren najwyższej jakości.”,
R.drawable.filter)
}; Konstruktor klasy Drink.
236 Rozdział 6.
Widoki list i adaptery
Pliki graficzne
Kod klasy Drink zawiera trzy zasoby graficzne, które są używane
w informacjach o napojach i mają następujące identyfikatory:
R.drawable.latte, R.drawable.cappuccino oraz R.drawable.
filter. Identyfikator R.drawable.latte odwołuje się do pliku
graficznego o nazwie latte, identyfikator R.drawable.cappuccino do
pliku cappuccino, a identyfikator R.drawable.filter do pliku filter.
name: „Latte”
<resources> res
...
<string name=”starbuzz_logo”>Logo kafeterii Coffeina</string> values
<xml>
</resources> </xml>
strings.xml
To już wszystko, co trzeba zrobić, by dodać obraz do układu, zajmijmy się więc listą.
238 Rozdział 6.
Widoki list i adaptery
Samą tablicę możemy zdefiniować w dokładnie taki sam sposób, w jaki robiliśmy
to już wcześniej — dodając odpowiedni element do pliku strings.xml:
<resources>
.... Coffeina
<string-array name=”options”>
<item>Napoje</item> app/src/main
<item>Przekąski</item>
<item>Kafeterie</item> res
</string-array>
values
....
<xml>
</resources> </xml>
strings.xml
W ten sposób w widoku listy zostaną wyświetlone trzy wartości: Napoje,
Przekąski oraz Kafeterie.
@array/options Atrybut entries wypełnia
komponent ListView
<resources> wartościami pobranymi
z tablicy options. Każdy
</resources> z elementów komponentu
Napoje ListView jest widokiem
ListView strings.xml tekstowym.
Przekąski
Kafeterie
<LinearLayout
xmlns:android=”http://schemas.android.com/apk/res/android”
xmlns:tools=”http://schemas.android.com/tools”
android:layout_width=”match_parent”
Używamy układu liniowego o układzie
android:layout_height=”match_parent” pionowym. Dzięki temu lista opcji zostanie
android:orientation=”vertical” wyświetlona bezpośrednio poniżej logo
kafeterii Coffeina.
tools:context=”.TopLevelActivity” >
<ImageView Coffeina
android:layout_width=”200dp”
android:layout_height=”100dp” app/src/main
android:src=”@drawable/starbuzz_logo”
android:contentDescription=”@string/starbuzz_logo” /> res
layout
<ListView
<xml>
android:id=”@+id/list_options” </xml>
android:layout_width=”match_parent” activity_
top_level.xml
android:layout_height=”wrap_content”
android:entries=”@array/options” />
</LinearLayout>
Jazda próbna
Upewnij się, że wprowadziłeś wszystkie modyfikacje w plikach
activity_top_level.xml i strings.xml. Kiedy uruchomisz aplikację,
powinieneś zobaczyć na ekranie urządzenia logo kafeterii
Coffeina, a pod nim widok listy. Ten widok powinien
prezentować trzy wartości pobrane z tablicy options.
Gdy klikniesz dowolną opcję z tej listy, nic się nie stanie —
To są wartości
wynika to z prostego faktu, że jeszcze nie kazaliśmy widokowi zapisane w tablicy
listy reagować na kliknięcia. Teraz mamy zamiar pokazać, jak options.
sprawić, by widok listy reagował na kliknięcia i uruchamiał inną
aktywność.
240 Rozdział 6.
Widoki list i adaptery
new AdapterView.OnItemClickListener(){
public void onItemClick(AdapterView<?> listView, To widok, który został kliknięty
Napoje to pierwszy element wyświetlony (w naszym przypadku jest to widok listy).
View itemView,
w widoku listy, co oznacza, że znajduje
się on w miejscu o indeksie 0. int position, Te parametry dają nam nieco więcej informacji o tym,
long id) { który element listy został kliknięty i jakie jest jego
położenie na liście.
if (position == 0) {
Intent intent = new Intent(TopLevelActivity.this, DrinkCategoryActivity.class);
startActivity(intent);
Intencja pochodzi z aktywności Ma ona spowodować
} TopLevelActivity. uruchomienie aktywności
} DrinkCategoryActivity.
};
AdapterView.OnItemClickListener itemClickListener =
new AdapterView.OnItemClickListener(){
public void onItemClick(AdapterView<?> listView,
...
}
};
ListView listView = (ListView) findViewById(R.id.list_options);
listView.setOnItemClickListener(itemClickListener); To jest utworzony wc
ześniej
obiekt nasłuchujący.
Dodanie obiektu nasłuchującego do widoku listy ma kluczowe znaczenie, gdyż
zapewnia on, że obiekt ten będzie powiadamiany o kliknięciach elementów
listy. Jeśli tego nie zrobimy, to elementy naszego widoku listy nie będą w stanie
reagować na kliknięcia.
onItemClick() Intencja
package com.hfad.coffeina;
Coffeina
import android.app.Activity;
app/src/main
import android.content.Intent;
import android.os.Bundle;
java
import android.widget.AdapterView;
import android.widget.ListView; Korzystamy z tych dodatkowych klas. com.hfad.coffeina
import android.view.View;
TopLevel
public class TopLevelActivity extends Activity { Activity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_top_level);
Tworzymy obiekt
// Tworzymy obiekt nasłuchujący OnItemClickListener nasłuchujący.
AdapterView.OnItemClickListener itemClickListener =
new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> listView,
View v,
Implementujemy jego
int position, metodę onItemClick().
long id) {
if (position == 0) {
Intent intent = new Intent(TopLevelActivity.this,
DrinkCategoryActivity.class);
startActivity(intent);
} Jeśli użytkownik kliknie element
Napoje, to uruchamiamy aktywność
} DrinkCategoryActivity. Utworzymy ją już
za chwilę, więc nie przejmuj się, jeśli
}; Android Studio zacznie Cię ostrzegać,
że jej nie ma.
// Dodajemy obiekt nasłuchujący do widoku listy
ListView listView = (ListView) findViewById(R.id.list_options);
listView.setOnItemClickListener(itemClickListener); Dodajemy obiekt nasłuchujący
} do widoku listy.
}
jesteś tutaj 243
Jesteś tutaj
<Layout>
<Layout>
</Layout>
</Layout>
Utworzyliśmy
aktywność Drink.java activity_drink.xml
TopLevelActivity activity_top_level.xml
i jej układ.
Tą aktywnością zajmiemy
się w następnej kolejności.
Nie istnieją
głupie pytania
244 Rozdział 6.
Widoki list i adaptery
Poniżej przedstawiliśmy kod aktywności pochodzący z innego projektu. Kiedy użytkownik kliknie jeden
z elementów widoku listy, jej kod ma wyświetlić w komponencie TextView tekst prezentowany w danym
elemencie listy. Czy przedstawiony kod działa zgodnie z założeniami? A jeśli nie działa, to dlaczego?
Ćwiczenie Komponent TextView ma identyfikator text_view, a widok listy list_view.
package com.hfad.ch06_ex;
import android.app.Activity;
import android.os.Bundle;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.TextView;
import android.view.View;
Poniżej przedstawiliśmy kod aktywności pochodzący z innego projektu. Kiedy użytkownik kliknie jeden
z elementów widoku listy, jej kod ma wyświetlić w komponencie TextView tekst wyświetlony w danym
elemencie listy. Czy przedstawiony kod działa zgodnie z założeniami? A jeśli nie działa, to dlaczego?
Ćwiczenie Komponent TextView ma identyfikator text_view, a widok listy list_view.
Rozwiązanie
package com.hfad.ch06_ex;
import android.app.Activity;
import android.os.Bundle;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.TextView;
import android.view.View;
Kod nie działa zgodnie z założeniami, gdyż na jego końcu brakuje wywołania
metody listView.setOnItemClickListener(itemClickListener);.
Z wyjątkiem tego jednego błędu kod jest w porządku.
246 Rozdział 6.
Widoki list i adaptery
Aktywność DrinkCategoryActivity
wyświetla listę napojów. Kiedy
Kiedy użytkownik klinie użytkownik kliknie jeden z nich,
element Napoje, zostanie zostanie uruchomiona aktywność
uruchomiona aktywność DrinkActivity, która wyświetli
DrinkCategoryActivity. szczegółowe informacje
o wybranym napoju.
android.app.Activity
...
Aktywność listy
zawsze dysponuje
swoim własnym
widokiem listy, więc
nie musimy go tworzyć
własnoręcznie. Wciąż android.app.ListActivity
musimy dostarczyć getListView()
dane na listę i już
zaraz pokażemy, onListItemClick()
jak to należy robić.
...
248 Rozdział 6.
Widoki list i adaptery
@Override
com.hfad.coffeina
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Klasa ListActivity dziedziczy metodę
} onCreate() po klasie Activity. DrinkCategory
Już zaraz dodamy kod tej metody. Activity.java
}
Powyższy kod tworzy prostą aktywność listy o nazwie DrinkCategoryActivity. Ponieważ
jest to aktywność listy, musi dziedziczyć po klasie ListActivity, a nie Activity.
Podobnie jak wszystkie inne aktywności, także aktywności list muszą być zarejestrowane
w pliku manifestu — AndroidManifest.xml. Jest to niezbędne, by mogły być używane
w aplikacji. Podczas tworzenia aktywności listy Android Studio automatycznie dodaje
odpowiedni kod do pliku manifestu.
<application
... >
<activity
android:name=”.TopLevelActivity” To jest pierwsza z aktywności,
android:label=”@string/app_name” które utworzyliśmy.
...
To jest nowa aktywność. Każdej
</activity> aktywności musi odpowiadać wpis
<activity w pliku AndroidManifest.xml.
Coffeina
android:name=”.DrinkCategoryActivity”
android:label=”@string/title_activity_drink_category” >
app/src/main
</activity> <xml>
</application> </xml>
AndroidManifest.xml
Skoro już utworzyliśmy aktywność listy, musimy teraz wypełnić ją danymi.
jesteś tutaj 249
Adaptery
android:entries=”@array/options”
package com.hfad.coffeina;
Coffeina
import android.app.ListActivity;
import android.os.Bundle; app/src/main
import android.widget.ArrayAdapter;
import android.widget.ListView; Używamy tych dodatkowych klas. java
com.hfad.coffeina
public class DrinkCategoryActivity extends ListActivity {
DrinkCategory
@Override Activity.java
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ListView listDrinks = getListView();
ArrayAdapter<Drink> listAdapter = new ArrayAdapter<Drink>(
this,
Ten fragment kodu
android.R.layout.simple_list_item_1, wypełnia widok listy
danymi pochodzącymi
Drink.drinks); z tablicy drinks.
listDrinks.setAdapter(listAdapter);
}
}
To są napoje pochodzące
z tablicy Drink.drinks.
252 Rozdział 6.
Widoki list i adaptery
DrinkCategoryActivity ArrayAdapter<Drink>
3 Źródłem danych dla adaptera ArrayAdapter jest tablica drinks zdefiniowana w klasie Drink.
Adapter używa metody Drink.toString(), by zwrócić nazwę każdego z napojów.
Drink.toString()
ListView
Drink.toString()
DrinkCategoryActivity
ArrayAdapter<Drink> Drink.drinks
<Layout>
<Layout>
</Layout>
</Layout>
Te pliki już napisaliśmy.
activity_top_level.xml Drink.java activity_drink.xml
254 Rozdział 6.
Widoki list i adaptery
Zagadkowy basen
Twoim celem jest napisanie aktywności, która powiąże
zdefiniowaną w kodzie Javy tablicę kolorów z listą
rozwijaną. Wybierz kawałki kodu pływające
w basenie i umieść jest w pustych miejscach
w kodzie aktywności. Każdego fragmentu
z basenu możesz użyć tylko raz, lecz nie
wszystkie fragmenty będą Ci potrzebne.
...
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Spinner spinner = ( ........ ) findViewById(R.id.spinner);
ArrayAdapter< ...... > adapter = new ArrayAdapter< ......... >(
. ....... ,
android.R.layout.simple_spinner_item,
colors);
spinner. ...........(adapter); Ten układ wyświetla każdą wartość
z tablicy jako pojedynczy wiersz listy
} rozwijanej.
}
Uwaga: Każdego fragmentu
kodu z basenu można użyć
tylko jeden raz!
String
this
colors Spinner
colors
setAdapter String
Ten fakt ma bardzo duże znaczenie w kontekście obsługi kliknięć elementów listy. Podstawowa różnica
między aktywnościami Activity i ListActivity polega na tym, że klasa ListActivity domyślnie
implementuje interfejs obiektu nasłuchującego kliknięć. Dzięki temu, kiedy używamy aktywności
ListActivity, zamiast tworzyć własny obiekt nasłuchujący, wystarczy zaimplementować metodę
onListItemClick().
public void onListItemClick(ListView listView,
View itemView, To są te same argumenty, którymi
dysponuje przedstawiona powyżej
int position, metoda onItemClick(): widok listy,
widok reprezentujący kliknięty element
long id) { listy oraz identyfikator wiersza
// Tu coś robimy nieprzetworzonych danych.
}
256 Rozdział 6.
Widoki list i adaptery
package com.hfad.coffeina;
Coffeina
import android.app.ListActivity;
app/src/main
import android.os.Bundle;
import android.widget.ArrayAdapter; java
import android.widget.ListView;
klas. com.hfad.coffeina
import android.view.View; Używamy tych dwóch dodatkowych
import android.content.Intent;
DrinkCategory
Activity.java
public class DrinkCategoryActivity extends ListActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ListView listDrinks = getListView();
ArrayAdapter<Drink> listAdapter =
new ArrayAdapter<Drink>(this,
android.R.layout.simple_list_item_1,
Drink.drinks);
listDrinks.setAdapter(listAdapter);
}
Implementujemy metodę
@Override onListItemClick(), tak aby kliknięcie
któregoś z napojów wyświetlonych
public void onListItemClick(ListView listView, na liście spowodowało uruchomienie
aktywności DrinkActivity.
View itemView,
int position,
long id) {
Intent intent = new Intent(DrinkCategoryActivity.this, DrinkActivity.class);
intent.putExtra(DrinkActivity.EXTRA_DRINKNO, (int) id);
startActivity(intent); Aktywność DrinkActivity dodamy
} do naszej aplikacji już za chwilę,
oid
więc nie przejmuj się, gdy Andr u.
Stud io poin form uje Cię o jej brak
}
258 Rozdział 6.
Widoki list i adaptery
¨ Dodanie zasobów
Aktywność szczegółów wyświetla informacje ¨ TopLevelActivity
o jednym rekordzie ¨ DrinkCategoryActivity
¨ DrinkActivity
Jak już zaznaczyliśmy wcześniej, aktywność DrinkActivity jest przykładem aktywności
szczegółów. Aktywności tego rodzaju prezentują szczegółowe informacje o konkretnym
rekordzie, a użytkownik zazwyczaj przechodzi do nich z aktywności kategorii.
Poniżej przedstawiliśmy kod układu. Dodaj zatem do swojego projektu aktywność Nie zapomnij utworzyć
nowej aktywności.
o nazwie DrinkActivity, korzystającą z układu o nazwie activity_drink, a następnie
zastąp zawartość pliku activity_drink.xml poniższym kodem:
<LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
xmlns:tools=”http://schemas.android.com/tools”
android:layout_width=”match_parent” Coffeina
android:layout_height=”match_parent”
android:orientation=”vertical” app/src/main
tools:context=”com.hfad.coffeina.DrinkActivity” >
res
<ImageView
layout
android:id=”@+id/photo” <xml>
android:layout_width=”190dp” </xml>
activity_drink.xml
android:layout_height=”190dp” />
<TextView
android:id=”@+id/name”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content” />
<TextView
android:id=”@+id/description”
android:layout_width=”match_parent”
android:layout_height=”wrap_content” />
</LinearLayout>
name=”Latte”
description=”Czarne espresso z gorącym mlekiem i mleczną pianką.”
imageResourceId=R.drawable.latte
drink
260 Rozdział 6.
Widoki list i adaptery
Magnesiki z napojami
Przekonajmy się, czy potrafisz użyć przedstawionych poniżej
magnesików do uzupełnienia kodu aktywności DrinkActivity
i wypełnienia jej widoków odpowiednimi danymi.
...
...
...
setImageResourceId
setContent
262 Rozdział 6.
Widoki list i adaptery
Coffeina
import android.app.Activity;
import android.os.Bundle; app/src/main
import android.widget.ImageView;
import android.widget.TextView; java
name.setText(drink.getName());
¨ Dodanie zasobów
Co się stanie po uruchomieniu aplikacji? ¨ TopLevelActivity
¨ DrinkCategoryActivity
¨ DrinkActivity
1 Kiedy użytkownik uruchamia aplikację, zostaje wyświetlona aktywność TopLevelActivity.
TopLevelActivity
Urządzenie
3 Kiedy użytkownik klika na liście opcję Napoje, zostaje wywołana metoda onItemClick()
obiektu nasłuchującego OnItemClickListener.
Jeśli użytkownik kliknie na liście opcję Napoje, to obiekt OnItemClickListener utworzy intencję,
która doprowadzi do uruchomienia aktywności DrinkCategoryActivity.
Intencja
onItemClick()
ListView
DrinkCategoryActivity
ArrayAdapter<Drink> Drink.drinks
264 Rozdział 6.
Widoki list i adaptery
onListItemClick()
ListView DrinkCategoryActivity
drinkNo=0
DrinkCategoryActivity
DrinkActivity
drinks[0]? Latte,
świetny wybór. To
jest wszystko, czym
drinks[0] dysponuję na jego
temat.
Latte
DrinkActivity Drink
Zaimplementowaliśmy
wyłącznie część aplikacji
dotyczącą napojów. Klikanie
pozostałych opcji tej listy
nie da żadnych rezultatów.
266 Rozdział 6.
Widoki list i adaptery
...
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); Używamy tablicy
Spinner ) findViewById(R.id.spinner);
Spinner spinner = ( ........ typu String.
String > adapter = new ArrayAdapter< .........
ArrayAdapter< ...... String >(
this
......... ,
android.R.layout.simple_spinner_item,
colors);
setAdapter (adapter);
spinner. ...........
}
} Aby komponent Spinner pobierał dane z adaptera,
musimy wywołać jego metodę setAdapter().
Te fragmenty kodu
nie były potrzebne.
colors
colors
ale mo żes z
w tym rozdzi
Opanowałeś już rozdział 6. i dodałeś pobrać z ser we ra FT P
do swojego przybornika z narzędziami wydawnictwa Helion:
klady/
znajomość widoków list i projektowania ftp://ftp.helion.pl/przy
aplikacji. andrrg.zip
CELNE SPOSTRZEŻENIA
Podziel pomysły dotyczące aplikacji na aktywności ListActivity to aktywność wykorzystująca
głównego poziomu, aktywności kategorii oraz komponent ListView. Referencję do tego
aktywności szczegółów/edycji. Zastosuj aktywność komponentu można pobrać, używając metody
kategorii, by przechodzić z aktywności głównego getListView().
poziomu do aktywności szczegółów/edycji. Aktywności typu ListActivity dysponują
Zasoby graficzne są umieszczane w jednym lub kilku domyślnym układem, który jednak można zastąpić
katalogach drawable*. Odwołujemy się do nich, własnym.
używając identyfikatorów o postaci @drawable/ Adaptery pełnią rolę mostów łączących widoki typu
nazwa_obrazu. W kodzie aktywności można ich
AdapterView ze źródłami danych. Widoki ListView
używać, stosując zapis R.drawable.nazwa_obrazu.
i Spinner są przykładami klas dziedziczących po
Obrazy są wyświetlane za pomocą widoków AdapterView.
ImageView. Dodajemy je do układów, ArrayAdapter to typ adaptera, który operuje na
stosując element <ImageView>. Do określenia
przekazanej tablicy.
źródła obrazu służy atrybut android:src,
a etykietę poprawiającą dostępność aplikacji Sposób obsługi kliknięć przycisków można
możemy określić, używając atrybutu określać w kodzie układu, używając atrybutu
android:contentDescription. Atrybutom android:onClick.
tym odpowiadają metody setImageResource() Kliknięcia elementów listy prezentowanej
i setContentDescription().
w aktywności ListActivity można obsługiwać,
Komponent ListView wyświetla elementy w formie implementując metodę onListItemClick().
listy. Dodajemy go do układów, używając elementu W pozostałych przypadkach obsługa kliknięć
<ListView>.
wymaga utworzenia obiektu nasłuchującego
Do określania źródła elementów listy w kodzie i zaimplementowania jego odpowiedniej metody.
układu służy atrybut android:entries; należy
w nim podać identyfikator tablicy łańcuchów znaków
zdefiniowanych w pliku zasobów strings.xml.
268 Rozdział 6.
7. Fragmenty
Zadbaj o modularyzację
Robię to samo w wielu
różnych miejscach…
To chyba czyni mnie
fragmentem.
Wiesz już, jak tworzyć aplikacje, które działają tak samo niezależnie od tego,
na jakim urządzeniu zostały uruchomione…
…ale co zrobić w przypadku, kiedy akurat chcesz, by aplikacja wyglądała i działała inaczej
w zależności od tego, czy zostanie uruchomiona na telefonie, czy na tablecie? W tym rozdziale
pokażemy Ci, co zrobić, aby aplikacja wybierała układ, który najlepiej pasuje do wielkości
ekranu urządzenia. Oprócz tego przedstawimy fragmenty, czyli modularne komponenty
kodu, które mogą być wielokrotnie używane przez różne aktywności.
Na telefonie
Te obrazki pokazują aplikację
uruchomioną na telefonie. Prezentuje
ona listę treningów, a po kliknięciu
jednego z nich zostają wyświetlone
szczegółowe informacje na jego temat.
Na tablecie
Na większych urządzeniach,
takich jak tablety, mamy
do dyspozycji znacznie
więcej miejsca na ekranie. Na tablecie jest znacznie
więcej miejsca, więc
Optymalnym rozwiązaniem dostępny obszar możemy
będzie zatem wyświetlanie wykorzystać w inny sposób.
wszystkich informacji
na tym samym ekranie.
Na tabletach lista
treningów zajmuje tylko
część ekranu — po
kliknięciu któregoś z nich
szczegółowe informacje na
jego temat są wyświetlane
z prawej strony ekranu.
270 Rozdział 7.
Fragmenty
Na telefonie
Tu mamy dwie
aktywności: jedna
obsługuje listę,
a druga prezentuje
szczegóły.
Na tablecie
ąca
To jest jedna aktywność, obsługuj
zarówno listę, jak i informacje
szczegółowe.
Jeśli zastosujemy
fragment Inny fragment
prezentujący listę może służyć
treningów, będziemy do wyświetlania
mogli używać go szczegółów
w wielu konkretnego
aktywnościach. treningu.
272 Rozdział 7.
Fragmenty
<Layout>
</Layout>
fragment_
<Layout>
workout_list.xml
</Layout>
3
activity_main.xml
5
1 WorkoutList
2 Fragment.java
Workout.java
Urządzenie MainActivity.java
WorkoutDetail
Fragment.java
4 <Layout>
</Layout>
fragment_
workout_detail.xml
273
Etapy pracy
1 Utworzenie fragmentów.
Utworzymy dwa fragmenty. Pierwszego,
WorkoutListFragment, użyjemy do wyświetlania listy
treningów, natomiast drugiego, WorkoutDetailFragment,
do wyświetlania szczegółowych informacji o konkretnym
treningu. Oba te fragmenty wyświetlimy w jednej
aktywności. Oprócz nich dodamy do aplikacji klasę
Workout, z której oba fragmenty będą pobierały
prezentowane dane.
Utworzenie projektu
Projekt na potrzeby tej aplikacji utworzysz dokładnie tak samo
jak wszystkie inne projekty, które tworzyliśmy w poprzednich ¨ Utworzenie fragmentów
rozdziałach. ¨ Powiązanie fragmentów
¨ Utworzenie odrębnych układów
Utwórz zatem nowy projekt aplikacji z pustą aktywnością,
nadaj aplikacji nazwę Trenażer i umieść jej kod w pakiecie
com.hfad.trenazer. Minimalną wersją SDK musi być API
poziomu 17 lub wyższego. Tworzonej aktywności nadaj nazwę
MainActivity, a plik układu nazwij activity_main.
274 Rozdział 7.
Fragmenty
package com.hfad.trenazer;
Każdy trening — obiekt Workout
— zawiera nazwę i opis.
public class Workout {
private String name;
private String description; workouts to tablica czterech obiektów Workout.
275
Dodajemy fragment
Tworzymy pusty
fragment.
Kiedy klikniesz przycisk Finish, Android Studio utworzy nowy plik fragmentu o nazwie
WorkoutDetailFragment.java i nowy plik układu o nazwie fragment_workout_detail.xml,
zapisując je odpowiednio w katalogach app/src/main/java i app/src/res/layout.
276 Rozdział 7.
Fragmenty
<TextView
android:layout_width=”wrap_content”
Nazwę i opis android:layout_height=”wrap_content”
treningu
wyświetlamy android:textAppearance=”?android:attr/textAppearanceLarge”
w dwóch
android:text=”” To jest nazwa treningu.
odrębnych
komponentach
TextView. android:id=”@+id/textTitle” />
<TextView
android:layout_width=”wrap_content” To jest opis
treningu.
android:layout_height=”wrap_content”
android:text=””
android:id=”@+id/textDescription” />
To jest fragment,
</LinearLayout> którego będziemy
mogli używać
w aktywnościach.
Jak widać, kod układów używanych przez fragmenty wygląda tak samo jak kod
układów używanych przez aktywności. Układ naszego fragmentu jest bardzo
prosty — składa się z dwóch komponentów TextView: pierwszy prezentuje nazwę
treningu wyświetloną dużą czcionką, a drugi wyświetla opis treningu mniejszą
czcionką. Tworząc układy na potrzeby fragmentów, możemy używać wszystkich
poznanych układów i widoków, z których korzystaliśmy już przy tworzeniu układów
wykorzystywanych przez aktywności.
Skoro przygotowaliśmy już układ dla naszego fragmentu, zajmijmy się teraz jego
kodem.
277
Kod fragmentu
Jak się zapewne spodziewałeś, Android Studio wygenerowało kod tego pliku.
Zastąp tę wygenerowaną zawartość kodem przedstawionym poniżej:
package com.hfad.trenazer;
Powyższy kod tworzy prosty fragment. Jak widać, klasa fragmentu dziedziczy
po klasie android.app.Fragment. Wszystkie fragmenty są klasami
pochodnymi klasy Fragment. Wszystkie fragmenty
Obejrzyj to!
muszą definiować
Nasz fragment implementuje także metodę onCreateView(). Metoda ta jest
publiczny konstruktor
wywoływana za każdym razem, gdy system potrzebuje układu fragmentu, i to
bezargumentowy.
właśnie wewnątrz tej metody musimy określić, którego układu używa dany
fragment. Metoda onCreateView() jest opcjonalna, ponieważ jednak trzeba ją Wynika to z faktu, że Android używa
implementować we wszystkich fragmentach używających układów, w praktyce tego konstruktora do tworzenia instancji
trzeba ją implementować niemal zawsze. fragmentu za każdym razem, gdy jest ona
potrzebna, i jeśli takiego konstruktora nie
Układ używany przez fragment należy określać w następujący sposób:
uda się znaleźć, to w trakcie działania
inflater.inflate(R.layout.fragment_workout_detail, aplikacji system zgłosi wyjątek.
container, false);
W praktyce konstruktor bezargumentowy
Ta instrukcja stanowi odpowiednik wywołania metody setContentView() trzeba definiować w przypadkach, gdy
aktywności i podobnie jak ta metoda, także powyższe wywołanie jest fragment zawiera także inny konstruktor
używane do określania układu, którego ma używać dany fragment. Argument z co najmniej jednym argumentem. Dzieje
container jest przekazywany przez aktywność używającą fragmentu — jest to się tak dlatego, że jeśli klasa nie definiuje
obiekt typu ViewGroup należący do tej aktywności, do której należy wstawić żadnego konstruktora, to kompilator Javy
układ fragmentu. automatycznie dodaje do niej publiczny
278 Rozdział 7. konstruktor bezargumentowy.
Fragmenty
activity_main.xml
<?xml version=”1.0” encoding=”utf-8”?>
<LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
android:orientation=”horizontal”
android:layout_width=”match_parent”
android:layout_height=”match_parent”>
<fragment
class=”com.hfad.trenazer.WorkoutDetailFragment”
android:id=”@+id/detail_frag” Ten fragment kodu dodaje do
układu aktywności fragment
android:layout_width=”match_parent” WorkoutDetailFragment.
android:layout_height=”match_parent” />
</LinearLayout>
class=”com.hfad.trenazer.WorkoutDetailFragment”
279
Metoda setWorkout()
Kiedy aktywność używa fragmentu, to zazwyczaj musi w jakiś sposób się z nim
komunikować. Na przykład jeśli fragment prezentuje szczegółowe informacje
o konkretnym rekordzie, to aktywność musi przekazać do niego informację o tym,
który rekord ma zostać wyświetlony.
package com.hfad.trenazer;
Trenazer
import android.app.Fragment;
import android.os.Bundle; app/src/main
import android.view.LayoutInflater;
import android.view.View; java
import android.view.ViewGroup;
com.hfad.trenazer
public class WorkoutDetailFragment extends Fragment {
private long workoutId; To jest identyfikator treningu wybranego przez WorkoutDetail
użytkownika. Za chwilę użyjemy go do wyświetlenia Fragment.java
w widokach fragmentu informacji o wybranym
@Override treningu.
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_workout_detail, container, false);
}
280 Rozdział 7.
Fragmenty
Poniżej przedstawiliśmy kompletny kod aktywności (zastąp nim zawartość pliku MainActivity.java):
package com.hfad.trenazer;
Trenazer
import android.app.Activity;
import android.os.Bundle; app/src/main
@Override com.hfad.trenazer
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); MainActivity.java
setContentView(R.layout.activity_main);
WorkoutDetailFragment frag = (WorkoutDetailFragment)
getFragmentManager().findFragmentById(R.id.detail_frag);
frag.setWorkout(1);
To wywołanie zwraca referencję do
} Już zaraz zmienimy fragment WorkoutDetailFragment tak, fragmentu WorkoutDetailFragment.
} by wyświetlał informacje o treningu, aby sprawdzić, Identyfikatorem tego fragmentu w kodzie
czy to działa. układu aktywności jest detail_frag.
Kolejną rzeczą, którą się zajmiemy, będzie modyfikacja widoków fragmentu w momencie jego
wyświetlania. Zanim jednak będziemy mogli to zrobić, musimy poznać cykl życia fragmentu.
281
Powtórka z aktywności
282 Rozdział 7.
Fragmenty
onResume()
Aktywność wznowiona onResume() Ta metoda jest wywoływana, kiedy fragment jest widoczny
i aktywnie działa.
onPause()
Aktywność wstrzymana onPause() Ta metoda jest wywoływana, kiedy fragment jest widoczny,
lecz nie prowadzi już interakcji z użytkownikiem.
onStop()
Aktywność zatrzymana onStop() Ta metoda jest wywoływana, kiedy fragment przestaje być
widoczny dla użytkownika.
onDestroyView()
Aktywność usunięta onDestroyView() Ta metoda zapewnia fragmentowi możliwość usunięcia
wszystkich zasobów, które mogą być skojarzone z jego
widokiem.
onDestroy() onDestroy()
W tej metodzie fragment może zwolnić wszystkie inne
używane zasoby.
onDetach()
onDetach() Ta metoda jest wywoływana, gdy fragment ostatecznie traci
kontakt ze swoją aktywnością.
283
Klasa Fragment
package com.hfad.trenazer;
Trenazer
import android.app.Fragment;
import android.os.Bundle; app/src/main
import android.view.LayoutInflater;
import android.view.View; Używamy tej klasy
w metodzie onStart(). java
import android.view.ViewGroup;
import android.widget.TextView;
com.hfad.trenazer
public class WorkoutDetailFragment extends Fragment {
private long workoutId;
WorkoutDetail
@Override Fragment.java
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_workout_detail, container, false);
}
@Override
public void onStart() { Metoda getView() pobiera główny widok fragmentu.
super.onStart(); Możemy go później użyć do pobrania referencji do
widoków wyświetlających tytuł i opis treningu.
View view = getView();
if (view != null) {
TextView title = (TextView) view.findViewById(R.id.textTitle);
Workout workout = Workout.workouts[(int) workoutId];
title.setText(workout.getName());
TextView description = (TextView) view.findViewById(R.id.textDescription);
description.setText(workout.getDescription());
}
}
Urządzenie MainActivity
setWorkout(1)
WorkoutDetail
MainActivity Fragment
286 Rozdział 7.
Fragmenty
Dokąd dotarliśmy?
Do tej pory utworzyliśmy plik MainActivity.java, układ aktywności głównej
activity_main.xml, klasę fragmentu zapisaną w pliku WorkoutDetailFragment.java, układ
używany przez ten fragment — fragment_workout_detail.xml oraz zwyczajną klasę
Javy zawierającą dane treningów i zapisaną w pliku Workout.java. Aktywność
MainActivity używa fragmentu WorkoutDetailFragment do wyświetlania
szczegółowych informacji o treningu, a ten pobiera dane treningu z klasy Workout.
<Layout> <Layout>
</Layout> </Layout>
activity_main.xml fragment_
workout_detail.xml
WorkoutDetail
MainActivity.java Fragment.java Workout.java
Nie istnieją
głupie pytania
P: Dlaczego aktywność nie może pobrać fragmentu, P: Dlaczego fragmenty nie dysponują metodą
używając metody findViewById()? findViewById()?
O : Dlatego, że metoda findViewById() zawsze zwraca obiekt O: Ponieważ fragmenty nie są ani widokami, ani aktywnościami.
typu View, a choć to zadziwiające, okazuje się, że fragmenty Zamiast niej musimy użyć metody getView() fragmentu, by
nie są widokami. pobrać jego widok główny, a następnie skorzystać z jego metody
findViewById(), by odszukać jego widoki podrzędne.
P : Skoro aktywności mają metodę findViewById(),
to czemu nie mają metody findFragmentById()? P: Aktywności trzeba rejestrować w pliku manifestu
AndroidManifest.xml, gdyż w przeciwnym razie aktywność
O: To dobre pytanie. Fragmenty nie były dostępne we wczesnych nie będzie w stanie ich używać. Czy podobnie trzeba
wersjach systemu Android. Dlatego wprowadzono do systemu rejestrować fragmenty?
menedżery fragmentów, stanowiące sposób dodania do niego
przydatnego kodu do zarządzania fragmentami, a jednocześnie O: Nie. Aktywności muszą być rejestrowane w pliku
pozwalające uniknąć konieczności dodawania obszernego kodu AndroidManifest.xml, a fragmenty nie.
do klasy bazowej aktywności.
287
Dodajemy listę
Wiesz już, jak dodać do aktywności widok listy. Możemy więc utworzyć
fragment, którego układ będzie zawierał pojedynczy widok listy,
a następnie wyświetlić w nim nazwy dostępnych treningów.
288 Rozdział 7.
Fragmenty
android.app.ListFragment
Fragment listy
dysponuje swoim getListView()
własnym widokiem
listy, dzięki czemu nie getListAdapter()
musimy sami tworzyć
takiej listy. Wystarczy, setListAdapter()
że dostarczymy
niezbędnych danych. onListItemClick()
...
289
Tworzymy ListFragment
package com.hfad.trenazer;
Trenazer
import android.os.Bundle;
import android.app.ListFragment; app/src/main
import android.view.LayoutInflater;
import android.view.View; Nasz fragment musi dziedziczyć java
po klasie ListFragment, a nie
import android.view.ViewGroup; po klasie Fragment.
com.hfad.trenazer
public class WorkoutListFragment extends ListFragment {
WorkoutList
@Override Fragment.java
290 Rozdział 7.
Fragmenty
Aktualnie naszym celem jest przekazać do widoku listy wyświetlanego we fragmencie WorkoutListFragment
tablicę z nazwami treningów. Do powiązania tej tablicy z widokiem listy użyjemy adaptera.
Utworzymy adapter typu
ArrayAdapter i użyjemy go, aby
To jest widok listy. powiązać widok listy z tablicą. To jest tablica.
Jeżeli stosujemy powyższe wywołanie w kodzie aktywności, jako kontekstu możemy użyć referencji this.
To możliwe, gdyż aktywność jest typem kontekstu — klasa Activity dziedziczy po klasie Context.
Jak już się jednak przekonałeś, Fragment nie jest klasą dziedziczącą po Context, dlatego w tym
przypadku nie możemy użyć this. Oznacza to, że bieżący kontekst musimy zdobyć w jakiś inny sposób.
Jeśli tak jak my tworzysz adapter w metodzie onCreateView() fragmentu, to możesz pobrać kontekst,
używając metody getContext() obiektu LayoutInflator:
setListAdapter(listAdapter);
Spróbujmy zatem użyć adaptera ArrayAdapter do określenia zawartości naszego fragmentu
prezentującego listę treningów.
291
Kod klasy WorkoutListFragment
package com.hfad.trenazer;
Trenazer
import android.os.Bundle;
import android.app.ListFragment; app/src/main
import android.view.LayoutInflater;
import android.view.View; java
Używamy tej klasy
import android.view.ViewGroup; w metodzie onCreate().
com.hfad.trenazer
import android.widget.ArrayAdapter;
WorkoutList
public class WorkoutListFragment extends ListFragment { Fragment.java
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
String[] names = new String[Workout.workouts.length];
for (int i = 0; i < names.length; i++) {
names[i] = Workout.workouts[i].getName();
} Tworzymy adapter Tworzymy tablicę łańcuchów znaków
ArrayAdapter. z nazwami treningów.
292 Rozdział 7.
Fragmenty
293
Jazda próbna
<Layout>
</Layout>
fragment_
<Layout>
workout_list.xml
</Layout>
activity_main.xml
WorkoutList
Fragment.java
Workout.java
Urządzenie MainActivity.java
WorkoutDetail
Fragment.java
<Layout>
Niemniej jednak jeszcze nie zakończyliśmy prac nad kodem
naszej aplikacji. Musimy zadbać o to, aby we fragmencie </Layout>
294 Rozdział 7.
Fragmenty
¨ Utworzenie fragmentów
Powiązanie listy z widokiem szczegółów ¨ Powiązanie fragmentów
¨ Utworzenie odrębnych układów
Zmienianie szczegółowych informacji o treningu w odpowiedzi na kliknięcie
elementu listy można zaimplementować na kilka sposobów. My zastosujemy
następujący:
295
Zastosowanie interfejsu
interface WorkoutListListener {
void itemClicked(long id);
};
@Override
public void onAttach(Activity activity) {
....
}
296 Rozdział 7.
Fragmenty
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
String[] names = new String[Workout.workouts.length];
for (int i = 0; i < names.length; i++) {
names[i] = Workout.workouts[i].getName();
}
ArrayAdapter<String> adapter = new ArrayAdapter<String>(
inflater.getContext(), android.R.layout.simple_list_item_1,
names);
setListAdapter(adapter);
return super.onCreateView(inflater, container, savedInstanceState);
}
Ta metoda jest wywoływana
@Override w momencie dołączania
fragmentu do aktywności.
public void onAttach(Activity activity) {
super.onAttach(activity);
this.listener = (WorkoutListListener)activity;
}
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
if (listener != null) {
Ta metoda przekazuje obiektowi
listener.itemClicked(id); nasłuchującemu informację
} o kliknięciu jednego
} z elementów ListView.
}
297
Implementacja interfejsu WorkoutListListener
package com.hfad.trenazer;
import android.app.Activity;
import android.os.Bundle;
Zaimplementuj interfejs
WorkoutListListener.
public class MainActivity extends Activity
implements WorkoutListFragment.WorkoutListListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
WorkoutDetailFragment frag = (WorkoutDetailFragment)
getFragmentManager().findFragmentById(R.id detail_frag);
frag.setWorkout(1);
Usuniemy te wiersze, gdyż
} nie podajemy już na stałe
identyfikatora wyświetlanego
treningu.
@Override
Trenazer
public void itemClicked(long id) {
// Tu umieścimy kod aktualizujący wyświetlane informacje o treningu
app/src/main
}
Ta metoda została zdefiniowana
} w interfejsie WorkoutListListener.
java
298 Rozdział 7.
Fragmenty
Jednak transakcjami umieszczanymi na tym stosie nie muszą być wyłącznie aktywności.
Mogą nimi być także zmiany fragmentów wyświetlanych na ekranie urządzenia:
Transakcja: Zastąp fragment szczegółów Siła i dystans fragmentem szczegółów Ogólna agonia.
Transakcja: Zastąp fragment szczegółów Ogólna agonia fragmentem szczegółów Tylko dla mięczaków.
app/src/main
<fragment
class=”com.hfad.trenazer.WorkoutListFragment”
res
android:id=”@+id/list_frag”
android:layout_width=”0dp”
layout
android:layout_weight=”2”
<xml>
android:layout_height=”match_parent”/> </xml>
Teraz zajmiemy się napisaniem kodu, który doda fragment do układu FrameLayout.
300 Rozdział 7.
Fragmenty
Aby utworzyć transakcję fragmentu, musimy zacząć od pobrania z menedżera fragmentów obiektu
FragmentTransaction:
Następnie musimy określić wszystkie akcje, które chcemy wykonać w ramach tej transakcji.
W przypadku naszej aplikacji chcemy zastąpić fragment wyświetlony w układzie FrameLayout,
a możemy to zrobić za pomocą metody replace() transakcji:
To jest fragment umieszczony
transaction.replace(R.id.pojemnik_fragmentu, fragment); w pojemniku fragmentów.
gdzie parametr przejscie określa typ animacji. Może on przyjmować następujące wartości:
TRANSIT_FRAGMENT_CLOSE (fragment jest usuwany ze stosu), TRANSIT_FRAGMENT_OPEN (fragment
jest dodawany), TRANSIT_FRAGMENT_FADE (fragment powinien zniknąć i ponownie się pojawić)
lub TRANSIT_NONE (brak animacji).
Po określeniu wszystkich czynności, które mają zostać wykonane w ramach transakcji, musimy
wywołać metodę addToBackStack(), aby dodać ją do stosu cofnięć. Dzięki temu, klikając
przycisk Wstecz, użytkownik będzie mógł cofnąć się do wcześniejszego stanu fragmentu.
Metoda addToBackStack() ma jeden parametr typu String — nazwę identyfikującą transakcję:
W przeważającej większości przypadków nie będziemy musieli
transaction.addToBackStack(null); pobierać transakcji, a to oznacza, że zamiast określać ich etykiety,
w wywołaniu tej metody możemy przekazywać wartość null.
Aby zatwierdzić zmiany w aktywności, należy wywołać metodę commit():
transaction.commit();
301
Kod aktywności MainActivity
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
To wywołanie
public void itemClicked(long id) { rozpoczyna transakcję
WorkoutDetailFragment details = new WorkoutDetailFragment(); fragmentu.
FragmentTransaction ft = getFragmentManager().beginTransaction();
details.setWorkout(id);
Zastępujemy fragment
ft.replace(R.id.fragment_container, details); i dodajemy go do stosu cofnięć.
ft.addToBackStack(null); Wygaszamy stary
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); i wyświetlamy
nowy fragment.
ft.commit();
Zatwierdzamy
} transakcję.
}
302 Rozdział 7.
Fragmenty
Bezpośrednio po uruchomieniu
aplikacji prawa część
ekranu jest pusta, ponieważ
użytkownik nie wybrał jeszcze
żadnego treningu.
Użytkownik kliknął
trening Tylko dla
mięczaków, więc
aplikacja wyświetliła
szczegółowe informacje
o tym treningu.
303
Obracanie po raz wtóry
Wybierz jeden
z treningów,
a szczegółowe
informacje
o nim zostaną
wyświetlone Kiedy obrócimy
z prawej strony. urządzenie, zostaną
wyświetlone
szczegółowe informacje
o pierwszym treningu,
a nie treningu
Gdy po raz pierwszy przedstawialiśmy cykl życia aktywności, dowiedziałeś wybranym wcześniej.
Będzie to trening,
się, że w efekcie obrócenia urządzenia Android usuwa i ponownie tworzy który w tablicy
aktywność. Kiedy to się dzieje, wszystkie zmienne lokalne mogą zostać treningów ma indeks 0.
utracone. Jeśli aktywność zawiera fragmenty, to te fragmenty są usuwane
i ponownie odtwarzane wraz z aktywnością. Oznacza to, że wszystkie
zmienne lokalne używane przez fragmenty także tracą swój stan.
304 Rozdział 7.
Fragmenty
y.
... Nie są potrzebne żadne nowe instrukcje import, więc je pomijam
app/src/main
@Override
public void onStart() {
super.onStart();
View view = getView();
if (view != null) {
TextView title = (TextView) view.findViewById(R.id.textTitle);
Workout workout = Workout.workouts[(int) workoutId];
title.setText(workout.getName());
TextView description = (TextView) view.findViewById(R.id.textDescription);
description.setText(workout.getDescription());
}
} Przed usunięciem fragmentu zapisujemy wartość zmiennej workoutId
w obiekcie savedInstanceState typu Bundle. Wartość tę odtworzymy
@Override w metodzie onCreateView().
public void onSaveInstanceState(Bundle savedInstanceState) {
savedInstanceState.putLong(“workoutId”, workoutId);
}
Na tablecie
Jeśli uruchomimy aplikację
na tablecie, chcemy,
by wyglądała i działała tak
jak pokazaliśmy w tym
rozdziale. Fragmenty z listą
i ze szczegółami treningów
mają być wyświetlane obok
siebie i działać w ramach
tej samej aktywności. Jeśli
klikniemy trening, jego
szczegóły mają zostać
wyświetlone obok.
Na telefonie
Jeśli uruchomimy aplikację na telefonie, chcemy,
by działała nieco inaczej. Lista treningów ma
być wyświetlana w jednej aktywności i zajmować
cały obszar ekranu. Kiedy użytkownik kliknie
trening, ma zostać uruchomiona druga
aktywność, która wyświetli szczegółowe
informacje o tym treningu.
To są dwie
różne
aktywności,
każda
wyświetla
inny
fragment.
306 Rozdział 7.
Fragmenty
Na tablecie <Layout>
</Layout>
Wersja aplikacji przeznaczona na tablety
będzie działała tak jak aktualnie działa: <Layout>
fragment_
</Layout>
workout_list.xml
activity_main.xml
WorkoutList
Fragment.java
Workout.java
MainActivity.java
Tablet
WorkoutDetail <Layout>
fragment_
W tym przypadku, zamiast używać obu fragmentów w aktywności workout_detail.xml
MainActivity, w tej aktywności umieścimy tylko fragment
WorkoutListFragment i dodamy drugą aktywność, DetailActivity,
w której użyjemy fragmentu WorkoutDetailFragment.
<Layout> <Layout>
</Layout> </Layout>
activity_main.xml activity_detail.xml
MainActivity.java DetailActivity.java
Telefon
<Layout> <Layout>
WorkoutList WorkoutDetail
</Layout> </Layout>
Fragment.java Fragment.java
fragment_ fragment_
workout_list.xml Workout.java workout_detail.xml
307
Różne zasoby dla różnych ekranów
layout
<xml>
Te dwa układy będą </xml>
używane na urządzeniach
z mniejszymi ekranami. activity_main.xml
<xml>
</xml>
activity_detail.xml
308 Rozdział 7.
Fragmenty
Android określi, które zasoby zastosować, w trakcie działania aplikacji, starając się dobrać
je jak najlepiej. Jeśli nie uda mu się znaleźć dokładnego dopasowania, to użyje zasobów
przeznaczonych dla mniejszych ekranów niż ten, w który jest wyposażone urządzenie.
Jeśli dostępne zasoby będą przeznaczone wyłącznie dla większych ekranów niż ekran
Więcej informacji na
danego urządzenia, to aplikacja przestanie działać.
temat przedstawionych
z
Jeśli chcemy, by aplikacja działała wyłącznie na urządzeniach z ekranami o określonej wielkości, tutaj ustawień możes
zna leź ć na str oni e
to możemy tego zażądać, dodając w pliku manifestu aplikacji, AndroidManifest.xml, odpowiednie
https://developer.
atrybuty do elementu <supported-screens>. Na przykład jeśli nie chcemy, by aplikacja działała
android.com/guide/
na urządzeniach z małymi ekranami, to możemy użyć elementu o poniższej postaci: practices/screens_
<supported-screens android:smallScreens=”false”>
support.html.
309
Ćwiczenie
import android.app.Activity;
import android.os.Bundle;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
...
}
}
A B
app/src/main app/src/main
res res
layout layout
<xml> <xml>
</xml> </xml>
activity_main.xml activity_main.xml
layout-tablet layout-large-land
<xml> <xml>
</xml> </xml>
activity_main.xml activity_main.xml
310 Rozdział 7.
Fragmenty
C D
app/src/main app/src/main
res res
layout layout
<xml> <xml>
</xml> </xml>
activity_main.xml activity_main.xml
<xml>
layout-large </xml>
<xml> activity_main_tablet.xml
</xml>
activity_main.xml
E F
app/src/main app/src/main
res res
layout-large layout
<xml> <xml>
</xml> </xml>
activity_main.xml activity_main.xml
layout-normal layout-large-land
<xml> <xml>
</xml> </xml>
activity_main.xml activity_main.xml
layout-large-port
<xml>
</xml>
activity_main.xml
311
Rozwiązanie
import android.app.Activity;
import android.os.Bundle;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
...
}
}
A B
app/src/main app/src/main
activity_main.xml activity_main.xml
312 Rozdział 7.
Fragmenty
C D
app/src/main app/src/main
activity_main.xml
E F
app/src/main app/src/main
activity_main.xml
313
Katalog layout-large
Aby skopiować plik activity_main.xml, zaznacz go, a potem wybierz z menu opcję
Edit/Copy. Następnie zaznacz katalog layout-large i wybierz opcję Edit/Paste.
W rezultacie Android Studio skopiuje plik activity_main.xml do katalogu
app/src/main/res/layout-large.
<fragment
app/src/main
class=”com.hfad.trenazer.WorkoutListFragment”
android:id=”@+id/list_frag” res
Nie wprowadzaliśmy
android:layout_width=”0dp” w układzie żadnych
android:layout_weight=”2” zmian — po prostu layout-large
skopiowaliśmy go do
android:layout_height=”match_parent”/> katalogu layout-large. <xml>
</xml>
<FrameLayout activity_main.xml
android:id=”@+id/fragment_container”
android:layout_width=”0dp”
android:layout_weight=”3”
android:layout_height=”match_parent” />
</LinearLayout>
314 Rozdział 7.
Fragmenty
layout
<xml>
Ponieważ w przypadku uruchamiania aplikacji na telefonach </xml>
315
Utworzenie aktywności DetailActivity
Intencja
setWorkout(1)
id: 1
wności.
Musimy zmodyfikować obie te akty
Zaczniemy do DetailActivity.
316 Rozdział 7.
Fragmenty
Zagadkowy basen
Twoim zadaniem jest wyłowienie fragmentów
z basenu i umieszczenie ich w pustych
miejscach kodu pliku DetailActivity.java.
Żadnego fragmentu kodu nie możesz użyć
więcej niż jeden raz i nie wszystkie z tych
fragmentów będą Ci potrzebne. Twoim
celem jest odczytanie identyfikatora
treningu zapisanego w intencji i przekazanie go
dalej do fragmentu WorkoutDetailFragment.
package com.hfad.trenazer;
import android.app.Activity;
import android.os.Bundle; Nazwę informacji dodatkowej
przekazywanej w intencji
określiliśmy za pomocą stałej,
public class DetailActivity extends Activity { aby mieć pewność, że ta sama
nazwa będzie używana w obu
public static final String EXTRA_WORKOUT_ID = ”id”; aktywnościach — MainActivity
i DetailActivity.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_detail);
. ...................workoutDetailFragment = (.....................)
. ..............................(R.id.detail_frag);
int workoutId = (int) getIntent().getExtras().get(EXTRA_WORKOUT_ID);
workoutDetailFragment.setWorkout(workoutId);
}
}
WorkoutDetailFragment
findFragmentById getFragmentManager()
.
findViewById
317
Rozwiązanie
import android.app.Activity;
import android.os.Bundle;
@Override
y,
protected void onCreate(Bundle savedInstanceState) { Referencję do fragmentu pobieram )
yId(
wywołując metodę findFragmentB
super.onCreate(savedInstanceState); menedżera fragmentów.
setContentView(R.layout.activity_detail);
WorkoutDetailFragment workoutDetailFragment = (..........................
........................ WorkoutDetailFragment )
getFragmentManager() . findFragmentById
. ................ ........... ..........(R.id.detail_frag);
int workoutId = (int) getIntent().getExtras().get(EXTRA_WORKOUT_ID);
workoutDetailFragment.setWorkout(workoutId);
}
}
Te fragmenty kodu
nie były potrzebne.
findViewById
318 Rozdział 7.
Fragmenty
@Override DetailActivity.java
319
Który układ
Układ fragment_container,
w którym jest wyświetlany
fragment WorkoutDetailFragment,
jest używany wyłącznie w układzie
activity_main.xml zapisanym
w katalogu layout-large.
package com.hfad.trenazer;
app/src/main
import android.app.Activity;
java
import android.app.FragmentTransaction;
import android.content.Intent;
com.hfad.trenazer
import android.os.Bundle; Tych dodatkowych klas
import android.view.View; używamy w kodzie metody
itemClicked(). MainActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); układu
Próbujemy pobrać referencję do fragment
o
} typu FrameLayout, zawierająceg
ona dostępna,
WorkoutDetailFragment. Będzie na urządzeniu
jeśli aplik acja zost ała uruc hom iona
@Override z dużym ekra nem .
public void itemClicked(long id) {
View fragmentContainer = findViewById(R.id.fragment_container);
if (fragmentContainer != null) {
Ten kod WorkoutDetailFragment details = new WorkoutDetailFragment();
ma być
wykonywany FragmentTransaction ft = getFragmentManager().beginTransaction();
wyłącznie,
jeśli uda details.setWorkout(id);
się odnaleźć ft.replace(R.id.fragment_container, details);
poszukiwany
układ ft.addToBackStack(null);
FrameLayout. ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.commit();
} else {
Intent intent = new Intent(this, DetailActivity.class);
intent.putExtra(DetailActivity.EXTRA_WORKOUT_ID, (int)id);
startActivity(intent);
Jeśli układu FrameLayout nie uda się znaleźć, będzie
} to oznaczało, że aplikacja została uruchomiona na
} urządzeniu z mniejszym ekranem. Wówczas musimy
uruchomić aktywność DetailActivity, przekazując do
} niej identyfikator klikniętego treningu.
Sprawdźmy teraz, co się stanie po uruchomieniu aplikacji.
321
Jazda próbna
lądała
Na tabletach aplikacja będzie wyg
j.
dokładnie tak samo jak wcześnie
W przypadku uruchomienia
aplikacji na telefonie będzie …a po kliknięciu jednego
ona wyglądała inaczej. z wyświetlonych na niej treningów
Aktywność MainActivity informacje o nim zostaną
wyświetla pojedynczą listę… wyświetlone w osobnej aktywności.
322 Rozdział 7.
Fragmenty
Rozdział 7.
Opanowałeś już rozdział 7. i dodałeś
do swojego przybornika z narzędziami
umiejętność tworzenia i stosowania
fragmentów. Metody cyklu życia fragmentów
onAttach()
CELNE SPOSTRZEŻENIA
j
Pełny kod przykładowe onDestroyView()
tow ane j
aplikacji prezen
dzi ale mo żes z
w tym roz
pob rać z ser we ra FT P
wydawnictwa Helion: onDestroy()
ftp://ftp.helion.pl/
przyklady/andrrg.zip
onDetach()
323
324 Rozdział 7.
8. Fragmenty zagnieżdżone
Zadbaj o potomstwo
Przycisk Wstecz wprost
oszalał, wszędzie wokół
pełno było transakcji. Więc
walnąłem w nie wywołaniem
getChildFragmentManager()
i — BUM! Wszystko wróciło
do normalnego stanu.
Ale nie tylko aktywności mogą zawierać fragmenty — okazuje się, że fragmenty można
umieszczać także wewnątrz innych fragmentów. Aby się o tym przekonać, napiszemy
teraz fragment stopera, który następnie umieścimy we fragmencie ze szczegółowymi
informacjami o treningu.
Fragment
WorkoutDetailFragment
wyświetla szczegółowe
informacje o treningu
klikniętym przez użytkownika.
Do fragmentu
WorkoutDetailFragment
dodamy fragment stopera.
Fragment
WorkoutListFragment
prezentuje listę
treningów.
onCreate()
onCreateView()
onActivityCreated()
onStart()
onPause()
onResume()
onStop()
onDestroyView()
onRestart()
onDestroy()
onDetach()
Oprócz tego we fragmentach nie można bezpośrednio wywoływać takich metod jak
findViewById(). Zamiast tego najpierw należy pobrać referencję do obiektu View,
a dopiero potem można używać wywołania o postaci view.findViewById().
Zaostrz ołówek
Poniżej przedstawiliśmy napisany już wcześniej kod aktywności StopwatchActivity.
Teraz masz za zadanie skonwertować go na fragment o nazwie StopwatchFragment.
Weź do ręki ołówek i wprowadź niezbędne zmiany. Pamiętaj przy tym o następujących
zagadnieniach:
– Zamiast pliku układu o nazwie activity_stopwatch.xml fragment ma używać pliku układu
o nazwie fragment_stopwatch.xml.
– Upewnij się, że zastosowałeś odpowiednie ograniczenia dostępu do metod.
– W jaki sposób określisz używany układ?
– Metoda runTimer() nie będzie mogła korzystać z metody findViewById(), dlatego
może wygodnie będzie przekazać do metody runTimer() jakiś obiekt klasy View.
@Override
protected void onPause() { W tej metodzie zatrzymujemy stop
jeśli działanie aktywności zostało er,
super.onPause(); wstr zymane.
wasRunning = running;
running = false;
}
328 Rozdział 8.
Fragmenty zagnieżdżone
działanie
@Override Ta metoda uruchamia stoper, jeśli
protected void onResume() { aktywności zostało wznowione.
super.onResume();
if (wasRunning) {
running = true;
} W tej metodzie zapisujemy stan
} aktywności przed jej usunięciem.
@Override
protected void onSaveInstanceState(Bundle savedInstanceState) {
savedInstanceState.putInt(”seconds”, seconds);
savedInstanceState.putBoolean(”running”, running);
savedInstanceState.putBoolean(”wasRunning”, wasRunning);
}
Zaostrz ołówek
Rozwiązanie Poniżej przedstawiliśmy napisany już wcześniej kod aktywności StopwatchActivity.
Teraz masz za zadanie skonwertować go na fragment o nazwie StopwatchFragment.
Weź do ręki ołówek i wprowadź niezbędne zmiany. Pamiętaj przy tym o następujących
zagadnieniach:
– Zamiast pliku układu o nazwie activity_stopwatch.xml fragment ma używać pliku układu
o nazwie fragment_stopwatch.xml.
– Upewnij się, że zastosowałeś odpowiednie ograniczenia dostępu do metod.
– W jaki sposób określisz używany układ?
– Metoda runTimer() nie będzie mogła korzystać z metody findViewById(), dlatego
może wygodnie będzie przekazać do metody runTimer() jakiś obiekt klasy View.
To jest nowa nazwa.
330 Rozdział 8.
Fragmenty zagnieżdżone
Po kliknięciu przycisku Finish Android Studio wygeneruje nowy fragment, zapisany w pliku
StopwatchFragment.java umieszczonym w katalogu app/src/main/java. Teraz zastąp zawartość
tego pliku wygenerowaną przez Android Studio poniższym kodem (to zaktualizowany kod
aktywności, który zmodyfikowaliśmy w ramach ostatniego ćwiczenia):
package com.hfad.trenazer;
Trenazer
import android.os.Bundle;
import android.os.Handler; app/src/main
import android.app.Fragment;
import android.view.LayoutInflater; java
import android.view.View;
import android.view.ViewGroup; com.hfad.trenazer
import android.widget.TextView;
Stopwatch
public class StopwatchFragment extends Fragment { Fragment.java
// Liczba sekund wyświetlana przez stoper
private int seconds = 0; Liczba zmierzonych sekund.
// Czy stoper działa? Zmienna running informuje, czy
stoper
private boolean running; aktualnie działa, czy nie.
332 Rozdział 8.
Fragmenty zagnieżdżone
@Override
public void onPause() {
super.onPause(); Trenazer
Jeśli działanie fragmentu
wasRunning = running; zostało wstrzymane, zapisujemy,
running = false; czy stoper był włączony, app/src/main
po czym go zatrzymujemy.
}
java
@Override
com.hfad.trenazer
public void onResume() {
super.onResume();
Stopwatch
if (wasRunning) { Fragment.java
ał
running = true; Jeśli stoper działał, zanim zost
wstr zym any, to tuta j go pono wnie
} uruchamiamy.
}
@Override
public void onSaveInstanceState(Bundle savedInstanceState) { Ta metoda zapisuje
wartości zmiennych
savedInstanceState.putInt(”seconds”, seconds); w obiekcie Bundle
przed usunięciem
savedInstanceState.putBoolean(”running”, running); aktywności. Są one
savedInstanceState.putBoolean(”wasRunning”, wasRunning); używane w przypadku,
gdy użytkownik zmieni
} orientację urządzenia.
334 Rozdział 8.
Fragmenty zagnieżdżone
336 Rozdział 8.
Fragmenty zagnieżdżone
dwa fragmenty:
Aktywność MainActivity zawiera lFragment.
ent i Work outD etai
WorkoutListFragm
To jest fragment
WorkoutDetailFragment.
Fragment
WorkoutDetailFragment
będzie teraz
To jest fragment zawierał fragment
WorkoutListFragment. StopwatchFragment.
<TextView
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:textAppearance=”?android:attr/textAppearanceLarge”
android:text=””
android:id=”@+id/textTitle” />
<TextView
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=””
android:id=”@+id/textDescription” />
338 Rozdział 8.
Fragmenty zagnieżdżone
Kiedy chcemy wyświetlić jeden fragment wewnątrz innego, to musimy użyć nieco
innego menedżera fragmentów. W tym przypadku musimy użyć menedżera
fragmentów powiązanego z fragmentem nadrzędnym. Oznacza to, że wszelkie
transakcje fragmentów będą powiązane z fragmentem nadrzędnym, a nie
z aktywnością.
Metoda getFragmentManager()
tworzy transakcje na poziomie aktywności
W pierwszej kolejności sprawdźmy, co by się stało, gdyby nasz fragment
WorkoutDetailFragment utworzył transakcję wyświetlającą fragment
StopwatchFragment, używając metody getFragmentManager().
340 Rozdział 8.
Fragmenty zagnieżdżone
Jak widać, kod jest niemal identyczny z tym, którego używaliśmy do wyświetlania
fragmentów w aktywnościach. Najważniejsza różnica polega na tym, że w tym przypadku
wyświetlamy jeden fragment wewnątrz drugiego i to właśnie z tego względu zamiast
wywołania getFragmentManager() używamy wywołania getChildFragmentManager().
Nie istnieją
głupie pytania
342 Rozdział 8.
Fragmenty zagnieżdżone
@Override
public void onStart() {
super.onStart();
View view = getView();
if (view != null) {
TextView title = (TextView) view.findViewById(R.id.textTitle);
Workout workout = Workout.workouts[(int) workoutId];
title.setText(workout.getName());
TextView description = (TextView) view.findViewById(R.id.textDescription);
description.setText(workout.getDescription());
}
} d nie musimy zmieniać.
Kodu tych meto
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
savedInstanceState.putLong(”workoutId”, workoutId);
}
W przypadku naciśnięcia
przycisku Wstecz zostają
wycofane obie transakcje:
transakcja wyświetlająca
Kiedy klikniesz stoper i transakcja
trening, zostaną wyświetlająca szczegółowe
wyświetlone informacje o treningu. Dzieje
szczegółowe się tak, gdyż transakcja
informacje o nim wyświetlająca stoper została
wraz ze stoperem. zagnieżdżona wewnątrz
transakcji wyświetlającej
szczegółowe informacje
o treningu.
Kliknij jeden
z przycisków
stopera…
344 Rozdział 8.
Fragmenty zagnieżdżone
<RelativeLayout xmlns:android=”http://schemas.android.com/apk/res/android”
...
<Button
android:id=”@+id/start_button”
Trenazer
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
app/src/main
android:layout_below=”@+id/time_view”
android:layout_centerHorizontal=”true” res
android:layout_marginTop=”20dp”
android:onClick=”onClickStart” layout
android:text=”@string/start” /> <xml>
</xml>
<Button
fragment_
android:id=”@+id/stop_button” stopwatch.xml
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_below=”@+id/start_button”
android:layout_centerHorizontal=”true”
android:layout_marginTop=”10dp”
Do określenia metod, które mają
android:onClick=”onClickStop” być wywołane po kliknięciu
android:text=”@string/stop” /> przycisku, używamy atrybutów
android:onClick umieszczonych
<Button w kodzie układu.
android:id=”@+id/reset_button”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_below=”@+id/stop_button”
android:layout_centerHorizontal=”true”
android:layout_marginTop=”10dp”
android:onClick=”onClickReset”
android:text=”@string/reset” />
</RelativeLayout>
346 Rozdział 8.
Fragmenty zagnieżdżone
android:id=”@+id/stop_button” fragment_
stopwatch.xml
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_below=”@+id/start_button”
android:layout_centerHorizontal=”true”
android:layout_marginTop=”10dp” Z układu fragmentu
stopera, ze
android:onClick=”onClickStop” wszystkich
android:text=”@string/stop” /> przycisków,
usuń wiersz
<Button zawierający atrybut
android:id=”@+id/reset_button” andr oid:onClick.
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_below=”@+id/stop_button”
android:layout_centerHorizontal=”true”
android:layout_marginTop=”10dp”
android:onClick=”onClickReset”
android:text=”@string/reset” />
</RelativeLayout>
348 Rozdział 8.
Fragmenty zagnieżdżone
@Override
public void onClick(View v) {
W kodzie naszego fragmentu musimy
... przesłonić metodę onClick().
}
Te magnesiki okazały
case R.id.reset_button:
true
się niepotrzebne.
onClickReset (...... v ...);
}
}
case R.id.start_button:
onClickStart(v); Jeśli użytkownik kliknął przycisk
Start, to wywołujemy metodę
break; onClickStart().
case R.id.stop_button: Jeśli użytkownik kliknął przycisk
Stop, to wywołujemy metodę
onClickStop(v); onClickStop().
break;
case R.id.reset_button: Jeśli użytkownik kliknął przycisk
Kasuj, to wywołujemy metodę
onClickReset(v); onClickReset().
}
}
350 Rozdział 8.
Fragmenty zagnieżdżone
Na przykład w ten oto sposób możemy skojarzyć obiekt nasłuchujący z przyciskiem Start:
Pobieramy referencję
do przycisku.
Button startButton = (Button) layout.findViewById(R.id.start_button);
startButton.setOnClickListener(this);
Dołączamy do przycisku
obiekt nasłuchujący.
Metodę setOnClickListener() poszczególnych przycisków należy wywoływać
dopiero po utworzeniu widoków fragmentu. Oznacza to, że operację dołączenia
obiektu nasłuchującego do przycisków musimy wykonać w metodzie onCreateView()
fragmentu StopwatchFragment:
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View layout = inflater.inflate(R.layout.stopwatch, container, false);
runTimer(layout);
Button startButton = (Button)layout.findViewById(R.id.start_button);
startButton.setOnClickListener(this);
Button stopButton = (Button)layout.findViewById(R.id.stop_button);
stopButton.setOnClickListener(this);
Button resetButton = (Button)layout.findViewById(R.id.reset_button);
resetButton.setOnClickListener(this);
return layout;
} Te wywołania dołączają
do poszczególnych prz obiekt nasłuchujący
ycisków. Trenazer
app/src/main
Kompletny kod aktywności StopwatchFragment przedstawiliśmy
na następnej stronie.
java
com.hfad.trenazer
Stopwatch
Fragment.java
package com.hfad.trenazer;
Używamy klasy Button, więc
... musimy ją zaimportować.
Fragment musi implementować
interfejs View.OnClickListener.
import android.widget.Button;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View layout = inflater.inflate(R.layout.stopwatch, container, false);
runTimer(layout);
Button startButton = (Button) layout.findViewById(R.id.start_button);
startButton.setOnClickListener(this);
Button stopButton = (Button) layout.findViewById(R.id.stop_button);
stopButton.setOnClickListener(this);
Button resetButton = (Button) layout.findViewById(R.id.reset_button);
resetButton.setOnClickListener(this);
return layout;
}
352 Rozdział 8.
Fragmenty zagnieżdżone
... java
com.hfad.trenazer
public void onClickStart(View view) {
running = true;
} Stopwatch
Fragment.java
To są dokładnie te
same metody co
public void onClickStop(View view) { wcześniej. Będą
running = false; one wywoływane
w celu obsługi kliknięć
} przycisków fragmentu.
...
}
er
Kiedy klikniesz przycisk Start, stopTakże
.
zacznie mierzyć upływający czas da
j
klikanie przycisków Stop i Kasu .
prawidłowe, oczekiwane rezultaty
Kiedy obrócisz
urządzenie, stoper
zostanie wyzerowany.
Co się z nim dzieje?
354 Rozdział 8.
Fragmenty zagnieżdżone
Aktywność Fragment
onCreate()
Aktywność
Aktywność Fragment
356 Rozdział 8.
Fragmenty zagnieżdżone
Trenazer
Aby uchronić się przed tym niepożądanym działaniem aplikacji, musimy zadbać o to,
by fragment był zastępowany wyłącznie, jeśli parametr saveInstanceState typu Bundle
ma wartość null. W efekcie całkowicie nowa wersja fragmentu stopera będzie wyświetlana
tylko w momencie, gdy aktywność zostanie utworzona po raz pierwszy.
@Override
public void onStart() {
super.onStart();
View view = getView();
if (view != null) {
TextView title = (TextView) view.findViewById(R.id.textTitle);
Workout workout = Workout.workouts[(int) workoutId];
title.setText(workout.getName());
TextView description = (TextView) view.findViewById(R.id.textDescription);
description.setText(workout.getDescription());
}
}
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
savedInstanceState.putLong(”workoutId”, workoutId);
}
358 Rozdział 8.
Fragmenty zagnieżdżone
Nie istnieją
głupie pytania
P: Czy jeśli użyję atrybutu android:onClick w kodzie P: Czy należy używać fragmentów we własnych
układu fragmentu, to Android faktycznie spróbuje aplikacjach?
wykonać metodę zdefiniowaną w aktywności?
O: To zależy od aplikacji i od tego, co chcesz uzyskać. Jedną
O: Tak, spróbuje. Dlatego zamiast określać metody z głównych zalet fragmentów jest to, że można ich używać,
obsługujące kliknięcia widoków fragmentu przy użyciu atrybutu by zapewnić działanie aplikacji na szerokiej gamie urządzeń,
android:onClick, należy zaimplementować interfejs z ekranami o różnych wielkościach. Na przykład możesz
OnClickListener. zdecydować, by na tabletach fragmenty były wyświetlane jeden
obok drugiego, a na urządzeniach z mniejszymi ekranami
P : Czy to dotyczy zagnieżdżonych fragmentów, w osobnych aktywnościach. W kilku następnych rozdziałach
czy w ogóle wszystkich fragmentów? poznasz także inne, bardzo przydatne sposoby używania
O: To dotyczy wszystkich fragmentów, niezależnie od tego, fragmentów.
czy są one zagnieżdżone w innych, czy nie.
jesteś tutaj 359
Ćwiczenie
A <LinearLayout 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:orientation=”vertical”
tools:context=”com.hfad.ch10ex.SwitchFragment”>
<Switch
android:id=”@+id/switch_view”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content” />
</LinearLayout>
B <LinearLayout 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:orientation=”vertical”
tools:context=”com.hfad.ch10ex.SwitchFragment”>
<Switch
android:id=”@+id/switch_view”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:onClick=”onClick” />
</LinearLayout>
360 Rozdział 8.
Fragmenty zagnieżdżone
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_switch, container, false);
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.switch_view) {
if (((Switch) v).isChecked()) {
Toast.makeText(v.getContext(), ”Włączony”, Toast.LENGTH_SHORT).show();
}
}
}
}
To są dwie implementacje
fragmentów napisane w Javie.
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View layout = inflater.inflate(R.layout.fragment_switch, container, false);
Switch switchView = (Switch) layout.findViewById(R.id.switch_view);
switchView.setOnClickListener(this);
return layout;
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.switch_view) {
if (((Switch) v).isChecked()) {
Toast.makeText(v.getContext(), ”Włączony”, Toast.LENGTH_SHORT).show();
}
}
}
}
jesteś tutaj 361
Rozwiązanie
<LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
A
xmlns:tools=”http://schemas.android.com/tools”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:orientation=”vertical”
tools:context=”com.hfad.ch10ex.SwitchFragment”> To jest prawidłowy kod układu.
<Switch
android:id=”@+id/switch_view”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content” />
</LinearLayout>
B <LinearLayout 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:orientation=”vertical”
tools:context=”com.hfad.ch10ex.SwitchFragment”>
<Switch
android:id=”@+id/switch_view” Element Switch umieszczony w kodzie
układu zawiera atrybut android:onClick.
android:layout_width=”wrap_content” Jego użycie spowoduje wywołanie kodu
zdefiniowanego w aktywności, a nie we
android:layout_height=”wrap_content” fragmencie.
android:onClick=”onClick” />
</LinearLayout>
362 Rozdział 8.
Fragmenty zagnieżdżone
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_switch, container, false);
}
Ten kod implementuje interfejs View.OnClickListener,
@Override ale nie dołącza do przycisku przełącznika żadnego
obiektu nasłuchującego. Metoda onClick() nigdy nie
public void onClick(View v) { zostanie wywołana.
if (v.getId() == R.id.switch_view) {
if (((Switch) v).isChecked()) {
Toast.makeText(v.getContext(), ”Włączony”, Toast.LENGTH_SHORT).show();
}
}
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View layout = inflater.inflate(R.layout.fragment_switch, container, false);
Switch switchView = (Switch) layout.findViewById(R.id.switch_view);
switchView.setOnClickListener(this);
return layout;
} To jest prawidłowa implementacja fragmentu
napisana w Javie. Po kliknięciu przycisku
przełącznika zostanie wywołana metoda onClick().
@Override
public void onClick(View v) {
if (v.getId() == R.id.switch_view) {
if (((Switch) v).isChecked()) {
Toast.makeText(v.getContext(), ”Włączony”, Toast.LENGTH_SHORT).show();
}
}
}
}
jesteś tutaj 363
Przybornik
ale mo żes z
w tym rozdzi
Opanowałeś już rozdział 8. i dodałeś pobrać z ser we ra FT P
do swojego przybornika z narzędziami wydawnictwa Helion:
klady/
umiejętność tworzenia i stosowania ftp://ftp.helion.pl/przy
zagnieżdżonych fragmentów. andrrg.zip
CELNE SPOSTRZEŻENIA
Fragmenty mogą zawierać inne fragmenty.
Jeśli zagnieżdżamy fragment wewnątrz innego fragmentu, to musimy go dodać
programowo, pisząc odpowiedni kod Javy.
Gdy wykonujesz transakcje na zagnieżdżonym fragmencie, tworząc je należy skorzystać
z metody getChildFragmentManager().
W przypadku zastosowania we fragmencie atrybutu android:onClick Android będzie
szukał metody o podanej nazwie w aktywności, w której fragment jest używany.
Zamiast używać we fragmencie atrybutu android:onClick, lepiej będzie
zaimplementować w klasie fragmentu interfejs View.OnClickListener i definiowaną
przez niego metodę onClick().
W przypadku zmiany konfiguracji urządzenia zarówno aktywność, jak i używane
w niej fragmenty są usuwane. Kiedy aktywność jest ponownie tworzona, transakcje jej
fragmentów zostają odtworzone podczas wykonywania metody setContentView()
wywoływanej w metodzie onCreate() aktywności.
Metoda onCreateView() fragmentu jest wykonywana po tym, jak aktywność
odtworzy transakcje fragmentów.
364 Rozdział 8.
9. Paski akcji
Na skróty
Bylibyśmy tu już
Widzisz, kilka godzin temu, gdyby
Kasztanko, mówiłam, wiedziała o przycisku
że kiedyś dojedziemy Strona główna. Iiiiha!
do domu.
Te ekrany wyświetlają
szczegółowe informacje
o wybranym rekordzie,
umożliwiają użytkownikom
edycję wybranego rekordu
lub dodanie nowego.
366 Rozdział 9.
Paski akcji
Pizze
Restauracje
Aby dodać do aplikacji pasek akcji, musimy wybrać motyw (ang. theme), który taki pasek
zawiera. Motyw to styl, który odnosi się do całej aktywności lub aplikacji, dzięki czemu będzie
ona miała spójny wygląd i sposób obsługi. Motywy określają takie aspekty wyglądu aplikacji
jak kolor tła aktywności i paska akcji czy też postać wyświetlanych tekstów.
Motyw Motyw
Theme. Theme.
Material. Holo.
Light Light.
368 Rozdział 9.
Paski akcji
Pakiet Support Libraries zawiera kilka takich bibliotek. Każda z nich jest przeznaczona dla
konkretnego, bazowego poziomu API i implementuje ściśle określony zestaw możliwości.
Nazwa każdej z tych bibliotek wskazuje najniższą wersję systemu Android, w której danej
biblioteki można używać. Na przykład biblioteka wsparcia v4 może być używana z API
poziomu 4 lub wyższym, a biblioteka wsparcia v7 — z API poziomu 7 lub wyższym.
Każda z tych bibliotek jest dostępna w wielu wersjach, uzupełnianych stopniowo o nowe
możliwości systemu i wprowadzane do niego poprawki.
Biblioteka wsparcia v4
Biblioteka gridlayout v7
Obejmuje największy zestaw możliwości,
Zapewnia wsparcie dla klasy
takich jak obsługa komponentów aplikacji
GridLayout.
i różnych możliwości interfejsu użytkownika.
Biblioteka appcompat v7
Zawiera wsparcie dla pasków akcji Bibolioteka recycleview v7
w systemach obsługujących API s Zapewnia wsparcie dla widżetu
poziomu 7 i wyższego oraz dla tworzenia arie RecyclerView.
ibr
i stosowania Material Design.
rtl
po
Biblioteka cardview v7 Sup Biblioteka leanback v17
Dodaje wsparcie dla widżetu Zawiera API umożliwiające
To są tylko wybrane
CardView, pozwalającego spośród wszystkich tworzenie interfejsów użytkownika
prezentować informacje na kartach. bibliotek wsparcia. aplikacji przeznaczonych do
działania na telewizorach.
Android Studio bardzo często domyślnie dodaje biblioteki z pakietu Support
Libraries do tworzonych projektów. Aby się o tym przekonać, utwórzmy nowy
projekt, który będzie stanowił prototyp aplikacji dla restauracji Włoskie Co Nieco,
i zobaczmy, czy faktycznie pojawią się w nim odwołania do tych bibliotek.
jesteś tutaj 369
Biblioteki wsparcia
Chcemy się teraz przekonać, czy ten nowy projekt będzie domyślnie korzystał
z jakichś bibliotek wsparcia. W pierwszej kolejności zajrzyj do pliku MainActivity.java.
Poniżej przedstawiliśmy fragmenty kodu wygenerowanego przez Android Studio.
Domyślnie klasa MainActivity dziedziczy po klasie android.support.v7.app.
ActionBarActivity. Innymi słowy: używa biblioteki wsparcia v7.
package com.hfad.wloskieconieco; Fragment android.support.v7 w instrukcji
importu klasy ActionBarActivity informuje,
że pochodzi ona z biblioteki appcompat v7.
import android.support.v7.app.ActionBarActivity;
...
public class MainActivity extends ActionBarActivity { u MainActivity.java
Kod zapisany w Twoim plik
... e wyg ląda ć niec o inac zej. Jego postać
moż
działania używanego
} może bowiem zależeć od o środowiska
przez Ciebie zint egr owa neg
Klasa ActionBarActivity jest używana w połączeniu z motywami Theme. programistycznego.
AppCompat, by zapewnić możliwość stosowania pasków akcji w aplikacjach
obsługujących API poziomów od 7 do 10. Jeśli zastosujemy klasę
ActionBarActivity jako klasę bazową naszej aktywności, to musimy użyć
jednego z tych motywów, gdyż w przeciwnym razie aplikacja nie będzie działać.
Nie możemy jednak używać bardziej nowoczesnych motywów, takich jak Material.
Nawet jeśli usuniemy z aplikacji odwołania do klasy ActionBarActivity, to Android Studio automatycznie dodało
bibliotekę appcompat v7 do zależności
jednak biblioteka wsparcia v7 wciąż będzie stanowiła zależność projektu. Możesz aplikacji. Zależnie od używanej wersji
się o tym przekonać, wybierając z menu głównego Android Studio opcję File/ Android Studio biblioteka ta może,
Project structure. Kiedy klikniesz moduł app i przejdziesz na kartę Dependencies, lecz nie musi, być używana.
zobaczysz na niej odwołanie do biblioteki appcompat v7.
370 Rozdział 9.
Paski akcji
MainActivity.java
Teraz, kiedy już mamy pewność, że aktywność MainActivity nie używa
klasy ActionBarActivity, pokażemy Ci, jak można zastosować motyw.
372 Rozdział 9.
Paski akcji
Podczas tworzenia projektu Android Studio tworzy domyślny plik zasobów stylów
o nazwie styles.xml i zapisuje go w katalogu app/src/main/res/values. Jego kod będzie
przypominał ten przedstawiony poniżej: Studio
Nie przejmuj się, jeśli Android
użyło inne go tema tu — i tak go
<resources>
zmienimy na następnej stronie.
<!-- Base application theme. -->
<style name=”AppTheme” parent=”Theme.AppCompat.Light.DarkActionBar”>
<!-- Customize your theme here. -->
</style>
</resources>
Plik zasobów stylów zawiera co najmniej jeden styl. Każdy styl jest definiowany
przy użyciu elementu <style>.
Każdy styl musi mieć nazwę, którą definiuje się za pomocą atrybutu name. WloskieCoNieco
Określenie nazwy stylu jest niezbędne, gdyż umożliwia ona odwołanie się
do danego stylu w atrybucie android:theme, w pliku AndroidManifest.xml. app/src/main
W naszym przypadku styl ma nazwę ”AppTheme”, dlatego w pliku manifestu,
AndroidManifest.xml, możemy się do niego odwołać, używając zapisu
res
”@style/AppTheme”.
Atrybut parent określa, skąd dany styl dziedziczy swoje właściwości. values
<xml>
W przedstawionym przykładzie jest to ”Theme.AppCompat.Light.DarkActionBar”. </xml>
styles.xml
Pliku zasobów stylów można także używać do dostosowywania wyglądu
aplikacji poprzez modyfikowanie właściwości istniejącego motywu. W tym celu
do elementu <style> należy dodać element <item> opisujący wprowadzaną
modyfikację. Na przykład poniższy przykład pokazuje, w jaki sposób można
zmodyfikować motyw, by aktywności miały czerwone tło:
<resources>
<style name=”AppTheme” parent=”Theme.AppCompat.Light.DarkActionBar”>
<item name=”android:background”>#FF0000</item>
określa, że
</style> Ten wiersz ści ma mieć
tło ak ty w no
</resources> lor.
czerwony ko
Zaczniemy od zmiany domyślnego tematu aplikacji. W tym celu otwórz plik zasobów stylów,
styles.xml, zapisany w katalogu app/src/main/res/values. Jest to domyślny plik zasobów stylów.
Chcemy, by aplikacja domyślnie używała motywu Theme.Holo.Light, więc musimy to
podać w atrybucie elementu <style>:
WloskieCoNieco
<resources>
<style name=”AppTheme” parent=”android:Theme.Holo.Light”> app/src/main
<!-- Customize your theme here. -->
</style> res
</resources>
values
<xml>
styles.xml
Jak już wiesz z rozdziału 8., możemy skorzystać z różnych struktur katalogów,
by aplikacja używała różnych zasobów odpowiednio dobieranych w trakcie jej
działania. W ramach przykładu pokazaliśmy, jak można używać w aplikacji
różnych układów, wybieranych w zależności od rozmiaru ekranu urządzenia.
<resources> WloskieCoNieco
<style name=”AppTheme” parent=”android:Theme.Material.Light”>
<!-- Customize your theme here. --> app/src/main
</style> Zastosujemy ten motyw, jeśli
</resources> używana wersja systemu
obsługuje API poziomu 21. res
Nazwa stylu podana w każdym z plików zasobów stylów powinna być taka values-v21
sama, gdyż dzięki temu wybrany motyw będzie mógł być stosowany <xml>
</xml>
w trakcie działania aplikacji. Zobaczmy, jak to się dzieje.
styles.xml
374 Rozdział 9.
Paski akcji
Android
2 Jeśli aplikacja została uruchomiona w wersji systemu obsługującej API poziomu 21,
to ma użyć stylów o nazwie AppTheme z katalogu values-v21.
Ten styl określa, że należy użyć motywu o nazwie Theme.Material.Light, więc zostanie on zastosowany.
3 Jeśli aplikacja została uruchomiona w wersji systemu obsługującej API poziomu niższego
od 21, to zastosowany zostanie styl o nazwie AppTheme z katalogu values.
Ten styl określa, że należy użyć motywu o nazwie Theme.Holo.Light, więc zostanie on zastosowany.
Użyję stylu
z katalogu values. values
<xml> Name: AppTheme
</xml>
Parent: Theme.Holo.Light
Android styles.xml
W systemie obsługującym
API poziomu niższego od
W systemie obsługującym API 21 zostanie użyty motyw
poziomu 21 zostanie użyty Theme.Holo.Light.
motyw Theme.Material.Light.
Aby dodać do paska akcji elementy akcji, musimy wykonać następujące czynności:
376 Rozdział 9.
Paski akcji
<menu xmlns:android=”http://schemas.android.com/apk/res/android”
xmlns:tools=”http://schemas.android.com/tools”
xmlns:app=”http://schemas.android.com/apk/res-auto”
WloskieCoNieco
tools:context=”.MainActivity”>
app/src/main
<item android:id=”@+id/action_settings”
To jest
element akcji android:title=”@string/action_settings”
Settings. res
android:orderInCategory=”100”
app:showAsAction=”never” /> menu
</menu> <xml>
</xml>
menu_main.xml
W każdym pliku zasobów menu elementem głównym jest <menu>. Każdy taki plik definiuje
pojedyncze menu bądź zbiór elementów, które należy dodać do paska akcji. Aplikacja
może zawierać wiele takich plików zasobów menu, co jest bardzo przydatne, gdy chcemy,
by na paskach akcji w poszczególnych aktywnościach były wyświetlane różne elementy.
Poszczególne elementy dodaje się do menu za pomocą elementów <item>. Każdy taki
element akcji jest opisywany przez odrębny element <item>. Elementy <item> mogą mieć
kilka różnych atrybutów, a najczęściej używane przedstawiliśmy poniżej:
android:id Określa unikalny identyfikator elementu menu. Identyfikator ten jest niezbędny,
by można było odwoływać się do elementu menu w kodzie aktywności.
android:icon Ikona elementu. Może to być zasób graficzny lub zasób typu mipmap.
android:title Tekst elementu. Ten tekst może nie być prezentowany, jeśli w elemencie menu
została określona ikona i jeśli na pasku akcji nie będzie dostatecznie dużo miejsca
zarówno na ikonę, jak i na tekst. Jeżeli element jest wyświetlany
w obszarze nadmiarowym paska akcji, to będzie wyświetlany wyłącznie jego tekst.
android:orderInCategory Liczba całkowita, która pozwala systemowi określać kolejność wyświetlania
poszczególnych elementów na pasku akcji.
“ifRoom” Sprawia, że element zostanie umieszczony na pasku akcji, jeśli będzie tam dostatecznie
dużo miejsca, a w przeciwnym razie zostanie umieszczony w obszarze nadmiarowym.
“withText” Oznacza, że ma zostać dołączony tytuł elementu.
“never” Oznacza, że element ma zostać umieszczony w obszarze nadmiarowym i nigdy nie ma
być prezentowany na głównym pasku akcji.
“always” Sprawia, że element zawsze będzie umieszczany w głównym obszarze paska akcji.
Ta wartość powinna być używana sporadycznie — jeśli dodamy ją do zbyt wielu
elementów, to będą one wzajemnie na siebie nachodzić.
Spójrzmy jeszcze raz na atrybut showAsAction umieszczony w kodzie menu. Zauważ, że poprzedza go
prefiks app:, a nie android:.
<menu xmlns:android=”http://schemas.android.com/apk/res/android”
xmlns:app=”http://schemas.android.com/apk/res-auto” Ten atrybut dodaje
przestrzeń nazw app.
...>
ategory
<item android:id=”@+id/action_settings” Atrybuty ID, title oraz orderInC
android:title=”@string/action_settings” używają przestrzeni nazw android.
android:orderInCategory=”100”
app:showAsAction=”never” /> Atrybut showAsAction używa
przestrzeni nazw app.
</menu>
Wcześniej w tym rozdziale przekonałeś się, że nasz projekt jest zależny od biblioteki
appcompat v7. W przypadku tej biblioteki atrybut showAsAction nie jest dostępny WloskieCoNieco
w przestrzeni nazw android.
app/src/main
Jeśli projekt jest zależny od biblioteki appcompat v7, to atrybut showAsAction musi być
poprzedzony prefiksem app:, a do elementu <menu> należy dodać poniższy atrybut:
res
xmlns:app=”http://schemas.android.com/apk/res-auto”
Jeśli projekt nie jest zależny od biblioteki appcompat v7, to atrybut showAsAction menu
może być poprzedzony prefiksem android:, a nie app:; oprócz tego w elemencie <xml>
</xml>
<menu> można pominąć atrybut
menu_main.xml
xmlns:app=”http://schemas.android.com/apk/res-auto”
378 Rozdział 9.
Paski akcji
package com.hfad.wloskieconieco;
WloskieCoNieco
import android.view.Menu; Klasa Menu jest używana przez
metodę onCreateOptionsMenu().
... app/src/main
380 Rozdział 9.
Paski akcji
package com.hfad.wloskieconieco;
@Override MainActivity.java
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) { Sprawdzamy, który element
case R.id.action_create_order: został kliknięty.
// Kod wykonywany po kliknięciu przycisku Złóż zamówienie.
Android Studio return true;
utworzyło Musimy zadbać o to, by przycisk Złóż zamówienie coś robił.
element Settings case R.id.action_settings:
(Ustawienia) za // Kod wykonywany po kliknięciu elementu Settings (ustawienia).
nas. Możemy
tu umieścić kod, return true;
który coś zrobi Zwrócenie wartości true informuje system, że kliknięcie
po kliknięciu tego default: elementu paska akcji zostało już obsłużone.
elementu. return super.onOptionsItemSelected(item);
}
}
}
package com.hfad.wloskieconieco;
@Override
java
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); com.hfad.wloskieconieco
setContentView(R.layout.activity_order);
} OrderActivity.java
382 Rozdział 9.
Paski akcji
package com.hfad.wloskieconieco;
@Override
com.hfad.wloskieconieco
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_create_order: MainActivity.java
WloskieCoNieco
import android.app.Activity;
import android.content.Intent;
app/src/main
import android.os.Bundle;
import android.view.Menu; java
import android.view.MenuItem;
com.hfad.wloskieconieco
public class MainActivity extends Activity {
MainActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
} W tej metodzie dodajemy elementy
do paska akcji.
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Przygotowujemy menu; jeśli jest pasek akcji, to dodajemy do niego elementy
getMenuInflater().inflate(R.menu.menu_main, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_create_order:
// Kod wykonywany po kliknięciu przycisku Złóż zamówienie
Intent intent = new Intent(this, OrderActivity.class);
startActivity(intent);
Tu uruchamiamy aktywność Orde
return true; rActivity
w odpowiedzi na kliknięcie przy
cisku
case R.id.action_settings: Złóż zamówienie.
// Kod wykonywany po kliknięciu elementu Settings (Ustawienia)
return true;
default:
return super.onOptionsItemSelected(item);
}
}
}
384 Rozdział 9.
Paski akcji
Kliknięcie elementu
Złóż zamówienie
spowoduje uruchomienie
aktywności
OrderActivity.
Nie istnieją
głupie pytania
P: Moja aplikacja już ma etykietę P: Dlaczego muszę używać klasy P: Twierdzicie, że można używać
i ikonę. Skąd one się wzięły? ActionBarActivity, jeśli chcę, by moja różnych motywów w różnych
aplikacja mogła działać w systemie aktywnościach. Czy może się
O: Gdy tworzymy projekt aplikacji na obsługującym API poziomu niższego zdarzyć, że zechcę zastosować takie
Androida w IDE takim jak Android Studio, od 11? rozwiązanie?
środowisko programistyczne tworzy za nas
pewne fragmenty kodu. Należy do nich O: Ponieważ wyświetlenie paska akcji O: Owszem. Motywy Holo i Material
także określenie nazwy aplikacji i jej ikony. wymaga zastosowania biblioteki Android mają kilka podrzędnych klas motywów,
Support Library. które nadają aktywnościom nieco
P: Czy można używać pasków akcji odmienny wygląd. Gdybyś chciał nadać
w aplikacjach korzystających z API P: Czy kiedykolwiek naprawdę będę jednej z aktywności odmienny wygląd,
poziomu niższego od 7? chciał używać różnych motywów dla to faktycznie mógłbyś chcieć zastosować
różnych poziomów API?
O: Nie, nie można. Nie jest to jednak w niej inny motyw.
wielki problem, gdyż aktualnie jest już O: Tak, to się może zdarzyć. Motyw
bardzo mało urządzeń używających tak Material został wprowadzony w API
starych wersji systemu Android. poziomu 21, więc możesz chcieć,
by aplikacje używały tego motywu,
jeśli będzie dostępny.
jesteś tutaj 385
Dzielenie się to troska
ACTION_SEND
type: “text/plain”
messageText:”Cześć!” ShareAction
TwojaAktywnosc
Provider
Intencja
ACTION_SEND
type: “text/plain”
ShareAction messageText:”Cześć!”
AktywnoscAplikacji
Provider
386 Rozdział 9.
Paski akcji
strings.xml
Oto kod, który dodaje do paska akcję udostępniania:
<menu xmlns:android=”http://schemas.android.com/apk/res/android”
xmlns:app=”http://schemas.android.com/apk/res-auto”
xmlns:tools=”http://schemas.android.com/tools”
tools:context=”.MainActivity”>
WloskieCoNieco
package com.hfad.wloskieconieco;
WloskieCoNieco
...
import android.widget.ShareActionProvider; app/src/main
ShareActionProvider.
... Dodaliśmy zmienną prywatną typu MainActivity.java
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
MenuItem menuItem = menu.findItem(R.id.action_share);
shareActionProvider = (ShareActionProvider) menuItem.getActionProvider();
setIntent(“To jest przykładowy tekst.”); Ten fragment pobiera dostawcę akcji
return super.onCreateOptionsMenu(menu); udostępniania i zapisuje go w zmiennej
prywatnej. Następnie wywołuje jego metodę
} setIntent().
388 Rozdział 9.
Paski akcji
package com.hfad.wloskieconieco;
WloskieCoNieco
import android.app.Activity;
import android.content.Intent; app/src/main
import android.os.Bundle;
import android.view.Menu; Używamy klasy java
ShareActionProvider,
import android.view.MenuItem; więc musimy ją com.hfad.wloskieconieco
import android.widget.ShareActionProvider; zaimportować.
MainActivity.java
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Przygotowujemy menu; jeśli jest pasek akcji, to dodajemy do niego elementy
getMenuInflater().inflate(R.menu.menu_main, menu);
MenuItem menuItem = menu.findItem(R.id.action_share);
shareActionProvider = (ShareActionProvider) menuItem.getActionProvider();
setIntent(“To jest przykładowy tekst.”);
return super.onCreateOptionsMenu(menu);
Te fragmenty kodu określają
} domyślny tekst, który
dostawca powinien udostępnić.
startActivity(intent);
return true; MainActivity.java
case R.id.action_settings:
// Kod wykonywany po kliknięciu elementu Settings (Ustawienia)
return true;
default: Tej metody nie zmienialiśmy.
return super.onOptionsItemSelected(item);
}
}
}
390 Rozdział 9.
Paski akcji
Aktywność nadrzędna.
Aktywność podrzędna.
Użyj przycisku
Klikając przycisk
W górę w aktywności
W górę, by przejść
podrzędnej, przechodzimy
w górę hierarchii do w górę hierarchii
aktywności nadrzędnej.
aktywności.
Aby móc w praktyce wypróbować ten sposób poruszania się po aplikacji,
dodamy teraz przycisk W górę do paska akcji naszej aktywności OrderActivity.
Kliknięcie tego przycisku spowoduje wyświetlenie aktywności MainActivity.
jesteś tutaj 391
Hierarchia aktywności
AndroidManifest.xml
<activity
android:name=”.MainActivity”
android:label=”@string/app_name” >
<intent-filter>
<action android:name=”android.intent.action.MAIN” />
<category android:name=”android.intent.category.LAUNCHER” />
</intent-filter>
</activity>
<activity
omu 16 lub
android:name=”.OrderActivity” Aplikacje korzystające z API pozi Określa on,
wyżs zego używ ają tego atrybutu.
android:label=”@string/title_activity_order” nadrzędną
że MainActivity jest aktywnością
android:parentActivityName=”.MainActivity”> akty wnoś ci Orde rAct ivity .
<meta-data
android:name=”android.support.PARENT_ACTIVITY”
android:value=”.MainActivity” /> Elementu <meta-data> trzeba używ
</activity> w przypadkach, gdy aplikacja ma ać wyłącznie
</application> z API poziomu niższego od 16. korzystać
Dodaliśmy go
tu tylko po to, byś mógł poznać
ten
</manifest> a poza tym dołączanie go niczemu element,
nie szkodzi.
Na koniec musimy wyświetlić przycisk W górę na pasku akcji
aktywności OrderActivity.
392 Rozdział 9.
Paski akcji
package com.hfad.wloskieconieco;
WloskieCoNieco
public class OrderActivity extends Activity {
app/src/main
@Override
protected void onCreate(Bundle savedInstanceState) { java
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_order); com.hfad.wloskieconieco
}
} Ten fragment kodu wyświetla
przycisk W górę na pasku
akcji aktywności.
Kliknij przycisk
Złóż zamówienie,
by uruchomić
aktywność
OrderActivity.
Na pasku akcji aktywności
OrderActivity będzie
dostępny przycisk W górę.
Kiedy go klikniesz…
…zostanie wyświetlona
aktywność nadrzędna
aktywności OrderActivity.
394 Rozdział 9.
Paski akcji
Rozdział 9.
Opanowałeś już rozdział 9. i dodałeś
do swojego przybornika z narzędziami j
Pełny kod przykładowe
umiejętność tworzenia i stosowania aplikacji pre zen tow ane j
pasków akcji. roz dzi ale mo żes z
w tym
pob rać z ser we ra FT P
wydawnictwa Helion:
klady/
ftp://ftp.helion.pl/przy
CELNE SPOSTRZEŻENIA andrrg.zi p
Aby dodać pasek akcji do aplikacji zgodnych Elementy paska akcji dodajemy, zapisując je
z API poziomu 11 lub wyższego, wystarczy w pliku zasobów menu.
wybrać motyw Holo lub Material. Elementy z pliku zasobów menu dodajemy
Aby dodać pasek akcji do aplikacji zgodnych do paska akcji, implementując metodę
z API poziomu 7 lub wyższego, należy onCreateOptionsMenu().
zastosować motyw AppCompat i klasę Aby określić czynności, jakie należy wykonać
ActionBarActivity. W razie zastosowania
po kliknięciu poszczególnych elementów
klasy ActionBarActivity musimy wybrać
paska akcji, należy zaimplementować metodę
motyw AppCompat.
onOptionsItemSelected().
Klasa ActionBarActivity i motyw Treści możemy udostępniać, dodając do paska
AppCompat są dostępne w bibliotece Android
akcji dostawcę akcji udostępniania. Należy go
Support Library v7.
dodać do pliku zasobów menu. Następnie należy
Używany motyw można określić w pliku wywołać jego metodę setShareIntent(),
manifestu aplikacji, AndroidManifest.xml, przekazując do niej intencję opisującą treści,
za pomocą atrybutu android:theme. które chcemy udostępnić.
Style definiujemy w pliku zasobów stylów, Na pasku akcji można także wyświetlać
używając elementu <style>. Nazwę stylu przycisk W górę pozwalający na przechodzenie
określa atrybut name. Atrybut parent pozwala do aktywności nadrzędnej. Hierarchię
określić, skąd będą dziedziczone właściwości aktywności definiujemy w pliku manifestu,
definiowanego stylu. AndroidManifest.xml. W celu wyświetlenia
przycisku W górę musimy wywołać metodę
Pliki zasobów stylów są domyślnie umieszczane
setDisplayHomeAsUpEnabled() obiektu
w katalogu app/src/main/res/values. Aby plik
klasy ActionBar.
zasobów stylów był stosowany w API poziomu 21,
należy go umieścić w katalogu app/src/main/res/
values-v21.
Z miejsca na miejsce
Wiem, że ze swoją
cudowną szufladą
nawigacyjną nigdy się
nie zgubię!
Pizze
Restauracje
Wiesz już, jak dodawać nowe elementy do paska akcji. To rozwiązanie najlepiej się
nadaje do umieszczania opcji aktywnych, takich jak tworzenie zamówień. Co jednak
można zrobić w przypadku odnośników do ekranów kategorii? Ponieważ są to opcje
pasywne, służące za elementy nawigacyjne w obrębie całej aplikacji, musimy je
potraktować nieco inaczej.
Włoskie Co Nieco
Ekran główny
To jest szuflada
nawigacyjna. Zawiera Po kliknięciu elementu szuflady
ona listę kluczowych Pizze nawigacyjnej odpowiadająca mu
węzłów aplikacji. zawartość jest wyświetlana
w tym miejscu ekranu.
Makarony
Restauracje
Widok listy będzie zawierał opcje Ekran główny, Pizze, Makarony oraz Restauracje, dzięki
którym użytkownik będzie mógł w prosty sposób poruszać się pomiędzy głównymi węzłami
aplikacji. Dla każdej z tych opcji utworzymy następnie odrębny fragment. Oznacza to,
że będziemy mogli podmieniać fragmenty w trakcie działania aplikacji, a użytkownik będzie
w stanie uzyskać dostęp do szuflady nawigacyjnej z każdego ekranu aplikacji.
Włoskie Co Nieco
...
Ekran główny
Pizze
TopFragment PizzasFragment
Makarony
Makarony Restauracje
Spaghetti Restauracje Wrocław
bolognese Kraków
Lasagne
MainActivity
PastaFragment StoresFragment
package com.hfad.wloskieconieco;
WloskieCoNieco
import android.os.Bundle;
import android.app.Fragment; app/src/main
import android.view.LayoutInflater;
import android.view.View; java
Nasz TopFragment jest
import android.view.ViewGroup; zwyczajnym fragmentem.
com.hfad.wloskieconieco
public class TopFragment extends Fragment {
TopFragment.java
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_top, container, false);
}
}
fragment_top.xml
<string-array name=”pizzas”>
<item>Diavolo</item>
Dodajemy tablicę z nazwami pizz
<item>Funghi</item> do pliku strings.xml.
</string-array>
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
ArrayAdapter<String> adapter = new ArrayAdapter<String>(
inflater.getContext(),
android.R.layout.simple_list_item_1,
getResources().getStringArray(R.array.pizzas));
setListAdapter(adapter);
return super.onCreateView(inflater, container, savedInstanceState);
}
}
<string-array name=”pasta”>
<item>Spaghetti bolognese</item> Dodajemy do pliku strings.xml tabli
z nazwami dań z makaronu. cę
<item>Lasagne</item>
</string-array>
package com.hfad.wloskieconieco;
WloskieCoNieco
import android.app.ListFragment;
import android.os.Bundle; app/src/main
import android.view.LayoutInflater;
import android.view.View; java
Fragmentu typu ListFragment
import android.view.ViewGroup; użyjemy do wyświetlenia listy
import android.widget.ArrayAdapter; dań z makaronu. com.hfad.wloskieconieco
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
ArrayAdapter<String> adapter = new ArrayAdapter<String>(
inflater.getContext(),
android.R.layout.simple_list_item_1,
getResources().getStringArray(R.array.pasta));
setListAdapter(adapter);
return super.onCreateView(inflater, container, savedInstanceState);
}
}
<string-array name=”stores”>
<item>Wrocław</item> Dodajemy do pliku strings.xml tabli
z nazwami miast, w których są cę
<item>Kraków</item> restauracje.
</string-array>
package com.hfad.wloskieconieco;
WloskieCoNieco
import android.app.ListFragment;
import android.os.Bundle; app/src/main
import android.view.LayoutInflater;
import android.view.View; Fragmentu typu ListFragment java
użyjemy do wyświetlenia
import android.view.ViewGroup; listy miast, w których są
restauracje. com.hfad.wloskieconieco
import android.widget.ArrayAdapter;
StoresFragment.java
public class StoresFragment extends ListFragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
ArrayAdapter<String> adapter = new ArrayAdapter<String>(
inflater.getContext(),
android.R.layout.simple_list_item_1,
getResources().getStringArray(R.array.stores));
setListAdapter(adapter);
return super.onCreateView(inflater, container, savedInstanceState);
}
}
<android.support.v4.widget.DrawerLayout
xmlns:android=”http://schemas.android.com/apk/res/android”
android:id=”@+id/drawer_layout”
android:layout_width=”match_parent”
android:layout_height=”match_parent”> WloskieCoNieco
Obejrzyj to!
Do określenia wymiarów szuflady używamy atrybutów layout_width nie zawiera
i layout_height. Pierwszemu z nich przypisaliśmy szerokość ”240dp”, zależności
dzięki czemu po otworzeniu szuflada będzie miała szerokość 240 dp. od biblioteki wsparcia
appcompat v7, to
Przypisanie atrybutowi layout_gravity wartości ”start” sprawi, że w razie przedstawiony w tym
wyboru języka, w którym znaki są zapisywane od lewej do prawej, szuflada zostanie rozdziale kod szuflady
umieszczona po lewej stronie ekranu, a w przypadku języków, w których znaki są nawigacyjnej nie będzie
działał.
zapisywane od prawej do lewej, zostanie ona umieszczona po prawej stronie.
W Android Studio zależnościami
Atrybuty divider, dividerHeight oraz backgroud zastosowaliśmy, aby ukryć
możesz zarządzać, wybierając
linie oddzielające poszczególne elementy listy i określić kolor jej tła.
z menu opcje File/Project
I wreszcie przypisanie atrybutowi choiceMode wartości ”singleChoice” sprawi, Structure/App/Dependencies.
że w danym momencie użytkownik będzie mógł wybrać tylko jeden element listy.
... app/src/main
Używamy tych klas,
import android.view.View; więc musimy je
import android.widget.AdapterView; zaimportować. java
com.hfad.wloskieconieco
public class MainActivity extends Activity { Ta klasa opisuje nasz obiekt
... nasłuchujący typu OnItemClickL
istener.
MainActivity.java
private class DrawerItemClickListener implements ListView.OnItemClickListener {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id){
// Kod, który należy wykonać po kliknięciu elementu listy
}
}; Kiedy użytkownik kliknie któryś z elementów w szufladzie
nawigacyjnej, zostanie wywołana metoda onItemClick().
@Override
protected void onCreate(Bundle savedInstanceState) {
...
drawerList.setOnItemClickListener(new DrawerItemClickListener());
}
}; Do widoku ListView w szufladzie nawigacyjnej dodajemy
nową instancję obiektu OnItemClickListener.
Metoda onItemClick() musi zawierać kod, który ma zostać wykonany, kiedy użytkownik kliknie
któryś z elementów widoku listy. W naszym przypadku w odpowiedzi na kliknięcie wywołamy
metodę selectItem(), przekazując do niej pozycję klikniętego elementu na liście. Już zaraz
zajmiemy się zaimplementowaniem tej metody.
Obecnie wiesz już wszystko, co jest niezbędne do zaimplementowania pierwszej z tych operacji,
dlatego zajmiesz się tym samodzielnie w ramach ćwiczenia przedstawionego na następnej stronie.
StoresFragment()
fragment = ............................;
break; PastaFragment()
default:
new commit()
fragment = ............................;
}
new
ft.replace(R.id.content_frame, .................);
ft.addToBackStack(null);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft. .................;
}
new PastaFragment()
fragment = . .;
break;
case 3:
beginTrans
FragmentTransaction ft = getFragmentManager(). . action() ..;
Zatwierdzamy transakcję.
ft. . commit() ..;
}
Układ DrawerLayout
obejmuje cały ekran.
Zawiera on układ
FrameLayout używany do
wyświetlania zawartości
i widok ListView tworzący
szufladę nawigacyjną.
package com.hfad.wloskieconieco;
WloskieCoNieco
...
DrawerLayout należy app/src/main
import android.support.v4.widget.DrawerLayout; do biblioteki Support
Library v4.
java
public class MainActivity extends Activity {
... Dodajemy DrawerLayout jako zmienną com.hfad.wloskieconieco
prywatną, gdyż będziemy jej używać
private DrawerLayout drawerLayout; w wielu metodach.
MainActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
titles = getResources().getStringArray(R.array.titles); Pobieramy referencję do
drawerList = (ListView)findViewById(R.id.drawer); układu DrawerLayout.
drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
// Określamy zawartość widoku ListView
drawerList.setAdapter(new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_activated_1, titles));
drawerList.setOnItemClickListener(new DrawerItemClickListener());
if (savedInstanceState == null) {
selectItem(0); ała właśnie
Jeśli aktywność MainActivity zost selectItem(),
ia
} utworzona, to używamy wywołan ragment.
by wyświetlić w niej fragment TopF Dalsza część kodu
}
znajduje się na
następnej stronie.
Szufladę
otwieramy
i zamykamy,
używając
przycisku
umieszczonego
na pasku akcji.
Stosowanie ActionBarDrawerToggle
Najlepszym sposobem na utworzenie obiektu nasłuchującego DrawerListener
jest skorzystanie z przycisku klasy ActionBarDrawerToggle. Jest to
specjalny rodzaj komponentu implementującego interfejs DrawerListener
przystosowany do wykorzystania na pasku akcji. Pozwala on nasłuchiwać
zdarzeń generowanych przez układ DrawerLayout, jak robią to wszystkie
obiekty nasłuchujące DrawerListener, oraz otwierać i zamykać szufladę
poprzez klikanie ikony umieszczonej na pasku akcji.
...
import android.support.v7.app.ActionBarDrawerToggle; WloskieCoNieco
@Override
com.hfad.wloskieconieco
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
MainActivity.java
drawerToggle.onConfigurationChanged(newConfig);
}
Tę metodę musisz dodać do
aktywności, tak by wszystkie
Na kolejnej stronie pokażemy Ci, w którym miejscu pliku MainActivity.java zmiany konfiguracji były
przekazywane do klasy
należy wprowadzić ostatnie zmiany, a potem sprawdzimy, co się stanie po ActionBarDrawerToggle.
uruchomieniu aplikacji.
@Override com.hfad.wloskieconieco
protected void onCreate(Bundle savedInstanceState) {
... MainActivity.java
getActionBar().setDisplayHomeAsUpEnabled(true); Włączamy przycisk W górę,
getActionBar().setHomeButtonEnabled(true); dzięki czemu będzie on
mógł być używany przez
} ActionBarDrawerToggle.
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
Synchronizujemy stan przycisku
drawerToggle.syncState(); ActionBarDrawerToggle ze stanem
szuflady nawigacyjnej.
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
Ten parametr przekazuje wszelkie
drawerToggle.onConfigurationChanged(newConfig); zmiany konfiguracji do przycisku
} ActionBarDrawerToggle.
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (drawerToggle.onOptionsItemSelected(item)) {
Dzięki temu wywołaniu przycisk
return true; ActionBarDrawerToggle będzie mógł
} obsługiwać kliknięcia.
// Kod obsługujący pozostałe przyciski umieszczone na pasku akcji
switch (item.getItemId()) {
...
}
...
}
Po uruchomieniu
aplikacji na pasku akcji
jest wyświetlana ikona
szuflady nawigacyjnej.
Domyślnie zostaje
wyświetlony fragment
TopFragment, a na
pasku akcji jest
widoczna nazwa
aplikacji.
Po kliknięciu opcji Pizze
Po kliknięciu zostaje wyświetlony
ikony szuflady fragment PizzasFragment,
nawigacyjnej tytuł prezentowany na
szuflada jest pasku akcji zostaje
otwierana, a jej zmieniony na „Pizze”
ikona na pasku i w końcu szuflada
akcji zmienia zostaje zamknięta.
wygląd. Zostaje
wyświetlona lista
opcji dodanych
do szuflady
nawigacyjnej.
Akcja
Udostępnij
jest widoczna,
kiedy
szuflada jest
zamknięta.
Kiedy jednak klikniemy przycisk Wstecz, tytuł na pasku akcji nie jest aktualizowany
i przestaje odpowiadać wyświetlanemu fragmentowi. Na przykład załóżmy, że
w szufladzie nawigacyjnej kliknęliśmy opcję Restauracje, a następnie opcję Pizze.
W efekcie na ekranie zostanie wyświetlona lista pizz, a tytuł na pasku akcji będzie
temu odpowiadał. Jeśli teraz klikniemy przycisk Wstecz, to ponownie zostanie
wyświetlony fragment StoresFragment, ale tytuł na pasku akcji nie zmieni się
i dalej będzie na nim widoczny tekst „Pizze”:
Po obróceniu urządzenia
tytuł na pasku akcji zostaje
przywrócony do stanu
początkowego.
...
public class MainActivity extends Activity { WloskieCoNieco
...
private int currentPosition = 0; app/src/main
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(”position”, currentPosition);
}
... Jeśli aktywność ma zostać usunięta, to
zapisujemy stan zmiennej currentPosition.
}
jesteś tutaj 425
Obserwowanie stosu cofnięć
getFragmentManager().addOnBackStackChangedListener(
new FragmentManager.OnBackStackChangedListener() {
public void onBackStackChanged() { mentManager.
Musimy dodać nowy obiekt Frag
// Kod wykonywany po zmianie stanu stosu cofnięć OnBackStackChangedListener
} i zaimplementować jego metodę
da jest
onBackStackChanged(). Ta meto
} oływ ana za każd ym raze m, gdy zmienia
wyw
się stan stos u cofn ięć.
);
Kiedy zmieni się stan stosu cofnięć, zostaje wywołana metoda onBackStackChanged()
obiektu OnBackStackChangedListener. To właśnie w niej należy umieścić kod,
który chcemy wykonywać w przypadku, gdy użytkownik naciśnie przycisk Wstecz.
Wykonanie każdej z tych czynności wymaga znajomości pozycji, którą na liście opcji
szuflady nawigacyjnej zajmuje aktualnie wyświetlony fragment. A jak możemy tę
pozycję określić?
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(R.id.content_frame, fragment);
ft.addToBackStack(null);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.commit();
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(R.id.content_frame, fragment, ”visible_fragment”);
Ten dodatkowy argument
ft.addToBackStack(null); powoduje dodanie znacznika
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); „visible_fragment” do fragmentu,
który jest umieszczany na
ft.commit(); stosie cofnięć.
package com.hfad.wloskieconieco;
r,
Używamy klasy FragmentManage WloskieCoNieco
import android.app.Activity; zatem musimy ją zaimportować.
titles = getResources().getStringArray(R.array.titles);
com.hfad.wloskieconieco
drawerList = (ListView)findViewById(R.id.drawer);
drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
MainActivity.java
// Określamy zawartość widoku ListView
drawerList.setAdapter(new ArrayAdapter<String>(this,
Wypełniamy elementy
android.R.layout.simple_list_item_activated_1, titles)); listy ListView
w szufladzie nawigacyjnej
drawerList.setOnItemClickListener(new DrawerItemClickListener()); i zapewniamy, aby
reagowała na kliknięcia.
// Wyświetlamy odpowiedni fragment
if (savedInstanceState != null) {
currentPosition = savedInstanceState.getInt(”position”);
setActionBarTitle(currentPosition); Jeśli aktywność została usunięta i ponownie odtworzona,
to ustawiamy prawidłowy tytuł na pasku akcji.
} else {
selectItem(0); Domyślnie jest wyświetlany
fragment TopFragment.
}
// Tworzymy obiekt ActionBarDrawerToggle
drawerToggle = new ActionBarDrawerToggle(this, drawerLayout,
R.string.open_drawer, R.string.close_drawer) {
// Wywoływana, kiedy stan szuflady odpowiada jej całkowitemu zamknięciu
@Override
public void onDrawerClosed(View view) {
super.onDrawerClosed(view);
invalidateOptionsMenu(); Podczas otwierania i zamykania szuflady
nawigacyjnej wywołujemy metodę
} invalidateOptionsMenu(), gdyż chcemy zmieniać
elementy wyświetlane na pasku akcji.
drawerLayout.setDrawerListener(drawerToggle);
getActionBar().setDisplayHomeAsUpEnabled(true);
To wywołanie włącza przycisk W
getActionBar().setHomeButtonEnabled(true); akcji, dzięki czemu będziemy moggórę na pasku
wyświetlania szuflady nawigacyjneli go używać do
j.
getFragmentManager().addOnBackStackChangedListener(
new FragmentManager.OnBackStackChangedListener() {
public void onBackStackChanged() {
Ta metoda jest
wywoływana FragmentManager fragMan = getFragmentManager();
w przypadku zmiany Fragment fragment = fragMan.findFragmentByTag(”visible_fragment”);
stanu stosu cofnięć.
if (fragment instanceof TopFragment) {
currentPosition = 0;
} WloskieCoNieco
Te instrukcje if (fragment instanceof PizzaFragment) {
warunkowe
sprawdzają currentPosition = 1; app/src/main
klasę fragmentu
aktualnie }
dołączonego if (fragment instanceof PastaFragment) { java
do aktywności
i w zależności currentPosition = 2;
od niej com.hfad.wloskieconieco
odpowiednio }
ustawiają if (fragment instanceof StoresFragment) {
wartość zmiennej MainActivity.java
currentPosition. currentPosition = 3;
}
setActionBarTitle(currentPosition);
drawerList.setItemChecked(currentPosition, true);
}
Te dwa wywołania ustawiają tytuł wyświetlany
} na pasku akcji i zaznaczają odpowiedni element
); listy w szufladzie nawigacyjnej.
}
WloskieCoNieco
private void selectItem(int position) {
// Aktualizujemy główną zawartość aplikacji, podmieniając prezentowany fragment
app/src/main
currentPosition = position;
Fragment fragment; java
switch(position) {
case 1: com.hfad.wloskieconieco
case 2:
Na podstawie pozycji elementu
fragment = new PastaFragment(); zaznaczonego przez użytkownika
w szufladzie nawigacyjnej określam
break; który fragment należy wyświetlić. y,
case 3:
fragment = new StoresFragment();
break;
default:
Wyświetlamy fragment.
fragment = new TopFragment();
}
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(R.id.content_frame, fragment, ”visible_fragment”);
ft.addToBackStack(null);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.commit();
// Ustawiamy tytuł paska akcji
Wyświetlamy odpowiedni tytuł na pasku akcji.
setActionBarTitle(position);
// Zamykamy szufladę nawigacyjną
drawerLayout.closeDrawer(drawerList);
}
Zamykamy szufladę nawigacyjną.
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
// Synchronizumemy stan przycisku przełącznika po wywołaniu
// metody onRestoreInstanceState
drawerToggle.syncState(); Synchronizujemy stan przycisku
} ActionBarDrawerToggle ze stanem
szuflady nawigacyjnej.
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
drawerToggle.onConfigurationChanged(newConfig);
WloskieCoNieco
} Wszelkie szczegółowe informacje
o zmianach konfiguracji przekazujemy
do przycisku ActionBarDrawerToggle. app/src/main
@Override
public void onSaveInstanceState(Bundle outState) { java
super.onSaveInstanceState(outState);
outState.putInt(”position”, currentPosition); com.hfad.wloskieconieco
} Jeśli aktywność ma zostać usunięta, to zapisujemy
bieżący stan zmiennej currentPosition.
MainActivity.java
private void setActionBarTitle(int position) {
String title;
if (position == 0) {
title = getResources().getString(R.string.app_name);
} else {
title = titles[position];
} Ustawiamy tytuł wyświetlany Dalszy ciąg kodu
na pasku akcji, tak by znajduje się na
getActionBar().setTitle(title); odpowiadał on aktualnie następnej stronie.
} prezentowanemu fragmentowi.
jesteś tutaj 433
To już prawie koniec
ale mo żes z
w tym rozdzi
Opanowałeś już rozdział 10. i dodałeś pobrać z ser we ra FT P
do swojego przybornika z narzędziami wydawnictwa Helion:
klady/
układy DrawerLayout. ftp://ftp.helion.pl/przy
andrrg.zip
CELNE SPOSTRZEŻENIA
Użyj układu DrawerLayout, aby stworzyć aktywność z szufladą nawigacyjną.
Zastosuj tę szufladę, by przechodzić do głównych węzłów aplikacji.
Jeśli używasz paska akcji, to zastosuj przycisk typu ActionBarDrawerToggle
jako obiekt nasłuchujący typu DrawerListener. Dzięki temu będziesz mógł
reagować na otwieranie i zamykanie szuflady nawigacyjnej i w odpowiedzi na
te zdarzenia odpowiednio modyfikować elementy widoczne na pasku akcji.
Do zmieniania elementów paska akcji w trakcie działania aplikacji musisz
zastosować metodę invalidateOptionsMenu() i wprowadzić odpowiednie
zmiany w metodzie onPrepareOptionsMenu() aktywności.
Aby reagować na zmiany zachodzące na stosie cofnięć, należy zaimplementować
interfejs FragmentManager.OnBackStackChangedListener.
Metoda findFragmentByTag() menedżera fragmentów pozwala odszukać
fragment na podstawie znacznika.
Aktywność głównego
poziomu wyświetla
listę opcji. Kliknięcie opcji
Napoje powoduje
wyświetlenie listy
dostępnych napojów.
Po kliknięciu
konkretnego napoju
wyświetlane
są szczegółowe
informacje na jego
temat.
Pomocnik SQLite
Pomocnika SQLite tworzymy
poprzez rozszerzenie klasy
Baza danych SQLite
SQLiteOpenHelper. Obiekty Obiekt klasy SQLiteDatabase
tego typu umożliwiają zapewnia dostęp do bazy
tworzenie baz danych SQLite danych. Można go porównać
i zarządzanie nimi. z obiektem SQLConnection
JDBC.
Kursory
Obiekty klasy Cursor służą
do odczytywania i zapisywania
informacji w bazie danych.
Można je porównać
z obiektami ResultSet JDBC.
Nie istnieją
głupie pytania
P: Skoro baza danych nie używa O : Nie ma żadnych powodów, dla O: Wiemy, że będziemy używać baz
nazwy użytkownika ani hasła, których nie moglibyśmy uzyskać dostępu danych SQLite, dlatego stosowanie JDBC
to jak może być bezpieczna? do zewnętrznych baz danych za pomocą byłoby grubą przesadą. Te wszystkie
<Layout>
<Layout>
</Layout>
</Layout>
1 2 3
Informacje o napojach
Aplikacja ma niemal taką samą strukturę jak wcześniej, z tą różnicą, że klasę zapiszemy teraz w bazie
Drink (Drink.java) zastąpimy pomocnikiem SQLite i bazą danych SQLite. danych, a nie w klasie
Drink.
Pomocnik będzie zarządzał bazą danych i zapewniał wszystkim aktywnościom
dostęp do niej. Modyfikacją kodu samych aktywności zajmiemy się
w następnym rozdziale.
Baza danych
Coffeina
<Layout>
<Layout>
</Layout>
</Layout>
Tworzenie Zapewnianie
bazy danych dostępu
Podczas pierwszego do bazy danych
instalowania aplikacji plik
Ponieważ nie ma potrzeby,
bazy danych nie będzie
by aplikacja dysponowała
istnieć. Pomocnik SQLite
wszystkimi informacjami
może zadbać o prawidłowe
o lokalizacji plików
utworzenie pliku
bazy danych za każdym
o odpowiedniej nazwie
razem, gdy będziemy tego
i zawierającego tabele
potrzebowali, pomocnik
o określonej strukturze.
SQLite może nam
udostępniać łatwy w użyciu
obiekt bazy danych.
O każdej porze dnia i nocy.
Pomocnik SQLite
import android.database.sqlite.SQLiteDatabase;
Coffeina
class CoffeinaDatabaseHelper extends SQLiteOpenHelper {
app/src/main
CoffeinaDatabaseHelper(Context context) {
}
java
@Override com.hfad.coffeina
public void onCreate(SQLiteDatabase db) {
} Metody onCreate() i onUpgrade() są obowiązkowe. CoffeinaDatabase
Na razie zostawiliśmy je puste, ale w dalszej części
Helper.java
rozdziału zajmiemy się nimi bardziej szczegółowo.
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
Aby nasz pomocnik SQLite mógł coś zrobić, musimy uzupełnić kod jego
metod. Dlatego naszym pierwszym zadaniem będzie przekazanie pomocnikowi
informacji na temat bazy danych, którą będzie musiał utworzyć.
Nazwę bazy danych i numer jej wersji określamy, przekazując je Nazwa: „coffeina”
w wywołaniu konstruktora klasy bazowej SQLiteOpenHelper. Wersja: 1
W naszej aplikacji bazie danych nadamy nazwę „coffeina”,
a ponieważ będzie to pierwsza wersja naszej bazy, będzie ona
miała numer wersji 1. Poniżej przedstawiliśmy kod, który definiuje
Pomocnik SQLite
te informacje i ich używa (wprowadź te modyfikacje w swojej wersji
pliku CoffeinaDatabaseHelper.java):
...
class CoffeinaDatabaseHelper extends SQLiteOpenHelper {
Kiedy już określimy, jaką bazę danych chcemy utworzyć, musimy CoffeinaDatabase
także podać strukturę jej tabel. Helper.java
Dla każdego z odrębnych rodzajów informacji, które chcemy przechowywać w bazie Kolumny tabeli mają odpowiednio
danych, musimy utworzyć tabelę. Na przykład w przypadku naszej aplikacji dla kafeterii nazwy _id, NAME, DESCRIPTION
i IMAGE_RESOURCE_ID.
Coffeina musimy utworzyć tabelę do przechowywania informacji o napojach. Klasa Drink definiuje atrybuty
Można by ją przedstawić jak na poniższym rysunku: o podobnych nazwach.
Niektóre kolumny można wskazać jako klucz główny tabeli. Klucz główny W systemie Android
w unikalny sposób identyfikuje każdy wiersz tabeli. Jeśli zatem określimy, że jakaś
kolumna pełni rolę klucza głównego, to baza danych nie pozwoli zapisać w tabeli stosowana jest
wierszy, w których wartość tego klucza się powtarza. konwencja,
Sugerujemy, żebyś w swoich tabelach stosował klucz główny składający się z jednej by kolumna klucza
kolumny o nazwie _id. Wynika to z faktu, że Andoid oczekuje, by tabele zawierały
kolumnę o nazwie _id typu liczbowego; brak takiej kolumny może być przyczyną głównego miała
późniejszych problemów.
nazwę _id. Kod
Klasy i typy danych systemu oczekuje,
Każda kolumna tabeli jest przeznaczona do przechowywania danych określonego typu. że kolumna _id
Na przykład w naszej tabeli DRINK kolumna DESCRIPTION będzie zawierała wyłącznie
dane tekstowe. Poniżej przedstawiliśmy zestawienie najczęściej używanych typów
będzie dostępna
danych SQLite wraz z informacjami, co przy ich użyciu można zapisywać w bazie: w tabelach.
INTEGER Dowolne liczby całkowite Zignorowanie tej
TEXT Dowolne typy znakowe
REAL Dowolne liczby zmiennoprzecinkowe
konwencji może
NUMERIC Wartości logiczne, daty oraz daty i godziny utrudnić pobieranie
BLOB Duże obiekty binarne (ang. Binary Large Objects)
danych z bazy
W odróżnieniu od większości systemów baz danych w SQLite nie musimy określać
wielkości kolumn. Za kulisami SQLite przekształca te typy danych na klasy i wyświetlanie
o znacznie szerszym zakresie. Dzięki temu rodzaj informacji, które mamy zamiar ich w interfejsie
przechowywać w kolumnach, możemy określać w sposób bardzo ogólny i nie musimy
podawać wielkości tych danych. użytkownika.
446 Rozdział 11.
Bazy danych SQLite
Polecenie CREATE TABLE określa, jakie kolumny ma mieć tabela i jakiego typu ma
być każda z nich. Kolumna _id jest kluczem głównym tabeli, a zastosowane w niej
słowo kluczowe AUTOINCREMENT oznacza, że podczas zapisywania w tabeli nowego
wiersza SQLite automatycznie wygeneruje i zapisze w tej kolumnie unikalną wartość.
@Override Coffeina
public void onCreate(SQLiteDatabase db) {
db.execSQL(“CREATE TABLE DRINK (_id INTEGER PRIMARY app/src/main
KEY AUTOINCREMENT, “
+ “NAME TEXT, “ java
+ “DESCRIPTION TEXT, “
+ “IMAGE_RESOURCE_ID INTEGER);”); com.hfad.coffeina
}
Wykonanie tej metody utworzy pustą tabelę DRINK, ale co moglibyśmy zrobić, CoffeinaDatabase
gdybyśmy chcieli wstępnie wypełnić ją jakimiś danymi? Helper.java
Na przykład poniższy fragment kodu pozwala zmodyfikować wiersz tabeli DRINK, którego pole
NAME ma wartość ”Latte” i zapisać w jego kolumnie DESCRIPTION łańcuch ”Pyszności”.
Drugi parametr metody określa modyfikowane wartości. Podobnie jak Jeśli dwa ostatnie
Obejrzyj to!
w przypadku metody insert(), także teraz wartości te określamy, parametry metody
tworząc obiekt ContentValues zawierający pary nazwa – wartość: update() przyjmą
wartość null, to zostaną
ContentValues drinkValues = new ContentValues(); zmodyfikowane WSZYSTKIE
drinkValues.put(”DESCRIPTION”, ”Pyszności”); rekordy tabeli.
Trzeci parametr określa warunki, które muszą zostać spełnione, aby można
Na przykład poniższe wywołanie:
było wykonać aktualizację. W powyższym przykładzie warunek ”NAME = ?”
oznacza, że zawartość kolumny NAME musi być równa pewnej określonej db.update(”DRINK”,
wartości. Znak pytajnika (?) jest symbolem zastępczym reprezentującym drinkValues,
tę wartość. Konkretna wartość, która zostanie zastosowana w miejscu tego
null, null);
symbolu, jest określana na podstawie ostatniego parametru metody update().
zmodyfikuje wszystkie wiersze tabeli
Istnieje także możliwość określenia większej liczby takich kryteriów, DRINK.
o czym przekonasz się na następnej stronie.
jesteś tutaj 449
Warunki
Jeśli w zapytaniu chcemy zastosować więcej warunków, to koniecznie musimy upewnić się, że podamy je
dokładnie w takiej samej kolejności, w jakiej podane są dane. Na przykład poniżej przedstawiliśmy kod, który
zaktualizuje te rekordy tabeli DRINK, w których pole NAME ma wartość ”Latte”, lub w polu DESCRIPTION jest
umieszczony napis ”Czarna kawa ze świeżo mielonych ziaren najwyższej jakości”.
ME = “Latte ” or
db.update(”DRINK”, To oznacza: Where NA a kawa ze świeżo
DE SC RIP TIO N = “Czarn
drinkValues, ższej jakości.”.
“NAME = ? OR DESCRIPTION = ?”, mielonych ziaren najwy
new String[] {“Latte”, “Czarna kawa ze świeżo mielonych ziaren najwyższej jakości.”});
Wartościami warunków muszą być łańcuchy znaków (String), niezależnie od tego, czy same kolumny,
których będą dotyczyły te warunki, zawierają łańcuchy znaków czy dane innych typów. Jeśli kolumny używane
w warunkach zawierają dane innych typów, to będziemy musieli skonwertować je na łańcuchy znaków. Na przykład
poniższe wywołanie metody update() aktualizuje rekord tabeli DRINK, który w kolumnie _id ma wartość 1:
db.update(”DRINK”, Konwertujemy liczbę całkowitą
drinkValues, na łańcuch znaków.
”_id = ?”,
new String[] {Integer.toString(1)});
Pierwszym parametrem metody delete() jest nazwa tabeli, z której chcemy usuwać rekordy (w powyższym
przykładzie jest to tabela DRINK). Drugi i trzeci parametr pozwalają określić warunki, które muszą zostać
spełnione, aby można było usunąć rekord (w naszym przykładzie warunek ten ma postać: NAME = ”Latte”).
Skoro już znasz rodzaje operacji używanych do operowania na danych w tabelach SQLite, dysponujesz już całą
wiedzą niezbędną do utworzenia bazy danych SQLite wraz z jej tabelami oraz wstępnego wypełnienia tych tabel
danymi. Na następnej stronie wykorzystamy całą tę wiedzę, by przedstawić kompletny kod pomocnika SQLite,
który zastosujemy w aplikacji dla kafeterii Coffeina.
package com.hfad.coffeina;
app/src/main
import android.content.ContentValues;
import android.content.Context; java
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper; com.hfad.coffeina
Jaśnie panie,
czy potrzebuje pan bazy
danych? Pozwolę sobie sprawdzić,
czy baza dla jaśnie pana została
już przygotowana.
Pomocnik SQLite
Nazwa: "coffeina"
Wersja: 1
Pomocnik SQLite
Jaśnie panie,
oto pańska baza DRINK
danych. Czy mogę
jeszcze czymś służyć? Nazwa: "coffeina"
Wersja: 1
onCreate()
Pomocnik SQLite
Zaostrz ołówek
Oto metoda onCreate() klasy SQLiteOpenHelper. Twoim zadaniem jest
określenie, jakie wartości będą zapisane w kolumnach NAME i DESCRIPTION
tabeli DRINK po wykonaniu tej metody.
@Override
public void onCreate(SQLiteDatabase db) {
ContentValues cappuccino= new ContentValues();
cappucino.put(”NAME”, ”Cappuccino”);
ContentValues americano = new ContentValues();
americano.put(”NAME”, ”Americano”);
ContentValues latte = new ContentValues();
latte.put(”NAME”, ”Latte”);
ContentValues espresso = new ContentValues();
espresso.put(”DESCRIPTION”, ”Espresso”);
ContentValues mochachino = new ContentValues();
mochachino.put(”NAME”, ”Mochachino”);
Zaostrz ołówek
Rozwiązanie Oto metoda onCreate() klasy SQLiteOpenHelper. Twoim
zadaniem jest określenie, jakie wartości będą zapisane w kolumnach
NAME i DESCRIPTION tabeli DRINK po wykonaniu tej metody.
@Override
public void onCreate(SQLiteDatabase db) {
ContentValues cappuccino = new ContentValues();
cappuccino.put(”NAME”, ”Cappuccino”);
ContentValues americano = new ContentValues();
americano.put(”NAME”, ”Americano”);
ContentValues latte = new ContentValues();
latte.put(”NAME”, ”Latte”);
ContentValues espresso = new ContentValues();
espresso.put(”DESCRIPTION”, ”Espresso”);
ContentValues mochachino = new ContentValues();
mochachino.put(”NAME”, ”Mochachino”);
mny _id,
Tworzymy tabelę zawierającą kolu
db.execSQL(”CREATE TABLE DRINK (” NAME oraz DESCRIPTION.
+ ”_id INTEGER PRIMARY KEY AUTOINCREMENT, ”
+ ”NAME TEXT, ”
+ ”DESCRIPTION TEXT);”);
Wstawiamy łańcuch Cappuccino do kolumny NAME.
db.insert(”DRINK”, null, cappuccino);
Wstawiamy łańcuch Americano do kolumny NAME.
db.insert(”DRINK”, null, americano);
db.delete(”DRINK”, null, null); Usuwamy wszystkie wiersze tabeli.
db.insert(”DRINK”, null, latte); Wstawiamy łańcuch Latte do kolumny NAME.
db.update(”DRINK”, mochachino, ”NAME = ?”, new String[] {”Cappuccino”});
db.insert(”DRINK”, null, espresso);
W wierszu, w którym pole NAME ma
} wartość Cappuccino, zmieniamy wartość
Wstawiamy łańcuch Espresso do kolumny DESCRIPTION. tego pola na Mochachino. Żaden rekord
tabeli nie zostaje zmieniony.
No cóż… Moglibyśmy
zmienić polecenie CREATE TABLE podane
w metodzie onCreate(), ale wydaje mi się,
że takie rozwiązanie nie jest całkowicie
prawidłowe. Chodzi o to, że przecież mogą
istnieć urządzenia, na których baza już
będzie zainstalowana.
Pomocnik SQLite może określić, czy baza danych wymaga aktualizacji, na podstawie
jej numeru wersji. Numer wersji bazy danych określamy w konstruktorze pomocnika
SQLite, w którym jest on przekazywany w wywołaniu konstruktora klasy bazowej
— SQLiteOpenHelper.
Urządzenie
Użytkownik
Nazwa: „coffeina”
Wersja: 2
Pomocnik SQLite
DRINK
Pomocnik SQLite
Nazwa: „coffeina”
Wersja: 1
Pomocnik SQLite
DRINK
Nazwa: „coffeina”
Wersja: 1 2
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion == 1) {
Ten kod zostanie wykonany
// Kod wykonywany, gdy używana jest baza danych o numerze wersji 1 tylko wówczas, gdy
} używana jest baza danych
o numerze wersji 1.
}
@Override
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// Tu możemy umieścić swój kod
} Aby przywrócić wcześniejszą wersję bazy, nowy
numer wersji musi być niższy do poprzedniego.
@Override
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion == 3) {
// Kod wykonywany, jeśli używana jest baza danych o numerze wersji 3
}
Ten kod zostanie wykonany, jeśli
} użytkownik korzysta z bazy danych
o numerze wersji 3, lecz chcemy
przywrócić jej wcześniejszą wersję.
A teraz zastosujmy te metody do zmodyfikowania struktury bazy
danych.
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
updateMyDatabase(db, oldVersion, newVersion); Wywołujemy metodę updateMyDatabase()
} w metodzie onUpdate(), przekazując do niej
wszelkie niezbędne argumenty.
private void updateMyDatabase(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion < 1) {
db.execSQL(“CREATE TABLE DRINK (_id INTEGER PRIMARY KEY AUTOINCREMENT, “
+ “NAME TEXT, “
Ten kod + “DESCRIPTION TEXT, “
wcześniej był + “IMAGE_RESOURCE_ID INTEGER);”);
umieszczony insertDrink(db, “Latte”, “Czarne espresso z gorącym mlekiem i mleczną pianką.”,
w metodzie
onCreate(). R.drawable.latte);
insertDrink(db, “Cappuccino”, “Czarne espresso z dużą ilością spienionego mleka.”,
R.drawable.cappuccino);
insertDrink(db, “Espresso”, “Czarna kawa ze świeżo mielonych ziaren najwyższej jakości.”,
R.drawable.filter);
}
if (oldVersion < 2) { Ten kod zostanie wykonany, jeśli
// Kod, który dodaje nową kolumnę na urządzeniu użytkownika będzie
już zainstalowana baza danych
} o numerze wersji 1.
}
...
Usuwanie tabel
Oprócz dodawania i modyfikowania tabel można je także usuwać.
Służy do tego polecenie DROP TABLE:
SQLiteDatabase.execSQL(String sql);
Metody tej możemy używać zawsze, gdy chcemy wykonać na bazie danych
jakieś polecenie SQL.
import android.content.ContentValues;
java
import android.content.Context;
import android.database.sqlite.SQLiteDatabase; com.hfad.coffeina
import android.database.sqlite.SQLiteOpenHelper;
CoffeinaDatabase
class CoffeinaDatabaseHelper extends SQLiteOpenHelper { Helper.java
@Override
public void onCreate(SQLiteDatabase db) {
updateMyDatabase(db, 0, DB_VERSION); Kod, który wcześniej był umieszczony
} w metodzie onCreate(), teraz przenieśliśmy
do metody updateMyDatabase().
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
updateMyDatabase(db, oldVersion, newVersion);
} Kod służący do aktualizacji bazy
danych zaimplementowaliśmy w
metodzie updateMyDatabase().
Pomocnik SQLite
2 Jeśli baza jeszcze nie istnieje, to pomocnik SQLite tworzy ją, a następnie wywołuje
swoją metodę onCreate().
W naszym przypadku metoda onCreate() wywołuje metodę updateMyDatabase(). Ta druga
metoda tworzy tabelę DRINK (włącznie z nową kolumną) i zapisuje w niej trzy wiersze danych.
DRINK
Nazwa: „coffeina”
onCreate()
Wersja: 2
3 Jeśli baza danych już istnieje, to pomocnik SQLite porównuje numer wersji bazy
z numerem wersji podanym w jego kodzie.
Jeśli numer wersji podany w kodzie pomocnika SQLite jest wyższy od numeru wersji bazy danych,
to pomocnik wywołuje metodę onUpgrade(). Jeśli natomiast numer wersji podany w pomocniku
SQLite jest niższy od numeru wersji bazy, pomocnik wywołuje metodę onDowngrade(). W naszym
przypadku numer wersji podany w pomocniku SQLite jest wyższy od numeru wersji bazy, dlatego
wywołana zostanie metoda onUpgrade(). Ta metoda wywoła z kolei metodę updateMyDatabase(),
która doda do tabeli DRINK nową kolumnę FAVORITE.
DRINK
Nazwa: „coffeina”
onUpgrade()
Wersja: 1 2
CELNE SPOSTRZEŻENIA
W systemie Android do trwałego Podczas tworzenia bazy danych
przechowywania danych używane są bazy wywoływana jest metoda onCreate().
danych SQLite. W razie konieczności aktualizacji bazy
Dostęp do baz danych SQLite zapewnia danych wywoływana jest metoda
klasa SQLiteDatabase. onUpgrade().
W tworzeniu baz danych i zarządzaniu nimi Polecenia SQL można wykonywać za
pomaga nam pomocnik SQLite. Aby go pomocą metody execSQL(String)
utworzyć, należy napisać klasę dziedziczącą zdefiniowanej w klasie SQLiteDatabase.
po SQLiteOpenHelper. Nowe rekordy można dodawać do tabel
Tworząc pomocnik SQLite, trzeba za pomocą metody insert().
zaimplementować metody onCreate() Do modyfikacji istniejących rekordów służy
i onUpgrade() klasy SQLiteOpenHelper.
metoda update().
Baza danych jest tworzona, gdy aplikacja Istniejące rekordy można usuwać za pomocą
po raz pierwszy zażąda dostępu do niej.
metody delete().
Konieczne jest przy tym określenie nazwy
bazy i jej numeru wersji, którego wartości
zaczynają się od 1. Jeśli nie podamy nazwy
bazy, zostanie ona utworzona tylko
w pamięci.
W poprzednim rozdziale…
Z rozdziału 11. dowiedziałeś się, jak napisać pomocnika SQLite, który utworzy
bazę danych, oraz jak utworzyć w takiej bazie danych tabelę i wypełnić ją danymi.
Dowiedziałeś się także, w jaki sposób stworzyć pomocnika SQLite, który będzie
sobie radzić z aktualizacjami bazy danych — modyfikacjami jej struktury
i zmianami przechowywanych w niej informacji.
W tym rozdziale pokażemy Ci, jak aktywności mogą prowadzić interakcję z bazą
danych, aby użytkownik, za pośrednictwem aplikacji, mógł odczytywać informacje
przechowywane w bazie i je zapisywać.
Przygotowaliśmy pomocnika SQLite
Oto aktualny schemat aplikacji dla kafeterii Coffeina: i napisaliśmy kod, który tworzy
bazę danych kafeterii Coffeina.
Aktualnie nie jest ona jeszcze
używana przez żadną aktywność.
Baza danych
kafeterii
Coffeina
Pomocnik SQLite
</Layout>
</Layout>
Klasy DrinkActivity
i DrinkCategoryActivity
wciąż używają klasy Drink.
Baza danych
kafeterii
<Layout> Coffeina <Layout>
</Layout>
</Layout>
package com.hfad.coffeina;
import android.app.Activity;
import android.os.Bundle;
import android.widget.ImageView;
import android.widget.TextView;
@Override app/src/main
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); java
Kursor odczytuje
interesujące nas
informacje zapisane
w bazie danych.
Zapytania dają nam możliwość precyzyjnego określenia, do których rekordów bazy danych
chcemy uzyskać dostęp. Na przykład możemy stwierdzić, że interesują nas wszystkie rekordy
zapisane w tabeli DRINK albo tylko te, których nazwa zaczyna się od litery „L”. Im bardziej
możemy ograniczyć zbiór danych do pobrania, tym wydajniej będzie działać zapytanie.
Tej wersji metody query() możemy używać, by określić, z jakiej tabeli mają
pochodzić dane, z jakich kolumn należy je pobrać, jakie warunki należy przy tym
sprawdzić oraz czy dane należy w jakiś sposób zagregować i posortować.
Dostępnych jest kilka innych, przeciążonych wersji tej metody, umożliwiających Za kulisami Android
podawanie dodatkowych szczegółów dotyczących zapytania, takich jak: czy
chcemy, by każdy wiersz był unikalny, albo jaka ma być maksymalna liczba używa metody query()
zwróconych wierszy. Nie będziemy tu przedstawiać wszystkich wariantów
tej metody, jeśli jednak Cię interesują, to informacje o nich znajdziesz do wygenerowania
w dokumentacji Androida na stronie:
polecenia SQL SELECT.
http://developer.android.com/reference/android/database/sqlite/SQLiteDatabase.html
NAME DESCRIPTION
To zapytanie zwraca wartości
kolumn NAME i DESCRIPTION ze “Latte” “Czarne espresso z gorącym
wszystkich wierszy tabeli DRINK. mlekiem i mleczną pianką.”
“Cappuccino” “Czarne espresso z dużą ilością
spienionego mleka.”
“Espresso” “Czarna kawa ze świeżo mielonych
ziaren najwyższej jakości.”
new String[] {“Latte”, “Czarna kawa ze świeżo mielonych ziaren najwyższej jakości.”},
null, null, null);
NAME DESCRIPTION
To zapytanie zwróci wartości kolumn NAME
i DESCRIPTION ze wszystkich wierszy tabeli, “Latte” “Czarne espresso z gorącym
w których kolumna NAME zawiera łańcuch mlekiem i mleczną pianką.”
znaków “Latte” lub kolumna DESCRIPTION
zawiera tekst “Czarna kawa ze świeżo “Espresso” “Czarna kawa ze świeżo mielonych
mielonych ziaren najwyższej jakości.”. ziaren najwyższej jakości.”
Jeśli warunki zostaną podane w innej kolejności niż wartości, to zapytanie zwróci
błędne dane. Na przykład w takim przypadku słowo „Latte” byłoby poszukiwane
w kolumnie DESCRIPTION, a nie NAME. W naszym przykładzie takie zapytanie nie
zwróciłoby żadnych rekordów.
Aby pobrać dane z kolumn NAME i FAVORITE posortowane w kolejności rosnącej NAME FAVORITE
według wartości kolumny NAME, musimy użyć następującego wywołania:
“Cappuccino” 0
Cursor cursor = db.query(”DRINK”,
“Espresso” 0
new String[] {”_id”, ”NAME”, ”FAVORITE”},
null, null, null, null, “Latte” 1
“NAME ASC”); Sortujemy na podstawie zawartoś
kolumny NAME w kolejności rosn ci
ącej .
Słowo kluczowe ASC oznacza, że zawartość podanej kolumny ma zostać
posortowana w kolejności rosnącej. Kolumny są domyślnie sortowane w kolejności
rosnącej, więc słowo kluczowe ASC możemy pomijać. Aby posortować kolumnę
w kolejności malejącej, należy użyć słowa kluczowego DESC.
SUM() Suma
To zapytanie zw count
ra
wierszy tabeli DR ca liczbę
INK. 3
Magnesiki z kodem
W naszej aktywności DrinkActivity chcemy pobrać nazwę, opis oraz
identyfikator zasobu graficznego dla napoju, którego identyfikator
został przekazany w intencji. Czy potrafisz napisać metodę query(),
która pobierze te dane?
...
”.....................”,
null, null,null);
...
Integer id "IMAGE_RESOURCE_ID"
"NAME" drinkNo
_id
?
=
"DESCRIPTION
"
.
toString
) "DRINK"
...
null, null,null);
drinkNo jest liczbą całkowitą,
dlatego musimy skonwertować ją
... na łańcuch znaków.
id
getReadableDatabase() getWritableDatabase()
SQLiteException SQLiteException
kolejności próbuje
Metoda getReadableDatabase() w pierwszej odczytu i zapisu.
uzyskać dostęp do bazy danych w trybie do p w trybie tylko
Jeśli to się nie uda, to próbuje uzyskać dostę niepowodzeniem,
się
do odczytu. Jeśli także ta próba zakończy
to metod a zgłosi wyjąt ek SQLit eExce ption.
jesteś tutaj 485
Kod używający kursora
coffeinaDatabaseHelper
coffeinaDatabaseHelper db
query(...)
coffeinaDatabaseHelper db cursor
Kursor zawiera
rekordy opisane
w zapytaniu.
Metoda moveToNext() działa tak samo jak moveToPrevious(), “Espresso” “Czarna kawa ze świeżo mielonych
ziaren najwyższej jakości.”
z tym, że zamiast do poprzedniego przenosi nas do następnego wiersza
w kursorze (jej wywołanie zwraca wartość true, jeśli udało się przejść Przechodzimy do poprzedniego wiersza.
do następnego wiersza, lub wartość false w przeciwnym razie):
if (cursor.moveToNext()) { NAME DESCRIPTION
// Coś robimy “Latte” “Czarne espresso z gorącym
mlekiem i mleczną pianką.”
};
“Cappuccino” “Czarne espresso z dużą ilością
Kiedy już przejdziemy do wiersza w kursorze, możemy uzyskać spienionego mleka.”
dostęp do zapisanych w nim wartości. Na następnej stronie “Espresso” “Czarna kawa ze świeżo mielonych
ziaren najwyższej jakości.”
pokazaliśmy, jak to zrobić.
cursor.close();
db.close();
W ten sposób przedstawiliśmy już cały kod, którym musimy zastąpić bieżący
kod aktywności DrinkActivity, aby pobierać informacje o napojach z bazy
danych. Zobaczmy zatem, jak wygląda ten kod.
package com.hfad.coffeina;
import android.app.Activity;
Coffeina
import android.os.Bundle;
import android.widget.ImageView;
app/src/main
import android.widget.TextView; Tych dodatkowych klas
używamy w kodzie
import android.widget.Toast; aktywności. java
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase; com.hfad.coffeina
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper; DrinkActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_drink);
To jest identyfikator napoju
wybranego przez użytkownika.
// Pobieramy identyfikator napoju z intencji
int drinkNo = (Integer)getIntent().getExtras().get(EXTRA_DRINKNO);
// Tworzymy kursor.
try {
SQLiteOpenHelper coffeinaDatabaseHelper = new CoffeinaDatabaseHelper(this);
SQLiteDatabase db = coffeinaDatabaseHelper.getReadableDatabase();
Cursor cursor = db.query (“DRINK”,
new String[] {“NAME”, “DESCRIPTION”,
Tworzymy kursor, który pobiera z tabeli “IMAGE_RESOURCE_ID”},
DRINK bazy danych wartości kolumn NAME,
DESCRIPTION oraz IMAGE_RESOURCE_ID “_id = ?”, Dalsza część kodu
wiersza, w którym wartość kolumny _id new String[] {Integer.toString(drinkNo)}, znajduje się na
odpowiada wartości zmiennej drinkNo. następnej stronie.
null, null,null);
490 Rozdział 12.
Kursory i zadania asynchroniczne
<Layout> <Layout>
</Layout> </Layout>
Aktywność
activity_top_level.xml DrinkCategoryActivity Drink.java activity_drink.xml
wciąż pobiera dane
z klasy Drink.
Aktywność
DrinkActivity już
zmodyfikowaliśmy,
dzięki czemu pobiera
dane z bazy danych
Obecnie aktywność DrinkActivity pobiera wszystkie kafeterii Coffeina,
używane dane z bazy danych kafeterii Coffeina. Naszym używając do tego
kolejnym zadaniem będzie aktualizacja kodu aktywności pomocnika SQLite.
DrinkCategoryActivity, tak by ona także pobierała Pomocnik
Baza danych
informacje z bazy danych, a nie z klasy Drink. SQLite
kafeterii Coffeina
Nie istnieją
głupie pytania
P: W jakim stopniu muszę znać język SQL, by tworzyć P: Napisaliście, że jeśli nie uda się uzyskać dostępu do bazy
kursory? danych, to zostanie zgłoszony wyjątek SQLiteException.
W jaki sposób powinienem go obsługiwać?
O: Przydałaby się znajomość polecenia SELECT języka SQL, gdyż
za kulisami metoda query() tworzy właśnie takie polecenie. O: Przede wszystkim sprawdź szczegółowe informacje zapisane
Ogólnie rzecz biorąc, zapytania, które będziesz tworzyć, raczej nie w wyjątku. Przyczyną wyjątku może być także błąd w składni
będą zbyt skomplikowane, niemniej jednak znajomość języka SQL zapytania SQL, a ten możesz samodzielnie poprawić.
może być przydatną umiejętnością. Sposób obsługi wyjątków tego rodzaju zależy od znaczenia
bazy danych dla działania tworzonej aplikacji. Na przykład jeśli
możesz uzyskać dostęp do bazy w trybie tylko do odczytu, lecz
nie możesz w niej zapisywać danych, to wciąż możesz zapewnić
użytkownikom możliwość odczytu danych z bazy, a jednocześnie
ostrzec ich, że zmian, które wprowadzą, nie będzie można zapisać.
Innymi słowy: wszystko zależy od aplikacji.
package com.hfad.coffeina;
import android.app.ListActivity;
import android.os.Bundle;
import android.widget.ArrayAdapter; Coffeina
import android.widget.ListView;
import android.view.View; app/src/main
import android.content.Intent;
java
public class DrinkCategoryActivity extends ListActivity {
com.hfad.coffeina
@Override
protected void onCreate(Bundle savedInstanceState) { DrinkCategory
Activity.java
super.onCreate(savedInstanceState);
ListView listDrinks = getListView();
ArrayAdapter<Drink> listAdapter = new ArrayAdapter<Drink>(
this,
android.R.layout.simple_list_item_1,
Drink.drinks); Obecnie, aby powiązać tablicę
listDrinks.setAdapter(listAdapter); z widokiem ListView, używamy
adaptera ArrayAdapter. Musimy
} zmodyfikować ten kod, aby
informacje były pobierane
z bazy danych.
@Override
public void onListItemClick(ListView listView,
View itemView,
int position,
long id) {
Intent intent = new Intent(DrinkCategoryActivity.this, DrinkActivity.class);
intent.putExtra(DrinkActivity.EXTRA_DRINKNO, (int)id);
startActivity(intent);
}
}
Adapter CursorAdapter w dużym stopniu przypomina ArrayAdapter, Nasze dane są przechowywane w kursorze,
dlatego możemy przekazać je do widoku
choć różni się od niego tym, że zamiast z tablicy pobiera dane z kursora. ListView, używając adaptera klasy
CursorAdapter.
Zobaczmy, jak działa taki adapter.
Widok ListView może prezentować jedynie ograniczoną liczbę elementów w każdej chwili.
Na niewielkich urządzeniach w widoku tym będzie widocznych tylko pierwszych jedenaście
napojów. Gdybyśmy używali tablicy, to musielibyśmy wczytać z bazy wszystkie trzysta napojów,
zapisać je w tablicy, a następnie wyświetlić.
To są elementy, które
będą widoczne po
wyświetleniu na
liście ListView. Aby
uprościć przykład,
ograniczymy tę liczbę
do pięciu, choć
w praktyce pewnie
będzie ich więcej. Widok ListView
A pewnie,
Hej, zaraz je
adapterze, czy zdobędę.
możesz mi przekazać
pięć pierwszych
elementów?
Hej, kursorze,
potrzebuję pięciu
elementów danych.
Kursor
Adapter CursorAdapter
Kursor
Baza danych
Kursor
Widok ListView Adapter CursorAdapter Baza danych
Jak więc widać, adapter CursorAdapter jest znacznie bardziej wydajny od kursora ArrayAdapter. Odczytuje
on bowiem tylko te dane, które są mu potrzebne. Oznacza to, że ten rodzaj adaptera działa szybciej i zużywa
mniej pamięci, a szybkość działania i pamięć to dwa bardzo ważne zagadnienia, na które trzeba zwracać uwagę.
Cappuccino
Klasy ArrayAdapter
Espresso i CursorAdapter są
Widok ListView Adapter CursorAdapter różnymi typami adapterów
— obie implementują
interfejs Adapter. Klasa
Nasza lista ListView wyświetla nazwę SimpleCursorAdapter
każdego napoju w odrębnym widoku TextView. dziedziczy po klasie
CursorAdapter.
Hej, kursorze,
Hej, adapterze, potrzebuję więcej…
potrzebuję więcej Kursorze? Stary,
danych. jesteś tam?
Kursor
Widok ListView Adapter CursorAdapter
Jeśli zamkniemy kursor
zbyt szybko, to adapter
nie będzie mógł pobierać
z niego dodatkowych
To wszystko oznacza, że nie będziemy mogli zwolnić kursora i bazy danych.
danych bezpośrednio po wywołaniu metody setAdapter() i przekazaniu
adaptera do widoku ListView. Zamiast tego powinniśmy to zrobić
w metodzie onDestroy() aktywności. W momencie usuwania aktywności
nie ma już bowiem potrzeby dalszego używania kursora lub połączenia
z bazą danych, można je więc usunąć:
Zagadkowy basen
Twoim zadaniem jest wyłowienie fragmentów kodu
z basenu i umieszczenie ich w pustych miejscach
kodu aktywności DrinkCategoryActivity.
Danego fragmentu kodu nie można użyć więcej
niż jeden raz, lecz nie wszystkie fragmenty
będą potrzebne. Twoim celem jest wypełnienie
listy ListView nazwami napojów odczytanymi
z bazy danych.
...
public class DrinkCategoryActivity extends ListActivity { Coffeina
@Override
com.hfad.coffeina
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ListView listDrinks = getListView(); DrinkCategory
Activity.java
try {
. .............. coffeinaDatabaseHelper = new CoffeinaDatabaseHelper(this);
db = coffeinaDatabaseHelper. . ..................;
getWritableDatabase()
SimpleCursorAdapter "NAME"
db
, cursor
cursor SQLiteOpenHelper
"NAME" getReadableDatabase()
"DESCRIPTION" "_id" SQLiteException
, DatabaseException
cursor = db.query(”DRINK”,
new String[]{ . ............................ },
null, null, null, null, null);
CursorAdapter listAdapter = new .................... (this,
android.R.layout.simple_list_item_1,
............. ,
new String[]{ ............... },
new int[]{android.R.id.text1},
0);
listDrinks.setAdapter(listAdapter);
} catch(.................. e) {
Toast toast = Toast.makeText(this, ”Baza danych jest niedostępna”,
Toast.LENGTH_SHORT);
toast.show();
}
}
Coffeina
@Override
public void onDestroy(){ app/src/main
super.onDestroy();
.......... .close(); java
.......... .close();
com.hfad.coffeina
}
@Override DrinkCategory
Activity.java
public void onListItemClick(ListView listView,
View itemView,
int position,
long id) {
Intent intent = new Intent(DrinkCategoryActivity.this, DrinkActivity.class);
intent.putExtra(DrinkActivity.EXTRA_DRINKNO, (int)id);
startActivity(intent);
}
}
...
public class DrinkCategoryActivity extends ListActivity { Coffeina
@Override
com.hfad.coffeina
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ListView listDrinks = getListView(); DrinkCategory
Activity.java
Dzięki użyciu obiektu SQLiteOpenHelper
try { możesz pobrać referencję do bazy danych.
SQLiteOpenHelper
................ coffeinaDatabaseHelper = new CoffeinaDatabaseHelper(this);
db = coffeinaDatabaseHelper. getReadableDatabase() ;
........................
Te fragmenty kodu
nie były potrzebne.
getWritableDatabase()
"DESCRIPTION"
DatabaseException
SimpleCursorAdapter
CursorAdapter listAdapter = new ....................... (this,
android.R.layout.simple_list_item_1,
cursor
............. , Wyświetlamy zawartość
j kursora.
Tu używamy utworzonego wcześnie kolumny NAME.
"NAME"
new String[]{ ............... },
new int[]{android.R.id.text1},
0);
listDrinks.setAdapter(listAdapter);
Jeśli baza danych nie jest dostępna,
SQLiteException e) { to przechwycimy wyjątek SQLiteException.
} catch(..................
Toast toast = Toast.makeText(this, ”Baza danych jest niedostępna”, Toast.LENGTH_SHORT);
toast.show();
}
} Coffeina
app/src/main
@Override
public void onDestroy(){
java
super.onDestroy();
.cursor
........ .close(); com.hfad.coffeina
Przed zamknięciem połączenia
db
. ........ .close(); z bazą danych zamykamy także
kursor.
} DrinkCategory
Activity.java
@Override
public void onListItemClick(ListView listView,
View itemView,
int position,
long id) {
Intent intent = new Intent(DrinkCategoryActivity.this, DrinkActivity.class);
intent.putExtra(DrinkActivity.EXTRA_DRINKNO, (int)id);
startActivity(intent);
}
}
package com.hfad.coffeina;
Coffeina
import android.app.ListActivity;
import android.content.Intent; app/src/main
import android.os.Bundle;
java
import android.view.View;
import android.widget.ListView;
com.hfad.coffeina
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
DrinkCategory
import android.database.sqlite.SQLiteException; Activity.java
import android.database.sqlite.SQLiteOpenHelper;
Używamy tych wszystkich
import android.widget.CursorAdapter; dodatkowych klas, więc
import android.widget.SimpleCursorAdapter; musimy je zaimportować.
import android.widget.Toast;
import android.widget.SimpleCursorAdapter;
try {
SQLiteOpenHelper coffeinaDatabaseHelper = new CoffeinaDatabaseHelper(this);
db = coffeinaDatabaseHelper.getReadableDatabase();
cursor = db.query(“DRINK”, Pobieramy referencję
do bazy danych.
new String[]{“_id”, “NAME”},
Tworzymy kursor.
null, null, null, null, null);
Dalsza część kodu znajduje się
na następnej stronie.
504 Rozdział 12.
Kursory i zadania asynchroniczne
@Override Coffeina
public void onDestroy(){ W metodzie onDestroy() aktywności zamykamy
kursor i bazę danych. Kursor pozostanie
super.onDestroy(); otwarty aż do momentu, gdy adapter nie app/src/main
będzie go już potrzebował.
cursor.close();
db.close(); java
}
com.hfad.coffeina
@Override
Tej metody nie DrinkCategory
public void onListItemClick(ListView listView, musieliśmy zmieniać. Activity.java
View itemView,
int position,
long id) {
Intent intent = new Intent(DrinkCategoryActivity.this, DrinkActivity.class);
intent.putExtra(DrinkActivity.EXTRA_DRINKNO, (int)id);
startActivity(intent);
}
}
Dokąd dotarliśmy?
Poniżej przedstawiliśmy obecną strukturę aplikacji dla kafeterii
Coffeina.
Baza danych
kafeterii
<Layout> Coffeina <Layout>
</Layout>
</Layout>
Jest jednak jeszcze jedna zmiana, którą planujemy wprowadzić w naszej Zmieniliśmy aktywności korzystające
aplikacji. Otóż chcemy, by aplikacja aktualizowała informacje zapisane w bazie. wcześniej z klasy Drink i obecnie
pobierają informacje z bazy danych.
506 Rozdział 12.
Kursory i zadania asynchroniczne
Te elementy doskonale
nadają się do umieszczenia
w szufladzie nawigacyjnej.
W tej aplikacji nie
zastosujemy jednak
szuflady nawigacyjnej,
gdyż jej implementacja
jest dosyć złożona,
a chcemy się skoncentrować
na bazach danych.
Do aktywności
TopLevelActivity
dodamy widok ListView
prezentujący ulubione Użytkownicy mogą wybierać swoje
napoje użytkownika. ulubione napoje, zaznaczając
to pole wyboru. Musimy je dodać
Kiedy użytkownik kliknie któryś do aktywności DrinkActivity
z ulubionych napojów, przejdzie i zadbać o to, by jego kliknięcie
bezpośrednio do szczegółowych odpowiednio modyfikowało bazę
informacji na jego temat, danych.
wyświetlanych w aktywności
DrinkActivity.
<string name=”favorite”>Ulubiony</string>
...
SQLiteOpenHelper coffeinaDatabaseHelper = new CoffeinaDatabaseHelper(this);
SQLiteDatabase db = coffeinaDatabaseHelper.getReadableDatabase();
Cursor cursor = db.query (”DRINK”,
new String[] {”NAME”,”DESCRIPTION”,”IMAGE_RESOURCE_ID”,”FAVORITE”},
”_id = ?”,
new String[] {Integer.toString(drinkNo)}, Dodajemy kolumnę
FAVORITE do kursora.
null, null,null);
Tych trzech metod możemy używać do wprowadzania zmian w zawartości bazy danych
z poziomu naszych aktywności. Na przykład możemy używać metody insert(),
by dodawać nowe rekordy napojów do tabeli DRINK, bądź metody delete(), by
je usuwać. W przypadku naszej aplikacji chcemy aktualizować zawartość kolumny
FAVORITE w tabeli DRINK, zapisując w niej bieżącą wartość pola wyboru. Możemy
do tego zastosować metodę update().
database.update(String tabela,
ContentValues wartosci,
String klauzulaWhere,
String[] argumentyWhere);
gdzie tabela jest nazwą tabeli, której zawartość chcemy zaktualizować, wartosci
to obiekt klasy ContentValues zawierający pary nazwa – wartość reprezentujące
aktualizowane kolumny i ich nowe wartości, a dwa ostatnie parametry, klauzulaWhere
i argumentyWhere, określają rekordy, które mają zostać zmodyfikowane.
update()
Magnesiki z kodem
W kodzie aktywności DrinkActivity chcemy aktualizować wartość
kolumny FAVORITE bazy danych, zapisując w niej wartość pola
wyboru używanego do wybierania ulubionych napojów. Czy potrafisz
uzupełnić kod metody onFavoriteClicked() tak, aby to robiła?
SQLiteOpenHelper coffeinaDatabaseHelper =
new CoffeinaDatabaseHelper(DrinkActivity.this);
try {
db.close();
} catch(SQLiteException e) {
Toast toast = Toast.makeText(this, ”Baza danych jest niedostępna”,Toast.LENGTH_SHORT);
toast.show();
}
} drinkValues
}
"FAVORITE"
View view "_id = ?" ContentValues
"DRINK"
ContentValues()
getReadableDatabase() favorite
getWritableDatabase()
SQLiteOpenHelper coffeinaDatabaseHelper =
new CoffeinaDatabaseHelper(DrinkActivity.this);
try {
getWritableDatabase()
SQLiteDatabase db = coffeinaDatabaseHelper. . .;
db.close();
} catch(SQLiteException e) {
Toast toast = Toast.makeText(this, ”Baza danych jest niedostępna”,Toast.LENGTH_SHORT);
toast.show();
}
} Te magnesiki nie były potrzebne.
getReadableDatabase() favorite
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_drink);
try {
Aktualizujemy SQLiteDatabase db = coffeinaDatabaseHelper.getWritableDatabase();
kolumnę
FAVORITE, db.update( “DRINK”, drinkValues,
zapisując w niej “_id = ?”, new String[] {Integer.toString(drinkNo)});
wartość pola db.close();
wyboru. } catch(SQLiteException e) {
Toast toast = Toast.makeText(this, “Baza danych jest niedostępna”, Toast.LENGTH_SHORT);
toast.show();
}
W razie wystąpienia problemów z bazą danych
} wyświetlamy komunikat.
}
Kursor
Widok Adapter Baza danych
ListView CursorAdapter
Intencja
drinkNo
TopLevelActivity DrinkActivity
package com.hfad.coffeina;
Coffeina
import android.app.Activity;
import android.content.Intent; app/src/main
import android.os.Bundle;
import android.widget.AdapterView; java
import android.widget.ListView;
com.hfad.coffeina
import android.view.View;
import android.database.Cursor;
TopLevel
import android.database.sqlite.SQLiteOpenHelper; Activity.java
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteDatabase; Dodajemy te
wszystkie klasy.
import android.widget.SimpleCursorAdapter;
import android.widget.CursorAdapter;
import android.widget.Toast;
private SQLiteDatabase db; Bazę danych i kursor dodajemy jako zmienne prywatne,
by mieć potem do nich dostęp w metodzie onDestroy().
private Cursor favoritesCursor;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_top_level);
long id) {
com.hfad.coffeina
if (position == 0) {
Intent intent = new Intent(TopLevelActivity.this,
TopLevel
DrinkCategoryActivity.class); Activity.java
startActivity(intent);
}
To jest kod, który początkowo znajdował się
} w metodzie onCreate(). Określa on zawartość
}; listy z opcjami i zapewnia, że będzie ona
reagować na kliknięcia.
Pobieramy referencję do
// Zapisujemy na liście list_favorites ulubione napoje użytkownika widoku ListView z ulubionymi
napojami użytkownika.
ListView listFavorites = (ListView)findViewById(R.id.list_favorites);
try {
SQLiteOpenHelper coffeinaDatabaseHelper = new CoffeinaDatabaseHelper(this);
db = coffeinaDatabaseHelper.getReadableDatabase();
favoritesCursor = db.query(“DRINK”,
new String[] { “_id”, “NAME”},
Tworzymy kursor,
który pobiera “FAVORITE = 1”, Pobieramy nazwy ulubionych
z bazy danych napojów użytkownika.
null, null, null, null);
wartości kolumn
_id i NAME tabeli
DRINK z wierszy,
w których kolumna
FAVORITE ma
wartość 1.
app/src/main
// W metodzie onDestroy() zamykamy kursor i bazę danych
@Override java
public void onDestroy(){
super.onDestroy(); com.hfad.coffeina
Powyższy kod wyświetla na liście napoje, które użytkownik oznaczył jako swoje ulubione. Kiedy
użytkownik kliknie nazwę jednego z tych napojów, to zostanie utworzona intencja, która uruchomi
aktywność DrinkActivity, a w tej intencji zostanie zapisany identyfikator klikniętego napoju. W efekcie
aplikacja wyświetli szczegółowe informacje o wybranym napoju. Na następnej stronie zobaczymy, jak
działa ta nowa wersja naszej aplikacji, a także pewien problem, który będziemy musieli rozwiązać.
WYSIL
SZARE KOMÓRKI
Jak myślisz, dlaczego napój, który właśnie wybraliśmy jako ulubiony, nie
został od razu wyświetlony na liście w aktywności głównej i pojawił się
na niej dopiero po obróceniu urządzenia? Zastanów się nad tym, zanim Ulubiony napój wciąż
nie został wyświetlony.
przewrócisz stronę. Jak sądzisz, dlaczego
tak się dzieje?
Kursory nie śledzą automatycznie, czy dane przechowywane w bazie danych zostały
zmodyfikowane, czy nie. Jeśli ulegną one zmianie po utworzeniu kursora, to kursor
nie zostanie zaktualizowany. Wciąż będzie on zawierał początkowe rekordy w ich Jeśli zaktualizujesz dane
niezmienionej postaci. w tabeli bazy danych…
”FAVORITE = 1”,
null, null, null, null);
// Pobieramy adapter CursorAdapter używany przez widok ListView
ListView listFavorites = (ListView)findViewById(R.id.list_favorites); Używając metody
getAdapter(),
CursorAdapter adapter = (CursorAdapter) listFavorites.getAdapter(); pobieramy adapter
używany w widoku
// Zmieniamy kursor używany przez adapter CursorAdapter na nowy ListView.
adapter.changeCursor(newCursor);
Zmieniamy kursor używany w adapterze.
Zaznaczamy pole
Początkowa lista ulubionych wyboru, oznaczając
napojów jest pusta. Latte jako ulubiony
napój.
Te wszystkie operacje trochę trwają. W przypadku małych baz, takich jak nasza baza
kafeterii Coffeina, wykonanie tych operacji nie zajmuje dużo czasu. Jednak wraz
z powiększeniem się rozmiarów bazy także czas konieczny na wykonanie tych czynności
będzie się wydłużał. W końcu, zanim się zorientujesz, Twoja aplikacja może stracić cały
powab i działać wolniej niż YouTube na święta Bożego Narodzenia.
Nie możemy wiele poradzić na szybkość wykonywania operacji tworzenia i odczytu z bazy
danych, ale możemy zrobić całkiem sporo, aby te operacje nie spowalniały interfejsu
użytkownika aplikacji.
Wątek wyświetlania.
Zazwyczaj nie używamy tego wątku bezpośrednio, jednak odczytuje on listę żądań dotyczących
aktualizacji ekranu i komunikuje się z komponentami sprzętowymi ekranu w celu jego
odświeżania i dbania o to, by aplikacja ślicznie wyglądała.
Jeśli nie zachowamy należytej uwagi, to praktycznie wszystkie operacje realizowane przez
aplikację będą wykonywane w wątku głównym. Dlaczego? Ponieważ to jest wątek, w którym
są wykonywane metody obsługujące wszystkie zdarzenia. Jeśli zatem umieścimy kod tworzący
bazę danych w metodzie onCreate() (czyli dokładnie tak, jak zrobiliśmy to w aplikacji
kafeterii Coffeina), to właśnie wątek główny będzie się mozolnie zajmował komunikacją z bazą
danych zamiast pędzić do przodu i błyskawicznie odbierać i obsługiwać wszystkie zdarzenia
przesyłane do aplikacji przez ekran urządzenia. Jeśli wykonanie kodu obsługującego bazę
danych trwa długo, użytkownik może odnieść wrażenie, że aplikacja go ignoruje.
A zatem cała sztuczka polega na tym, by usunąć kod związany z obsługą bazy danych
z głównego wątku aplikacji i przenieść go do innego wątku, działającego w tle.
Zaostrz ołówek
Planujemy wykonywać kod obsługujący bazę danych w aktywności DrinkActivity
w wątku działającym w tle, ale zanim zajmiemy się wprowadzaniem zmian w kodzie,
musimy poświęcić chwilę na zastanowienie się, co właściwie musimy zrobić.
Kod, którego obecnie używamy, wykonuje trzy podstawowe czynności. Jak uważasz:
w którym z wątków powinny być wykonywane poszczególne bloki kodu? Zapisz typ
wątku, w którym według Ciebie powinien działać każdy z przedstawionych bloków.
Główny wątek
Wątek działający w tle
obsługi zdarzeń
Główny wątek
Wątek działający w tle
obsługi zdarzeń
Zaostrz ołówek
Rozwiązanie Planujemy wykonywać kod obsługujący bazę danych w aktywności DrinkActivity
w wątku działającym w tle, ale zanim zajmiemy się wprowadzaniem zmian w kodzie,
musimy poświęcić chwilę na zastanowienie się, co właściwie musimy zrobić.
Kod, którego obecnie używamy, wykonuje trzy podstawowe czynności. Jak uważasz:
w którym z wątków powinny być wykonywane poszczególne bloki kodu? Zapisz typ
wątku, w którym według Ciebie powinien działać każdy z przedstawionych bloków.
Główny wątek
Wątek działający w tle
obsługi zdarzeń
Główny wątek
Wątek działający w tle
obsługi zdarzeń
Oto kod metody w jej dotychczasowej postaci (podzieliliśmy go przy tym na części,
które opisaliśmy u dołu strony):
SQLiteOpenHelper coffeinaDatabaseHelper =
new CoffeinaDatabaseHelper(DrinkActivity.this);
try {
2 SQLiteDatabase db = coffeinaDatabaseHelper.getWritableDatabase();
db.update( "DRINK" , drinkValues ,
"_id = ?" , new String[] {Integer.toString(drinkNo)});
db.close();
} catch(SQLiteException e) {
2 Kod korzystający z bazy danych, który ma być wykonany w wątku działającym w tle.
Ten kod aktualizuje tabelę DRINK.
Zaimplementujemy ten kod, używając obiektu klasy AsyncTask. Ale co to w ogóle jest?
jesteś tutaj 529
AsyncTask
Klasa AsyncTask jest definiowana przez trzy parametry ogólne: Params, Progress oraz
Results. Pierwszy z nich, Params, to typ obiektu używanego do przekazania parametrów do
metody doInBackground(), drugi, Progress, to typ obiektu stosowanego do przekazywania
informacji o postępach prac, a trzeci, Result, to typ wyniku zwracanego przez zadanie.
Jeśli nie zamierzamy używać któregoś z tych typów, to możemy go określić jako Void.
Sposobom stosowania tej klasy przyjrzymy się dokładniej na kilku następnych stronach,
na których utworzymy klasę UpdateDrinkTask dziedziczącą po AsyncTask i reprezentującą
zadanie służące do aktualizowania w tle informacji o napojach zapisanych w bazie danych.
W dalszej części rozdziału zastosujemy tę klasę w naszej aktywności DrinkActivity.
Metoda onPreExecute()
Zaczniemy od metody onPreExecute(). Jest ona wywoływana przed
rozpoczęciem wykonywania zadania uruchamianego w tle i służy do
jego przygotowania. Metoda ta jest wywoływana w głównym wątku
onPreExecute
aplikacji, zatem ma dostęp do wszystkich widoków tworzących jej interfejs
użytkownika. Metoda onPreExecute() nie ma żadnych parametrów
i zwraca wynik typu void.
...
Metoda doInBackground()
Metoda doInBackground() jest wywoływana i wykonywana w tle,
bezpośrednio po zakończeniu realizacji metody onPreExecute(). onPreExecute
Mamy możliwość zdefiniowania, jakiego typu będą parametry tej
metody i jakiego typu będzie zwracany przez nią wynik.
...
Metoda onProgressUpdate()
Metoda onProgressUpdate() jest wywoływana w głównym wątku aplikacji
onPreExecute
używanym do obsługi zdarzeń, dzięki czemu ma dostęp do wszystkich
widoków tworzących jej interfejs użytkownika. Można jej używać do
prezentowania użytkownikom postępu w wykonywanych operacjach poprzez
aktualizowanie stanu widoków. Mamy możliwość zdefiniowania typu
parametrów tej metody. doInBackground
Metoda onProgressUpdate() zostanie wywołana, jeśli w kodzie metody
doInBackground(), w pokazany poniżej sposób, wywołamy metodę
publishProgress():
...
Metoda onPostExecute()
onPreExecute
Metoda onPostExecute() jest wywoływana po zakończeniu zadania
wykonywanego w tle. Jest ona wykonywana w głównym wątku aplikacji,
więc ma dostęp do wszystkich widoków tworzących jej interfejs użytkownika.
Można jej używać do zaprezentowania wyników wykonanych operacji.
Do metody onPostExecute() przekazywany jest wynik zwrócony przez doInBackground
metodę doInBackground(), zatem typ jej parametru musi odpowiadać
typowi wyniku zwracanego przez metodę doInBackground().
ackground()
... To jest typ Boolean, gdyż nasza metoda doInB
zwraca właśn ie warto ść tego typu.
Klasa AsyncTask
Kiedy po raz pierwszy przedstawialiśmy klasę AsyncTask, napisaliśmy, że jest
ona definiowana przez trzy parametry ogólne: Params, Progress oraz Results.
Określiliśmy je na podstawie typów parametrów metod doInBackground(),
onProgressUpdate() oraz onPostExecute(). Params to typ parametrów metody
doInBackground(), Progress jest typem parametrów metody onProgressUpdate(),
a Result — typem parametru metody onPostExecute():
private class MyAsyncTask extends AsyncTask<Params, Progress, Result>
protected void onPreExecute() {
// Kod, który ma zostać wykonany przed rozpoczęciem zadania
}
Teraz już wiesz wszystko, co trzeba, by utworzyć zadanie — przekonajmy się zatem,
jak takie zadanie można wykonać.
package com.hfad.coffeina;
Coffeina
...
Importujemy klasę AsyncTask.
import android.os.AsyncTask; app/src/main
com.hfad.coffeina
... Metody onCreate() nie musimy zmieniać,
więc nie pokazywaliśmy jej tutaj.
DrinkActivity.java
// Aktualizujemy bazę po zaznaczeniu pola wyboru
public void onFavoriteClicked(View view){
int drinkNo = (Integer)getIntent().getExtras().get(”drinkNo”);
new UpdateDrinkTask().execute(drinkNo); Wykonujemy zadanie.
}
To już wszystko, co musisz wiedzieć, by móc tworzyć i stosować W idealnym świecie cały kod
zadania asynchroniczne, używając do tego klasy AsyncTask. Kiedy
użytkownik kliknie pole wyboru ulubionego napoju w aktywności realizujący operacje na bazie
DrinkActivity, to baza danych zostanie zmodyfikowana przez danych byłby wykonywany
wątek działający w tle.
w wątkach działających
w tle. My nie mamy jednak
zamiaru zmieniać pozostałych
aktywności naszej aplikacji
tak, by operacje na bazie były
wykonywane w tle, ale może Ty
to zrobisz?
Nie istnieją
głupie pytania
P: Kiedyś już napisałem kod, który O : Najkrócej rzecz ujmując, taka próba P: Jeśli odczytanie danych z bazy
po prostu wykonywał operacje na skończyłaby się zgłoszeniem wyjątku. Rozwijając zajmuje kilka sekund, to co zobaczy
bazie danych, i wszystko działało nieco ten temat, należałoby stwierdzić, że użytkownik?
świetnie. Czy takie operacje na bazie
naprawdę trzeba wykonywać w tle?
wielowątkowe interfejsy użytkownika są
niezwykle podatne na występowanie błędów. O: Użytkownik najpierw zobaczy puste
widoki, a dopiero po jakimś czasie zostaną
O: W przypadku naprawdę małych Android eliminuje ten problem, uniemożliwiając
stosowanie takich rozwiązań. one wypełnione danymi.
baz danych, takich jak nasza, czas
dostępu do bazy danych będzie zapewne P: Które fragmenty kodu P: Dlaczego w zadaniu AsyncTask
niezauważalny. Ale będzie to wynikało wykonującego operacje na bazie umieściliście kod wykonujący operacje
wyłącznie z małej wielkości bazy. Jeśli baza danych są najwolniejsze? na bazie danych z tylko jednej
będzie większa, albo jeśli uruchomimy aktywności?
aplikację na wolniejszym urządzeniu, czas O: Nie sposób tego ogólnie określić. Jeśli O: Chcieliśmy Ci pokazać sposób
dostępu do bazy zacznie mieć większe używana baza danych ma złożoną strukturę,
znaczenie. Dlatego owszem kod to jej pierwsze otwarcie może zająć sporo korzystania z klasy AsyncTask na jednej
operujący na bazie danych zawsze powinien czasu, bo będzie wymagało utworzenia aktywności w ramach przykładu. W praktyce
być wykonywany w tle. wszystkich tabel. Także wykonanie tak samo należy wykonywać wszystkie
złożonych zapytań może zajmować bardzo operacje na bazach danych we wszystkich
P: Przypomnijcie mi: dlaczego dużo czasu. Ogólnie rzecz biorąc, należy aktywnościach.
aktualizowanie widoków z wątku podejść do zagadnienia ostrożnie i wszystkie
wykonywanego w tle jest błędem? operacje na bazie danych wykonywać w tle.
jesteś tutaj 539
Przybornik
Opanowałeś już rozdział 12. i dodałeś j
do swojego przybornika z narzędziami Pełny kod przykładowe
lika cji pre zen tow ane j
ap
umiejętność podłączania aplikacji do baz mo żes z
w tym rozdziale
danych SQLite. pobrać z ser we ra FT P
wydawnictwa Helion:
ftp://ftp.helion.pl/
przyklady/andrrg.zip
CELNE SPOSTRZEŻENIA
Kursory obiekty klasy Cursor Po zakończeniu korzystania z kursora
umożliwiają odczytywanie i zapisywanie i połączenia z bazą danych należy je
informacji w bazach danych. zamknąć.
Kursory tworzy się, wywołując metodę Do korzystania z kursorów służy adapter
query klasy SQLiteDatabase. klasy CursorAdapter. Do wypełnienia
W niewidoczny sposób kursory tworzą widoku ListView wartościami
polecenie SQL SELECT. z kursora można używać adaptera
SimpleCursorAdapter.
Metoda getWritableDatabase() zwraca
obiekt SQLiteDatabase pozwalający Aplikacje należy projektować w taki sposób,
odczytywać i zapisywać informacje w bazie by przydatne treści były umieszczane
danych. w aktywności najwyższego poziomu.
Metoda getReadableDatabase() zwraca Metoda changeCursor() klasy
obiekt SQLiteDatabase. Obiekt ten CursorAdapter zastępuje kursor aktualnie
zapewnia dostęp do bazy danych w trybie używany przez adapter nowym kursorem
tylko do odczytu. Może on zapewniać także przekazanym w wywołaniu. Stary kursor
możliwość zapisu danych w bazie, ale nie ma jest następnie automatycznie zamykany.
takiej gwarancji. Kod wykonujący operacje na bazie danych
Po zawartości kursora można się poruszać, należy wykonywać w tle, korzystając z klasy
używając metod moveTo*(). AsyncTask.
Do odczytywania wartości z kursora służą
metody get*().
Czy wspominałem,
że świadczę usługę
WymuszanieHaraczyService?
Usługi uruchomione.
Usługi uruchomione (ang. started services) mogą działać w tle przez dowolnie
długi okres czasu, i to nawet po tym, jak aktywność, która je uruchomiła,
zostanie usunięta. Na przykład gdybyśmy chcieli pobrać z internetu duży plik,
to moglibyśmy do tego użyć właśnie usługi uruchomionej. Po zakończeniu
operacji taka usługa jest zatrzymywana.
Usługi powiązane.
Usługi powiązane (ang. bound services) są skojarzone z jakimś innym
komponentem, takim jak aktywność. Aktywność może prowadzić interakcję
z taką usługą — przesyłać do niej żądania i pobierać wyniki. Usługi tego
typu działają tak długo, jak długo działa komponent, z którym są powiązane.
Kiedy komponenty nie będą już powiązane, usługa jest niszczona. Gdybyśmy
chcieli napisać drogomierz zliczający przejechany dystans, to prawdopodobnie
użylibyśmy do tego właśnie usługi powiązanej. Dzięki temu wszystkie
aktywność powiązane z usługą mogłyby cyklicznie prosić usługę o przekazanie
informacji o pokonanym dystansie.
activity_main.xml
Usługa wyświetli
tekst po upływie
Aktywność przekaże do 10 sekund.
usługi fragment tekstu.
MainActivity.java DelayedMessageService.java
2 Wyświetlenie tosta.
Zadbamy, by komunikat był wyświetlany na ekranie jako tost, dzięki
czemu urządzenie nie będzie musiało być podłączone do Android Studio,
żebyśmy mogli się przekonać, że usługa działa.
Utworzenie projektu
Zaczniemy od utworzenia projektu. Utwórz zatem nowy projekt
aplikacji na Androida o nazwie Wic, a jej kod umieść w pakiecie
com.hfad.wic. Minimalną wersją SDK powinno być API poziomu 16,
dzięki czemu aplikacja będzie działać na większości urządzeń. Utwórz
w aplikacji pustą aktywność o nazwie MainActivity korzystającą
z układu o nazwie activity_main.
rvice.
package com.hfad.wic; To jest hierarchia klasy IntentSe
import android.app.IntentService;
import android.content.Intent; Rozszerzamy klasę IntentService
.
public DelayedMessageService() {
super(”DelayedMessageService”);
} musimy Wic
Kod, który ma wykonywać usługa, nt.
umieścić w metodzie onHandleInte
@Override app/src/main
protected void onHandleIntent(Intent intent) {
// Tu coś robimy java
}
com.hfad.wic
}
DelayedMessage
Ogólny opis tej usługi zamieścimy na następnej stronie. Service.java
1 Aktywność określa usługę, do której musi się odwołać, tworząc jawną intencję.
Intencja określa usługę, do której ma zostać przekazana.
Intencja
Do: DelayedMessageService
MainActivity tekst:”Już czas!”
Intencja Intencja
DelayedMessageService
Jak widać, usługi można uruchamiać tak samo jak intencje — poprzez
utworzenie odpowiedniej intencji. Różnica polega jednak na tym, że w przypadku
uruchamiania usługi na ekranie nic się nie zmienia, gdyż usługi nie mają
interfejsu użytkownika.
package com.hfad.wic;
import android.app.IntentService;
import android.content.Intent;
import android.util.Log; Rozszerzamy klasę IntentService
.
synchronized (this) {
app/src/main
try {
Czekamy 10 sekund.
wait(10000); java
} catch (InterruptedException e) {
com.hfad.wic
e.printStackTrace();
} Pobieramy tekst z intencji.
DelayedMessage
} Service.java
String text = intent.getStringExtra(EXTRA_MESSAGE);
showText(text);
} Wywołujemy metodę showText().
app/src/main
<activity
W taki sposób należy
<xml>
</xml>
... deklarować usługi w pliku
AndroidManifest.xml. Android AndroidManifest.xml
</activity> Studio powinno to zrobić za
nas automatycznie.
<service
android:name=”.DelayedMessageService”
android:exported=”false” >
Nazwa usługi jest poprzedzona
</service> znakiem kropki, aby Android móg
ł
umieścić przed nią nazwę pakietu,
</application> uzyskując w ten sposób pełną nazw
klasy. ę
</manifest>
<RelativeLayout xmlns:android=”http://schemas.android.com/apk/res/android”
xmlns:tools=”http://schemas.android.com/tools”
android:layout_width=”match_parent”
Wic
android:layout_height=”match_parent”
tools:context=”.MainActivity”>
app/src/main
<Button res
android:layout_width=”wrap_content” Ten element tworzy przycisk. Po
jego
kliknięciu zostanie wywołana meto layout
android:layout_height=”wrap_content” onClick() aktywności. da
<xml>
android:text=”@string/button_text” </xml>
android:id=”@+id/button” activity_
main.xml
android:onClick=”onClick”
android:layout_alignParentTop=”true”
android:layout_centerHorizontal=”true” />
</RelativeLayout>
package com.hfad.wic;
Wic
import android.app.Activity;
import android.os.Bundle;
app/src/main
import android.view.Menu;
import android.view.MenuItem;
java
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
Używamy tych wszystkich klas.
import android.view.View; DelayedMessage
Service.java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
} Ta metoda zostanie wywołana
po kliknięciu przycisku. Tworzymy intencję.
public void onClick(View view) {
Intent intent = new Intent(this, DelayedMessageService.class);
intent.putExtra(DelayedMessageService.EXTRA_MESSAGE,
getResources().getString(R.string.button_response));
startService(intent);
Dodajemy tekst do intencji.
}
} Uruchamiamy usługę.
A oto i przycisk.
To jest panel
z zawartością
dziennika.
...
import android.app.IntentService;
import android.content.Intent;
import android.os.Handler; klas.
Używamy tych dwóch dodatkowych
import android.widget.Toast;
public DelayedMessageService() {
super(”DelayedMessageService”);
}
Obiekt Handler tworzymy w wątku głównym.
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
handler = new Handler();
return super.onStartCommand(intent, flags, startId);
}
Tej metody nie zmieniamy.
@Override
protected void onHandleIntent(Intent intent) { Wic
synchronized (this) {
try { app/src/main
wait(10000);
} catch (InterruptedException e) { java
e.printStackTrace();
} com.hfad.wic
}
String text = intent.getStringExtra(EXTRA_MESSAGE);
showText(text); DelayedMessage
Service.java
}
”Już czas!”
MainActivity DelayedMessageService
tekst=”Już czas!”
DelayedMessageService Notification
NotificationManager
DelayedMessageService
tekst=”Już czas!”
Notification
Intencja
Do: MainActivity
DelayedMessageService
Do: MainActivity
DelayedMessageService TaskStackBuilder
¨ Dziennik
3. Pobranie intencji oczekującej z obiektu TaskStackBuilder ¨ Tost
Następnie musimy pobrać intencję oczekującą z obiektu TaskStackBuilder, ¨ Powiadomienie
używając do tego metody getPendingIntent(). Metoda ta ma dwa parametry
typu int: kod żądania, którego można używać do identyfikacji intencji, i flagę,
określającą zachowanie intencji oczekującej.
kującą.
To wywołanie tworzy intencję ocze
PendingIntent pendingIntent =
stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
Intencja oczekująca
Do: MainActivity
DelayedMessageService TaskStackBuilder
Do: MainActivity
DelayedMessageService Notification
Skoro już przekazaliśmy do powiadomienia intencję oczekującą, która określa, co należy zrobić,
gdy powiadomienie zostanie kliknięte, pozostaje nam jedynie zająć się jego wyświetleniem.
Wysyłanie powiadomień
za pomocą usługi systemowej
Pokazaliśmy już, jak utworzyć i skonfigurować powiadomienie. Kolejnym
krokiem jest przekazanie go do systemowej usługi powiadomień, dzięki
czemu zostanie ono wyświetlone.
Nie istnieją
głupie pytania
P: Dlaczego mam dodawać ikonę do P: Co się stanie, jeśli nie określę priorytetu i nie
powiadomienia? włączę wibracji?
O: System powiadomień potrzebuje ikony, by wyświetlić O: Takie powiadomienie także zostanie wysłane, lecz
powiadomienie na samej górze ekranu. nie pojawi się na ekranie. Oczywiście wciąż będzie można
zobaczyć je w szufladzie powiadomień.
package com.hfad.wic;
Wic
import android.app.IntentService;
import android.app.Notification; app/src/main
import android.app.NotificationManager;
import android.app.PendingIntent; java
.
Używamy tych dodatkowych klas
import android.app.TaskStackBuilder;
import android.content.Context; com.hfad.wic
import android.content.Intent;
import android.os.Handler; Nie wyświetlamy już komunikatów DelayedMessage
używając klasy Toast, więc te inst, Service.java
import android.widget.Toast; import nie będą nam już potrzebn rukcje
e.
stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
Notification notification = new Notification.Builder(this)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle(getString(R.string.app_name))
.setAutoCancel(true)
Konstruujemy powiadomienie.
.setPriority(Notification.PRIORITY_MAX)
.setDefaults(Notification.DEFAULT_VIBRATE)
.setContentIntent(pendingIntent)
.setContentText(text)
.build();
NotificationManager notificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(NOTIFICATION_ID, notification);
}
Wyświetlamy powiadomienie, używając usługi systemowej.
}
Intencja
”Już czas!”
MainActivity DelayedMessageService
1...2...3...4...
DelayedMessageService
Do: MainActivity
DelayedMessageService
Intencja
Do: MainActivity
DelayedMessageService TaskStackBuilder
Intencja oczekująca
Do: MainActivity
DelayedMessageService TaskStackBuilder
Intencja oczekująca
tekst=”Już czas!”
Do: MainActivity
DelayedMessageService Notification
Do: MainActivity
tekst=”Już czas!”
Intencja
Notification MainActivity
¨ Dziennik
Jazda próbna aplikacji ¨ Tost
¨ Powiadomienie
Kiedy klikniesz przycisk wyświetlony w aktywności MainActivity, po 10 sekundach
zostanie wyświetlone powiadomienie. Pojawi się ono na ekranie niezależnie od tego,
jaka aplikacja będzie w danej chwili widoczna.
Po opóźnieniu na ekranie zostanie się
e
wyświetlone powiadomienie. Moż iach,
zdarzyć, że na starszych urządzen
iał
aby je wyświetlić, będziesz mus
otworzyć szufladę powiadomień.
Kliknij przycisk.
Magnesiki usługowe
Poniżej przedstawiliśmy większość kodu niezbędnego do utworzenia
usługi uruchomionej o nazwie WombleService, która odtwarza w tle
pliki .mp3, i aktywności, która tej usługi używa. Przekonajmy się,
czy uda Ci się uzupełnić ich kod. To jest usługa.
@Override
protected void .................. (Intent intent) {
MediaPlayer mediaPlayer =
MediaPlayer.create(getApplicationContext(), R.raw.wombling_song);
mediaPlayer.start();
Ten fragment kodu używa dostępnej w systemie Android klasy
} MediaPlayer, aby odtworzyć plik wombling_song.mp3. Plik jest
} umieszczony w katalogu res/raw.
To jest aktywność.
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
startActivity
public WombleService() {
super(”WombleService”);
} Ten kod musi być wykonywany
w metodzie onHandleIntent.
@Override
onHandleIntent
protected void (Intent intent) {
MediaPlayer mediaPlayer =
MediaPlayer.create(getApplicationContext(), R.raw.wombling_song);
mediaPlayer.start();
}
}
To jest aktywność.
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
} Musisz utworzyć intencję jawną
skierowaną do WombleService.class.
startActivity
WombleService
</Layout>
2
activity_main.xml
1 getDistance() Systemowa
usługa
lokalizacyjna
3
1.11
MainActivity.java OdometerService.java
OdometerBinder
LocationListener Systemowa
usługa
lokalizacyjna
getDistance()
0.5
MainActivity OdometerService
Usługę dziedziczącą po klasie Service można dodać do projektu To jest hierarchia klasy Service.
w podobny sposób, jak zrobiliśmy to wcześniej. A zatem wybierz z menu
opcję File/New/Service/Service (a nie Service (IntentService)), a następnie
nadaj usłudze nazwę OdometerService. Usuń zaznaczenie z pola wyboru
Exported, gdyż ma ono być zaznaczone wyłącznie w przypadkach, kiedy
chcemy, by usługa była dostępna także poza bieżącą aplikacją. Upewnij się
także, że zaznaczone jest pole wyboru Enabled, ponieważ w przeciwnym
razie aktywność nie będzie w stanie uruchomić usługi.
package com.hfad.drogomierz;
Drogomierz
MainActivity ServiceConnection
Intencja Intencja
Binder Binder
MainActivity OdometerService
...
import android.os.Binder; Używamy tych dwóch klas.
Drogomierz
import android.os.IBinder;
app/src/main
public class OdometerService extends Service {
private final IBinder binder = new OdometerBinder();
java
...
@Override
public IBinder onBind(Intent intent) {
return binder;
} Metoda onBind() zwraca impleme
To właśnie ten interfejs ntację interfejsu IBinder.
} implementuje klasa Binder.
Systemowa
usługa
lokalizacyjna
OdometerService LocationListener
Daleko
jeszcze? Przejechaliśmy
1 kilometr.
getDistance()
1
MainActivity OdometerService
onStartCommand() Kiedy aktywność tworzy usługę, wywołując Jeśli nasza usługa nie jest usługą
metodę startService(). uruchomioną, to tej metody nie
musimy implementować. Jest ona
wykonywana tylko w przypadku
uruchamiania usługi za pomocą
metody startService().
onDestroy() Kiedy usługa nie jest już używana Można jej używać do zwalniania
i ma zostać usunięta. zasobów.
@Override
public void onStatusChanged(String arg0, int arg1, Bundle bundle) {}
};
...
public class OdometerService extends Service { Drogomierz
@Override
public void onStatusChanged(String arg0, int arg1, Bundle bundle) {}
};
}
}
locManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,
1000, To jest czas w milisekundach.
To jest odległość w metrach. 1,
obiekt
listener); A to jest utworzony przez nas
nasłuchujący LocationManager.
Teraz możemy już użyć metody onCreate() naszej usługi, aby zarejestrować obiekt
nasłuchujący w systemowej usłudze lokalizacyjnej i zagwarantować, że będzie on
otrzymywać regularne powiadomienia o zmianach położenia. Oto kod, który to robi:
Pierwszą jest śledzenie dystansu przebytego przez urządzenie. Tym już zajęliśmy
się, tworząc obiekt nasłuchujący LocationListener i rejestrując go w systemowej
usłudze lokalizacyjnej.
Drugą rzeczą, którą musimy się zająć, jest zapewnienie usłudze możliwości
przekazywania informacji o przebytym dystansie do aktywności, tak aby aktywność
mogła ten dystans wyświetlić użytkownikowi. W tym celu będziemy musieli napisać
w naszej usłudze metodę getDistance(), która będzie przeliczać dystans przebyty
przez urządzenie na kilometry. Aktywność będzie wywoływać tę metodę za każdym
razem, kiedy będzie chciała się dowiedzieć, jaki dystans pokonało urządzenie.
getDistance()
1.11
MainActivity OdometerService
package com.hfad.drogomierz;
Drogomierz
import android.app.Service;
import android.content.Context; app/src/main
import android.content.Intent;
import android.location.Location; java
import android.location.LocationListener;
com.hfad.drogomierz
import android.location.LocationManager;
import android.os.Binder;
Odometer
import android.os.Bundle; To są wszystkie używane klasy. Service.java
import android.os.IBinder;
@Override
public IBinder onBind(Intent intent) {
return binder;
Ta metoda jest wywoływana, kiedy aktywność
} prosi o powiązanie z usługą.
lastLocation = location;
com.hfad.drogomierz
}
distanceInMeters += location.distanceTo(lastLocation);
Odometer
lastLocation = location; Service.java
}
@Override
To jest nasza implementacja
public void onProviderDisabled(String arg0) {} obiektu nasłuchującego
LocationListener.
@Override
public void onProviderEnabled(String arg0) {}
@Override
public void onStatusChanged(String arg0, int arg1, Bundle bundle) {}
};
LocationManager locManager =
(LocationManager)getSystemService(Context.LOCATION_SERVICE);
locManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,1000,1,listener);
} Konwertujemy przebyty
dystans na kilometry. Rejestrujemy obiekt
nasłuchujący w systemowej
public double getDistance() { usłudze lokalizacyjnej.
<application
... >
<activity Drogomierz
...
</activity> Wszystkie usługi należy app/src/main
zadeklarować w pliku <xml>
AndroidManifest.xml. </xml>
<service AndroidManifest.xml
android:name=”.OdometerService”
android:exported=”false” Temu atrybutowi przypisujemy
wartość false, gdyż tylko nasza
gi.
android:enabled=”true” > aplikacja będzie używać tej usłu
</service> Atrybutowi android:enabled należy przypisać
</application> wartość true lub całkowicie go pominąć. Jeśli
przypiszemy mu wartość false, to aplikacja nie
</manifest> będzie mogła używać usługi.
Magnesiki usługowe
Przekonajmy się, czy potrafisz uzupełnić poniższy kod, aby utworzyć
usługę powiązaną o nazwie NumberService, która w odpowiedzi na
wywołanie metody getNumber() zwraca liczbę losową.
...
public class NumberService extends Service {
................. getNumberService() {
return NumberService.this;
}
}
@Override
public . ........................ (Intent intent) {
........................;
}
binder
IBinder NumberBinder
void
onBind
onHandleIntent
onCreate
return NumberService
...
public class NumberService extends Service {
@Override
public IBinder .
onBind
. (Intent intent) {
return . binder ...;
}
Metoda onBind() ma zwracać obie
kt Binder.
void
onHandleIntent
onCreate
Dokąd dotarliśmy?
Zastanówmy się jeszcze raz, jakimi możliwościami ma dysponować nasza aplikacja,
żebyśmy mogli określić, co jeszcze zostało do zrobienia:
<Layout>
</Layout>
2
activity_main.xml
1 Systemowa
getDistance()
usługa
lokalizacyjna
Teraz zajmiemy się
aktualizacją tej 3 Usługa już jest gotowa.
aktywności
1.11
MainActivity.java OdometerService.java
<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”
Drogomierz
tools:context=”.MainActivity”>
app/src/main
<TextView android:text=””
android:id=”@+id/distance”
res
android:textAppearance=”?android:attr/textAppearanceLarge”
android:layout_width=”match_parent” layout
android:layout_height=”match_parent” <xml>
</xml>
android:layout_centerHorizontal=”true” activity_
android:singleLine=”false” main.xml
android:textSize=”50dp”/>
</RelativeLayout>
Do wyświetlania pokonanego
dystansu użyjemy widoku
tekstowego.
@Override
protected void onStart() { To jest intencja skierowania
do usługi OdometerService.
super.onStart();
Intent intent = new Intent(this, OdometerService.class);
bindService(intent, connection, Context.BIND_AUTO_CREATE);
} W tym wywołaniu używamy inte
ncji i obiektu
połączen ia, by powiązać aktywność z usłu
gą.
Stała Context.BIND_AUTO_CREATE informuje Androida, że jeśli usługa nie istnieje,
to powinien ją utworzyć.
W tym celu napiszemy nową metodę o nazwie watchMileage(). Będzie ona Aktywność użyje metody getDistance()
działać w dokładnie taki sam sposób jak metoda runTimer() z rozdziału 4. usługi OdometerService, by określić
zawartość komponentu TextView.
Jedyna różnica między nimi będzie polegała na tym, że w tym projekcie
metoda będzie wyświetlać przebyty dystans, a nie zmierzony czas.
package com.hfad.drogomierz;
import android.app.Activity;
Drogomierz
import android.content.ComponentName;
import android.content.Context;
app/src/main
import android.content.Intent;
import android.content.ServiceConnection;
java
import android.os.Bundle;
import android.os.Handler; com.hfad.drogomierz
import android.os.IBinder;
import android.widget.TextView;
Main
Activity.java
public class MainActivity extends Activity {
nia
Ta zmienna służy do przechowywa ice.
private OdometerService odometer; referencji do usługi OdometerServ
private boolean bound = false; Ta zmienna przechowuje informację, czy
aktywność została powiązana z usługą, czy nie.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
watchMileage(); Podczas tworzenia aktywności wywołujemy metodę watchMilage().
}
if (bound) {
java
unbindService(connection);
bound = false; com.hfad.drogomierz
}
} Ta metoda aktualizuje wyświetlany dystans, Main
jaki pokonało urządzenie. Activity.java
MainActivity ServiceConnection
Intencja Intencja
onBind()
Binder Binder
onBind()
getDistance()
0
MainActivity OdometerService
<xml>
watchMileage() </xml>
Layout
MainActivity
MainActivity OdometerService
Początkowo prezentowany
przez aplikację przebyty
dystans wynosi 0,00
kilometra.
Rozdział 13.
Opanowałeś już rozdział 13. i dodałeś
do swojego przybornika z narzędziami j
Pełny kod przykładowe
umiejętność tworzenia i stosowania usług. aplikacji pre zen tow ane j
roz dzi ale mo żes z
w tym
pob rać z ser we ra FT P
wydawnictwa Helion:
klady/
ftp://ftp.helion.pl/przy
CELNE SPOSTRZEŻENIA andrrg.zi p
Usługa jest komponentem, który może Usługę powiązaną zazwyczaj tworzy się,
wykonywać zadania w tle. Usługi nie mają rozszerzając klasę Service. Należy przy tym
własnego interfejsu użytkownika. zdefiniować własny obiekt Binder i przesłonić
jego metodę onBind(). Ta metoda jest
Usługa uruchomiona może działać w tle
wywoływana, gdy komponent chce utworzyć
dowolnie długo, nawet po zakończeniu
powiązanie z usługą.
działania aktywności, która ją uruchomiła.
Po zakończeniu wykonywanych operacji usługa W momencie tworzenia usługi wywoływana jest
jest zatrzymywana. metoda onCreate() klasy Service. Można jej
używać do inicjalizacji usługi.
Usługi należy zadeklarować w pliku manifestu
AndroidManifest.xml, używając elementu W momencie usuwania usługi wywoływana jest
<service>. metoda onDestroy() klasy Service.
Aby utworzyć prostą usługę uruchomioną, Informacje o aktualnym położeniu urządzenia
należy napisać klasę dziedziczącą po można uzyskać przy użyciu systemowej
IntentService i zaimplementować jej metodę usługi lokalizacyjnej. W tym celu najpierw
onHandleIntent(). Klasa IntentService należy utworzyć obiekt LocationListener
została zaprojektowana do obsługi intencji. i zarejestrować go w usłudze lokalizacyjnej.
Można przy tym podać kryteria określające,
Do uruchamiania usług służy metoda
jak często obiekt ten będzie informowany
startService().
o zmianach położenia. W razie używania
Jeśli przesłonimy metodę onStartCommand() odbiornika GPS konieczne jest dodanie
klasy IntentService, to wewnątrz niej musimy odpowiednich uprawnień do pliku manifestu
wywołać metodę klasy bazowej. AndroidManifest.xml.
Do tworzenia powiadomień służy budowniczy Aby powiązać aktywność z usługą, należy
powiadomień. Aby powiadomienie spowodowało utworzyć obiekt ServiceConnection
uruchomienie aktywności, należy przygotować i przesłonić jego metodę
intencję oczekującą. Następnie trzeba wyświetlić onServiceConnected(), tak by zwracała
powiadomienie, używając odpowiedniej usługi referencję do usługi.
systemowej. Aby utworzyć powiązanie, należy wywołać
Usługa powiązana jest skojarzona z innym metodę bindService(). Do przerywania
komponentem, takim jak aktywność. Aktywność powiązania służy metoda unbindService().
może prowadzić interakcję z taką usługą
i pobierać z niej wyniki.
z takiego:
Dla maniaków
To jest normalny
widok ListView.
Material Design używa wielu efektów 3D.
Ale czy to nie spowalnia działania urządzenia?
W przypadku większości urządzeń odpowiedź
na to pytanie brzmi: nie. O ile to tylko możliwe,
na taki: widoki Material Design będą się starały
korzystać z mocy obliczeniowej graficznych
komponentów sprzętowych urządzenia
i generować cienie mniej więcej w taki sam
sposób, jak robią to gry. Oznacza to, że
To jest widok
wygenerowane cienie nie tylko będą piękne
RecyclerView wyglądać, lecz również ich wyświetlenie nie
zawierający dwa zajmie więcej czasu. W przypadku starszych
widoki CardView. urządzeń widoki Material Design wciąż będą
Z kolei każdy
z widoków umieszczały pod każdym z prezentowanych
CardView widoków specjalne obrazki cieni. To faktycznie
prezentuje będzie wymagało nieco dodatkowej mocy
nazwę pizzy obliczeniowej i zajmowało trochę więcej
i jej zdjęcie.
pamięci. A zatem, jeśli chcemy, by tworzona
aplikacja działała nawet na naprawdę starych
urządzeniach, to przed udostępnieniem
najlepiej będzie sprawdzić jej wydajność
na rzeczywistym, fizycznym urządzeniu.
5 Gdy użytkownik kliknie opcję Złóż zamówienie dostępną na pasku akcji aktywności
MainActivity lub PizzaDetailActivity, zostanie uruchomiona aktywność OrderDetail.
<Layout>
1 Pasta
Fragment.java
MainActivity.java
Urządzenie <Layout>
Stores </Layout>
2
Fragment.java card_
5 CaptionedImages captioned_
3 Adapter.java
OrderActivity.java image.xml
<Layout>
</Layout> PizzaMaterial
Fragment.java
activity_order.xml 4
Pizza.java
<Layout> PizzaDetail Te pliki musimy
dodać do
</Layout> Activity.java istniejącego
activity_pizza_ projektu.
detail.xml
600 Rozdział 14.
Material Design
Zdjęcia pizz umieszczamy w katalogu drawable-nodpi, gdyż chcemy, by urządzenie W tym rozdziale
używało ich niezależnie od gęstości ekranu. Gdybyś chciał, to oczywiście mógłbyś zmodyfikujemy aplikację
przygotować różne wersje tych obrazków przeznaczone dla ekranów o różnych gęstościach
dla restauracji Włoskie
i umieścić je w odpowiednich katalogach drawable*.
Co Nieco, więc otwórz
w Android Studio już
Dodanie klasy Pizza istniejący projekt.
Teraz dodamy do projektu klasę Pizza, z której widok RecyclerView będzie pobierał
dane pizz. Klasa ta definiuje tablicę zawierającą informacje o dwóch pizzach, przy
czym dla każdej z nich została podana nazwa i identyfikator zasobu. A zatem dodaj do
W prawdziwej aplikacji
projektu nową klasę należącą do pakietu com.hfad.wloskieconieco, nadaj jej nazwę użylibyśmy do tego baz zapewne
Pizza i zapisz w katalogu app/src/main/java. Oto kod tej klasy: Tutaj jednak, dla upr y danych.
osz
przykładu, używamy kla czenia
package com.hfad.wloskieconieco; sy Javy.
...
dependencies {
Dodanie bibliotek wsparcia
compile fileTree(include: [‚*.jar’], dir: ‚libs’) w oknie pokazanym na powyższym
rysunku powoduje dopisanie ich
compile ‚com.android.support:appcompat-v7:23.1.0’ do pliku build.gradle.
compile ‚com.android.support:recyclerview-v7:23.1.0’
compile ‚com.android.support:cardview-v7:23.1.0’ WloskieCoNieco
}
app
A ponieważ w naszej aplikacji chcemy także zastosować widok RecyclerView, nowy android.support.v7.widget.
widok CardView umieścimy w osobnym pliku. Dodaj zatem do projektu nowy pliku CardView
układu o nazwie card_captioned_image.xml i zapisz go w katalogu app/src/main/res/layout.
...
Postać widoku CardView zdefiniujemy w następujący sposób:
Ten element dodaje widok CardView
CardView. jest klasą
<android.support.v7.widget.CardView pochodną klasy
FrameLayout.
xmlns:android=”http://schemas.android.com/apk/res/android”
xmlns:card_view=”http://schemas.android.com/apk/res-auto” WloskieCoNieco
android:id=”@+id/card_view”
android:layout_width=”match_parent” app/src/main
android:layout_height=”200dp” Ten atrybut
android:layout_margin=”5dp” sprawia, że res
widok będzie
card_view:cardCornerRadius=”4dp”> mieć zaokrąglone
layout
... wierzchołki.
<xml>
</android.support.v7.widget.CardView> </xml>
card_captioned_
image.xml
Klasa CardView pochodzi z biblioteki wsparcia v7 CardView, dlatego musimy podać jej
pełną nazwę: android.support.v7.widget.CardView.
Aby wyświetlane karty mogły mieć zaokrąglone wierzchołki, musimy dodać przestrzeń nazw:
xmlns:card_view=”http://schemas.android.com/apk/res-auto”
card_view:cardCornerRadius=”4dp”
card_view:cardCornerRadius=”4dp”> card_captioned_
image.xml
<LinearLayout
android:layout_width=”match_parent”
android:layout_height=”fill_parent”
android:orientation=”vertical”> Szerokość zdjęcia będzie ograniczona
przez jego element nadrzędny.
Zastosowaliśmy także atrybut scaleType
<ImageView android:id=”@+id/info_image” o wartości centerCrop, aby zdjęcie
android:layout_height=”0dp” zostało przeskalowane proporcjonalnie.
android:layout_width=”match_parent”
android:layout_weight=”1.0”
android:scaleType=”centerCrop”/>
<TextView
android:id=”@+id/info_text”
android:layout_marginLeft=”5dp”
android:layout_marginBottom=”5dp”
android:layout_height=”wrap_content”
android:layout_width=”match_parent”/>
</LinearLayout>
</android.support.v7.widget.CardView>
Kolejną rzeczą, którą musimy się zająć, jest utworzenie widoku RecyclerView,
w którym będą wyświetlane powyższe widoki CardView.
Adapter używa
Widok Adapter Dane określonych przez
Widok RecyclerView
używa adaptera. Recycler nas danych.
W naszym
View przypadku będą
one pochodziły
z klasy Pizza.
Adapter pełni dwa główne zadania: tworzy każdy z widoków prezentowanych w widżecie
i konfiguruje widok, by dopasować go do danych.
W naszym przypadku widok RecyclerView musi wyświetlić listę kart, z których każda zawiera
obrazek i widok tekstowy. Oznacza to, że adapter musi tworzyć widoki dla tych elementów
i zastępować ich zawartość, kiedy dany element nie będzie już widoczny na ekranie.
2 Utworzyć widoki.
Adapter musi tworzyć wszystkie widoki, które będą wyświetlane na ekranie.
package com.hfad.wloskieconieco;
oteki wsparcia.
Klasa RecyclerView należy do bibli
import android.support.v7.widget.RecyclerView;
import android.view.ViewGroup;
na potrzeby adaptera
Obiekt ViewHolder udostępnia referencję do widoku lub widoków
każdego elementu danych prezentowanego w widżecie RecyclerView.
To właśnie w nim są przechowywane widoki, które chcemy wyświetlać.
W przypadku naszego widoku RecyclerView poszczególne elementy Każdy obiekt ViewHolder zawiera obiekt
danych są widokami CardView, dlatego to właśnie tego typu dane ma CardView.
przechowywać nasz obiekt ViewHolder. Oto jego kod:
package com.hfad.wloskieconieco;
...
import android.support.v7.widget.CardView;
super(v);
app/src/main
cardView = v; Nasz widok RecyclerView ma wyświetlać
widoki CardView, dlatego określamy, że obiekt
} ViewHolder używany w adapterze będzie
java
} zawierał obiekty CardView. Gdybyśmy w widoku
RecyclerView chcieli wyświetlać dane innego typu,
... to musielibyśmy go tutaj zdefiniować. com.hfad.wloskieconieco
}
CaptionedImages
Tworząc własny obiekt ViewHolder, musimy wywołać konstruktor jego Adapter.java
klasy bazowej:
super(v);
...
import android.view.LayoutInflater;
...
WloskieCoNieco
@Override
public CaptionedImagesAdapter.ViewHolder onCreateViewHolder( app/src/main
ViewGroup parent, int viewType){
CardView cv = (CardView) LayoutInflater.from(parent.getContext()) java
.inflate(R.layout.card_captioned_image, parent, false);
com.hfad.wloskieconieco
return new ViewHolder(cv);
any
} Określamy, jaki układ ma być używ
w widokach przechowywanych
} w obiektach ViewHolder. CaptionedImages
Adapter.java
...
@Override CaptionedImages
Adapter.java
public int getItemCount(){
return captions.length; Wielkość tablicy podpisów odpowiada liczbie
} elementów danych prezentowanych w widoku
RecyclerView.
Oto kod, który będzie zapisywał dane pizzy w widokach obiektu CardView:
java
...
com.hfad.wloskieconieco
import android.widget.ImageView; Używamy tych dodatkowych klas.
import android.widget.TextView; CaptionedImages
import android.graphics.drawable.Drawable; Adapter.java
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.support.v7.widget.CardView; To są wszystkie używane klasy.
import android.widget.ImageView;
import android.widget.TextView;
import android.graphics.drawable.Drawable;
@Override
public CaptionedImagesAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
CardView cv = (CardView) LayoutInflater.from(parent.getContext())
.inflate(R.layout.card_captioned_image, parent, false);
return new ViewHolder(cv);
} W widokach CardView użyjemy
przygotowanego wcześniej układu.
@Override
public void onBindViewHolder(ViewHolder holder, final int position) {
CardView cardView = holder.cardView;
ImageView imageView = (ImageView)cardView.findViewById(R.id.info_image);
Drawable drawable = cardView.getResources().getDrawable(imageIds[position]);
imageView.setImageDrawable(drawable);
imageView.setContentDescription(captions[position]);
TextView textView = (TextView)cardView.findViewById(R.id.info_text);
textView.setText(captions[position]);
} Zapisujemy dane w widokach
ImageView i TextView
@Override wyświetlanych w widoku CardView.
public int getItemCount() {
return captions.length; To jest liczba elementów danych.
}
}
jesteś tutaj 611
Utworzenie widoku RecyclerView
Zastosowanie adaptera
W pliku PizzaMaterialFragment.java zastosujemy adapter w zdefiniowanym
powyżej widoku RecyclerView. Musimy przy tym poinstruować adapter, jakich
danych ma używać — zastosujemy do tego konstruktor adaptera. Następnie
wywołamy metodę setAdapter() widoku RecyclerView, aby przypisać do
niego adapter.
package com.hfad.wloskieconieco;
WloskieCoNieco
import android.app.Fragment;
import android.os.Bundle;
app/src/main
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater; java
import android.view.View;
Używamy tych klas.
import android.view.ViewGroup; com.hfad.wloskieconieco
package com.hfad.wloskieconieco;
Używamy tej klasy, więc
musimy ją zaimportować.
import android.app.Fragment;
import android.os.Bundle; WloskieCoNieco
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView; app/src/main
import android.view.LayoutInflater;
import android.view.View; java
import android.view.ViewGroup;
com.hfad.wloskieconieco
public class PizzaMaterialFragment extends Fragment {
PizzaMaterial
@Override Fragment.java
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
RecyclerView pizzaRecycler = (RecyclerView)inflater.inflate(
R.layout.fragment_pizza_material, container, false);
CaptionedImagesAdapter adapter =
new CaptionedImagesAdapter(pizzaNames, pizzaImages);
pizzaRecycler.setAdapter(adapter);
LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
pizzaRecycler.setLayoutManager(layoutManager);
return pizzaRecycler; Widoki CardView chcemy wyświet
lać
} w formie liniowej listy, dlatego
używamy menedżera układu
} LinearLayoutManager.
package com.hfad.wloskieconieco;
...
WloskieCoNieco
public class MainActivity extends Activity {
...
app/src/main
@Override
protected void onCreate(Bundle savedInstanceState) {
java
...
getFragmentManager().addOnBackStackChangedListener(
com.hfad.wloskieconieco
new FragmentManager.OnBackStackChangedListener() {
public void onBackStackChanged() {
... MainActivity.java
if (fragment instanceof PizzaFragment) {
if (fragment instanceof PizzaMaterialFragment) {
currentPosition = 1;
}
...
} tu
Używamy naszego nowego fragmen
fragmentu
PizzaMaterialFragment zamiast y
PizzaFragment. To ozna cza, że kied
private void selectItem(int position) { szufladzie
użytkownik kliknie opcję Pizze w lony nasz
... nawigacyjnej, to zostanie wyświet iew.
switch(position) { lśniący nowością widżet RecyclerV
case 1:
fragment = new PizzaFragment();
fragment = new PizzaMaterialFragment();
break;
...
}
}
Zanim uruchomimy aplikację, przyjrzyjmy się jeszcze raz, jak będą działać
wszystkie zmiany, które do tej pory wprowadziliśmy w jej kodzie.
onCreateView()
MainActivity PizzaMaterialFragment
RecyclerView
PizzaMaterialFragment
LinearLayoutManager
RecyclerView LinearLayoutManager
Dane pizzy
PizzaMaterialFragment CaptionedImagesAdapter
ViewHolder CardView
CaptionedImagesAdapter
ViewHolder CardView
“Diavolo”
Diavolo data
TextView
Dane pizzy R.drawable.Diavolo
diavolo
ViewHolder CardView ImageView
Dane pizzy funghi
“Funghi”
Funghi data
CaptionedImagesAdapter
TextView
R.drawable.Funghi
ViewHolder CardView
ImageView
Po kliknięciu opcji
Pizze zostanie
wyświetlony fragment
PizzaMaterialFragment.
Zawiera on liniową
listę widoków CardView,
z których każdy
przedstawia dane jednej
pizzy.
Magnesiki RecyclerView
Użyj magnesików umieszczonych na tej i następnej stronie,
by przygotować zupełnie nowy widok RecyclerView, który będzie służył do
prezentowania dań z makaronu. Powinien on zawierać liniową listę widoków
CardView, z których każdy będzie prezentował nazwę i zdjęcie dania.
=
public int ........................ {
return imageResourceId; "vertical"
getImageResourceId()
}
}
<........................................
xmlns:android=”http://schemas.android.com/apk/res/android” app/src/main
android:id=”@+id/pasta_recycler”
res
.............................................
android:layout_width=”match_parent”
layout
android:layout_height=”match_parent”/> <xml>
</xml>
fragment_pasta_
material.xml
622 Rozdział 14.
Material Design
To jest kod z pliku PastaMaterialFragment.java
...
public class PastaMaterialFragment extends Fragment {
WloskieCoNieco
@Override app/src/main
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { java
RecyclerView pastaRecycler = (RecyclerView)inflater.inflate(
com.hfad.wloskieconieco
.......................... adapter =
CaptionedImagesAdapter
ArrayAdapter CaptionedImagesAdapter
LinearLayout
LinearLayoutManager ta_material
R.layout.fragment_pas
LinearLayout ArrayAdapter
pastaImages
LinearLayoutManager
package com.hfad.wloskieconieco;
WloskieCoNieco
public class Pasta {
private String name; app/src/main
private int imageResourceId; To jest tablica obiektów Pasta.
java
Pasta
public static final .... . [] pastas = {
com.hfad.wloskieconieco
new Pasta(”Spaghetti bolognese”, R.drawable.spag_bol),
new Pasta(”Lasagne”, R.drawable.lasagne)
}; Pasta.java
<. android.support.v7.widget.RecyclerView
.........
xmlns:android=”http://schemas.android.com/apk/res/android” app/src/main
android:id=”@+id/pasta_recycler”
res
android:scrollbars = "vertical" Ten atrybut dodaje pionowy
................... .................. pasek przewijania.
android:layout_width=”match_parent” layout
<xml>
android:layout_height=”match_parent”/> </xml>
fragment_pasta_
material.xml
624 Rozdział 14.
Material Design
...
public class PastaMaterialFragment extends Fragment {
WloskieCoNieco
@Override app/src/main
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { java
RecyclerView pastaRecycler = (RecyclerView)inflater.inflate(
com.hfad.wloskieconieco
Zastosuj ten
układ. R.layout.fragment_pasta_material ... container, false);
Pasta
String[] pastaNames = new String[Pasta.pastas.length]; Material
Fragment.java
for (int i = 0; i < pastaNames.length; i++) {
pastaNames[i] = Pasta.pastas[i].getName();
}
CaptionedImagesAdapter
new ...... (pastaNames, pastaImages );
pastaRecycler.setAdapter(adapter);
ager LinearLayoutManager
LinearLayoutMan...... layoutManager = new ............ ..(getActivity());
pastaRecycler.setLayoutManager(layoutManager);
return pastaRecycler;
} Do wyświetlenia widoków CardView
w formie liniowej listy użyjemy ager.
} menedżera układu LinearLayoutMan
Te magnesiki nie
były potrzebne.
LinearLayout
Dokąd dotarliśmy?
Poniżej zamieściliśmy krótkie podsumowanie tego, co już udało się nam zrobić w naszej aplikacji:
<Layout>
</Layout>
<Layout>
Top
Fragment.java fragment_
</Layout>
top.xml
activity_main.xml
1 Pasta
Fragment.java
Utworzyliśmy już
MainActivity.java wszystkie te pliki.
<Layout>
Urządzenie
Stores </Layout>
2
Fragment.java card_
CaptionedImages captioned_
3 Adapter.java image.xml
OrderActivity.java
<Layout>
</Layout> PizzaMaterial
Fragment.java
activity_order.xml
Pizza.java
<Layout> PizzaDetail
Tego pliku jeszcze nie przygotowaliśmy.
</Layout> Activity.java Zajmiemy się tym w następnej kolejności
i zadbamy, by ta aktywność była
activity_pizza_ uruchamiana z poziomu fragmentu
detail.xml PizzaMaterialFragment.
Kolejną rzeczą, którą musimy się zająć, jest zapewnienie, by widok RecyclerView reagował na
kliknięcia i w odpowiedzi na kliknięcie pizzy uruchamiał aktywność PizzaDetailActivity.
Aktywność ta będzie wyświetlać szczegółowe informacje o wybranej pizzy.
Utwórz więc nową, pustą aktywność. Jej klasie nadaj nazwę PizzaDetailActivity, plik
układu nazwij activity_pizza_detail, a jako tytuł podaj Informacje szczegółowe. Następnie
zmodyfikuj zawartość pliku układu activity_pizza_detail.xml, zapisując w nim poniższy kod
— umieszcza on w układzie używanym przez aktywność dwa widoki: TextView i ImageView.
<LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
xmlns:tools=”http://schemas.android.com/tools”
android:layout_width=”match_parent”
WloskieCoNieco
android:layout_height=”match_parent”
android:orientation=”vertical”
app/src/main
tools:context=”com.hfad.bitsandpizzas.PizzaDetailActivity”>
res
Układ aktywności
<TextView PizzaDetailActivity zawiera
android:id=”@+id/pizza_text” widoki TextView i ImageView. layout
W pierwszym z nich wyświetlimy
android:layout_width=”wrap_content” nazwę pizzy, a w drugim jej <xml>
</xml>
zdję cie.
android:layout_height=”wrap_content” activity_pizza_
android:textAppearance=”?android:attr/textAppearanceLarge” /> detail.xml
<ImageView
android:id=”@+id/pizza_image”
android:layout_height=”wrap_content”
android:layout_width=”match_parent”
android:adjustViewBounds=”true”/>
</LinearLayout>
Plik zasobu menu opisuje akcję Udostępnij, której możemy użyć do udostępnienia informacji
o pizzy. Dodamy to tej akcji intencję, która pozwoli udostępnić nazwę pizzy wybranej przez
użytkownika.
Plik zasobu menu zawiera także akcję Złóż zamówienie. Kiedy użytkownik kliknie tę opcję,
zostanie uruchomiona aktywność OrderActivity.
Włączymy także w aktywności PizzaDetailActivity przycisk W górę, tak aby klikając go,
użytkownik mógł wrócić do aktywności MainActivity.
<activity
android:name=”.PizzaDetailActivity” WloskieCoNieco
android:label=”@string/title_activity_pizza_detail”
android:parentActivityName=”.MainActivity”> app/src/main
<xml>
</activity> </xml>
MainActivity jest aktywnością
nadrzędną aktywności AndroidManifest.xml
PizzaDetailActivity.
package com.hfad.wloskieconieco;
WloskieCoNieco
import android.app.Activity;
import android.content.Intent;
app/src/main
import android.os.Bundle;
Używamy tych klas.
import android.view.Menu; java
import android.view.MenuItem;
import android.widget.ImageView; com.hfad.wloskieconieco
import android.widget.ShareActionProvider;
import android.widget.TextView; PizzaDetailActivity.java
android.view.View
...
android.widget. android.support.v7.widget.
AdapterView RecyclerView
... ...
Klasa ListView
dziedziczy także
po klasach
AdapterView Klasa RecyclerView nie
i AbsListView. android.widget. dziedziczy metod po żadnej
AbsListView innej klasie bazowej.
...
android.widget.ListView
...
Na przykład załóżmy, że chcemy, by za każdym razem, gdy użytkownik kliknie kartę pizzy, była
uruchamiana aktywność prezentująca szczegółowe informacje o tej pizzy. A to oznacza, że
moglibyśmy umieścić w klasie adaptera jakiś kod, który by uruchamiał odpowiednią aktywność:
class CaptionedImagesAdapter extends RecyclerView.Adapter<CaptionedImagesAdapter.ViewHolder> {
...
@Override
public void onBindViewHolder(ViewHolder holder, final int position) {
CardView cardView = holder.cardView;
ImageView imageView = (ImageView)cardView.findViewById(R.id.info_image);
Drawable drawable = cardView.getResources().getDrawable(imageIds[position]);
imageView.setImageDrawable(drawable);
TextView textView = (TextView)cardView.findViewById(R.id.info_text);
textView.setText(captions[position]);
cardView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(container.getContext(),
PizzaDetailActivity.class);
intent.putExtra(PizzaDetailActivity.EXTRA_PIZZANO, position);
container.getContext().startActivity(intent);
} magesAdapter
Dodanie tego kodu do adaptera CaptionedI
}); zostanie
sprawi, że po kliknięciu widoku CardView
} uruchomiona aktywność PizzaDetailActivity.
}
Niemniej jednak sama możliwość napisania takiego kodu nie oznacza
jeszcze, że powinniśmy go stosować.
WYSIL
SZARE KOMÓRKI
Moglibyśmy reagować na kliknięcia, dodając odpowiedni kod do klasy adaptera.
Ale czy jest jakiś powód, dla którego byłoby to niepożądane rozwiązanie?
Zastosujemy przy tym podobny wzorzec, którego użyliśmy już wcześniej w celu
oddzielenia fragmentu od aktywności — w klasie CaptionedImagesAdapter
utworzymy interfejs Listener, taki jak ten poniżej:
package com.hfad.wloskieconieco;
WloskieCoNieco
import android.graphics.drawable.Drawable;
import android.support.v7.widget.RecyclerView; app/src/main
import android.view.LayoutInflater;
import android.view.View;
Używamy tej dodatkowej klasy, java
import android.view.ViewGroup; więc musimy ją zaimportować.
import android.support.v7.widget.CardView; com.hfad.wloskieconieco
import android.widget.ImageView;
import android.widget.TextView; CaptionedImages
Adapter.java
To jest interfejs.
public static interface Listener {
public void onClick(int position);
}
@Override
public CaptionedImagesAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
CardView cv = (CardView) LayoutInflater.from(parent.getContext())
.inflate(R.layout.card_captioned_image, parent, false);
return new ViewHolder(cv);
}
@Override
public void onBindViewHolder(ViewHolder holder, final int position) {
CardView cardView = holder.cardView;
ImageView imageView = (ImageView)cardView.findViewById(R.id.info_image);
Drawable drawable = cardView.getResources().getDrawable(imageIds[position]);
imageView.setImageDrawable(drawable);
imageView.setContentDescription(captions[position]);
TextView textView = (TextView)cardView.findViewById(R.id.info_text);
textView.setText(captions[position]);
cardView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (listener != null) { WloskieCoNieco
listener.onClick(position);
} app/src/main
} Kliknięcie widoku CardView
}); spowoduje wywołanie metody java
onClick() obiektu implementująceg
} interfejs Listener. o
com.hfad.wloskieconieco
@Override
public int getItemCount() { CaptionedImages
Adapter.java
return captions.length;
}
}
package com.hfad.wloskieconieco;
com.hfad.wloskieconieco
public class PizzaMaterialFragment extends Fragment {
@Override PizzaMaterial
Fragment.java
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
RecyclerView pizzaRecycler = (RecyclerView)inflater.inflate(
R.layout.fragment_pizza_material, container, false);
adapter.setListener(new CaptionedImagesAdapter.Listener() {
public void onClick(int position) {
Intent intent = new Intent(getActivity(), PizzaDetailActivity.class);
intent.putExtra(PizzaDetailActivity.EXTRA_PIZZANO, position);
getActivity().startActivity(intent);
}
WloskieCoNieco
}); metody
Ten kod stanowi implementację onanie
onCl ick() inte rfejs u List ener . Wyk
return pizzaRecycler;
tej metody powoduje uruchomienie app/src/main
} aktywności PizzaDetailActivity a pizzy
} i przekazanie do niej identyfikator
wybranej przez użytkownika. java
PizzaMaterialFragment.java
<Layout>
Każdy z tych
fragmentów
może używać </Layout>
tego samego
adaptera PastaMaterialFragment.java CaptionedImages card_captioned_
i widoku image.xml
CardView. Adapter.java
StoresMaterialFragment.java
Kiedy klikniesz
pizzę, zostanie
uruchomiona aktywność
PizzaDetailActivity
ze szczegółowymi
informacjami
o wybranej pizzy.
Aplikacja Książki
Play wyświetla Aplikacja
ostatnio pobrane kalendarza
książki i dodaje wyświetla
do nich sugestie nadchodzące
odnośnie innych wydarzenia,
książek, które dzięki czemu
mogłyby Ci się bez trudu można
spodobać. je zobaczyć.
Zaostrz ołówek
Następnie dodaj do zaznaczonego poniżej obszaru pliku TopFragment.java kod,
dzięki któremu w widoku RecyclerView zostaną wyświetlone dwie pizze
w układzie siatki. Kiedy użytkownik kliknie którąś z wyświetlonych pizz, ma zostać
uruchomiona aktywność PizzaDetailActivity z informacjami o niej.
...
public class TopFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
RelativeLayout layout = (RelativeLayout)
inflater.inflate(R.layout.fragment_top, container, false);
Tutaj
zapisz
swój
kod.
CaptionedImagesAdapter adapter =
new CaptionedImagesAdapter(pizzaNames, pizzaImages);
pizzaRecycler.setAdapter(adapter);
adapter.setListener(new CaptionedImagesAdapter.Listener() {
public void onClick(int position) {
Intent intent = new Intent(getActivity(), PizzaDetailActivity.class);
intent.putExtra(PizzaDetailActivity.EXTRA_PIZZANO, position);
getActivity().startActivity(intent);
}
}); Większość kodu będzie taka sam
a
w pliku PizzaMaterialFragment.jav jak
a.
return layout;
}
}
xmlns:tools=”http://schemas.android.com/tools”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:paddingTop=”16dp”
android:paddingBottom=”16dp”
android:paddingRight=”16dp”
android:paddingLeft=”16dp”
tools:context=”.MainActivity”>
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
android:text=”@string/welcome_text”
android:scrollbars=”vertical”
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
android:layout_below=”@+id/welcome_text”
android:layout_marginTop=”10dp”/>
</RelativeLayout>
Zaostrz ołówek
Rozwiązanie Następnie dodaj do zaznaczonego poniżej obszaru pliku TopFragment.java kod,
dzięki któremu w widoku RecyclerView zostaną wyświetlone dwie pizze
w układzie siatki. Kiedy użytkownik kliknie którąś z wyświetlonych pizz, ma zostać
uruchomiona aktywność PizzaDetailActivity z informacjami o niej.
...
public class TopFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
RelativeLayout layout = (RelativeLayout)
inflater.inflate(R.layout.fragment_top, container, false);
CaptionedImagesAdapter adapter =
new CaptionedImagesAdapter(pizzaNames, pizzaImages);
pizzaRecycler.setAdapter(adapter);
adapter.setListener(new CaptionedImagesAdapter.Listener() {
public void onClick(int position) {
Intent intent = new Intent(getActivity(), PizzaDetailActivity.class);
intent.putExtra(PizzaDetailActivity.EXTRA_PIZZANO, position);
getActivity().startActivity(intent);
}
}); Większość kodu jest taka sama jak
return layout; w pliku PizzaMaterialFragment.java.
}
}
<string name=”welcome”>Oferujemy bogaty wybór świeżo wypiekanych pizz oraz dań z makaronu.
Przekonaj się, że warto spróbować naszych potraw!</string>
<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:paddingTop=”16dp”
android:paddingBottom=”16dp” WloskieCoNieco
android:paddingRight=”16dp”
app/src/main
android:paddingLeft=”16dp”
tools:context=”.MainActivity”>
res
<TextView layout
android:layout_width=”match_parent” <xml>
</xml>
android:layout_height=”wrap_content” fragment_top.xml
android:text=”@string/welcome_text”
android:id=”@+id/welcome_text” />
Do układu dodajemy widoki
<android.support.v7.widget.RecyclerView TextView i RecyclerView.
android:id=”@+id/pizza_recycler”
android:scrollbars=”vertical”
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
android:layout_below=”@+id/welcome_text”
android:layout_marginTop=”10dp”/>
</RelativeLayout>
WloskieCoNieco
import android.content.Intent;
import android.os.Bundle;
app/src/main
import android.app.Fragment;
import android.support.v7.widget.GridLayoutManager;
java
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
com.hfad.wloskieconieco
import android.view.View; Używamy tych wszystkich klas.
import android.view.ViewGroup;
import android.widget.RelativeLayout; TopFragment.java
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
RelativeLayout layout = (RelativeLayout)
inflater.inflate(R.layout.fragment_top, container, false);
RecyclerView pizzaRecycler = (RecyclerView)layout.findViewById(R.id.pizza_recycler);
String[] pizzaNames = new String[2];
for (int i = 0; i < 2; i++) {
pizzaNames[i] = Pizza.pizzas[i].getName();
}
int[] pizzaImages = new int[2]; Tworzymy tablice z nazwami i zdję
ciami pizz.
for (int i = 0; i < 2; i++) {
pizzaImages[i] = Pizza.pizzas[i].getImageResourceId(); Wyświetlamy pizze w układzie siatki.
}
GridLayoutManager layoutManager = new GridLayoutManager(getActivity(),2);
pizzaRecycler.setLayoutManager(layoutManager);
CaptionedImagesAdapter adapter = new CaptionedImagesAdapter(pizzaNames, pizzaImages);
pizzaRecycler.setAdapter(adapter);
Do wyświetlenia pizz
adapter.setListener(new CaptionedImagesAdapter.Listener() { używamy adaptera.
public void onClick(int position) {
Intent intent = new Intent(getActivity(), PizzaDetailActivity.class);
intent.putExtra(PizzaDetailActivity.EXTRA_PIZZANO, position);
getActivity().startActivity(intent);
}
uruchamiamy
}); Kiedy użytkownik kliknie pizzę, kazując do niej
akty wnoś ć Pizz aDet ailA ctivi ty, prze
return layout; indeks klikn ięte j pizz y.
}
}
jesteś tutaj 645
Kolejna jazda próbna
Rozdział 14.
Opanowałeś już rozdział 14. i dodałeś
do swojego przybornika z narzędziami j
Pełny kod przykładowe
umiejętność korzystania z Material Design. aplikacji pre zen tow ane j
roz dzi ale mo żes z
w tym
pob rać z ser we ra FT P
wydawnictwa Helion:
klady/
ftp://ftp.helion.pl/przy
CELNE SPOSTRZEŻENIA andrrg.zi p
Pożegnanie…
A więc to właśnie
to się dzieje pod
maską…
Co to jest ART?
Środowisko uruchomieniowe Androida, określane skrótowo jako ART, to system, który wykonuje
skompilowane kody na urządzeniu działającym po kontrolą systemu operacyjnego Android.
Po raz pierwszy zostało ono wprowadzone w wersji KitKat Androida, a wraz z udostępnieniem
wersji Lollipop stało się standardowym sposobem wykonywania kodu.
Kod źródłowy
napisany w Javie
jest kompilowany
do postaci plików
klasowych .class.
.java .class
ART działa zupełnie inaczej. Kiedy kompilujemy aplikację na Androida, wszystko rozpoczyna
się tak samo. Piszemy kod źródłowy, zapisując go w plikach .java, a te są kompilowane do
postaci plików klasowych (.class). Te pliki klasowe (.class, lub ich archiwum .jar) są następnie
konwertowane przez narzędzie o nazwie dx do postaci jednego pliku o nazwie classes.dex.
Plik classes.dex także zawiera kody bajtowe, różnią się one jednak od tych, które są zapisywane
w plikach .class. Kody bajtowe .dex są przeznaczone dla całkowicie innego procesora wirtualnego
o nazwie Dalvik. W rzeczywistości „dex” to skrót od słów Dalvik Executable1.
Procesor Dalvik nieco przypomina JVM. Zarówno JVM, jak i Dalvik są procesorami wirtualnymi
— to procesory teoretyczne. Jednak JVM firmy Oracle bazuje na stosie, a Dalvik na rejestrach.
Niektórzy wierzą, że kod przeznaczony na takie procesory bazujące na rejestrach możne
być mniejszy i działać szybciej. Dzięki konwertowaniu całego zestawu plików klasowych do
jednego pliku classes.dex program dx może sprawić, że skompilowana aplikacja będzie znacząco
mniejsza, gdyż jest w stanie usunąć wszystkie powtarzające się symbole, które normalnie mogłyby
występować w wielu różnych plikach .class.
1
Plik wykonywalny Dalvik — przyp. tłum.
650 Dodatek A
ART
Wydajność i wielkość
Urządzenia działające pod kontrolą systemu Android zazwyczaj mają
znacznie mniejszą moc obliczeniową i dysponują mniejszą pamięcią
niż komputery, które tradycyjnie były wykorzystywane do wykonywania
programów pisanych w języku Java. ART używa plików .dex, które
przeważnie są mniejsze od odpowiadających im plików klasowych
Javy .class. Wirtualna maszyna Javy (JVM) firmy Oracle potrafi
kompilować fragmenty interpretowanego kodu za pomocą techniki
kompilacji JIT (ang. Just-In-Time), co oznacza, że w trakcie
wykonywania aplikacji JVM konwertuje kody bajtowe Javy na kod
maszynowy. Takie rozwiązanie sprawdza się w przypadku aplikacji,
które mają działać przez długi okres czasu, takich jak aplikacje
serwerowe, jednak aplikacje na Androida mogą być często uruchamiane
i zatrzymywane. Dzięki wstępnemu skompilowaniu wszystkich kodów
bajtowych Dalvik do postaci natywnej biblioteki ART sprawia, że kod
aplikacji będzie musiał być kompilowany tylko jeden raz.
Bezpieczeństwo
Na urządzeniach z Androidem można wykonywać kod pochodzący
od wielu różnych programistów, dlatego bardzo duże znaczenie ma
całkowite odizolowanie od siebie poszczególnych aplikacji. Bez niego
jedna aplikacja mogłaby naruszyć bezpieczeństwo dowolnej innej
aplikacji zainstalowanej na tym samym urządzeniu. Aby zapewnić
odpowiednią izolację aplikacji, Android wykonuje każdą z nich
w ramach odrębnego procesu, używając do tego automatycznie
generowanych kont użytkowników. Dzięki temu aplikacje mogą być
izolowane za pomocą mechanizmów zabezpieczeń systemu operacyjnego
Linux. W przypadku użycia JVM firmy Oracle każdy proces musiałby
być wykonywany za pomocą odrębnego procesu Javy, co zazwyczaj
powodowałoby zwiększenie ilości pamięci potrzebnej do wykonywania
kilku aplikacji.
652 Dodatek A
Dodatek B. ADB
Android Debug Bridge
Czy dziewczynie,
która ma już wszystko,
można sprawić lepszy prezent
niż narzędzie obsługiwane Najdroższy,
z poziomu wiersza poleceń? jakiż to uroczy
wyraz troski!
W oknie terminala lub wiersza poleceń można tego programu używać w następujący sposób:
Urządzenie
Kiedy adbd odbierze polecenie, przekazuje je do odrębnego procesu adbd,
działającego na odpowiednim urządzeniu z Androidem. Dzięki temu możliwe
będzie wprowadzanie zmian na urządzeniu lub zwracanie żądanych informacji.
654
ADB
Czasami, gdy serwer adb nie będzie działał, wykonanie polecenia adb spowoduje jego
uruchomienie:
Uruchamianie powłoki
W przeważającej większości przypadków nie korzystamy jednak z ADB w sposób bezpośredni —
pozwalamy raczej, by przeważającą większość operacji wykonywało za nas jakieś zintegrowane
środowisko programistyczne (IDE), takie jak Android Studio. Zdarzają się jednak takie sytuacje,
w których przyda się nam możliwość przejścia do wiersza poleceń i bezpośredniego operowania
na podłączonym urządzeniu.
656
ADB
Treści z dziennika logcat będą wyświetlane tak długo, aż tego nie przerwiemy.
Zastosowanie polecenia adb logcat może być przydatne, jeśli na przykład
chcemy zapisywać komunikaty z dziennika w pliku. Polecenie to jest używane
przez Android Studio do pobierania komunikatów, które następnie są
wyświetlane na karcie logcat.
658
Dodatek C. Emulator
Emulator Androida
Czy to
może działać
b ?!
660 Dodatek C
Emulator
To sprawi, że zostanie zapisany stan pamięci działającego AVD. Emulator będzie mógł
odtworzyć ten stan pamięci bez konieczności uruchamiania systemu na wirtualnym urządzeniu.
W tym celu konieczne będzie zainstalowanie narzędzia firmy Intel o nazwie Hardware
Accelerated Execution Manager (w skrócie HAXM). Gdy pisaliśmy tę książkę, można go było
pobrać ze strony: A jeśli go przeniesiono
w inne miejsce, to szybkie
https://software.intel.com/en-us/android/articles/intel-hardware-accelerated-execution-manager wyszukiwanie powinno
pozwolić Ci go odnaleźć.
HAXM jest hipernadzorcą. Oznacza to, że przełącza procesor komputera w specjalny tryb
działania, pozwalający na bezpośrednie wykonywanie instrukcji wirtualnego komputera.
HAXM działa wyłącznie na procesorach Intela, które obsługują technologię Intel
Virtualization Technology. Jeśli Twój komputer jest z nią zgodny, to zastosowanie HAXM
sprawi, że AVD będą działały znacznie szybciej.
1. Rozpowszechnianie aplikacji
Kiedy już napiszemy swoją aplikację, zapewne zechcemy udostępnić ją
innym użytkownikom. Zazwyczaj będziemy chcieli to zrobić, używając
jednego z internetowych sklepów, takich jak Google Play.
http://developer.android.com/tools/publishing/preparing.html
Udostępnianie aplikacji
Ten etap obejmuje reklamowanie aplikacji, sprzedawanie jej
i rozpowszechnianie.
http://developer.android.com/distribute/googleplay/start.html
http://developer.android.com/distribute/index.html
664 Dodatek D
Pozostałości
2. Dostawcy treści
Z tej książki dowiedziałeś się, jak używać intencji do uruchamiania aktywności
należących do innych aplikacji. Na przykład możemy uruchomić aplikację Wiadomości,
aby wysłać przekazany fragment tekstu. Ale co zrobić, gdybyśmy chcieli używać we
własnej aplikacji danych pochodzących z innej aplikacji? Co zrobić, gdybyśmy chcieli
używać we własnej aplikacji danych z aplikacji Kontakty albo dodać jakieś nowe
wydarzenie do Kalendarza?
Dostępu do danych innej aplikacji nie można uzyskać, przeglądając jej bazę danych.
Rozwiązaniem jest skorzystanie z dostawców treści (ang. content providers). Dostawca
treści to interfejs pozwalający aplikacji na udostępnianie danych w kontrolowany
sposób. Umożliwia on wykonywanie zapytań w celu odczytu danych, dodawania
nowych rekordów oraz aktualizacji lub usuwania rekordów już istniejących.
http://developer.android.com/guide/topics/providers/content-providers.html
http://developer.android.com/guide/topics/providers/contacts-provider.html
http://developer.android.com/guide/topics/providers/calendar-provider.html
3. Klasa WebView
Jeśli chcemy zapewnić użytkownikom dostęp do treści pochodzących z internetu,
możemy to zrobić na dwa sposoby. Pierwszym jest napisanie aplikacji internetowej,
której użytkownicy będą mogli używać za pomocą przeglądarki WWW. Drugą
możliwością jest natomiast zastosowanie klasy WebView.
<WebView xmlns:android=”http://schemas.android.com/apk/res/android”
android:id=”@+id/webview”
android:layout_width=”match_parent”
android:layout_height=”match_parent” />
Tak utworzony widok informujemy o tym, którą stronę należy wyświetlić, używając
w kodzie aktywności metody loadUrl():
<manifest ...>
<uses-permission android:name=”android.permission.INTERNET” />
....
</manifest>
http://developer.android.com/guide/webapps/index.html
666 Dodatek D
Pozostałości
4. Animacje
Wraz z coraz większym wykorzystywaniem mocy graficznych komponentów sprzętowych
instalowanych na urządzeniach z Androidem w coraz większym stopniu wykorzystywane
są także animacje, które umożliwiają poprawę atrakcyjności tworzonych aplikacji.
Animowanie właściwości
Animacje właściwości bazują na fakcie, że bardzo wiele aspektów wyglądu wizualnych
komponentów aplikacji na Androida opisywanych jest przy użyciu właściwości liczbowych.
Jeśli zmienimy wartość takiej właściwości widoku jak wysokość lub szerokość, to zyskamy
możliwość animowania tego widoku. Na tym właśnie polega animowanie właściwości:
płynnym zmienianiu właściwości komponentu wizualnego wraz z upływem czasu.
Animowanie widoków
Wiele animacji można tworzyć w sposób deklaratywny, zapisując je w postaci zasobów
XML. Możemy zatem przygotować pliki XML zawierające standardowy zestaw animacji
(takich jak skalowanie, przejścia lub obroty), aby utworzyć efekty, które następnie
będziemy mogli wywoływać z poziomu kodu aktywności. Wspaniałą cechą deklaratywnych
animacji widoków jest to, że są one całkowicie oddzielone od kodu Javy, dzięki czemu
bardzo łatwo można je kopiować z jednej aplikacji do drugiej.
Przejścia aktywności
Załóżmy, że napisaliśmy aplikację wyświetlającą listę elementów składających się z nazwy
i obrazka. Po kliknięciu wybranego elementu tej listy zostaje wyświetlony widok ze
szczegółowymi informacjami na jego temat. W takim przypadku aktywność prezentująca
informacje szczegółowe będzie zapewne wyświetlać ten sam obrazek, który wcześniej był
widoczny na liście.
https://developer.android.com/guide/topics/graphics/index.html
https://developer.android.com/training/material/animations.html
jesteś tutaj 667
Mapy
5. Mapy
Urządzenie z Androidem może podróżować razem z nami, dlatego określanie położenia
i wyświetlanie map są bardzo ważnymi możliwościami wielu aplikacji firmy Google.
<fragment xmlns:android=”http://schemas.android.com/apk/res/android”
android:id=”@+id/map”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:name=”com.google.android.gms.maps.MapFragment”/>
Następnie, w kodzie aktywności, możemy się do niej odwołać i używać jej jako obiektu
GoogleMap:
668 Dodatek D
Pozostałości
map.setOnCameraChangeListener(new OnCameraChangeListener() {
@Override
public void onCameraChange(CameraPosition cameraPosition) {
// Mapę przeciągnięto w inne miejsce
}
});
map.setOnMapClickListener(new OnMapClickListener() {
@Override
public void onMapClick(LatLng latLng) {
// Kliknięto w miejscu o współrzędnych określonych w obiekcie latLng
}
});
https://developer.android.com/training/maps/index.html
6. Klasa CursorLoader
Jeśli tworzona aplikacja w bardzo dużym stopniu używa bazy danych i dostawców
treści, to wcześniej czy później spotkamy się z obiektami CursorLoader.
Umożliwiają one asynchroniczne wykonywanie zapytań w tle i zwracają wyniki
do aktywności lub fragmentu, który je wywołał. Zarządzają one kursorami, dzięki
czemu nie musimy tego robić samodzielnie. Oprócz tego obiekty CursorLoader
mogą nas powiadamiać o modyfikacji danych, co pozwoli nam odpowiednio na
to zareagować w prezentowanych widokach.
https://developer.android.com/training/load-data-background/setup-loader.html
7. Odbiorcy komunikatów
Załóżmy, że chcielibyśmy, by nasza aplikacja w jakiś sposób reagowała na
wystąpienie konkretnego zdarzenia systemowego. Na przykład napisaliśmy
aplikację do odtwarzania muzyki i chcemy zatrzymywać odtwarzanie
w przypadku odłączenia słuchawek od urządzenia. W jaki sposób aplikacja
może wykryć takie zdarzenie?
http://developer.android.com/reference/android/content/BroadcastReceiver.html
670 Dodatek D
Pozostałości
8. Widżety aplikacji
Widżet aplikacji to niewielki widok aplikacji, który można dodać do innej
aplikacji lub umieścić na ekranie głównym. Zapewnia on bezpośredni dostęp
do podstawowych treści lub funkcjonalności aplikacji z poziomu ekranu
głównego bez konieczności uruchamiania tej aplikacji.
http://developer.android.com/guide/topics/appwidgets/index.html
9. Grafika 9-patch
Grafika 9-patch (lub NinePatch) to elastyczne mapy bitowe, których można
używać do określania tła widoków. Rozmiary tych obrazów są automatycznie
dostosowywane do wielkości widoku i ekranu. Najciekawsze w nich jest to,
że mamy możliwość określania, które obszary mają być rozciągane, a które nie.
Chcemy, aby
wielkość tego Wielkość tego obszaru
obszaru była może być modyfikowana
elastycznie wyłącznie w pionie. Jego
modyfikowana. szerokość musi pozostać
taka sama.
modyfikowana
Wielkość tego obszaru może być aby był on
pozi omie . Nie chce my,
wyłącznie w ałoby jego
gdyż to spow odow
wyższy lub niższy,
zniekształcenie.
Jeśli zmienimy nasz obrazek w grafikę 9-patch, to jego rozmiary będzie można zmieniać
dokładnie w taki sposób, w jaki byśmy chcieli.
Android udostępnia narzędzie o nazwie Draw 9-patch tool, które pomaga w tworzeniu
grafiki 9-patch. Więcej informacji na temat tego programu i samej grafiki 9-patch można
znaleźć na stronie:
http://developer.android.com/guide/topics/graphics/2d-graphics.html#nine-patch
672 Dodatek D
Pozostałości
10. Testowanie
Nowoczesne sposoby tworzenia oprogramowania w dużym stopniu opierają się na
testowaniu, a Android udostępnia wiele wbudowanych mechanizmów wspomagających
testowanie. Ponieważ podstawowym językiem używanym do pisania aplikacji na Androida
jest Java, można używać standardowych frameworków do testowania programów pisanych
w Javie, ale Android idzie nawet dalej i udostępnia framework do testowania wbudowany
bezpośrednio w SDK. Co więcej, podczas tworzenia każdego nowego projektu Android
Studio automatycznie tworzy hierarchię plików przeznaczoną do tworzenia testów.
http://developer.android.com/tools/testing/testing_android.html
https://github.com/robotiumtech/robotium
674 Skorowidz
Skorowidz
B poziomu głównego, 366 intencja, 82, 83, 87, 88, 117, 386, 388, 545
back stack, Patrz: stos cofnięć proporcje, 309 filtr, 101, 102, 113
baza danych, 438, 601 szczegółów/edycji, 366 jawna, 113, 559
aktualizacja, 449, 455, 457, 459, 460, wielkość, 309, 359 niejawna, 98, 113
461, 462, 465, 510 wygląd, 12 oczekująca, 559, 560
JDBC, Patrz: JDBC element tworzenie, 96, 97
kopiowanie, 658 Button, 48 uruchamianie przez powiadomienie,
modyfikacja struktury, 465 ContentValues, 449 559, 560
nazwa, 466 FragmentTransaction, 301 wyznaczanie, 101
numer wersji, 456, 459 FrameLayout, 300 intent filter, Patrz: intencja filtr
odtwarzanie, 658 HorizontalScrollView, 219 intent resolution, Patrz: intencja
rekord ImageView, 215, 216 wyznaczanie
aktualizacja, 448, 449, 465 ListView, Patrz: widok listy interfejs, 296, 298
usuwanie, 450, 465 meta-data, 392 DrawerListener, 417
schemat zmiana, 456 RelativeLayout, 47 Listener, 634, 636
SQLite, Patrz: SQLite ScrollView, 219 OnClickListener, 347, 349, 351, 359
tworzenie, 443, 445, 447 service, 548 OnItemClickListener, 408
wydajność, 525, 526, 539 style, 373 programowania aplikacji, Patrz: API
zamykanie, 489, 499 TextView, 48, 193, 205 użytkownika
biblioteka definiowanie, 205 graficzny, Patrz: GUI
AWT, 4 emulator, 4, 23, 660 wygląd, 12
Support Libraries, 368, 369, 370, zrzut stanu, 661 iPhone Symulator, Patrz: symulator
406, 602 event listener, Patrz: obiekt nasłuchujący iPhone’a
Swing, 4
wsparcia, Patrz: biblioteka F J
Support Libraries filtr intencji, Patrz: intencja filtr Java, 2, 4, 5, 16
błąd, 345 fragment, 271, 272, 273, 287, 359 instalacja, 6
cykl życia, 283, 327 klasa własna, Patrz: klasa własna
C dodawanie Javy
card view, Patrz: widok karty do projektu, 276, 401 Java Virtual Machine, Patrz: JVM
cień, 598, 599 do układu aktywności, 279 język
interfejs, 295, 296, 298 Groovy, Patrz: Groovy
listy, 288, 289, 297 Java, Patrz: Java
D zagnieżdżanie, 326, 337, 341, 342 SQL, Patrz: SQL
Dalvik, 650 znacznik, Patrz: znacznik XML, Patrz: XML
dostawca FrameLayout, Patrz: układ ramki journal file, Patrz: plik magazynu
akcji, 386, 387 JVM, 650, 652
treści, 665
dp, Patrz: piksel niezależny od gęstości G
DrawerLayout, 399, 405 Google Play płatności, 5 K
dziennik, 543, 545 GPS, 579, 582 katalog
logcat, 657 gradle, 7 aplikacji, 16
grafika 9-patch, 672 app, 17
graphical user interface, Patrz: GUI build, 17
E GridLayout, Patrz: układ siatki com.hfad., 17
Eclipse, 7 Groovy, 7 debug, 17
edytor GUI, 2, 164, 201, 545, 552 drawable, 215, 237
kodu, 18, 32 drawable-hdpi, 308, 379
projektu, 18, 32, 43, 44, 49 generated, 17
tekstów, 7 I java, 17
efekt 3D, 598 ikona
layout, 17, 308, 320
ekran, 270, 306, 307, 308 ic_action_new_event, 379
layout-large, 308, 320
gęstość, 215, 309 zestaw systemowy, 379
main, 17
kategorii, 366 Instrumentation Testing, Patrz: test
opcje, 309
orientacja, 309, Patrz też: oprzyrządowania
platform-tools, 654
urządzenie obracanie IntelliJ IDEA, 5
r, 17
676 Skorowidz
Skorowidz
678 Skorowidz