Android. Programowanie aplikacji. Rusz głową! Wydanie II [2 ed.] 978-83-283-4080-0

Od poprzedniego wydania tej książki minęło parę lat, a kariera Androida wciąż jest dynamiczna! Kompleksowość, otwarty ko

1,212 132 45MB

Polish Pages 922 Year 2018

Report DMCA / Copyright

DOWNLOAD FILE

Polecaj historie

Android. Programowanie aplikacji. Rusz głową! Wydanie II [2 ed.]
 978-83-283-4080-0

Citation preview

Android Programowanie aplikacji

Dawn Griffiths, David Griffiths

Tytuł oryginału: Head First Android Development: A Brain-Friendly Guide, 2nd Edition Tłumaczenie: Piotr Rajca ISBN: 978-83-283-4080-0 © 2018 Helion SA Authorized Polish translation of the English edition of Head First Android Development 2e ISBN 9781491974056 © 2017 David Griffiths and Dawn Griffiths All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording or by any information storage retrieval system, without permission from the Publisher. Wszelkie prawa zastrzeżone. Nieautoryzowane rozpowszechnianie całości lub fragmentu niniejszej publikacji w jakiejkolwiek postaci jest zabronione. Wykonywanie kopii metodą kserograficzną, fotograficzną, a także kopiowanie książki na nośniku filmowym, magnetycznym lub innym powoduje naruszenie praw autorskich niniejszej publikacji. Wszystkie znaki występujące w tekście są zastrzeżonymi znakami firmowymi bądź towarowymi ich właścicieli. Autor oraz Helion SA dołożyli wszelkich starań, by zawarte w tej książce informacje były kompletne i rzetelne. Nie biorą jednak żadnej odpowiedzialności ani za ich wykorzystanie, ani za związane z tym ewentualne naruszenie praw patentowych lub autorskich. Autor oraz Helion SA nie ponoszą również żadnej odpowiedzialności za ewentualne szkody wynikłe z wykorzystania informacji zawartych w książce. Helion SA ul. Kościuszki 1c, 44-100 Gliwice tel. 32 231 22 19, 32 230 98 63 e-mail: [email protected] WWW: http://helion.pl (księgarnia internetowa, katalog książek)

Drogi Czytelniku! Jeżeli chcesz ocenić tę książkę, zajrzyj pod adres http://helion.pl/user/opinie/andrr2_ebook Możesz tam wpisać swoje uwagi, spostrzeżenia, recenzję.

x Poleć książkę na Facebook.com x Kup w wersji papierowej x Oceń książkę

x Księgarnia internetowa x Lubię to! » Nasza społeczność

Naszym Przyjaciołom i naszej Rodzinie — dziękujemy Wam bardzo za miłość i wsparcie

O autorach

Autorzy książki Android. Programowanie aplikacji. Rusz głową!

iths David Griff

Dawn Griffiths

Dawn Griffiths zaczynała jako matematyk na jednym z czołowych brytyjskich uniwersytetów, gdzie ukończyła studia matematyczne z wyróżnieniem. Później rozpoczęła karierę w branży produkcji oprogramowania i ma już dwudziestoletnie doświadczenie w branży IT. Przed napisaniem książki Android. Programowanie aplikacji. Rusz głową! Dawn napisała trzy inne książki z serii Head First (Head First Statistics — polskie wydanie: Head First. Statystyka. Edycja polska, Head First 2D Geometry oraz Head First C — polskie wydanie: C. Rusz głową!). Wraz ze swym mężem Davidem stworzyła także kurs wideo pt. The Agile Sketchpad, aby nauczać kluczowych pojęć i technik w sposób pozwalający zachować aktywność i zaangażowanie mózgu. Kiedy nie pracuje nad żadną z książek z serii Rusz głową!, doskonali się w tai-chi, biega, robi koronki i gotuje. Uwielbia także podróżować i spędzać czas ze swoim mężem Davidem.

David Griffiths zaczął programować w wieku 12 lat, kiedy obejrzał film dokumentalny poświęcony pracom Seymoura Paperta. W wieku 15 lat napisał implementację języka LOGO, opracowanego przez Paperta. Po zakończeniu studiów matematycznych na uniwersytecie zaczął pisać kod przeznaczony dla komputerów oraz artykuły dla różnych czasopism. Pracował jako instruktor zwinnych metod programowania, programista, parkingowy, ale nie w takiej kolejności. Potrafi programować w ponad dziesięciu językach i pisać prozę tylko w jednym, a kiedy nie pisze ani nie zajmuje się doradztwem, spędza większość czasu ze swoją uroczą żoną — i współautorką tej książki — Dawn. Przed napisaniem książki Android. Programowanie aplikacji. Rusz głową! David napisał trzy inne książki z tej serii: Head First Rails (polskie wydanie: Head First Ruby on Rails. Edycja polska), Head First Programming oraz Head First C (polskie wydanie: C. Rusz głową!) i wraz z Dawn stworzył kurs The Agile Sketchpad. Davida można śledzić na Twitterze: http://twitter.com/ HeadFirstDroid można też odwiedzić jego stronę WWW: https://tinyurl.com/HeadFirstAndroid.

iv

Spis treści

Spis treści (skrócony) Wprowadzenie

xxix

1

Zaczynamy: Skok na głęboką wodę

1

2

Tworzenie interaktywnych aplikacji: Aplikacje, które coś robią

37

3

Wiele aktywności i intencji: Jakie są Twoje intencje?

77

4

Cykl życia aktywności: Była sobie aktywność

119

5

Widoki i grupy widoków: Podziwiaj widoki

169

6

Układy z ograniczeniami: Rozmieszczaj rzeczy w odpowiednich miejscach

221

7

Widoki list i adaptery: Zorganizuj się

247

8

Biblioteki wsparcia i paski aplikacji: Na skróty

289

9

Fragmenty: Zadbaj o modularyzację

339

10

Fragmenty dla większych interfejsów: Różne wielkości, różne interfejsy

393

11

Fragmenty dynamiczne: Zagnieżdżanie fragmentów

433

12

Biblioteka wsparcia wzornictwa: Przeciągnięcie w prawo

481

13

Widoki RecyclerView i CardView: Stosuj recykling

537

14

Szuflady nawigacyjne: Z miejsca na miejsce

579

15

Bazy danych SQLite: Odpal bazę danych

621

16

Proste kursory: Pobieranie danych

657

17

Kursory i zadania asynchroniczne: Pozostając w tle

693

18

Usługi uruchomione: Do usług

739

19

Usługi powiązane i uprawnienia: Powiązane ze sobą

767

A

Układy względne i układy siatki: Poznaj krewnych

817

B

Gradle: Program do budowy Gradle

833

C

ART: Środowisko uruchomieniowe Androida

841

D

ADB: Android Debug Bridge

849

E

Emulator Androida: Przyspieszanie emulatora

857

F

Pozostałości: Dziesięć najważniejszych zagadnień (których nie opisaliśmy)

861

v

Spis treści

Spis treści (z prawdziwego zdarzenia)

W

Wprowadzenie Twój mózg jest nastawiony na Androida. Jesteś tu po to, by się czegoś nauczyć, a 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 na zastanowienie się nad tym, 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? Autorzy książki Android. Programowanie aplikacji. Rusz głową!

xxx

Wiemy, co sobie myślisz

xxxi

Wiemy, co sobie myśli Twój mózg

xxxi

Metapoznanie — myślenie o myśleniu

xxxiii

Oto co MY zrobiliśmy

xxxiv

Przeczytaj to Zespół recenzentów technicznych Podziękowania

Zastanawiam się, jak zmusić mózg do zapamiętania tych informacji…

vi

iv

Dla kogo jest ta książka?

xxxvi xxxviii xxxix

Spis treści

1

Zaczynamy

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…

DK dS

An

oi dr

Witamy w Androidowie

2

Platforma Android w szczegółach

3

Oto co mamy zamiar zrobić

4

Środowisko programistyczne

5

Zainstaluj Android Studio

6

Stwórzmy prostą aplikację

7

Jak stworzyć aplikację?

8

Aktywności i układy z wysokości 15 tysięcy metrów

12

Jak stworzyć aplikację? (ciąg dalszy)

13

Właśnie utworzyłeś swoją pierwszą aplikację na Androida

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

Uruchamianie aplikacji w emulatorze

27

Postępy możesz obserwować w konsoli

28

Ale co się właściwie stało?

30

Usprawnienie aplikacji

31

Czym jest układ?

32

Plik activity_main.xml zawiera dwa elementy

33

Aktualizacja tekstu wyświetlanego w układzie

34

Weź aplikację na jazdę próbną

35

Twój przybornik do Androida

36



Urządzenie



Aktywność

Układ

vii

Spis treści

2

Tworzenie interaktywnych aplikacji

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.



Układ



strings.xml

Aktywność

BeerExpert

viii

W tym rozdziale napiszemy aplikację Doradca piwny

38

Utworzenie projektu

40

Utworzyliśmy domyślną aktywność i układ

41

Dokładniejsza prezentacja edytora projektu

42

Dodawanie przycisku w edytorze projektu

43

Plik activity_find_beer.xml zawiera nowy przycisk

44

Dokładniejszy przegląd kodu układu

45

Weź swoją aplikację na jazdę próbną

49

Podawanie tekstów na stałe utrudnia lokalizację

50

Utworzenie zasobu łańcuchowego

51

Zastosowanie zasobu łańcuchowego w układzie

52

Kod pliku activity_find_beer.xml

53

Dodawanie wartości do komponentu Spinner

56

Dodanie elementu string-array do pliku strings.xml

57

Jazda próbna komponentu Spinner

58

Musimy zadbać o to, by przycisk coś robił

59

Niech przycisk wywołuje metodę

60

Jak wygląda kod aktywności

61

Dodaj do aktywności metodę onClickFindBeer()

62

Metoda onClickFindBeer() musi coś robić

63

Dysponując obiektem View, można odwoływać się do jego metod

64

Aktualizacja kodu aktywności

65

Pierwsza wersja aktywności

67

Co ten kod robi?

68

Tworzenie własnej klasy Javy

70

Co się dzieje podczas wykonywania tego kodu?

74

Jazda próbna — test aplikacji

75

Twój przybornik do Androida

76

Spis treści

3

Wiele aktywności i intencji

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 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.

Intencja

Do: InnaAktywnosc

Aplikacja może zawierać więcej niż jedną aktywność

78

Oto struktura naszej aplikacji

79

Zaczynamy: utworzenie projektu

79

Aktualizacja układu

80

Utworzenie drugiej aktywności i układu

82

Przedstawiamy plik manifestu aplikacji na Androida

84

Intencja jest rodzajem komunikatu

86

Co się dzieje po uruchomieniu aplikacji?

88

Przekazanie tekstu do drugiej aktywności

90

Aktualizacja właściwości widoku tekstowego

91

Metoda putExtra() zapisuje w intencji dodatkowe informacje

92

Aktualizacja kodu aktywności CreateMessageActivity

95

Zastosowanie informacji przekazanych w intencji w klasie ReceiveMessageActivity

96

Co się dzieje, gdy użytkownik kliknie przycisk Wyślij wiadomość

97

Możemy zmienić aplikację tak, by wiadomość była wysyłana do innych osób

98

Jak działają aplikacje na Androida

Hej, użytkowniku! Powiedz mi, proszę, której aktywności chciałbyś użyć tym razem.

99

Utworzenie intencji określającej akcję

101

Zmiana intencji w celu użycia akcji

102

Jak Android korzysta z filtrów intencji?

106

A co, jeśli chcemy, by użytkownik ZAWSZE wybierał aktywność?

112

Co się dzieje w momencie wywoływania metody createChooser()?

113

Zmień kod, by wyświetlać okno dialogowe

115

Twój przybornik do Androida

118

CreateMessageActivity

Android

Użytkownik

ix

Spis treści

4 Aktywność uruchomiona

Cykl życia aktywności

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 intencji. Ale co tak naprawdę dzieje się za kulisami? W tym rozdziale nieco dokładniej opiszemy 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 ekranie, a jakie, gdy aktywność traci miejsce wprowadzania i jest ukrywana? W jaki sposób można zapisywać i odtwarzać stan aktywności?

onCreate()

onStart()

onResume()

Aktywność działająca

onPause()

onStop()

onDestroy()

Aktywność usunięta x

onRestart()

Jak właściwie działają aktywności?

120

Aplikacja stopera

122

Dodanie zasobów łańcuchowych

123

Jak będzie działał kod aktywności?

125

Działanie kodu obsługującego przyciski

126

Metoda runTimer()

127

Pełny kod metody runTimer()

129

Kompletny kod aktywności StopwatchActivity

130

Obrót ekranu zmienia konfigurację urządzenia

136

Stany aktywności

137

Cykl życia aktywności: od utworzenia do usunięcia

138

Zaktualizowany kod aktywności StopwatchActivity

142

Co się stanie po uruchomieniu aplikacji?

143

Tworzenie i usuwanie to nie cały cykl życia aktywności

146

Zaktualizowany kod aktywności StopwatchActivity

151

Co się dzieje podczas działania aplikacji?

152

A co się dzieje, jeśli aplikacja jest tylko częściowo widoczna?

154

Cykl życia aktywności: życie na pierwszym planie

155

Zatrzymanie stopera w razie wstrzymania aktywności

158

Implementacja metod onPause() oraz onResume()

159

Kompletny kod aktywności

160

Co się stanie po uruchomieniu aplikacji?

163

Wygodny przewodnik po metodach cyklu życia aktywności

167

Twój przybornik do Androida

168

Spis treści

5

Widoki i grupy widoków

Podziwiaj widoki Zobaczyłeś już, jak można rozmieszczać elementy GUI, używając układu LinearLayout, ale to jedynie wierzchołek góry lodowej. W tym rozdziale nieco dokładniej opiszemy rzeczywisty sposób działania układu liniowego. Przedstawimy także FrameLayout, nazywany również układem ramki, będący prostym układem używanym do rozmieszczania widoków jeden na drugim, 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ć. Interfejs użytkownika aplikacji składa się z układów i komponentów GUI 170 Układ LinearLayout wyświetla widoki w jednym wierszu lub w jednej kolumnie

171

Dodawanie pliku zasobów wymiaru w celu zapewnienia spójnych wypełnień w układach

174

Stosowanie marginesów do oddalania widoków od siebie

176

Zmieńmy nieco prosty układ liniowy

177

Rozciągaaaaamy widok, zwiększając jego wagę

179

Wartości atrybutu android:gravity

183

Kompletny układ liniowy

186

Układy FrameLayout rozmieszczają widoki jeden na drugim

188

Dodanie obrazka do projektu

189

Kompletny kod układu

192

Układy FrameLayout: podsumowanie

193

Zabawy z widokami

201

Pola tekstowe

202

Przycisk

203

Przycisk przełącznika

204

Przełącznik

205

Pola wyboru

206

Przyciski opcji

208

Lista rozwijana

210

Widoki obrazów

211

Dodawanie obrazów do przycisków

213

Widoki przewijane

215

Krótkie komunikaty

216

Twój przybornik do Androida

220

adać Układy FrameLayout pozwalają nakł datne przy jedne widoki na inne. To bardzo rzyć interfejs w przypadkach, gdy chcemy stwo tekst jest użytkownika, w którym na przykład wyświetlany na tle obrazu.

Układ liniowy

ViewGroup

layout.xml

Przycisk

Pole tekstowe

View

View

xi

Spis treści

6

Tym razem kliknięcie przycisku Infer Constraints spowodowało dodanie ograniczeń do nowej kontrolki EditText.

xii

Układy z ograniczeniami

Rozmieszczaj rzeczy w odpowiednich miejscach Spójrzmy prawdzie w oczy: musisz wiedzieć, jak tworzyć piękne układy. Jeśli piszesz aplikacje i chcesz, by inni ich używali, to musisz mieć pewność, że będą one wyglądać dokładnie tak, jak sobie tego życzysz. Jak na razie dowiedziałeś się jedynie, jak używać układów LinearLayout oraz FrameLayout, ale co zrobić, jeśli projekt interfejsu aplikacji będzie bardziej złożony? Aby pokazać Ci, jak należy sobie radzić w takich sytuacjach, przedstawimy nowy układ dodany do Androida — układ z ograniczeniami (ConstraintLayout) — używany do wizualnego konstruowania układów na podstawie szkicu graficznego. Pokażemy Ci także, jak ograniczenia pozwalają na określanie położenia widoków, w sposób niezależny od wielkości i orientacji ekranu. I w końcu dowiesz się też, jak oszczędzać czas, korzystając z możliwości automatycznego przewidywania i dodawania ograniczeń, jaką dysponuje Android Studio. Zagnieżdżone układy mogą być nieefektywne

222

Przedstawiamy układy z ograniczeniami

223

Nie zapomnij dołączyć do projektu biblioteki Constrained Layout Library

224

Dodanie zasobów do strings.xml

225

Zastosowanie narzędzia do tworzenia szkicu

226

Rozmieszczanie widoków przy wykorzystaniu ograniczeń

227

Dodawanie ograniczenia w pionie

228

Zmiany szkicu są uwzględniane w kodzie XML

229

Jak wyśrodkowywać widoki

230

Zmiana położenia widoku poprzez określanie przesunięcia

231

Jak zmieniać wielkość widoku?

232

Jak wyrównywać widoki?

238

Stwórzmy prawdziwy układ

239

Zacznij od dodania widoków do górnego wiersza

240

Mechanizm wnioskowania odgaduje, jakie ograniczenia należy dodać

241

Dodaj do szkicu kolejny wiersz…

242

I w końcu dodaj widok na treść wiadomości

243

Jazda próbna aplikacji

244

Twój przybornik do Androida

245

Spis treści

7

Widoki list i adaptery

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.

Ekran początkowy z listą opcji

Lista oferowanych napojów

Szczegółowe informacje o każdym napoju

To jest nasz widok listy.

ListView

Każda aplikacja zaczyna się od pomysłu

248

Użyj widoku listy do nawigowania po danych

251

Aktywność szczegółów napoju

253

Struktura aplikacji dla kafeterii Coffeina

254

Klasa Drink

256

Układ aktywności głównego poziomu składa się z obrazka i listy

258

Kompletny kod układu aktywności głównego poziomu

260

Zapewnianie reakcji ListView na kliknięcia za pomocą obiektu nasłuchującego

261

Dodanie obiektu nasłuchującego do widoku listy

262

Aktywność kategorii wyświetla dane jednej kategorii

267

Aktualizacja układu activity_drink_category.xml

268

W przypadku danych statycznych należy użyć adaptera

269

Łączenie widoków ListView z tablicami przy użyciu adaptera

270

Dodanie adaptera ArrayAdapter do aktywności DrinkCategoryActivity

271

Przegląd aplikacji, czyli dokąd dotarliśmy

274

Jak obsługiwaliśmy kliknięcia w aktywności TopLevelActivity

276

Kompletny kod aktywności DrinkCategoryActivity

278

Wypełnienie widoków danymi

281

Kod aktywności DrinkActivity

283

Co się stanie po uruchomieniu aplikacji

284

Twój przybornik do Androida

288

Utworzymy adapter ArrayAdapter, aby powiązać widok listy z naszą tablicą.

Array Adapter

To jest nasza tablica.

Drink. drinks

xiii

Spis treści

8

Biblioteki wsparcia i paski aplikacji

Na skróty Każdy lubi chodzić na skróty. Z tego rozdziału dowiesz się, jak korzystając z pasków aplikacji, wzbogacić aplikację o możliwość chodzenia na skróty. Pokażemy Ci, jak uruchamiać inne aktywności za pomocą elementów akcji dodawanych do pasków aplikacji, 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 aplikacji. Jednocześnie przedstawimy Ci potężne biblioteki wsparcia Androida, mające kluczowe znaczenie dla zapewniania nowoczesnego wyglądu aplikacji na starszych wersjach systemu. Świetne aplikacje mają przejrzystą strukturę

290

Różne typy nawigacji

291

Zacznijmy od paska akcji

293

Utwórz aplikację Włoskie Co Nieco

295

Dodaj bibliotekę wsparcia AppCompat v7

296

Plik AndroidManifest.xml może zmieniać postać paska aplikacji

299

Jak zastosować motyw?

300

Zdefiniuj styl w pliku zasobów

301

Dostosuj wygląd aplikacji

303

Zdefiniuj kolory w pliku zasobów kolorów

304

Kod pliku activity_main.xml

305

Pasek aplikacji a pasek narzędzi

306

Dołącz pasek narzędzi do układu aktywności

312

Dodawanie akcji do paska aplikacji

315

Zmień pasek aplikacji, dodając do niego etykietę

318

Kod pliku AndroidManifest.xml

319

Określ wygląd akcji

322

Kompletny kod pliku MainActivity.java

325

Włączanie nawigacji w górę

327

onCreate(Bundle)

Dzielenie się treściami z poziomu paska aplikacji

331

twojaMetoda()

Dodawanie dostawcy akcji udostępniania do menu_main.xml

332

Określanie treści za pomocą intencji

333

Kompletny kod aktywności MainActivity

334

Twój przybornik do Androida

337

Activity onCreate(Bundle) onStart() onRestart() onResume() onPause() onStop() onDestroy() onSaveInstanceState() FragmentActivity

AppCompatActivity

TwojaKlasaActivity

Intencja

xiv

ShareAction Provider

ACTION_SEND type: “text/plain” messageText:”Cześć!”

AktywnoscAplikacji

Spis treści

9

Fragmenty

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 tej sytuacji będziesz potrzebował fragmentów, modularnych komponentów, które mogą być wielokrotnie używane w różnych aktywnościach. Pokażemy, jak można tworzyć proste fragmenty oraz fragmenty list, jak dodawać fragmenty do aktywności oraz w jaki sposób zapewniać wzajemną komunikację pomiędzy fragmentami i aktywnościami. Twoja aplikacja musi wyglądać świetnie na WSZYSTKICH urządzeniach

340

Może się zdarzyć, że aplikacja będzie musiała także działać inaczej

341

Fragmenty umożliwiają wielokrotne stosowanie kodu

342

Aplikacja w wersji na telefony

343

Utworzenie projektu i aktywności

345

Dodanie przycisku do układu aktywności

346

Jak dodać fragment do projektu?

348

Metoda onCreateView() fragmentu

350

Dodawanie fragmentu do układu aktywności

352

Zapewnienie interakcji fragmentu i aktywności

359

Klasa Workout

360

Przekazywanie identyfikatora treningu do fragmentu

361

Określenie identyfikatora treningu w kodzie aktywności

363

Cykl życia fragmentów

365

Określenie zawartości widoków w metodzie onStart() fragmentu

367

Jak utworzyć fragment typu ListFragment?

374

Zaktualizowany kod klasy WorkoutListFragment

377

Kod układu activity_main.xml

381

Powiązanie listy z widokiem szczegółów

384

Kod pliku WorkoutListFragment.java

387

Aktywność MainActivity musi implementować interfejs

388

Aktywność DetailActivity musi przekazać identyfikator do fragmentu WorkoutDetailFragment

389

Twój przybornik do Androida

392

Hmmm… element . Muszę wiedzieć, co się w nim znajduje.

activity_detail.xml

Fragment listy dysponuje swoim własnym widokiem listy, dzięki czemu nie musimy sami tworzyć takiej listy. Wystarczy, że dostarczymy niezbędnych danych.

onCreate()

Menedżer fragmentów

WorkoutDetail Fragment

xv

Spis treści

10

Fragmenty dla większych interfejsów

Różne wielkości, różne interfejsy Jak na razie uruchamialiśmy nasze aplikacje wyłącznie na urządzeniach z małymi ekranami. A co, jeśli użytkownicy będą mieli tablety? W tym rozdziale dowiesz się, w jaki sposób, dzięki zróżnicowaniu wyglądu i sposobu działania aplikacji w zależności od urządzenia, na jakim została ona uruchomiona, można projektować elastyczne interfejsy użytkownika. Pokażemy także, jak kontrolować działanie aplikacji po naciśnięciu przycisku Wstecz, prezentując przy okazji dwa nowe rozwiązania: stos cofnięć i transakcje fragmentów. I w końcu dowiesz się też, jak można zapisywać i odtwarzać stan fragmentów.

Ekran tego urządzenia nie jest duży, dlatego użyję wersji układu z katalogu layout.

layout

Android

activity_main.xml

Zostałem zatwierdzony. Niech się zatem stanie!

Nasza aplikacja Trenażer wygląda tak samo na telefonie i tablecie

394

Projektowanie z myślą o większych interfejsach

395

Wersja aplikacji na telefony

396

Wersja aplikacji na tablety

397

Utwórz AVD tabletu

399

Umieszczaj zasoby przeznaczone dla różnych rodzajów ekranów w odpowiednich katalogach

402

Różne opcje katalogów

403

Tablety używają układów zapisanych w katalogu layout-large

408

Jak działa zaktualizowany kod?

410

Musimy zmienić kod metody itemClicked()

412

Chcemy, by fragmenty współpracowały z przyciskiem Wstecz

413

Witamy stos cofnięć

414

Transakcje na stosie cofnięć nie muszą być aktywnościami

415

Użyj układu FrameLayout, by programowo zmieniać fragmenty

416

Skorzystaj z różnic w układach, aby określić, który z nich został użyty

417

Zmodyfikowany kod aktywności MainActivity

418

Stosowanie transakcji fragmentów

419

Zaktualizowany kod aktywności MainActivity

423

Zmiana orientacji tabletu wywołuje problem w aplikacji

427

Zapisywanie stanu aktywności (po raz wtóry)

428

Zaktualizowany kod pliku WorkoutDetailFragment.java

430

Twój przybornik do Androida

432

MainActivity

xvi

FragmentTransaction

Tablet

Spis treści

11

Fragmenty dynamiczne

Zagnieżdżanie fragmentów Jak na razie dowiedziałeś się, jak można tworzyć i stosować fragmenty statyczne. Ale co zrobić, jeśli zechcemy, by nasze fragmenty były nieco bardziej dynamiczne? Fragmenty dynamiczne mają wiele wspólnego z aktywnościami dynamicznymi, choć występują pomiędzy nimi pewne kluczowe różnice, z którymi będziemy musieli umieć odpowiednio postępować. W tym rozdziale dowiesz się, jak przekształcać aktywności dynamiczne na działające fragmenty dynamiczne. Dowiesz się także, jak korzystać z transakcji fragmentów w celu zachowania stanu fragmentów. I w końcu odkryjesz, jak można zagnieżdżać jedne fragmenty w innych oraz w jaki sposób menedżer fragmentów podrzędnych ułatwia zachowanie kontroli nad niesfornym stosem cofnięć.

Zawsze, gdy widzę atrybut android:onClick, zakładam, że chodzi o mnie. To moje metody mają zostać wywołane, a nie metody fragmentu.

Aktywność

Dodawanie fragmentów dynamicznych

434

Nowa wersja aplikacji

436

Utwórz aktywność TempActivity

437

Klasa TempActivity musi dziedziczyć po AppCompatActivity

438

Kod fragmentu StopwatchFragment

444

Układ fragmentu StopwatchFragment

447

Dodanie fragmentu StopwatchFragment do układu aktywności TempActivity

449

Atrybut onClick wywołuje metody aktywności, a nie fragmentu

452

Powiązanie obiektu nasłuchującego OnClickListener z przyciskami

457

Kod fragmentu StopwatchFragment

458

Obrócenie urządzenia zeruje stoper

462

Używaj dla statycznych fragmentów…

463

W układzie activity_temp.xml zastosuj układ FrameLayout

464

Kompletny kod aktywności TempActivity.java

467

Dodanie stopera do fragmentu WorkoutDetailFragment

469

Kompletny kod pliku WorkoutDetailFragment.java

476

Twój przybornik do Androida

480

Wyświetlam szczegółowe informacje o treningu, jak również stoper. Transakcja wyświetlająca fragment StopwatchFragment zostaje zagnieżdżona wewnątrz transakcji, która wyświetla fragment WorkoutDetailFragment.

Szczegóły treningu

Stoper xvii

Spis treści

12

Biblioteka wsparcia wzornictwa

Przeciągnięcie w prawo Czy kiedykolwiek zastanawiałeś się, jak napisać aplikację z bogatym i ładnym interfejsem użytkownika? Dzięki udostępnieniu biblioteki wsparcia wzornictwa, Android Design Support Library, teraz znacznie łatwiej można już tworzyć aplikacje o intuicyjnym interfejsie użytkownika. W tym rozdziale przedstawimy najważniejsze informacje na ten temat. Dowiesz się, jak dodawać karty, dzięki którym użytkownicy będą mogli łatwiej poruszać się po aplikacji. Poznasz sposoby animowania pasków narzędzi, by na żądanie mogły się zwijać lub przewijać. Nauczysz się także dodawać do aplikacji pływające przyciski akcji, ułatwiające dostęp do najczęściej wykonywanych operacji. I w końcu przedstawimy Ci snackbar — mechanizm do wyświetlania krótkich, informacyjnych komunikatów, z którymi użytkownik może wchodzić w interakcje.

Zapewnimy, że pasek narzędzi będzie przewijany wraz z zawartością fragmentu TopFragment.

Aplikacja Włoskie Co Nieco w nowej odsłonie

482

Struktura aplikacji

483

Użycie klasy ViewPager do przewijania fragmentów

489

Dodajemy ViewPager do układu aktywności MainActivity

490

Przekaż kontrolce informacje o stronach przy użyciu odpowiedniego adaptera

491

Kod naszego adaptera FragmentPagerAdapter

492

Pełny kod pliku MainActivity.java

494

Dodanie kart do aktywności MainActivity

498

Jak dodać karty do układu?

499

Połączenie układu kart z kontrolką ViewPager

501

Pełny kod pliku MainActivity.java

502

Biblioteka wsparcia wzornictwa pomaga implementować Material Design 506

Do fragmentu TopFragment dodamy możliwość przewijania zawartości.

xviii

Zapewnienie reagowania paska narzędzi na przewijanie

508

Dodanie CoordinatorLayout do układu aktywności MainActivity

509

Jak koordynować przewijanie?

510

Dodanie do fragmentu zawartości do przewijania

512

Pełny kod pliku fragment_top.xml

515

Dodanie zwijanego paska narzędzi do aktywności OrderActivity

517

Jak stworzyć prosty zwijany pasek narzędzi?

518

Jak dodać obrazek do zwijanego paska narzędzi?

523

Zaktualizowany kod układu activity_order.xml

524

Przyciski FAB i paski snackbar

526

Zaktualizowany kod pliku activity_order.xml

528

Pełny kod pliku OrderActivity.java

533

Twój przybornik do Androida

535

Spis treści

13

Widoki RecyclerView i CardView

Stosuj recykling Przekonałeś się już, że kluczowym elementem większości aplikacji jest skromny widok listy. Jednak w porównaniu z poznanymi wcześniej komponentami Material Design takie listy są bardzo proste. W tym rozdziale przedstawimy widok RecyclerView — bardziej zaawansowany typ widoku listy, który zapewnia znacznie większą elastyczność i idealnie pasuje do etosu wzornictwa Material Design. Nauczysz się tworzyć adaptery dostosowane do używanych danych i dowiesz się, jak całkowicie odmieniać wygląd list, używając do tego jedynie dwóch wierszy kodu. Pokażemy Ci także, jak korzystać z widoków kart (ang. card views), by stworzyć przestrzenną prezentację danych zgodną z wytycznymi Material Design.

ViewHolder CardView

Każdy z naszych obiektów ViewHolder będzie zawierać jeden widok CardView. Plik układu dla tych widoków CardView przygotowaliśmy już we wcześniejszej części rozdziału.

Wciąż jest wiele do zrobienia w aplikacji Włoskie Co Nieco

538

Widoki RecyclerView z wysokości 3000 metrów

539

Dodanie danych pizz

541

Wyświetlenie danych pizzy na karcie

542

Jak utworzyć widok karty?

543

Kompletny kod pliku card_captioned_image.xml

544

Dodanie adaptera widoku RecyclerView

546

Zdefiniowanie obiektu ViewHolder

548

Przesłonięcie metody onCreateViewHolder()

549

Dodanie danych do widoków CardView

550

Kompletny kod pliku CaptionedImagesAdapter.java

551

Utworzenie widoku RecyclerView

553

Dodanie widoku RecyclerView do układu fragmentu PizzaFragment

554

Kompletny kod pliku PizzaFragment.java

555

RecyclerView rozmieszcza swoje widoki, używając menedżera układu

556

Określanie menedżera układu

557

Pełny kod fragmentu PizzaFragment.java

558

Zapewnienie reakcji obiektu RecyclerView na kliknięcia

566

Utworzenie aktywności PizzaDetailActivity

567

Kod pliku PizzaDetailActivity.java

569

Zapewnienie reakcji widoku RecyclerView na kliknięcia

570

Można nasłuchiwać zdarzeń z widoków w adapterze

571

Zapewnianie możliwości wielokrotnego stosowania adapterów

572

Dodanie interfejsu do adaptera

573

Implementacja interfejsu we fragmencie PizzaFragment

575

Twój przybornik do Androida

578

xix

Spis treści

14

Szuflady nawigacyjne

Z miejsca na miejsce Przekonałeś się już, w jaki sposób karty ułatwiają użytkownikom poruszanie się po aplikacji. Jeśli jednak będziemy ich potrzebowali bardzo dużo lub jeśli trzeba je rozdzielić na sekcje, to szuflada nawigacyjna będzie Twoim nowym najlepszym przyjacielem. W tym rozdziale pokażemy Ci, jak tworzyć szufladę nawigacyjną, która wysuwa się z boku ekranu po jednym kliknięciu. Dowiesz się, jak przygotować nagłówek takiej szuflady przy użyciu widoku nawigacyjnego, jak wypełnić ją zestawem elementów o zadanej strukturze, które pozwolą użytkownikom docierać do wszystkich głównych punktów aplikacji. I w końcu dowiesz się, jak przygotować obiekt nasłuchujący widoku nawigacyjnego, dzięki któremu szuflada będzie w stanie reagować na najdelikatniejsze dotknięcia i przeciągnięcia.

To jest aplikacja Koci Czat.

Widoki kart zapewniają łatwą nawigację…

580

Planujemy utworzenie szuflady nawigacyjnej w nowej aplikacji pocztowej 581 Szuflady nawigacyjne rozmontowane na czynniki pierwsze

582

Utworzenie projektu Koci Czat

584

Utworzenie fragmentu InboxFragment

585

Utworzenie fragmentu DraftsFragment

586

Utworzenie fragmentu SentItemsFragment

587

Utworzenie fragmentu TrashFragment

588

Przygotowanie układu paska narzędzi

589

Aktualizacja motywu aplikacji

590

Utworzenie aktywności HelpActivity

591

Utworzenie aktywności FeedbackActivity

592

Utworzenie nagłówka szuflady nawigacyjnej

594

Kompletny kod pliku nav_header.xml

595

Jak można grupować elementy?

598

Sekcję wsparcia dodamy jako podmenu

600

Kompletny kod pliku menu_nav.xml

601

Jak utworzyć szufladę nawigacyjną?

602

Kompletny kod układu aktywności activity_main.xml

603

Dodanie fragmentu InboxFragment do układu aktywności MainActivity 604

xx

Dodanie przełącznika szuflady

607

Reagowanie na klikanie elementów szuflady

608

Implementacja metody onNavigationItemSelected()

609

Zamknięcie szuflady po naciśnięciu przycisku Wstecz

614

Kompletny kod aktywności MainActivity

615

Twój przybornik do Androida

619

Spis treści

15

Bazy danych SQLite

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.

Jaśnie panie, oto pańska baza danych. Czy mogę jeszcze czymś służyć?

onCreate()

Pomocnik SQLite

DRINK Nazwa: Coffeina Wersja: 1

Baza danych SQLite

Znowu w kafeterii Coffeina

622

Android trwale przechowuje dane, używając baz danych SQLite

623

Android udostępnia kilka klas związanych z SQLite

624

Obecna struktura aplikacji kafeterii Coffeina

625

Zmienimy aplikację, by korzystała z bazy danych

626

Pomocnik SQLite zarządza Twoją bazą danych

627

Tworzenie pomocnika SQLite

628

Wnętrze bazy danych SQLite

630

Tabele tworzymy w języku SQL

631

Wstawianie danych za pomocą metody insert()

632

Wstawianie wielu rekordów

633

Kod klasy CoffeinaDatabaseHelper

634

Co robi kod pomocnika SQLite?

635

Co zrobić, gdy trzeba będzie zmienić bazę?

636

Bazy danych SQLite mają numer wersji

637

Co się dzieje w przypadku zmiany numeru wersji?

638

Aktualizacja bazy w metodzie onUpgrade()

640

Przywracanie starszej wersji bazy za pomocą metody onDowngrade()

641

Zaktualizujmy bazę danych

642

Aktualizacja istniejącej bazy danych

645

Aktualizacja rekordów za pomocą metody update()

646

Stosowanie warunków odnoszących się do wielu kolumn

647

Modyfikacja struktury bazy danych

649

Usuwanie tabeli

650

Pełny kod pomocnika SQLite

651

Twój przybornik do Androida

656

xxi

Spis treści

16

Proste kursory

Pobieranie 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. W tym rozdziale skoncentrujemy się na sposobach odczytywania danych z bazy danych. Dowiesz się w nim, jak używać kursora do odczytywania danych z bazy. Nauczysz się także poruszać po kursorach i uzyskiwać dostęp do umieszczonych w nich danych. I w końcu dowiesz się, jak stosować adaptery operujące na kursorach, aby używać wspólnie kursorów i widoków list.

Co się wydarzyło wcześniej…

658

Struktura nowej wersji aplikacji kafeterii Coffeina

659

Co zrobimy, by aktywność DrinkActivity zaczęła korzystać z bazy danych? 660

Hej, kursorze, potrzebuję więcej… Kursorze? Stary, jesteś tam?

Kursor Adapter CursorAdapter Jeśli zamkniemy kursor zbyt szybko, to adapter nie będzie mógł pobierać z niego dodatkowych danych.

xxii

Aktualny kod aktywności DrinkActivity

661

Pobranie referencji do bazy danych

662

Pobieranie danych z bazy za pomocą kursora

663

Zwracanie wszystkich wierszy tabeli

664

Zwracanie wierszy w określonej kolejności

665

Zwracanie wybranych rekordów

666

Dotychczasowy kod aktywności DrinkActivity

669

Aby odczytać rekord z kursora, najpierw należy do niego przejść

670

Poruszanie się po kursorze

671

Pobieranie wartości z kursora

672

Kod aktywności DrinkActivity

673

Co udało się nam zrobić?

675

Aktualny kod aktywności DrinkCategoryActivity

677

Pobranie referencji do bazy danych kafeterii…

678

Jak zastąpić tablicę przekazywaną do komponentu ListView?

679

SimpleCursorAdapter odwzorowuje dane na widoki

680

Stosowanie adaptera SimpleCursorAdapter

681

Zamykanie kursora i bazy danych

682

Ciąg dalszy opowieści

683

Zmodyfikowany kod aktywności DrinkCategoryActivity

688

Kod aktywności DrinkCategoryActivity (ciąg dalszy)

689

Twój przybornik do Androida

691

Spis treści

17

Kursory i zadania asynchroniczne

Pozostając w tle Przeważająca większość aplikacji musi aktualizować swoje dane. W poprzednim rozdziale dowiedziałeś się, jak pisać aplikacje, które odczytują dane z bazy danych SQLite. A co w przypadku, kiedy chcemy zaktualizować dane aplikacji? W tym rozdziale dowiesz się, jak sprawić, by aplikacja reagowała na poczynania użytkownika i aktualizowała informacje zapisane w bazie danych. Dowiesz się także, jak odświeżać wyświetlone dane po ich aktualizacji. I w końcu przekonasz się, że pisanie wydajnego, wielowątkowego kodu przy użyciu klasy AsyncTask pozwala zapewnić odpowiednią szybkość działania aplikacji.

onPreExecute()

doInBackground()

onProgressUpdate()

onPostExecute()

Chcemy, by nasza aplikacja aktualizowała dane w bazie

694

Dodanie pola wyboru do układu aktywności DrinkActivity

696

Wyświetlanie wartości kolumny FAVORITE

697

Odpowiadanie na kliknięcia w celu aktualizacji bazy

698

Kompletny kod aktywności DrinkActivity

701

Wyświetlanie ulubionych napojów w aktywności TopLevelActivity

705

Refaktoryzacja pliku TopLevelActivity.java

707

Nowy kod aktywności TopLevelActivity

710

Kursor można zmieniać za pomocą metody changeCursor()

715

Który kod umieścić w którym wątku?

723

Klasa AsyncTask służy do wykonywania operacji asynchronicznych

724

Metoda onPreExecute()

725

Metoda doInBackground()

726

Metoda onProgressUpdate()

727

Metoda onPostExecute()

728

Parametry klasy AsyncTask

729

Kompletny kod klasy UpdateDrinkTask

730

Kompletny kod pliku DrinkActivity.java

732

Twój przybornik do Androida

737

Podsumowanie etapów działania zadań AsyncTask

737

Latte Cappuccino Filter

Widok ListView

Adapter CursorAdapter

Kursor Baza danych

xxiii

Spis treści

18 Usługa utworzona

Usługi uruchomione

Do usług Są operacje, które będziemy chcieli wykonywać niezależnie od tego, która aplikacja jest widoczna na ekranie. Na przykład jeśli rozpoczniesz pobieranie pliku, to zapewne nie będziesz chciał, by proces pobierania był przerywany, kiedy przełączysz się do innej aplikacji. W tym rozdziale przedstawimy usługi uruchomione, komponenty, które wykonują operacje w tle. Dowiesz się, jak stworzyć usługę uruchomioną, używając klasy IntentService, oraz w jaki sposób cykl życia takiej usługi jest powiązany z cyklem życia aktywności. W międzyczasie odkryjesz, jak można rejestrować komunikaty i zadbać o to, by użytkownik zawsze był dobrze poinformowany, wykorzystując do tego usługę powiadomień.

onCreate()

Usługi działają w tle

740

Utworzymy usługę URUCHOMIONĄ

741

Użycie klasy IntentService do utworzenia prostej usługi uruchomionej 742 onStartCommand()

onHandleIntent()

Usługa działająca

onDestroy()

Jak rejestrować komunikaty?

743

Kompletny kod usługi DelayedMessageService

744

Usługi są deklarowane w pliku AndroidManifest.xml

745

Dodajemy przycisk do układu activity_main.xml

746

Usługę uruchamiamy, wywołując metodę startService()

747

Stany usług uruchomionych

750

Cykl życia usług uruchomionych: od utworzenia do usunięcia

751

Nasza usługa dziedziczy metody cyklu życia

752

Android dysponuje wbudowaną usługą obsługi powiadomień

755

Użyjemy powiadomień z biblioteki wsparcia AppCompat

756

W pierwszej kolejności tworzymy budowniczego powiadomień

757

Wysyłanie powiadomień przy użyciu wbudowanej usługi systemowej

759

Kompletny kod usługi DelayedMessageService

760

Twój przybornik do Androida

765

Usługa usunięta Aktywność MainActivity będzie używała tego układu.



activity_main.xml Aktywność przekaże do usługi fragment tekstu.

MainActivity.java

xxiv

1… 2… 3… 4… 5… 6… 7… 8… 9… 10… A oto i tekst.

Usługa wyświetli tekst po upływie 10 sekund.

DelayedMessageService.java

Spis treści

19

Usługi powiązane i uprawnienia

Powiązane ze sobą Usługi uruchomione są doskonałe do wykonywania operacji w tle, ale co zrobić, gdy potrzebujemy usługi, która będzie bardziej interaktywna? W tym rozdziale dowiesz się, jak tworzyć usługi powiązane, czyli usługi kolejnego typu, z którymi aktywności mogą wchodzić w interakcje. Dowiesz się także, jak powiązać usługę, kiedy to będzie potrzebne, oraz jak ją odłączyć w celu oszczędzania zasobów, kiedy nie będzie już potrzebna. Przy okazji nauczysz się korzystać z usług lokalizacyjnych Androida, by pobierać z odbiornika GPS urządzenia aktualne informacje o położeniu. I w końcu dowiesz się, jak używać modelu uprawnień Androida, w tym także jak obsługiwać żądania przydzielenia uprawnień zgłaszane podczas działania aplikacji.

Czy jesteśmy już blisko?

OdometerService

Usługi powiązane są skojarzone z innymi komponentami

768

Utworzenie nowej usługi

770

Zdefiniowanie obiektu Binder

771

Dodanie metody getDistance() do usługi

772

Aktualizacja układu aktywności MainActivity

773

Utworzenie obiektu ServiceConnection

775

Użycie metody bindService() do powiązania usługi

778

Użycie metody unbindService() do odłączenia aktywności od usługi

779

Wyświetlenie przebytego dystansu

780

Kompletny kod aktywności MainActivity

781

Stany usług powiązanych

787

Dodanie biblioteki wsparcia AppCompat

790

Dodanie do usługi OdometerService obiektu nasłuchującego danych o lokalizacji

792

Zaktualizowany kod usługi OdometerService

795

Wyliczenie przebytego dystansu

796

Kompletny kod pliku OdometerService.java

798

Jak poprosić o uprawnienia z poziomu aplikacji?

802

Sprawdzenie odpowiedzi na prośbę

805

Dodanie kodu wyświetlającego powiadomienia do metody onRequestPermissionsResult()

809

Kompletny kod pliku MainActivity.java

811

Twój przybornik do Androida

815

Świetnie, że odwiedziliście nas w Androidowie

816

Intencja

Android

Odometer Service

onBind()

OdometerService

xxv

Spis treści

A

Układy względne i układy siatki

Poznaj krewnych Istnieją jeszcze dwa inne układy często stosowane w Androidowie. W tej książce koncentrowaliśmy się na stosowaniu prostych układów liniowych i układów ramek, jak również przedstawiliśmy nowy układ z ograniczeniami. Jednak istnieją także dwa inne rodzaje układów, które chcielibyśmy Ci zaprezentować: układ względny oraz układ siatki. W większości zostały one zastąpione układem z ograniczeniami, niemniej jednak i tak pozostaje wiele okazji do ich stosowania, dlatego też przypuszczamy, że będą one używane jeszcze przez ładnych parę lat.

Każdy z tych obszarów jest komórką.

B xxvi

Gradle

Program do budowy Gradle Większość aplikacji na Androida jest budowana przy użyciu programu narzędziowego o nazwie Gradle. Program Gradle działa za kulisami: odnajduje i pobiera biblioteki, kompiluje i wdraża kod, wykonuje testy, czyści fugi i tak dalej… W większości przypadków możemy sobie nawet nie zdawać sprawy, że Gradle istnieje, gdyż Android Studio udostępnia do jego obsługi graficzny interfejs użytkownika. Jednak czasami warto zajrzeć mu pod maskę i ręcznie go podregulować. W tym dodatku pokażemy Ci niektóre spośród wielu talentów i umiejętności programu Gradle.

Spis treści

C

ART

Środowisko uruchomieniowe Androida Czy kiedykolwiek zastanawiałeś się, jak to się dzieje, że aplikacje na Androida mogą działać na tak wielu rodzajach urządzeń? Aplikacje na Androida są wykonywane w wirtualnej maszynie nazywanej środowiskiem uruchomieniowym Androida (ART — Android runtime), a nie w wirtualnej maszynie Javy firmy Oracle. Oznacza to, że będą one szybciej uruchamiane na niewielkich urządzeniach o niewielkiej mocy obliczeniowej i będą na nich działać bardziej efektywnie. W tym dodatku dowiesz się, jak działa ART.

classes.dex

aapt .apk

Zasoby

D

ADB

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.

adb

adbd

polecenie adb

proces demona adb

Urządzenie

Urządzenie

xxvii

Spis treści

E

Emulator Androida

Przyspieszanie emulatora 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 sprawdzić, 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.

Wszystkie wirtualne urządzenia z Androidem działają na emulatorze nazywanym QEMU.

F S

AVD

AVD

AVD

AVD

Emulator QEMU

Pozostałości

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.

Jeśli kogoś to interesuje, to bateria niedługo się rozładuje.

xxviii

AVD

Skorowidz

1. Rozpowszechnianie aplikacji

862

2. Dostawcy treści

863

3. Klasy Loader

864

4. Adaptery synchronizujące

864

5. Odbiorcy komunikatów

865

6. Klasa WebView

866

7. Ustawienia

867

8. Animacje

868

9. Widżety aplikacji

869

10. Testy zautomatyzowane

870

872

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 aut ego acz pytanie: „Dl e niezwykłe rzeczy?”. w książce te wszystki

jesteś tutaj  xxix

Jak korzystać z tej książki

Dla kogo jest ta książka? Jeśli możesz odpowiedzieć twierdząco na wszystkie poniższe pytania:

1

Czy umiesz już programować w Javie?

2

Czy chcesz być mistrzem w pisaniu aplikacji na Androida, stworzyć wspaniały program, zarobić fortunę, a na emeryturze przenieść się na swoją własną wyspę?

3

Czy wolisz coś robić i wykorzystywać zdobytą wiedzę, czy słuchać kogoś na niekończących się wykładach?

to jest to książka dla Ciebie.

Kto raczej nie powinien sięgać po tę książkę? Jeśli możesz odpowiedzieć twierdząco na któreś z poniższych pytań:

1

Czy szukasz szybkiego wprowadzenia lub podręcznika do pisania aplikacji na Androida?

2

Czy wolisz, by piętnaście wrzeszczących małp wyrwało Ci paznokcie z palców u stóp, niż nauczyć się czegoś nowego? Czy uważasz, że książka o programowaniu aplikacji na Androida powinna opisywać każde możliwe zagadnienie, a przy okazji jej lektura powinna być śmiertelnie nużąca, i to im bardziej, tym lepiej?

to ta książka nie jest dla Ciebie.

[Notatka z działu marketingu: Ta książka jest dla każdego, kto ma kartę kredytową lub konto PayPal].

xxx

Wprowadzenie

No dobrze, być może te plany są nieco zbyt dalekosiężne. Ale przecież od czegoś trzeba zacząć, prawda?

Wprowadzenie

Wiemy, co sobie myślisz „Jakim cudem to może być poważną książką o tworzeniu aplikacji na Androida?” „Po co te wszystkie obrazki?” „Czy w taki sposób można się czegokolwiek nauczyć?”

Wiemy, co sobie myśli Twój mózg

Twój mózg myśli, że właśnie TO jest istotne.

Twój mózg pragnie nowości. Zawsze szuka, przegląda i wyczekuje czegoś niezwykłego. Tak został stworzony i to pomaga Ci przetrwać. Zatem co Twój mózg robi z tymi wszystkimi standardowymi, zwyczajnymi, normalnymi informacjami, które do niego docierają? Otóż robi wszystko, co tylko może, aby nie przeszkadzały mu w jego naprawdę ważnym zadaniu — zapamiętywaniu rzeczy, które są naprawdę ważne. Twój mózg nie traci czasu i energii na zapamiętywanie nudnych informacji; one nigdy nie przechodzą przez filtr „to jest ewidentnie nieważne”. Skąd Twój mózg wie, co jest istotne? Załóżmy, że jesteś na codziennej przechadzce i nagle przed Tobą staje tygrys. Co się dzieje w Twej głowie i w Twoim ciele? Neurony płoną. Emocje szaleją. Adrenalina napływa falami. I właśnie stąd Twój mózg wie, że…

Wspaniale. Zostało jeszcze tylko 900 głupich, nudnych i drętwych stron

To musi być ważne! Nie zapominaj o tym!!

ózg Twój m, że waża ie warto u n Ale wyobraź sobie, że jesteś w domu albo w bibliotece. Jesteś w bezpiecznym TEGO tywać. miejscu — przytulnym i pozbawionym tygrysów. Uczysz się. Przygotowujesz się zapamię

do egzaminu. Albo rozgryzasz jakiś trudny problem techniczny, którego rozwiązanie, według szefa, powinno zająć Ci tydzień, maksymalnie dziesięć dni. Jest tylko jeden mały kłopot. Twój mózg stara się Ci pomóc. Próbuje sprawić, aby te ewidentnie 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 za 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 w tej chwili doświadczam, to jednak naprawdę chcę zapamiętać wszystkie te informacje”.

jesteś tutaj  xxxi

Jak korzystać z tej książki

Wyobrażamy sobie, że czytelnik tej książki jest uczniem ci trzeba to coś A zatem co jest potrzebne, żeby się czegoś nauczyć? W pierwszej kolejnoś jedynie o wtłoczenie tu poznać, a następnie postarać się tego czegoś nie zapomnieć. I nie chodzi jania przyswa ie dziedzin w zone do głowy suchych faktów. Najnowsze badania prowad czegoś więcej wymaga się uczenie że ą, pokazuj ia nauczan informacji, neurobiologii i psychologii a. działani do mózgi nasze ić pobudz potrafi co wiemy, My niż tylko czytania tekstu.

Oto niektóre z głównych założeń niniejszej książki: i sprawiają, że nauka i przekazywaniem sobie inaniem przypom staje się zdecydowanie bardziej efektywna (badania nad tywania o 89%). zapamię ność efektyw a poprawi rysunków informacji dowodzą, że wykorzystanie zy umieścić Wystarc łe. zrozumia bardziej znacznie się stają je informac że ą, Poza tym rysunki sprawiaj na następnej nie a , okolicach słowa bezpośrednio na rysunku, do którego się odnoszą, lub w jego te słowa którego , problem ć rozwiąza stanie w stronie, a prawdopodobieństwo, że osoby uczące się będą dotyczą, wzrośnie niemal dwukrotnie. zych badań, w testach Stosuj konwersacyjny i spersonalizowany styl. Jak wynika z najnows wana w sposób przekazy była treść jeśli lepsze, 40% o końcowych studenci uzyskiwali wyniki . Zamiast robić wykład, formalny sposób w nie a y, rozmow ji konwenc w i osobie j bezpośredni, w pierwsze e. Kiedy byłbyś poważni zbyt osoby opowiadaj historyjki. Używaj zwyczajnego języka. Nie traktuj swojej ? wykładu podczas czy bardziej uważny — podczas rozmowy przy obiedzie zmusisz neuronów do Zmuś ucznia do głębszego zastanowienia się. Innymi słowy: jeśli nie musi być zmotywowany, Czytelnik . wielkiego nic się aktywnego wysiłku, w Twojej głowie nie zdarzy ów, wyciąganiem wniosków problem waniem rozwiązy ytowany podeksc i zaangażowany, zaciekawiony stawianie wyzwań, i zdobywaniem nowej wiedzy. A osiągnięcie tego wszystkiego jest możliwe poprzez ienia się oraz zastanow do cych zapraszanie do wykonywania ćwiczeń i zadawanie pytań zmuszają ych i kilku zmysłów. mózgow półkul obu owania zaangaż ją poprzez nakłanianie do działań, które wymaga się kiedyś w sytuacji, gdy Przyciągnij — i zatrzymaj na dłużej — uwagę czytelnika. Każdy znalazł Mózg zwraca uwagę na strony. j pierwsze aniu przeczyt po zasypiał bardzo chciał się czegoś nauczyć, lecz poznawanie nowego Jednak iwane. nieoczek wzrok, ające przykuw dziwne, ące, rzeczy niezwykłe, interesuj ące, Twój mózg interesuj nie zagadnienia technicznego wcale nie musi być nudne. Jeśli będzie to zagadnie przyswoi je znacznie szybciej. ji jest w znacznej mierze Wyzwól emocje. Teraz już wiemy, że zdolność zapamiętywania informac Zapamiętujemy zależy. nam czym na to, tujemy zależna od ich zawartości emocjonalnej. Zapamię jących historii wzrusza myśli na tu mamy nie cie Oczywiś my. w sytuacjach, w których coś odczuwa podekscytowanie, radosne ść, ciekawo nie, zaskocze jak takie emocje o nam Chodzi psie. o chłopcu i jego nie zagadki, rozwiąza y znajdziem gdy „o rany…” i poczucie satysfakcji — „jestem wielki!” — jakie mamy, więcej znamy że sprawę, sobie zdamy lub trudne, nauczymy się czegoś, co powszechnie uchodzi za i. inżynieri działu z szczegółów technicznych niż Zenek

Zobrazuj to. Rysunki są znacznie łatwiejsze do zapamiętania niż same słowa

xxxii Wprowadzenie

Wprowadzenie

Metapoznanie — myślenie o myśleniu Jeśli naprawdę chcesz się czegoś nauczyć i jeśli chcesz się tego nauczyć szybciej i lepiej, to zwracaj uwagę na to, jak koncentrujesz uwagę. Myśl o tym, jak myślisz. Dowiedz się, jak przyswajasz wiedzę. Większość z nas w okresie dorastania nie uczestniczyła w zajęciach z metapoznania albo teorii nauczania. Oczekiwano od nas, że będziemy się uczyć, ale nie uczono nas, jak mamy to robić.

Zastanawiam się, jak zmusić mózg do zapamiętania tych informacji…

Zakładamy jednak, że jeśli trzymasz w ręku tę książkę, to chcesz się nauczyć programować aplikacje na Androida. I prawdopodobnie nie chcesz na to tracić zbyt wiele czasu. Jeśli chcesz wykorzystać to, co przeczytałeś w tej książce, musisz to zapamiętać. A do tego musisz to zrozumieć. Aby w możliwie jak największym stopniu wykorzystać zarówno tę, jak i dowolną inną książkę lub jakikolwiek sposób uczenia się, musisz wziąć odpowiedzialność za swój mózg. Myśl o tym, czego się uczysz. Sztuczka polega na tym, aby przekonać mózg, że poznawany materiał jest naprawdę ważny. Kluczowy dla Twojego dobrego samopoczucia. Tak ważny jak tygrys stojący naprzeciw Ciebie. W przeciwnym razie będziesz prowadzić nieustającą wojnę z własnym mózgiem, który ze wszystkich sił będzie się starał, aby nowe informacje nie zostały utrwalone.

A zatem jak ZMUSIĆ mózg, aby potraktował programowanie jak głodnego tygrysa? Można to zrobić w sposób powolny i męczący lub szybki i bardziej efektywny. Powolny sposób polega na wielokrotnym powtarzaniu. Oczywiście wiesz, że jesteś w stanie przyswoić i zapamiętać nawet najnudniejsze zagadnienie, mozolnie je wkuwając. Po odpowiedniej liczbie powtórzeń Twój mózg stwierdzi: „Zdaje się, że to nie jest dla niego szczególnie ważne, lecz w kółko to czyta i powtarza, więc przypuszczam, że jakąś wartość to jednak musi mieć”. Szybszy sposób polega na zrobieniu czegoś, co zwiększy aktywność mózgu, zwłaszcza jeśli czynność ta wyzwoli kilka różnych typów aktywności. Wszystkie zagadnienia, o których pisaliśmy na poprzedniej stronie, są kluczowymi elementami rozwiązania i udowodniono, że wszystkie potrafią pomóc w zmuszeniu mózgu do tego, aby pracował na Twoją korzyść. Na przykład badania dowodzą, że umieszczenie słów na opisywanych rysunkach (a nie w innych miejscach tekstu na stronie, na przykład w nagłówku lub wewnątrz akapitu) sprawia, iż mózg stara się zrozumieć relację pomiędzy słowami a rysunkiem, a to z kolei zwiększa aktywność neuronów. Większa aktywność neuronów to większa szansa na to, że mózg uzna informacje za warte zainteresowania i, ewentualnie, zapamiętania. Prezentowanie informacji w formie konwersacji pomaga, ponieważ ludzie zdają się wykazywać większe zainteresowanie w sytuacjach, gdy uważają, że biorą udział w rozmowie, bo oczekuje się od nich, iż będą śledzić jej przebieg i brać w niej czynny udział. Zadziwiające jest to, iż mózg zdaje się nie zważać na to, że rozmowa jest prowadzona z książką! Natomiast jeśli sposób przedstawiania informacji jest formalny i suchy, mózg postrzega to tak samo jak w sytuacji, gdy uczestniczysz w wykładzie na sali pełnej sennych studentów. Nie ma potrzeby wykazywania jakiejkolwiek aktywności. Ale rysunki i rozmowa to dopiero początek…

jesteś tutaj  xxxiii

Jak korzystać z tej książki

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ąca słów. W sytuacjach, gdy mieliśmy do czynienia zarówno z tekstem, jak i z rysunkiem, 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. Zastosowaliśmy powtórzenia, wielokrotnie podając tę samą informację na różne sposoby i przy wykorzystaniu różnych środków przekazu oraz odwołując się do różnych zmysłów. Wszystko to po to, aby zwiększyć szansę na to, że informacja zostanie zakodowana w większej liczbie obszarów Twojego mózgu. 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 bardziej się koncentruje, kiedy 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 lubić instrukcje opisujące sposób postępowania krok po kroku, ktoś inny może woleć analizowanie zagadnienia opisanego ogólnie, a jeszcze inna osoba może chcieć przejrzeć przykładowy fragment kodu. Jednak niezależnie od ulubionego sposobu nauki każdy skorzysta na tym, że te same informacje będą przedstawiane kilkakrotnie, 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 będziesz mógł koncentrować się na nauce. Ponieważ angażowanie jednej półkuli mózgu często oznacza, że druga może odpocząć, będziesz mógł się uczyć bardziej produktywnie przez dłuższy czas. Dodatkowo zamieściliśmy w tej książce opowiadania i ćwiczenia prezentujące więcej niż jeden punkt widzenia, ponieważ mózg uczy się łatwiej, gdy jest zmuszony do przetwarzania danych i wyrażania 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 nad czymś popracować. Pomyśl sam: nie można poprawić swojej kondycji, obserwując ćwiczenia w telewizji. Ale dołożyliśmy wszelkich starań, aby zapewnić, że gdy pracujesz, 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. Przedstawiliśmy ludzi — w opowiadaniach, w przykładach, na rysunkach i tak dalej — bo Ty też jesteś człowiekiem. Dlatego Twój mózg zwraca większą uwagę na ludzi niż na rzeczy.

xxxiv Wprowadzenie

Wprowadzenie

Oto co możesz zrobić TY, aby zmusić swój mózg do posłuszeństwa Wytnij te porady A zatem zrobiliśmy, co było w naszej mocy. Reszta zależy od Ciebie. Możesz zacząć i przyklej na lodówce. od poniższych porad. Posłuchaj swojego mózgu i określ, które sprawdzają się w Twoim

przypadku, a które nie dają pozytywnych rezultatów. Próbuj nowych rzeczy.

1 Zwolnij — im więcej rozumiesz, tym mniej musisz

zapamiętać Nie ograniczaj się jedynie do czytania. Przerwij na chwilę lekturę i pomyśl. Kiedy znajdziesz w tekście pytanie, nie zaglądaj od razu na stronę z odpowiedzią. Wyobraź sobie, że ktoś faktycznie zadaje Ci pytanie. Im bardziej zmusisz swój mózg do myślenia, tym większa będzie szansa na to, że się czegoś nauczysz i coś zapamiętasz.

2 Rób ćwiczenia, notuj Zamieściliśmy ćwiczenia w książce, ale gdybyśmy zrobili je za Ciebie, to niczym nie różniłoby się to od sytuacji, w której ktoś za Ciebie wykonywałby ćwiczenia fizyczne. I nie ograniczaj się jedynie do czytania ćwiczeń. Używaj ołówka. Można znaleźć wiele dowodów na to, że fizyczna aktywność podczas nauki może poprawić jej efekty.

3 Czytaj fragmenty oznaczone jako „Nie istnieją

głupie pytania” Chodzi tu o wszystkie fragmenty umieszczone z boku tekstu. Nie są to fragmenty opcjonalne — stanowią one część podstawowego tekstu książki! Nie pomijaj ich.

4 Niech lektura tej książki będzie ostatnią rzeczą, jaką

robisz przed pójściem spać — a przynajmniej ostatnią rzeczą stanowiącą wyzwanie intelektualne Pewne elementy procesu uczenia się (a w szczególności przenoszenie informacji do pamięci długoterminowej) następują po odłożeniu książki. Mózg potrzebuje trochę czasu dla siebie i musi dodatkowo przetworzyć dostarczone informacje. Jeśli w tym potrzebnym na wykonanie dodatkowego przetwarzania czasie zmusisz mózg do innej działalności, to część z przyswojonych informacji może zostać utracona.

5 Mów o zdobywanych informacjach — głośno Mówienie aktywuje odmienne obszary mózgu. Jeśli próbujesz coś zrozumieć lub zwiększyć szansę na zapamiętanie informacji na dłużej, powtarzaj to na głos. A jeszcze lepiej — staraj się to na głos komuś wytłumaczyć. W ten sposób nauczysz się szybciej, a ponadto będziesz mógł odkryć kwestie, o których istnieniu nie wiedziałeś podczas czytania książki.

6 Pij wodę, dużo wody

Mózg pracuje najlepiej, gdy dostarcza mu się dużo płynów. Odwodnienie (do którego może dojść, nawet zanim poczujesz pragnienie) obniża zdolność percepcji.

7 Posłuchaj swojego mózgu

Zauważaj momenty, kiedy Twój mózg staje się przeciążony. Jeśli spostrzeżesz, że zaczynasz czytać pobieżnie i zapominać, o czym przeczytałeś przed chwilą, to najwyższy czas, żeby sobie zrobić przerwę. Po przekroczeniu pewnego punktu nie będziesz się uczył szybciej, „wciskając” do głowy więcej informacji; co gorsze, może to zaszkodzić całemu procesowi nauki.

8 Poczuj coś!

Twój mózg musi wiedzieć, że to, czego się uczysz, jest ważne. Z zaangażowaniem śledź zamieszczane w tekście opowiadania. Nadawaj własne tytuły zdjęciom. Zalewanie się łzami ze śmiechu po przeczytaniu głupiego dowcipu jest lepsze od braku jakiejkolwiek reakcji.

9 Pisz jak najwięcej kodu

Istnieje tylko jeden sposób, by nauczyć się programowania aplikacji na Androida: pisanie kodu. A im go będzie więcej, tym lepiej. I właśnie to będziesz robić podczas lektury tej książki. Pisanie programów jest umiejętnością, a jedynym sposobem jej nabycia jest ciągła praktyka. Mamy zamiar dać Ci do niej wiele okazji: w każdym rozdziale zamieściliśmy ćwiczenia stawiające przed Tobą problemy, które możesz rozwiązać. Nie pomijaj ich — podczas wykonywania ćwiczeń możesz się bardzo wiele nauczyć. W książce znajdziesz także rozwiązania wszystkich ćwiczeń — śmiało do nich zaglądaj, jeśli utkniesz na jakimś zadaniu! (Łatwo jest utknąć na jakiejś drobnostce). Staraj się jednak rozwiązać problem samodzielnie, zanim zajrzysz do odpowiedzi. I koniecznie, zanim przejdziesz do dalszej części książki, postaraj się uruchomić programy, nad którymi pracujesz.

jesteś tutaj  xxxv

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.

Zakładamy, że nowością jest dla Ciebie Android, ale nie Java Aplikacje na Androida będziemy pisać, używając kombinacji kodu pisanego w Javie i kodu XML. Zakładamy, że miałeś już wcześniej kontakt z językiem Java. Jeśli jeszcze nie napisałeś w ogóle żadnego programu w Javie, to prawdopodobnie powinieneś sięgnąć po książkę Java. Rusz głową! Wydanie II.

Zaczynamy od napisania aplikacji w rozdziale 1. 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 się rzucić na głęboką wodę i zacząć pisać własne aplikacje. Przy okazji poznasz trochę Android Studio — oficjalne IDE do pisania aplikacji na Androida.

Przykłady zostały zaprojektowane pod kątem nauki Podczas lektury niniejszej książki napiszesz kilka różnych aplikacji. Niektóre z nich są bardzo małe, co pozwoli Ci się skoncentrować 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/andrr2.zip.

Aktywności NIE są opcjonalne Ć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 opisywanych zagadnień, a jeszcze inne — pomóc w wykorzystaniu nabytej wiedzy i zdobytych umiejętności. Nie pomijaj tych ćwiczeń!

xxxvi Wprowadzenie

Wprowadzenie

Powtórzenia są celowe i ważne 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 wiadomości nie jest celem, ale w tej książce chodzi o naukę, dlatego znajdziesz w niej wiele pojęć, które pojawiają się kilka razy.

Do ćwiczeń z cyklu „Wysil szare komórki” nie podaliśmy odpowiedzi 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.

jesteś tutaj xxxvii

Zespół recenzentów

Zespół recenzentów technicznych

Andy

Jacqui

Recenzenci techniczni książki Andy Parker pracuje obecnie jako kierownik działu programistycznego, jednak w różnych momentach swojej kariery zajmował się także badaniami fizycznymi oraz był nauczycielem, projektantem, recenzentem i kierownikiem zespołu. Jednak niezależnie od zajmowanego stanowiska i pełnionej funkcji nigdy nie stracił pasji do tworzenia doskonale zaprojektownego oprogramowania o najwyższej jakości. Obecnie najwięcej czasu spędza, kierując zespołami pracującymi według metodologii programowania zwinnego i dzieląc się swoim ogromnym doświadczeniem z następnymi generacjami programistów.

xxxviii Wprowadzenie

Jacqui Cope zaczęła programować, by uniknąć szkolnych zajęć z netballu. Od tego czasu zdobyła już 30-letnie doświadczenie w pracy nad przeróżnymi systemami finansowymi, zaczynając do programowania w języku COBOL, a kończąc na zarządzaniu. Ostatnio uzyskała tytuł magistra w dziedzinie bezpieczeństwa komputerowego i zajęła się pracą związaną z kontrolą jakości w sektorze akademickim. W wolnym czasie Jacqui lubi gotować, wędrować po okolicy i ukrywając się za sofą, oglądać Doktora Who.

Wprowadzenie

Podziękowania Dla naszej redaktorki Chcielibyśmy serdecznie podziękować naszej cudownej redaktorce Dawn Schanafelt za pracę nad drugim wydaniem tej książki. Dawn była naprawdę cudowna, a praca z nią była wspaniała. Dawn sprawiła, że czuliśmy się docenieni; na każdym kroku odczuwaliśmy jej wsparcie i zawsze dawała Dawn Schanafelt nam bezcenne rady dokładnie wtedy, gdy ich potrzebowaliśmy. Bardzo dziękujemy także Bertowi Batesowi za nauczenie nas, jak odrzucić stary zestaw reguł, oraz za wpuszczenie nas do swojego mózgu. Dla zespołu O’Reilly Wielkie podziękowania składamy Mike’owi Hendricksonowi za wiarę, jaką w nas pokładał, i za zaproponowanie nam napisania drugiego wydania tej książki; Heather Scherer za jej ukryte talenty organizacyjne i kierownicze, zespołowi wstępnego wydania za pracę nad udostępnieniem do pobrania wstępnej wersji niniejszej książki oraz zespołowi projektantów za dodatkową pomoc. I w końcu chcieliśmy także podziękować całemu zespołowi produkcyjnemu za czuwanie nad tą książką na każdym etapie jej produkcji oraz za bardzo ciężką, choć czasami trudną do zauważenia pracę. Dla rodziny, przyjaciół i kolegów Pisanie książki z serii Rusz głową!, i to nawet jej drugiego wydania, jest jak jazda kolejką górską, a praca nad tą książką nie była żadnym wyjątkiem. Naprawdę bardzo doceniamy uprzejmość i wsparcie, jakich podczas przygotowywania tej publikacji doświadczyliśmy od naszej rodziny i naszych przyjaciół. Specjalne podziękowania składamy: Ianowi, Stevowi, Colin, Angeli, Paulowi B, Chrisowi, Michaelowi, Mamie, Tacie, Carlowi, Robowi oraz Lorraine. Dla pozostałych osób Nasz zespół recenzentów technicznych wykonał doskonałą robotę, trzymając nas w ryzach i dbając o to, żeby zagadnienia przedstawione w tej książce były opisane dokładnie i prawidłowo. Jesteśmy niezwykle wdzięczni Ingowi Krotzkiemu za jego cenne uwagi dotyczące wstępnego wydania tej książki oraz wszystkim osobom, które podzieliły się z nami opiniami na temat jej pierwszego wydania. Uważamy, że dzięki Wam ta książka stała się dużo, dużo lepsza. I w końcu chcieliśmy podziękować Kathy Sierze i Bertowi Batesowi za stworzenie tej niesamowitej serii książek.

jesteś tutaj  xxxix

xl

Wprowadzenie

1. Zaczynamy

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…

to jest nowy rozdział 

1

Android — przegląd

Nasze aplikacje na Androida będziemy tworzyć, używając połączenia Javy i XML-a. W trakcie prac wszystko dokładnie wyjaśnimy, jednak aby w pełni skorzystać z tej książki, musisz dysponować w miarę dobrą znajomością języka Java.

Witamy w Androidowie Android jest najpopularniejszą na świecie mobilną platformą systemową. Według ostatnich szacunków na świecie jest obecnie używanych ponad miliard urządzeń z tym systemem, przy czym ich liczba błyskawicznie rośnie.

Android jest kompleksową platformą o otwartym kodzie źródłowym, bazującą na Linuksie i wspieraną przez Google’a. Stanowi on potężną platformę programistyczną, zawierającą absolutnie wszystko, czego potrzeba do tworzenia wspaniałych aplikacji z użyciem języków Java i XML. Co więcej, Android pozwala na rozpowszechnianie tych aplikacji i uruchamianie ich na bardzo wielu różnych urządzeniach — telefonach, tabletach itd. jak mają Układy informują, A zatem z czego składa się typowa aplikacja na Androida?

ególne wyglądać poszcz ji. ac lik ap ny ekra

Układy określają postać poszczególnych ekranów Typowa aplikacja na Androida składa się z jednego lub kilku ekranów. Postać każdego z tych ekranów definiowana jest przy użyciu układu. Te układy zazwyczaj są tworzone w języku XML i mogą zawierać komponenty graficznego interfejsu użytkownika (w skrócie GUI, od angielskich słów graphical user interface), takie jak przyciski, pola tekstowe czy etykiety.

Aktywności definiują, co robi aplikacja Jednak układy określają jedynie wygląd aplikacji. To, co aplikcja robi, definiujemy, pisząc jedną lub więcej aktywności. Aktywność to specjalna klasa napisana w języku Java, określająca, którego układu należy użyć i jak należy odpowiadać na czynności wykonywane przez użytkownika. Na przykład jeśli układ zawiera przycisk, to w aktywności musimy Aktywności napisać kod, który określi, co aplikacja ma zrobić po naciśnięciu definiują, co aplikacja tego przycisku. ma robić.

Czasami konieczne są także dodatkowe zasoby Oprócz kodu Javy oraz układów aplikacje na Androida często będą potrzebowały także zasobów dodatkowych, takich jak obrazki lub dane. A zatem do tworzonych aplikacji można dołączać takie dodatkowe pliki. Innymi słowy: aplikacje na Androida są w zasadzie grupami plików umieszczonych w odpowiednich katalogach. Podczas budowania aplikacji wszystkie te pliki zostają ze sobą połączone i wspólnie tworzą aplikację, którą można uruchomić na urządzeniu.

2

Rozdział 1.

Zasoby mogą zawierać na przykład pliki dźwiękowe i graficzne.

Zaczynamy

Platforma Android w szczegółach

Spokojnie

Platforma Android składa się z kilku różnych komponentów. Tworzą ją standardowe aplikacje, takie jak Kontakty, interfejsy programistyczne, API, pomagające nam w kontrolowaniu wyglądu i działania aplikacji, jak również bardzo wiele dodatkowych plików i bibliotek. Poniższy schemat pokazuje, w jaki sposób tworzą one jedną spójną platformę systemową. Android jest wyposażony w zestaw podstawowych aplikacji, takich jak Kontakty, Kalendarz i Mapy, oraz w przeglądarkę WWW. Pisząc własne aplikacje, mamy dostęp do tych samych podstawowych API, które są używane przez podstawowe aplikacje. Używając tych API, kontrolujemy wygląd aplikacji i jej działanie.

Poniżej frameworku aplikacji ukryty jest zestaw bibliotek C i C++. Dostęp do nich zapewniają API należące do frameworku aplikacji. Poniżej wszystkich pozostałych warstw platformy Android umieszczone jest jądro systemu Linux. To ono odpowiada za obsługę sterowników oraz podstawowych usług, takich jak zabezpieczenia i zarządzanie pamięcią.

Nie przejmuj się, jeśli masz wrażenie, że to bardzo dużo jak na początek.

Pokazujemy Ci tutaj poglądowy schemat wszystkiego, co składa się na platformę systemu Android. Jego poszczególne komponenty będziemy opisywać bardziej szczegółowo w dalszej części książki, kiedy pojawi się taka konieczność.

Aplikacje Aparat

Kontakty

Internet

Telefon

...

Framework aplikacji Menedżer okien

Menedżer aktywności Menedżer pakietów

Menedżer telefonii

System widoków

Dostawcy treści

Menedżer lokalizacji

Menedżer zasobów

Biblioteki Surface Manager

Media Framework

SQLite

OpenGL | ES

FreeType

WebKit

SGL

SSL

libc

Menedżer powiadomień

Środowisko wykonawcze Android Podstawowe biblioteki

Jądro systemu Linux Sterownik ekranu

Sterownik aparatu

Sterownik pamięci flash

Binder (IPC)

Sterownik klawiszy

Sterownik WiFi

Sterownik audio

Zarządzanie energią

Środowisko uruchomieniowe Androida jest wyposażone w zestaw podstawowych bibliotek, implementujących przeważającą część języka programowania Java. Każda aplikacja na Androida jest wykonywana we własnym procesie.

Na szczęście okazuje się, że te wszystkie potężne biblioteki Androida są udostępniane przez interfejsy programowania aplikacji — API — wchodzące w skład frameworku do tworzenia aplikacji i to właśnie z tych API będziemy korzystać, pisząc własne programy. Zatem wszystkim, czego potrzebujesz, by zacząć, jest znajomość języka programowania Java i pomysł na wspaniałą aplikację.

jesteś tutaj 

3

Czynności

Oto, co mamy zamiar zrobić A zatem, bez niepotrzebnego przeciągania, spróbujmy utworzyć pierwszą, prostą aplikację na Androida. W tym celu musimy wykonać kilka czynności:

1

Przygotować środowisko programistyczne.

2

Napisać i zbudować prostą aplikację.

3

Uruchomić aplikację w emulatorze.

W pierwszej kolejności musimy zainstalować Android Studio — środowisko programistyczne zawierające wszystkie narzędzia niezbędne do pisania aplikacji na Androida.

Używając Android Studio, przygotujemy prostą aplikację, która wyświetla na ekranie jakiś tekst.

Skorzystamy z wbudowanego emulatora, by uruchomić aplikację i przekonać się, jak działa.

4

Zmodyfikować aplikację.

I na koniec wprowadzimy kilka zmian w aplikacji stworzonej w kroku 2.

Nie istnieją

P: Czy wszystkie aplikacja na

P: W jakim stopniu muszę znać

P: Czy muszę znać biblioteki Swing

O: Aplikacje na Androida można pisać

O: Musisz naprawdę dobrze znać Java

O: Android nie używa ani biblioteki

Androida są pisane w Javie?

także w innych językach. Większość programistów używa języka Java, dlatego też to właśnie jego będziemy używać w tej książce.

4

głupie pytania

Rozdział 1.

Javę, by pisać aplikacje na Androida?

SE (Standard Edition). Jeśli nie czujesz się pewnie, to radzimy sięgnąć najpierw po książkę Java. Rusz głową! napisaną przez Kathy Sierrę i Berta Batesa.

i AWT?

Swing, ani biblioteki AWT, dlatego nie przejmuj się, jeśli nie masz doświadczeń w pisaniu w Javie aplikacji o graficznym interfejsie użytkownika.

Zaczynamy

Jesteś tutaj.

¨  Przygotowanie środowiska

Środowisko programistyczne Java jest najpopularniejszym językiem używanym do pisania aplikacji na Androida. 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.

¨  Stworzenie aplikacji ¨  Uruchomienie aplikacji ¨  Modyfikacja aplikacji

Wszystkie te narzędzia wchodzą w skład Android SDK. Spójrzmy zatem, co on zawiera.

Android SDK Android SDK (Software Development Kit) to zestaw narzędzi i bibliotek niezbędnych do pisania aplikacji na Androida. Oto kilka jego głównych elementów: Platforma SDK

Dokumentacja

Istnieje jedna taka platforma dla każdej wersji Androida.

By móc korzystać z najnowszej dokumentacji bez połączenia z internetem.

Narzędzia SDK

Narzędzia do debugowania i testowania oraz inne przydatne programy.

Aplikacje przykładowe

Wsparcie dla Androida

id ro

Dodatkowe API, które nie wchodzą w skład standardowej platformy.

SDK

d An

Jeśli do zrozumienia niektórych API potrzebujesz kodu praktycznych przykładów, to mogą Ci w tym pomóc aplikacje przykładowe.

Płatności Google Play

Pozwalają na zintegrowanie z aplikacją mechanizmów do rozliczeń finansowych.

Android Studio to specjalna wersja IntelliJ IDEA IntelliJ IDEA jest jednym z najpopularniejszych zintegrowanych środowisk programistycznych (w skrócie: IDE) do pisania programów w Javie. Android Studio jest wersją tego IDE, która zawiera Android SDK i dodatkowe narzędzia graficzne wspomagające tworzenie aplikacji na Androida. Oprócz edytora oraz dostępu do narzędzi i bibliotek wchodzących w skład Android SDK Android Studio udostępnia także szablony, które mogą bardzo pomóc w tworzeniu nowych aplikacji i klas i znacząco ułatwiają wykonywanie takich czynności jak pakowanie i uruchamianie aplikacji.

jesteś tutaj 

5

Instalacja

Zainstaluj Android Studio Zanim zajmiemy się czymkolwiek innym, będziesz musiał zainstalować na swoim komputerze Android Studio. Nie zamieszczamy instrukcji instalacji w tej książce, gdyż całkiem szybko mogą się one zdezaktualizować, jednak bez problemu poradzisz sobie, korzystając z informacji zamieszczonych w internecie. Zacznij od sprawdzenia wymagań systemowych Android Studio, które można znaleźć na stronie: http://developer.android.com/sdk/index.html#Requirements Następnie postępuj zgodnie z instrukcją instalacji Android Studio dostępną na stronie: https://developer.android.com/studio/install.html?pkg=studio Po zainstalowaniu Android Studio uruchom je i postępuj zgodnie z instrukcjami, by zainstalować najnowsze narzędzia SDK i biblioteki. 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.

¨  Przygotowanie środowiska ¨  Stworzenie aplikacji ¨  Uruchomienie aplikacji ¨  Modyfikacja aplikacji W tej książce używamy Android Studio w wersji 3.0. Aby w pełni wykorzystać informacje zamieszczone w tej książce, będziesz musiał używać Android Studio w wersji 2.3 lub nowszej. witrynie firmy Adresy stron w do czasu się u as cz od dą Google zatem te nie bę zmieniają. Jeśli o, to będziesz ow idł działać praw szukać. musiał trochę po

Zaczynamy Nie istnieją

głupie pytania

P: Napisaliście, że do pisania aplikacji P: A czy można pisać aplikacje na na Androida będziemy używać Android Android bez używania żadnego IDE? Studio. Czy to konieczne? O: Tak, można, lecz wymaga to znacznie więcej pracy. Większość aplikacji na O: Precyzyjnie rzecz ujmując, Android Androida jest obecnie przygotowywana Studio nie jest niezbędne do pisania aplikacji na Androida. Będziesz potrzebować dowolnego narzędzia umożliwiającego pisanie i kompilację kodu Javy oraz paru innych specjalistycznych narzędzi służących do konwersji skompilowanego kodu do postaci, w której urządzenia z Androidem będą go w stanie wykonywać. Android Studio jest oficjalnym IDE służącym do pisania aplikacji na Androida, a zespół twórców Androida zaleca stosowanie właśnie tego środowiska. Całkiem sporo programistów używa jednak środowiska IntelliJ IDEA.

przy użyciu programu do budowania o nazwie Gradle. Projekty gradle można tworzyć i budować, używając dowolnego edytora tekstów i wiersza poleceń.

P: Narzędzie do budowania?

Czy gradle to coś takiego jak ANT?

O: Owszem, lecz gradle ma nieporównanie

większe możliwości. Gradle, podobnie jak ANT, potrafi kompilować i wdrażać kod, ale poza tym korzysta także z Maven do pobierania wszystkich dodatkowych bibliotek używanych w kodzie. Gradle używa Groovy

jako języka skryptowego, co oznacza, że całkiem łatwo można tworzyć bardzo złożone skrypty do budowy projektów.

P: Większość aplikacji jest budowana przy użyciu gradle? Napisaliście chyba wcześniej, że bardzo wielu programistów używa Android Studio?

O: Android Studio udostępnia graficzny

interfejs do obsługi gradle, jak również wiele innych narzędzi służących do tworzenia układów, odczytu dzienników oraz debugowania. Więcej informacji na temat Gradle możesz znaleźć w Dodatku B.

Stwórzmy prostą aplikację Skoro już przygotowałeś własne środowisko programistyczne, jesteś gotowy do stworzenia pierwszej aplikacji na Androida. Oto, jak ona będzie wyglądać: Ta aplikacja jest bardzo prosta, ale jak na Twoją pierwszą aplikację na Androida w zupełności wystarczy.

To jest nazwa aplikacji.

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.

Jak stworzyć aplikację? Tworząc nową aplikację, zawsze trzeba utworzyć dla niej nowy projekt. Upewnij się, czy Android Studio jest uruchomione, a później wykonaj następujące czynności:

1. Utworzenie nowego projektu Ekran powitalny Android Studio udostępnia kilka opcji. Ponieważ chcemy utworzyć nowy projekt, kliknij opcję Start a new Android Studio project.

Kliknij tę opcję, aby rozpocząć tworzenie nowego projektu Android Studio.

Tu będą wyświetlane wszystkie projekty, które stworzyłeś już wcześniej. Ponieważ ten projekt jest pierwszy, lista jest jeszcze pusta.

8

Rozdział 1.

¨  Przygotowanie środowiska ¨  Stworzenie aplikacji ¨  Uruchomienie aplikacji ¨  Modyfikacja aplikacji

Zaczynamy

Jak stworzyć aplikację? (ciąg dalszy)

¨  Przygotowanie środowiska ¨  Stworzenie aplikacji ¨  Uruchomienie aplikacji ¨  Modyfikacja aplikacji

2. Konfiguracja projektu Teraz musisz skonfigurować aplikację, podając Android Studio jej nazwę, nazwę firmowej domeny oraz lokalizację plików aplikacji na dysku. Android Studio używa nazwy firmowej domeny i nazwy aplikacji do wygenerowania nazwy pakietu aplikacji. Na przykład jeśli nadasz aplikacji nazwę Moja pierwsza apka, a nazwą firmowej domeny będzie hfad.com, to nazwą pakietu użytą przez Android Studio będzie com.hfad. mojapierwszaapka. Nazwa pakietu jest naprawdę ważna w Androidzie, gdyż urządzenia działające pod kontrolą tego systemu operacyjnego używają jej, by w unikalny sposób identyfikować każdą aplikację.

Obejrzyj to!

Nazwa pakietu już na zawsze zostanie taka sama.

Stanowi ona unikalny identyfikator aplikacji i jest używana do zarządzania jej wieloma wersjami.

Wpisz zatem Moja pierwsza apka jako nazwę aplikacji (w polu Application name) i hfad.com jako nazwę domeny (w polu Company domain), usuń zaznaczenie pola wyboru Include C++ support, a potem zaakceptuj sugerowaną lokalizację projektu. Następnie kliknij przycisk Next. Niektóre wersje Android Studio mogą wyświetlać jeszcze jedno pole wyboru, Include Kotlin support, pozwalające określić, czy należy dodać do projektu wsparcie dla języka programowania Kotlin, czy nie. Jeśli zobaczysz to pole wyboru, to upewnij się, że nie będzie zaznaczone.

Kreator utworzył nazwę pakietu na podstawie nazwy aplikacji i nazwy firmowej domeny.

Nazwa aplikacji jest wyświetlana w sklepie Google Play i w wielu innych miejscach.

W polu Company domain wpisz hfad.com

Wszystkie pliki tego projektu będą przechowywane w tym katalogu.

Usuń zaznaczenie pola wyboru Include C++ support. Jeśli w oknie będzie widoczne pole wyboru Include Kotlin support, to upewnij się, że także ono nie będzie zaznaczone.

jesteś tutaj 

9

Poziom API

Jak stworzyć aplikację? (ciąg dalszy) 3. Określenie poziomu 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. W tym przykładzie wybierzemy API poziomu 19, co oznacza, że aplikacja będzie 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. Kiedy już wybierzesz odpowiednie opcje, kliknij przycisk Next.

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.

¨  Przygotowanie środowiska ¨  Stworzenie aplikacji ¨  Uruchomienie aplikacji ¨  Modyfikacja aplikacji

Na następnej stronie znajdziesz . więcej informacji o poziomach API

Zaczynamy

Wersje Androida pod lupą Prawdopodobnie słyszałeś sporo słodkich rzeczy związanych z Androidem. Chodzi o takie terminy jak Jelly Bean, KitKat, Lollipop czy też Nougat1. O co chodzi z tymi słodyczami? Wszystkie wersje Androida mają swój numer i swoją nazwę kodową. Numer wersji dokładnie określa numer platformy Android (na przykład 7.0), a nazwa kodowa to nieco bardziej „przyjazna” nazwa, która może obejmować kilka kolejnych wersji Androida (tak jest na przykład w przypadku wersji Nougat). Poziom API odnosi się do wersji API używanych przez aplikacje. Na przykład Androidowi 7.1.1 odpowiada poziom API 25. Version

Codename

API level

1.0

1

1.1

2

1.5

Cupcake

3

1.6

Donut

4

2.0 - 2.1

Eclair

5-7

2.2.x

Froyo

8

2.3 - 2.3.7

Gingerbread

9 - 10

3.0 - 3.2

Honeycomb

11 - 13

4.0 - 4.0.4

Ice Cream Sandwich

14 - 15

4.1 0 4.3

Jelly Bean

16 - 18

4.4

KitKat

19 - 20

5.0 - 5.1

Lollipop

21 - 22

6.0

Marshmallow

23

7.0

Nougat

24

7.1 - 7.1.2

Nougat

25

Niemal nikt już nie używa tych wersji API.

Większość urządzeń używa obecnie tych wersji API.

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, lollipop to lizak, a nougat to nugat — przyp. tłum.

jesteś tutaj 

11

15 000 metrów

Aktywności i układy z wysokości 15 tysięcy metrów Kolejną rzeczą, którą musisz zrobić, jest dodanie do projektu aktywności. Każda aplikacja na Androida jest zestawem ekranów, a każdy z ekranów składa się z aktywności i układu. Aktywność (ang. activity) jest pojedynczą, precyzyjnie zdefiniowaną czynnością, 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 są zazwyczaj skojarzone z jednym ekranem, a ich kod jest pisany w Javie. 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 poszczególne elementy interfejsu użytkownika. Przyjrzyjmy się nieco dokładniej, jak aktywności i układy współpracują ze sobą, by utworzyć interfejs użytkownika aplikacji:

1

2

Urządzenie uruchamia aplikację i tworzy obiekt aktywności.

jak będzie wyglądał interfejs użytkownika. Aktywności definiują akcje.

2

Obiekt aktywności określa układ. Aktywność nakazuje urządzeniu wyświetlenie układu na ekranie.

4

Użytkownik prowadzi interakcje z układem wyświetlonym na ekranie.

5

Aktywność odpowiada na te interakcje, wykonując odpowiedni kod aplikacji.

6

Aktywność aktualizuje treści w układzie…

7

…które użytkownik widzi na ekranie.

Urządzenie

Rozdział 1.

3

Aktywność

Układ

Użytkownik



4

Skoro już wiesz nieco więcej o aktywnościach i układach, możemy wykonać kilka ostatnich kroków kreatora Create New Project i wygenerować prostą aktywność i prosty układ.

12

Układy definiują,

1

3

¨  Przygotowanie środowiska ¨  Stworzenie aplikacji ¨  Uruchomienie aplikacji ¨  Modyfikacja aplikacji

7

5

Urządzenie

6

Aktywność

Zaczynamy

Jak stworzyć aplikację? (ciąg dalszy) 4. Dodanie aktywności

¨  Przygotowanie środowiska ¨  Stworzenie aplikacji ¨  Uruchomienie aplikacji ¨  Modyfikacja aplikacji

Kolejne okno kreatora pozwala wybrać jeden szablon z grupy szablonów określających tworzoną aktywność oraz jej układ. Musisz wskazać jeden z nich. Ponieważ mamy zamiar utworzyć aplikację składającą się z prostej aktywności i układu, wybierz szablon Empty Activity (pusta aktywność) i kliknij przycisk Next.

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 Empty Aczony tivity.

jesteś tutaj 

13

Konfiguracja aktywności

Jak stworzyć aplikację? (ciąg dalszy) 5. Dostosowywanie aktywności

¨  Przygotowanie środowiska ¨  Stworzenie aplikacji ¨  Uruchomienie aplikacji ¨  Modyfikacja aplikacji

Teraz zostaniesz poproszony o podanie nazwy ekranu aktywności i układu. Jako nazwę aktywności (w polu Activity Name) wpisz MainActivity, w polu Layout Name wpisz activity_main, po czym usuń zaznaczenie z pola wyboru Backwards Compatibility (AppCompat). Aktywność jest klasą Javy, a układ — plikiem XML. Oznacza to, że podane informacje spowodują utworzenie pliku źródłowego Javy o nazwie MainActivity.java i pliku XML o nazwie activity_main.xml. Kiedy klikniesz przycisk Finish, Android Studio wygeneruje aplikację.

Jako nazwę aktywności podaj „MainActivity”, a jako nazwę układu „activity_main”. Ponadto upewnij się, że będzie zaznaczone pole wyboru Generate Layout File. Usuń zaznaczenie z pola wyboru Backwards Compatibility (AppCompat). Więcej informacji na temat tej opcji znajdziesz w dalszej części książki.

14

Rozdział 1.

Zaczynamy

Właśnie utworzyłeś swoją pierwszą aplikację na Androida

¨  Przygotowanie środowiska ¨  Stworzenie aplikacji ¨  Uruchomienie aplikacji ¨  Modyfikacja aplikacji

A co się właściwie stało?



Kreator Create New Project utworzył projekt aplikacji i skonfigurował ją zgodnie z wybranymi opcjami.

Określiłeś, z którą wersją Androida ma być zgodna tworzona aplikacja, a kreator utworzył wszystkie pliki i katalogi, których będzie potrzebowała prosta, działająca aplikacja.



Kreator wygenerował także prostą aktywność i układ, zawierające kody z wybranego szablonu.

Kod wchodzący w skład szablonu obejmuje kod XML układu i kod aktywności napisany w Javie. Ich działanie ogranicza się do wyświetlenia na ekranie prostego tekstu „Hello world!”. Po zakończeniu tworzenia projektu — podaniu wymaganych informacji we wszystkich oknach dialogowych kreatora — Android Studio automatycznie wyświetli projekt. Oto, jak będzie wyglądał nasz nowo utworzony projekt (nie przejmuj się, jeśli na razie wygląda na bardzo skomplikowany — jego poszczególne elementy wyjaśnimy na kilku kolejnych stronach):

To jest projekt wyświetlony w Android Studio.

jesteś tutaj 

15

Struktura katalogów

Android Studio utworzy pełną strukturę katalogów aplikacji

¨  Przygotowanie środowiska ¨  Stworzenie aplikacji ¨  Uruchomienie aplikacji ¨  Modyfikacja aplikacji

Aplikacja na Androida jest w rzeczywistości grupą prawidłowych plików rozmieszczonych w określonej strukturze katalogów. Android Studio przygotowuje dla nas te wszystkie pliki i katalogi podczas tworzenia nowej aplikacji. Najprostszym sposobem przejrzenia tej struktury katalogów jest skorzystanie z eksploratora plików umieszczonego w lewej kolumnie Android Studio. Eksplorator plików prezentuje wszystkie aktualnie otwarte projekty. Aby rozwinąć lub zwinąć katalog, wystarczy kliknąć strzałkę wyświetloną z lewej strony ikony katalogu. Kliknij widoczną tu strzałkę, a następnie wybierz opcję Project, aby wyświetlić pliki i katalogi wchodzące w skład projektu. To jest nazwa projektu.

Struktura katalogów projektu zawiera pliki różnych typów Jeśli przejrzysz zawartość projektu, to zauważysz, że kreator utworzył wiele katalogów, a w nich pliki wielu różnych typów:



To wygenerowane przez kreator pliki aktywności i układów.

 Klikając te strzałki, możesz rozwijać i zwijać katalogi. Wszystkie te pliki i katalogi wchodzą w skład projektu.

Pliki źródłowe Java i XML

Pliki Javy wygenerowane przez Androida

To dodatkowe pliki źródłowe Javy wygenerowane automatycznie przez Android Studio; nie musisz się przejmować ich zawartością ani zmieniać jej ręcznie.



Pliki zasobów



Biblioteki Androida

Zaliczają się do nich domyślne pliki graficzne ikon, pliki stylów oraz pliki z wartościami łańcuchowymi używanymi przez aplikację. W kreatorze określana jest minimalna wersja SDK, z którą będzie zgodna aplikacja. Android Studio zadba, by w projekcie znalazły się biblioteki odpowiednie dla wybranej wersji SDK.



Pliki konfiguracyjne

Pliki konfiguracyjne informują Androida o zawartości aplikacji oraz o tym, jak powinna ona działać.

Przyjrzymy się teraz nieco dokładniej wybranym, kluczowym plikom i katalogom Androidowa.

16

Rozdział 1.

Zaczynamy

Przydatne pliki projektu

¨  Przygotowanie środowiska ¨  Stworzenie aplikacji ¨  Uruchomienie aplikacji ¨  Modyfikacja aplikacji

Projekty Android Studio używają narzędzia do budowy projektów gradle do kompilacji i wdrażania aplikacji. Projekty gradle mają określoną, standardową strukturę. Poniżej przedstawiliśmy kilka kluczowych plików i katalogów, których będziemy używać: Katalog app jest nazwą modułu w aplikacji. Mojapierwszaapka

Nazwa katalogu głównego odpowiada nazwie projektu. W tym katalogu umieszczane są wszystkie pliki projektu.

Katalog build zawiera pliki utworzone dla nas przez Android Studio. Zazwyczaj nie musimy samodzielnie modyfikować zawartości tych plików i katalogów.

app build

Każdy projekt aplikacji na Androida musi zawierać plik o nazwie R.java, który jest tworzony automatycznie i umieszczany w katalogu generated/ source. Android używa go do zarządzania zasobami aplikacji.

generated/source r/debug

Katalog src zawiera kody źródłowe, które tworzymy i edytujemy.

com.hfad.mojapierwszaapka src

Katalog java zawiera tworzone przez nas kody źródłowe aplikacji. To właśnie tu są umieszczone wszystkie aktywności aplikacji. Zasoby aplikacji można znaleźć w katalogu res. Katalog layout zawiera pliki układów, a katalog values pliki zasobów z wartościami, takimi jak łańcuchy znaków. Istnieją także inne typy zasobów.

R.java

main java com.hfad.mojapierwszaapka

MainActivity.java res layout



Każda aplikacja na Androida w swoim katalogu głównym musi zawierać plik o nazwie AndroidManifest.xml. To tak zwany plik manifestu, zawierający kluczowe informacje dotyczące aplikacji, takie jak komponenty, z których się ona składa, wymagane biblioteki i inne deklaracje.

Plik MainActivity.java definiuje aktywność. Aktywność informuje system, w jaki sposób aplikacja ma prowadzić interakcję z użytkownikiem.

Plik activity_main.xml definiuje układ. Układ informuje system, jak ma wyglądać aplikacja.

activity_main.xml

values



strings.xml

AndroidManifest.xml

Strings.xml to plik zawierający zasoby łańcuchowe. W tym pliku przechowywane są takie łańcuchy znaków jak nazwa aplikacji i wszelkie inne domyślne wartości tekstowe. Inne pliki, takie jak pliki aktywności lub układów, mogą odczytywać zapisane w nim łańcuchy znaków.

jesteś tutaj 

17

Edytory

Edycja kodu z użyciem edytorów Android Studio Do przeglądania i edytowania plików w Android Studio służą edytory. Wystarczy dwukrotnie kliknąć plik, z którym chcemy pracować, a Android Studio natychmiast wyświetli jego zawartość w środkowej, największej części okna.

¨  Przygotowanie środowiska ¨  Stworzenie aplikacji ¨  Uruchomienie aplikacji ¨  Modyfikacja aplikacji

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. Dwukrotnie kliknij plik w eksploratorze, a jego zawartość zostanie wyświetlona w panelu edytora.

Edytor projektu W przypadku edycji układu mamy do dyspozycji dodatkową opcję. Zamiast edytować bezpośrednio kod XML (jak pokazaliśmy na następnej stronie), możemy skorzystać z edytora projektu, który pozwala przeciągać do układu graficzne komponenty interfejsu użytkownika i rozmieszczać je w wybrany sposób. Edytor kodu i edytor projektu 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.

Układy można przygotowywać w edytorze wizualnym, przeciągając i upuszczając komponenty.

To różne sposoby prezentacji zawartości tego samego pliku: kod, pierwszy widok przedstawia jego du. ukła ląd wyg zuje poka a drugi





?

Zaczynamy



DO CZEGO SŁUŻĘ?

Poniżej przedstawiamy kod źródłowy przykładowego pliku układu (przy czym nie jest to plik wygenerowany dla nas 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



Dodaje wypełnienie do marginesów ekranu.

Dodaje do układu komponent GUI , który służy do wyświetlania tekstów.

Sprawia, że wielkość komponentu GUI zostanie dostosowana do wielkości jego zawartości.

Wyświetla zawartość zasobu łańcuchowego o nazwie hello_world.

Sprawia, że szerokość i wysokość układu będą odpowiadały szerokości i wysokości ekranu urządzenia.

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



Dodaje wypełnienie do marginesów ekranu.

Dodaje do układu komponent GUI o nazwie TextView, który służy do wyświetlania tekstów.

Sprawia, że tekst będzie dostosowywany do wielkości komponentu zarówno w poziomie, jak i w pionie.

20

Rozdział 1.

Wyświetla zawartość zasobu łańcuchowego o nazwie hello_world.

Sprawia, że szerokość i wysokość układu będą odpowiadały szerokości i wysokości ekranu urządzenia.





?

Zaczynamy



DO CZEGO SŁUŻĘ?

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;

public class MainActivity extends Activity {

@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } }

To jest nazwa pakietu.

To są klasy Androida używane przez naszą klasę MainActivity.

Określa, którego układu należy użyć.

Implementacja metody onCreate() zdefiniowanej w klasie Activity. Ta metoda jest wywoływana w momencie pierwszego tworzenia aktywności.

MainActivity rozszerza klasę android.app.Activity.

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;

public class MainActivity extends Activity {

@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } }

To jest nazwa pakietu.

To są klasy Androida używane przez naszą klasę MainActivity.

Określa, którego układu należy użyć.

22

Rozdział 1.

Implementacja metody onCreate() zdefiniowanej w klasie Activity. Ta metoda jest wywoływana w momencie pierwszego tworzenia aktywności.

MainActivity rozszerza klasę android.app.Activity.

Zaczynamy

Uruchamianie aplikacji w emulatorze Androida Dotychczas przekonałeś się jedynie, jak wygenerowana aplikacja będzie 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? Jeśli chodzi o uruchamianie aplikacji, to mamy do wyboru kilka opcji. Pierwszą z nich jest uruchomienie aplikacji na fizycznym urządzeniu. Ale co zrobić, jeśli nie będziemy mieli takiego pod ręką albo jeśli będziemy chcieli sprawdzić działanie aplikacji na urządzeniu konkretnego typu, którego nie posiadamy? W takich przypadkach można skorzystać z alternatywnego rozwiązania, jakim jest uruchomienie aplikacji na emulatorze Androida wchodzącym w skład Android SDK. Emulator umożliwia przygotowanie jednego lub kilku wirtualnych urządzeń z Androidem (określanych skrótowo jako AVD), a następnie uruchamianie aplikacji w emulatorze w taki sposób, jak gdyby działały na fizycznym urządzeniu.

Jak zatem wygląda emulator? Rysunek zamieszczony obok przedstawia AVD uruchomione w emulatorze Androida. Jak widać, emulator wygląda jak telefon działający na komputerze. Emulator jest aplikacją, która odtwarza konkretne środowisko sprzętowe urządzenia z Androidem: zaczynając od jego procesora i pamięci, a kończąc na układzie dźwiękowym i ekranie. Emulator Androida opiera się na istniejącym już emulatorze o nazwie QEMU (wymawiane jako: kiu em ju), podobnym do wielu innych aplikacji obsługujących maszyny wirtualne, z którymi być może już się zetknąłeś, takimi jak VirtualBox lub VMWare.

¨  Przygotowanie środowiska ¨  Stworzenie aplikacji ¨  Uruchomienie aplikacji ¨  Modyfikacja aplikacji

Emulator Androida pozwala wykonywać aplikacje na wirtualnych urządzeniach z Androidem (AVD). Takie wirtualne urządzenie działa tak samo jak urządzenie fizyczne. Można utworzyć wiele urządzeń wirtualnych, emulujących różne typy urządzeń. Kiedy już skonfigurujesz AVD, możesz uruchomić na nim aplikację i zobaczyć, jak działa. Android Studio samo uruchamia emulator.

Dokładny wygląd i zachowanie urządzenia wirtualnego zależą od jego konfiguracji. To przedstawione obok ma symulować telefon Nexus 5X, dlatego będzie wyglądało i działało tak jak Nexus 5X uruchomiony na komputerze. A zatem przygotuj teraz własne urządzenie wirtualne, żeby zobaczyć swoją aplikację działającą w emulatorze.

jesteś tutaj 

23

Tworzenie AVD

Tworzenie wirtualnego urządzenia z Androidem Utworzenie wirtualnego urządzenia z Androidem w Android Studio wymaga wykonania kilku czynności. Spróbujemy teraz utworzyć AVD symulujące telefon Nexus 5 obsługujący API poziomu 21. Dzięki temu przekonasz się, jak Twoja nowa aplikacja wygląda i działa na urządzeniach tego typu. Czynności wykonywane w kreatorze są niemal takie same, niezależnie od typu tworzonego urządzenia.

¨  Przygotowanie środowiska ¨  Stworzenie aplikacji ¨  Uruchomienie aplikacji ¨  Modyfikacja aplikacji

Otwórz Android Virtual Device Manager Program AVD Manager umożliwia tworzenie nowych urządzeń wirtualnych oraz przeglądanie i edytowanie już istniejących. Aby go otworzyć, należy wybrać z menu Android Studio opcję Tools/AVD Manager. Jeśli wcześniej nie utworzyłeś żadnego AVD, to na ekranie zostanie wyświetlone okno z sugestią, abyś to zrobił. Kliknij ten przycisk, aby utworzyć nowe AVD.

Wybierz komponenty sprzętowe W następnym oknie dialogowym kreatora znajdzie się prośba o wybranie definicji urządzenia. Ta definicja określa typ urządzenia, które tworzone AVD będzie emulować. Możesz wybierać spośród wielu rodzajów telefonów, tabletów, urządzeń ubieralnych oraz przystawek telewizyjnych. My chcielibyśmy się przekonać, jak nasza nowa aplikacja wygląda na telefonie Nexus 5X. A zatem najpierw na liście Category zaznacz opcję Phone, a potem z listy obok wybierz opcję Nexus 5X. Następnie kliknij przycisk Next.

24

Rozdział 1.

Kiedy wybierzesz urządzenie, tutaj zostaną wyświetlone szczegółowe informacje o nim.

Zaczynamy

Tworzenie AVD (ciąg dalszy) Wybierz obraz systemu

¨  Przygotowanie środowiska ¨  Stworzenie aplikacji ¨  Uruchomienie aplikacji ¨  Modyfikacja aplikacji

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. 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 19., to przy tworzeniu AVD wybierz obraz systemu obsługujący co najmniej API poziomu 19. W tym przykładzie wybierzemy obraz systemu dla API poziomu 25. A zatem wybierz opcję Nougat z wartością Android 7.1.1 (API level 25) 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.

Na następnej stronie będziemy kontynuować konfigurowanie AVD.

jesteś tutaj 

25

Sprawdzenie konfiguracji

Tworzenie AVD (ciąg dalszy)

¨  Przygotowanie środowiska ¨  Stworzenie aplikacji ¨  Uruchomienie aplikacji ¨  Modyfikacja aplikacji

Sprawdź konfigurację AVD W następnym oknie dialogowym kreatora zostaniesz poproszony o zweryfikowanie konfiguracji AVD. To okno zawiera podsumowanie wszystkich wybranych wcześniej opcji oraz daje możliwość ich zmiany. Zaakceptuj opcje i kliknij przycisk Finish.

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.

Twój wirtualny Nexus 5X został utworzony.

26

Rozdział 1.

Zaczynamy

Uruchamianie aplikacji w emulatorze Skoro już przygotowałeś swoje urządzenie wirtualne, możesz spróbować uruchomić na nim aplikację. W tym celu wystarczy wybrać opcję Run ‘app’ z menu Run. Kiedy zostaniesz poproszony o wybranie urządzenia, upewnij się czy z listy poniżej został wybrany utworzony wcześniej wirtualny Nexus 5X.

¨  Przygotowanie środowiska ¨  Stworzenie aplikacji ¨  Uruchomienie aplikacji ¨  Modyfikacja aplikacji

Uruchomienie AVD może potrwać parę minut, a zatem, cierpliwie czekając, aż to nastąpi, przyjrzyjmy się, co się dzieje po wybraniu opcji Run.

Skompilowanie, spakowanie, wdrożenie i wykonanie Wybranie opcji Run nie powoduje jedynie uruchomienia aplikacji — oprócz tego wykonywane są wszystkie inne niezbędne czynności przygotowawcze:

Biblioteki

Zasoby

Plik APK to pakiet

2

1

aplikacji na Androida.

APK Plik Javy

Kod bajtowy

W zasadzie jest to

Plik APK

4

Run

To jest utworzone przed chwilą urządzenie wirtualne.

3

zwyczajny plik JAR

5

lub ZIP zawierający aplikację. Emulator

Emulator

1

Pliki źródłowe Javy zostają skompilowane do postaci kodów bajtowych.

2

Zostaje utworzony pakiet aplikacji, czyli plik APK.

Plik APK zawiera skompilowane pliki Javy oraz wszystkie biblioteki i zasoby niezbędne do uruchomienia aplikacji.

3

4

5

Po uruchomieniu emulatora i wybranego urządzenia wirtualnego zostanie na nie wgrany plik APK aplikacji, po czym aplikacja zostanie zainstalowana w systemie. AVD uruchomi główną aktywność aplikacji.

W tym momencie aplikacja zostanie wyświetlona na ekranie AVD i będzie gotowa do testowania.

Jeśli emulator jeszcze nie działa, to zostanie uruchomiony, a na nim zostanie uruchomione wybrane urządzenie wirtualne.

jesteś tutaj 

27

Prosimy o cierpliwość

Postępy możesz obserwować w konsoli

¨  Przygotowanie środowiska ¨  Stworzenie aplikacji ¨  Uruchomienie aplikacji Uruchomienie emulatora i urządzenia wirtualnego czasami może zająć całkiem sporo czasu ¨  Modyfikacja aplikacji — często jest to nawet kilka minut. Na szczęście okazuje się, że postępy wykonywanych czynności można obserwować w konsoli Android Studio. Konsola udostępnia szczegółowy Sugerujemy, żebyś w trakcie oczekiwania na uruchomienie dziennik wszystkich czynności wykonywanych przez narzędzie gradle, a jeśli podczas tych emulatora znalazł sobie jakieś operacji wystąpią jakieś błędy, to zostaną one wyraźnie wyróżnione. inne zajęcie, na przykład Panel konsoli jest wyświetlany u dołu okna Android Studio (jeśli konsola nie zostanie wyświetlona automatycznie, to kliknij przycisk Run, umieszczony u dołu ekranu):

Poniżej zamieściliśmy zawartość konsoli wyświetloną podczas wykonanej przez nas próby wykonania aplikacji:

Android Studio właśnie ukończyło uruchamianie przygotowanego przez nas AVD.

28

Rozdział 1.

możesz szydełkować lub ugotować sobie szybki obiad.

Instalowanie aplikacji.

Emulator uruchamia naszą aplikację, wykonując w tym celu jej aktywność główną. To aktywność, którą wygenerował dla nas kreator.

Zaczynamy ¨  Przygotowanie środowiska ¨  Stworzenie aplikacji ¨  Uruchomienie aplikacji ¨  Modyfikacja aplikacji

Jazda próbna Sprawdźmy zatem, co widać na ekranie podczas uruchamiania aplikacji. W pierwszej kolejności w osobnym oknie jest wyświetlany emulator. Następnie, przez dłuższą chwilę, emulator wczytuje AVD, ale potem przekonasz się, że wygląda ono zupełnie jak rzeczywiste urządzenie z Androidem. Najpierw zostaje uruchomiony emulator…

Poczekaj jeszcze chwilę, a na ekranie zostanie wyświetlona utworzona przed chwilą aplikacja. Na samej górze ekranu będzie widoczna jej nazwa, a domyślny przykładowy tekst „Hello World!” zostanie wyświetlony na środku ekranu.

Android Studio, bez zawracania nam tym głowy, wygenerowało tekst „Hello World!”.

…a to jest zablokowane urządzenie wirtualne — ono i działa tak samo wygląda prawdziwy telefon Ne jak xus 5X.

To jest nazwa aplikacji.

Ten przykładowy tekst został dla nas wygenerowany przez kreatora. A to jest aplikacja działająca w AVD.

jesteś tutaj 

29

Co się stało?

Ale co się właściwie stało?

¨  Przygotowanie środowiska ¨  Stworzenie aplikacji ¨  Uruchomienie aplikacji ¨  Modyfikacja aplikacji

Przyjrzyjmy się dokładnie wszystkiemu, co się dzieje podczas uruchamiania aplikacji:

1 2

3 4

Android Studio uruchamia emulator, wczytuje AVD i instaluje aplikację.

1

2

Po uruchomieniu aplikacji zostaje utworzony obiekt aktywności, której kod pochodzi z pliku MainActivity.java.

3

Aktywność informuje, że używa układu activity_main.xml. Aktywność nakazuje systemowi wyświetlenie układu na ekranie.

Aktywność

Urządzenie

4

Układ

W naszym przypadku jest to urządzenie wirtualne.

W efekcie zostaje wyświetlony tekst „Hello World!”.

Nie istnieją

głupie pytania

P: Wspominaliście, że podczas tworzenia pliku APK kody źródłowe Javy zostają skompilowane do postaci kodów bajtowych i dodane do pliku APK. Przypuszczam, że chodziło Wam o to, że zostają one skompilowane do kodów bajtowych Javy, prawda?

O: Owszem, zostają, ale to nie wszystko. Na Androidzie sytuacja wygląda nieco inaczej.

Kluczowa różnica polega na tym, że na Androidzie kod nie jest wykonywany w standardowej wirtualnej maszynie Javy — Java VM. Zamiast tego działa on w środowisku uruchomieniowym Androida (ART), a na starszych urządzeniach — w poprzedniku ART, środowisku uruchomieniowym noszącym nazwę Dalvik. Oznacza to, że piszemy kod w Javie, kompilujemy go do postaci 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. Środowisko uruchomieniowe wykonuje te kody DEX. Więcej informacji na ten temat można znaleźć w Dodatku C.

P: To wszystko wygląda na bardzo skomplikowane. Nie

można po prostu użyć zwyczajnej wirtualnej maszyny Javy?

O: Środowisko ART jest w stanie konwertować kody DEX na kod

maszynowy, wykonywany bezpośrednio przez procesor urządzenia z Androidem. To sprawia, że aplikacja działa znacznie szybciej i zużywa znacznie mniej baterii.

P

: Czy wirtualna maszyna Javy naprawdę powoduje aż tak duże narzuty?

O: Tak. Ponieważ na Androidzie każda aplikacja działa

w odrębnym procesie. Oznacza to, że gdyby była używana standardowa wirtualna maszyna Javy, to urządzenia z Androidem musiałyby mieć znacznie więcej pamięci.

P: Czy za każdym razem, gdy tworzę nową aplikację, muszę także tworzyć nowe AVD?

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 ¨  Uruchomienie aplikacji ¨  Modyfikacja aplikacji

Podczas lektury kilku ostatnich stron stworzyłeś prostą aplikację na Androida i przekonałeś się, jak ona wygląda i działa w emulatorze. Teraz zajmiemy się nieznacznym usprawnieniem tej aplikacji. Obecnie aplikacja wyświetla tekst „Hello World!” umieszczony w niej przez kreator. Mamy zamiar zmienić go na coś nieco bardziej interesującego. A co należy w tym celu zrobić? Aby odpowiedzieć na to pytanie, musimy się trochę cofnąć i przyjrzeć się konstrukcji aplikacji.

Aplikacja składa się z jednej aktywności i jednego układu

Aplikacja obecnie wyświetla tekst „Hello World!”, my jednak już niebawem zmienimy go na coś innego.

Podczas tworzenia aplikacji określiliśmy, w jaki sposób ma być skonfigurowana, a kreator Android Studio zrobił całą resztę — przede wszystkim utworzył prostą aktywność i domyślny układ.

Aktywność kontroluje działanie aplikacji Android Studio utworzyło aktywność o nazwie MainActivity.java. Ta aktywność określa, co aplikacja robi i jak ma reagować na poczynania użytkownika.

MainActivity.java

Nasza aktywność określa, co aplikacja robi i w jaki sposób prowadzi interakcję z użytkownikiem.

Układ określa wygląd aplikacji Aktywność MainActivity.java określa, że używa układu activity_main.xml, który także został wygenerowany przez Android Studio. Ten układ określa, jak wygląda aplikacja.



Nasz układ określa, jak aplikacja wygląda.

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

Czym jest układ? Chcemy zmienić przykładowy tekst „Hello World!” wygenerowany przez Android Studio, zacznijmy zatem od pliku układu activity_main.xml. Jeśli ten plik jeszcze nie jest otwarty w edytorze, to otwórz go teraz — odszukaj plik w katalogu app/src/main/ res/layout i dwukrotnie go kliknij.

Kliknij tę strzałkę, by zmienić sposób prezentacji plików i katalogów.

Jeśli w eksploratorze nie jest widoczna struktura katalogów projektu, to przełącz się do widoku Project.

Edytor projektu.

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. Jeśli skorzystamy z tej drugiej opcji, przekonamy się, że zgodnie z oczekiwaniami w układzie zostanie wyświetlony tekst „Hello World!”. A jak wygląda źródłowy kod XML tego pliku układu?

A to jest przykładowy tekst.

Aby się przekonać, wystarczy przełączyć się do edytora kodu. Aby przełączyć się do edytora projektu, wystarczy kliknąć kartę „Design”.

Edytor kodu W przypadku wybrania edytora kodu jest w nim wyświetlana zawartość pliku activity_main.xml. Przyjrzymy się jej teraz nieco dokładniej.

Edytor kodu

Aby wyświetlić edytor kodu, wystarczy kliknąć kartę „Text” wyświetloną u dołu okna IDE.

32

Rozdział 1.

Zaczynamy

Plik activity_main.xml zawiera dwa elementy Poniżej przedstawiliśmy kod pliku activity_main.xml wygenerowanego przez Android Studio. Usunęliśmy z niego fragmenty, którymi na razie nie musisz zaprzątać sobie głowy — opiszemy je szczegółowo w dalszej części książki.

¨  Przygotowanie środowiska ¨  Stworzenie aplikacji ¨  Uruchomienie aplikacji ¨  Modyfikacja aplikacji

Oto kod układu: To jest pełna ścieżka dostępu do pliku activity_ main.xml

Ten element określa, jak powinny być wyświetlane komponenty, czyli, w naszym przypadku, tekst „Hello World!”.



Pominęliśmy także fragment kodu elementu .

activity_main.xml

Jak widać, przedstawiony kod zawiera dwa elementy. Pierwszym z nich jest . To jeden z kilku typów układów, które informują system Android, w jaki sposób należy rozmieszczać komponenty na ekranie urządzenia. Dostępnych jest kilka różnych typów elementów układu, które można stosować w tworzonych aplikacjach, a w dalszej części książki można znaleźć więcej informacji na ich temat. Jak na razie najważniejszy jest jednak drugi element — . To właśnie tego elementu używamy do wyświetlenia na ekranie tekstu „Hello World!”. Kluczowym fragmentem kodu elementu jest wiersz rozpoczynający się od android:text. To właściwość text opisująca tekst, który powinien zostać wyświetlony:

wyświetlany

w układzie.

Spokojnie

Nie przejmuj się, jeśli Twój układ wygląda inaczej od naszego.

W zależności od tego, której wersji Android Studio używasz, postać wygenerowanego kodu XML układu może być nieco inna. Nie musisz się tym przejmować, gdyż począwszy od następnego rozdziału zaczniesz się uczyć pisania kodu własnych układów, którymi będziesz zastępować większość kodu generowanego automatycznie przez Android Studio.

To jest właśnie wyświetlany tekst.

Zmieńmy teraz prezentowany tekst na jakiś inny.

jesteś tutaj 

33

Aktualizacja tekstu

Aktualizacja tekstu wyświetlanego w układzie

¨  Przygotowanie środowiska ¨  Stworzenie aplikacji ¨  Uruchomienie aplikacji ¨  Modyfikacja aplikacji

Kluczowym fragmentem elementu jest wiersz kodu przedstawiony poniżej: android:text=”Hello World!” />

Zapis android:text oznacza, że jest to właściwość text elementu , czyli właściwość określająca tekst, który powinien zostać wyświetlony w układzie. W naszym przypadku tym wyświetlanym tekstem jest ”Hello World!”. Wyświetl tekst…

…“Hello

android:text=”Hello World!” />

World!”

Aby zmodyfikować tekst wyświetlany w układzie, wystarczy zmienić wartość właściwości text z ”Hello World!” na ”Siemka, stary!”. Nowy kod elementu powinien wyglądać jak w poniższym przykładzie: ...

Zmień zapisany tu tekst y!”. z “Hello World!” na “Siemka, star

Po zmodyfikowaniu pliku zapisz wszystkie zmiany, wybierając z menu głównego Android Studio opcje File/Save All.

layout

activity_main.xml

Nie istnieją

głupie pytania

P: Kod mojego układu wygląda inaczej. Czy tak może być?

P: Katalogi w moim eksploratorze projektu wyglądają inaczej, niż pokazaliście w książce. Dlaczego?

O: Tak, wszystko jest w porządku. Jeśli używasz nieco innej wersji O: Android Studio pozwala na prezentowanie hierarchii katalogów Android Studio niż my, to wygenerowany kod może różnić się od naszego, ale nie ma to większego znaczenia. Od teraz będziesz się uczył pisania kodu własnych układów, którymi będziesz zastępował większość kodu generowanego przez Android Studio.

P: Czy mam rację, sądząc, że wyświetlany tekst określiliśmy w kodzie na stałe?

O: Tak, ale wyłącznie po to, byś mógł się przekonać, jak można aktualizować tekst w układach. Istnieje lepszy sposób określania tekstów niż podawanie ich na stałe w układzie, jednak aby go poznać, będziesz musiał poczekać do następnego rozdziału.

34

Rozdział 1.

na kilka różnych sposobów, przy czym domyślnie stosowany jest widok o nazwie „Android”. My preferujemy widok „Project”, gdyż odpowiada on faktycznej strukturze katalogów na dysku. Obecnie używany widok możesz zmienić na „Project”, klikając strzałkę umieszczoną na górze panelu eksploratora i wybierając opcję Project.

My używamy widoku Project.

Kliknij tę strzałkę, by zmienić używany widok.

Zaczynamy

Weź aplikację na jazdę próbną Po wprowadzeniu zmian w pliku układu spróbuj ponownie uruchomić aplikację w emulatorze — w tym celu wybierz z menu Run opcję Run app. Przekonasz się, że zamiast „Hello World!” aplikacja wyświetla teraz tekst „Siemka, stary!”.

¨  Przygotowanie środowiska ¨  Stworzenie aplikacji ¨  Uruchomienie aplikacji ¨  Modyfikacja aplikacji

Oto zaktualizowana wersja aplikacji.

Obecnie zamiast „Hello World!” wyświetla ona tekst „Siemka, stary!”.

W ten sposób utworzyłeś i zmodyfikowałeś swoją pierwszą aplikację na Androida.

jesteś tutaj 

35

Przybornik

Rozdział 1.

Twój przybornik do Androida Opanowałeś już rozdział 1. i dodałeś do swojego przybornika z narzędziami podstawowe pojęcia i informacje związane z tworzeniem aplikacji na Androida.

j Pełny kod przykładowe j ane tow aplikacji prezen z żes mo ale w tym rozdzi P FT ra we ser z pobrać wydawnictwa Helion: ftp://ftp.helion.pl/ przyklady/andrr2.zip

CELNE SPOSTRZEŻENIA 











36

Rozdział 1.

Kolejne wersje Androida są identyfikowane na podstawie numeru wersji, poziomu API oraz nazwy kodowej. Android Studio jest specjalną wersją InelliJ IDEA, dostosowaną do współpracy z Android Software Development Kit (Android SDK) i z systemem budowy Gradle.





Typowa aplikacja na Androida składa się z aktywności, układów oraz plików zasobów. Układy opisują, jak aplikacja ma wyglądać. Są one przechowywane w katalogu app/src/main/res/ layout. Aktywności opisują, co aplikacja robi i w jaki sposób prowadzi interakcję z użytkownikiem. Pisane przez nas aktywności są przechowywane w katalogu app/src/main/java. Plik AndroidManifest.xml zawiera informacje o samej aplikacji. Jest on przechowywany w katalogu app/src/main.





AVD to skrót od angielskich słów Android Virtual Device, oznaczający wirtualne urządzenie z Androidem. Takie wirtualne urządzenia są wykonywane w emulatorze Androida i udają urządzenia fizyczne. Plik APK to plik pakietu aplikacji. Przypomina on plik JAR, przy czym zawiera aplikację na Androida — jej kody bajtowe, biblioteki i zasoby. Instalacja aplikacji na urządzeniu z Androidem polega na zainstalowaniu pliku APK. Aplikacje na Androida działają w niezależnych procesach wykonywanych przez środowisko uruchomieniowe Androida (ART). Element TextView służy do wyświetlania tekstów.

2. Tworzenie interaktywnych aplikacji

Aplikacje, które coś robią Zastanawiam się, co się stanie, jeśli nacisnę przycisk z napisem „katapulta”…

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.

to jest nowy rozdział 

37

Doradca piwny

W tym rozdziale napiszemy aplikację Doradca piwny Z poprzedniego rozdziału wiesz, jak można utworzyć prostą aplikację, używając do tego celu kreatora New Project udostępnianego przez Android Studio, oraz jak zmienić tekst wyświetlany w układzie. Zazwyczaj jednak, tworząc aplikacje na Androida, będziemy chcieli, aby coś robiły. W tym rozdziale pokażemy Ci, jak napisać aplikację, z którą użytkownik będzie mógł prowadzić interakcję, a konkretnie rzecz biorąc, będzie to aplikacja o nazwie Doradca Piwny. Pozwoli ona użytkownikowi wybrać ulubiony rodzaj piwa, kliknąć przycisk i otrzymać listę smacznych piw, które warto spróbować.

biony Wybierz ulu a, w pi rodzaj cisk… kliknij przy

…a aplikacja wyświetli listę sugerowanych gatunków piw.

Oto struktura aplikacji:

1

Wygląd aplikacji zostanie określony przez układ.

Tak będzie wyglądał układ naszej aplikacji.

Układ będą tworzyć trzy komponenty graficznego interfejsu użytkownika: • komponent typu Spinner, czyli rozwijana lista pozwalająca użytkownikowi wybrać ulubiony rodzaj piwa; • przycisk, którego kliknięcie spowoduje zwrócenie listy piw wybranego rodzaju; • pole tekstowe wyświetlające dobrane gatunki piwa.

2

Plik strings.xml zawiera wszystkie zasoby łańcuchowe używane przez układ, na przykład etykietę przycisku oraz pole z gatunkami piw wyświetlane na ekranie.

3

Aktywność określa, w jaki sposób aplikacja powinna prowadzić interakcję z użytkownikiem.

1

Napisana przez nas klasa Javy zawiera logikę działania aplikacji.

Ta klasa będzie zawierała metodę pobierającą rodzaj piwa (przekazywany jako parametr) i zwracającą listę piw tego rodzaju. Aktywność będzie wywoływać tę metodę, przekazywać do niej wybrany rodzaj piwa i wyświetlać zwrócone wyniki.

38

Rozdział 2.

Układ

2



strings.xml

3

Aktywność pobierze rodzaj piwa wybrany przez użytkownika i użyje go do wyświetlenia listy piw, którymi użytkownik mógłby być zainteresowany. Czynności te będą wykonywane przy użyciu niestandardowej, napisanej przez nas klasy Javy.

4

Aktywność

4 Nasza klasa Javy

Tworzenie interaktywnych aplikacji

A oto, co musisz zrobić A zatem bierzmy się do pracy i stwórzmy naszego doradcę piwnego. W tym celu musisz wykonać kilka czynności (w dalszej części rozdziału będziemy je odznaczać na liście rzeczy to zrobienia):

1

Utworzyć projekt.

2

Zaktualizować układ.

3

Powiązać układ z aktywnością.

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.

Na następnej stronie szczegółowo pokażemy, jak to zrobić.

Po wygenerowaniu aplikacji musisz zaktualizować jej układ i dodać do niego wszystkie niezbędne komponenty GUI.

Układ określa jedynie wizualne elementy aplikacji. Aby aplikacja była nieco bardziej inteligentna, trzeba powiązać układ z kodem Javy w aktywności.



Układ

4

Aktywność

Napisać logikę działania aplikacji.

Dodasz do aplikacji własną klasę Javy i posłużysz się nią, by mieć pewność, że będzie wyświetlać odpowiednie gatunki piwa, dostosowane do opcji wybranej przez użytkownika. Klasa Javy

jesteś tutaj 

39

Utworzenie projektu

¨  Utworzenie projektu

Utworzenie projektu Zabierzmy się zatem za pisanie nowej aplikacji (czynności, jakie należy w tym celu wykonać, są bardzo podobne do tych, które wykonaliśmy w poprzednim rozdziale):

¨  Aktualizacja układu ¨  Połączenie aktywności ¨  Implementacja logiki

1

Otwórz Android Studio i na ekranie powitalnym kliknij opcję Start a new Android Studio project. Spowoduje to uruchomienie kreatora przedstawionego w rozdziale 1.

2

Kiedy zostaniesz poproszony o podanie nazwy aplikacji, wpisz Doradca Piwny, oraz hfad.com w polu Company domain, co automatycznie spowoduje zmianę nazwy pakietu na com.hfad. doradcapiwny. Upewnij się również, że pole wyboru Include C++ support nie będzie zaznaczone.

3

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 19 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 19. Kryterium to spełnia przeważająca większość aktualnie używanych urządzeń z Androidem.

4

Jako domyślną aktywność wybierz szablon Empty 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ć. Upewnij się, że jest zaznaczone pole wyboru nakazujące wygenerowanie pliku układu, i ewentualnie usuń zaznaczenie pola wyboru Backward Compatibility (AppCompat).

Jeśli używan a ciebie wersja przez Studio udostę Android wyboru umoż pnia pole liw użycie wspar iające języka Kotlin,cia dla zaznaczenie tato usuń kże z niego.

wszystkie te Kreator przeprowadzi Cię przez im rozdziale. czynności, podobnie jak w poprzedn dca Piwny, Dora ę nazw j nada acji aplik j zone Twor m API określ, że jej minimalnym poziome y aktywności, nazw j poda e ępni nast a będzie 19, activity_find_beer. FindBeerActivity, i pliku układu,

2

3

4

Koniecznie wybierz szablon Empty Activity.

40

Rozdział 2.

Upewnij się, że pole Backwards Compatibility (AppCompat) NIE JEST ZAZNACZONE!

Tworzenie interaktywnych aplikacji

Utworzyliśmy domyślną aktywność i układ Kiedy klikniesz przycisk Finish, Android Studio utworzy nowy projekt zawierający aktywność zdefiniowaną w pliku FindBeerActivity.java oraz plik układu o nazwie activity_find_beer.xml.

Kliknij zakładkę Text, by otworzyć edytor kodu.

Zacznijmy od wprowadzenia zmian w pliku układu. W tym celu wyświetl zawartość katalogu aap/src/main/res/layout i otwórz plik activity_find_beer.xml. Następnie wyświetl tekstową wersję kodu, aby otworzyć edytor tekstowy, i zastąp cały kod pliku activity_find_beer.xml kodem przedstawionym poniżej (wszystkie nowe fragmenty kodu wyróżniliśmy pogrubioną czcionką).

Zastępujemy kod wygenerowany przez Android Studio.



res

Właśnie zmieniliśmy kod wygenerowany przez Android Studio i użyliśmy w nim elementu . Służy on do wyświetlania komponentów interfejsu użytkownika jeden obok drugiego, w pionie bądź w poziomie. Jeśli układ ten rozmieszcza komponenty w pionie, to będą one tworzyć jedną kolumnę; jeśli z kolei układ jest poziomy, to umieszczone w nim elementy utworzą jeden wiersz. Więcej informacji na temat działania tego układu znajdziesz w dalszej części rozdziału.

layout

activity_find_beer.xml

Kliknij kartę Design, aby przełączyć się do edytora projektu.

Wszelkie zmiany wprowadzane w kodzie XML układu będą odzwierciedlane w edytorze projektu Android Studio, który można wyświetlić, klikając kartę Design. Temu edytorowi przyjrzymy się dokładniej na następnej stronie.

jesteś tutaj 

41

Edytor projektu

Dokładniejsza prezentacja edytora projektu Edytor projektu zapewnia nam możliwość tworzenia kodu układu w nieco bardziej wizualny sposób niż w przypadku bezpośredniej edycji kodu XML. Udostępnia on dwa odrębne sposoby prezentacji tworzonego układu. Pierwszy z nich pokazuje, jak dany układ będzie wyglądał na rzeczywistym urządzeniu, a drugi — projekt struktury układu. Ten widok projektu pozwala się zorientować, jak układ będzie wyglądał na rzeczywistym urządzeniu.

Widok tekstowy umieszczony w kodzie XML układu jest widoczny na obu widokach edytora projektu.

¨  Utworzenie projektu ¨  Aktualizacja układu ¨  Połączenie aktywności ¨  Implementacja logiki Jeśli Android Studio nie pokazuje obu widoków układu, kliknij przycisk Show Design + Blueprint, umieszczony na pasku narzędzi edytora projektu.

To jest widok planu (Blueprint), który koncentruje się bardziej na strukturze układu.

Z lewej strony edytora projektu jest umieszczona paleta, zawierająca komponenty, które można przeciągać i umieszczać w układzie. Już za chwilę z niej skorzystamy. Ta lista przedstawia różne kategorie komponentów, które można dodawać do układu. Można je klikać, by filtrować komponenty wyświetlane w pale cie.

42

Rozdział 2.

To są komponenty. Więcej na ich temat dowiesz się w dalszej części książki.

Tworzenie interaktywnych aplikacji

¨  Utworzenie projektu ¨  Aktualizacja układu

Dodawanie przycisku w edytorze projektu Zamierzamy teraz dodać do naszego układu przycisk, korzystając przy tym z możliwości, jakie daje edytor projektu. Odszukaj komponent Button w palecie, kliknij go, a następnie przeciągnij do edytora projektu i umieść tak, by znalazł się nad widokiem tekstowym. W efekcie przycisk pojawi się w projekcie układu:

¨  Połączenie aktywności ¨  Implementacja logiki

A to jest przycisk — komponent Button. Przeciągnij go do układu.

Umieść przycisk powyżej widoku tekstowego. Możesz go umieścić . na dowolnym z widoków projektu

Zmiany wprowadzane w edytorze projektu są odzwierciedlane w kodzie XML Przeciąganie komponentów w opisany powyżej sposób jest bardzo wygodną metodą aktualizowania układów. Jeśli teraz przełączysz się do edytora projektu, to zauważysz, że dodanie przycisku spowodowało wprowadzenie zmian w kodzie pliku układu: ...

kilku następnych stronach żki. ksią

...

layout

activity_find_beer.xml

jesteś tutaj 

43

Komponenty GUI są widokami

Plik activity_find_beer.xml zawiera nowy przycisk Edytor dodał do pliku activity_find_beer.xml nowy element Button:

W Androidowie termin „przycisk” oznacza standardowe przyciski, które użytkownik może kliknąć, aby wykonać pewną akcję. Każdy element zawiera właściwości służące do kontrolowania jego wielkości i wyglądu. Te wszystkie właściwości nie są unikalne wyłącznie dla przycisków — dysponują nimi także inne komponenty GUI, choćby takie jak TextView.

Przyciski i widoki tekstowe są klasami pochodnymi klasy View Istnieje pewien ważny powód, który sprawia, że przyciski i widoki tekstowe, czyli komponenty TextView, mają wiele wspólnych właściwości — oba te rodzaje komponentów dziedziczą po tej samej klasie: View. Więcej informacji na jej temat znajdziesz w dalszej części książki, a na razie przedstawiliśmy poniżej kilka jej najczęściej używanych właściwości.

android:id Ta właściwość określa nazwę identyfikującą dany komponent. Właściwość ID umożliwia kontrolę działania komponentu z poziomu kodu aktywności:

android:layout_width, android:layout_height

setId(int)

TextView jest typu View…

Te właściwości określają podstawową szerokość i wysokość komponentu. Wartość ”wrap_content” oznacza, że wielkość komponentu powinna być na tyle duża, by można było wyświetlić w nim jego zawartość; z kolei właściwość ”match_parent” oznacza, że komponent ma zajmować całą szerokość układu.

android.widget.TextView setText(CharSequence, TextView.BufferType) ...

android:layout_width=”wrap_content”

android:text

android.view.View ...

android:id=”@+id/button”

android:layout_height=”wrap_content”

Klasa View zawiera wiele różnych metod. Przyjrzymy się im dokładniej w dalszej części książki.

…a Button jest typu TextView, co oznacza, że jest także typu View.

Ta właściwość określa, jaki tekst powinien zostać wyświetlony w komponencie. W przypadku komponentu Button będzie to tekst wyświetlany na przycisku: android:text=”Button”

44

Rozdział 2.

android.widget.Button ...

Tworzenie interaktywnych aplikacji

¨  Utworzenie projektu ¨  Aktualizacja układu

Dokładniejszy przegląd kodu układu

¨  Połączenie aktywności ¨  Implementacja logiki

Przyjrzyjmy się nieco dokładniej kodowi układu i podzielmy go na poszczególne elementy, 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 element LinearLayout.

To jest przycisk.

To jest widok tekstowy.

res



Ten znacznik zamyka element Line arLayout.

Element LinearLayout Pierwszym elementem w kodzie układu jest . Element informuje system Android, że umieszczone w nim komponenty GUI należy rozmieszczać obok siebie, w jednym wierszu lub jednej kolumnie.

ślania Istnieją także inne sposoby okre ego iczn graf ntów pone kom a położeni je interfejsu użytkownika. Poznasz w dalszej części książki.

Do określania orientacji układu służy atrybut android:orientation. Oto przykład jego zastosowania: android:orientation=”vertical”

Przypisanie atrybutowi tej wartości oznacza, że komponenty mają być rozmieszczane w jednej, pionowej kolumnie.

jesteś tutaj 

45

Bliższe spojrzenie

Dokładniejszy przegląd kodu układu (ciąg dalszy)

¨  Utworzenie projektu ¨  Aktualizacja układu

Element zawiera dwa elementy: oraz .

¨  Połączenie aktywności ¨  Implementacja logiki

Element Button Pierwszym elementem układu jest : ...

...

Ponieważ jest to pierwszy element układu , zostanie wyświetlony na samym początku układu, na górze ekranu. Atrybutowi layout_width przycisku przypisaliśmy wartość ”match_parent”, co oznacza, że ma on mieć szerokość elementu nadrzędnego, czyli . Z kolei atrybut layout_height ma wartość ”wrap_content”, co oznacza, że wysokość elementu powinna wystarczać do wyświetlenia jego zawartości.

Zastosowanie układu LinearLayout oznacza, że komponenty GUI będą wyświetlane w jednym wierszu lub jednej kolumnie.

Element TextView Ostatnim elementem umieszczonym wewnątrz układu jest : ...

Przycisk jest wyświetlony na górze, ponieważ jest pierwszym elementem w kodzie XML.

...

Ponieważ jest do drugi element, a orientację układu LinearLayout określiliśmy jako pionową (”vertical”), ten widok tekstowy zostanie wyświetlony poniżej przycisku (który jest pierwszym elementem układu). Właściwościom layout_width oraz layout_height tego widoku tekstowego została przypisana wartość ”wrap_content”, tak by zabierał on tylko tyle miejsca, ile jest konieczne do wyświetlenia prezentowanego w nim tekstu.

46

Rozdział 2.

Widok tekstowy jest wyświetlony poniżej przycisku, gdyż w kodzie XML znajduje się za nim.

Tworzenie interaktywnych aplikacji

Element TextView Jak widać, w kodzie tego elementu nie zostały podane żadne właściwości 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 (zmiany zostały wyróżnione pogrubioną czcionką).

app/src/main

Spinner to stosowana w Androidzie nazwa na rozwijaną listę konkretnych wartości. Pozwala on wybrać jedną z dostępnych opcji.

Ten element wyświetla w układzie komponent Spinner, czyli rozwijaną listę wartości.

res layout

activity_find_beer.xml

Zmieniamy identyfikator przycisku

Wyśrodkowujemy przycisk w poziomie i dodajemy do niego margines.

Zmień identyfikator widoku tekstowego na "brands".



Zrób to sam!

Zastąp zawartość swojego pliku activity_find_beer.xml przedstawionym tu kodem XML.

jesteś tutaj 

47

Edytor projektu

…są uwzględniane w edytorze projektu Kiedy już wprowadzisz modyfikacje w kodzie XML układu, przełącz się do widoku projektu. Zamiast układu zawierającego przycisk i widok tekstowy umieszczony poniżej, teraz powinieneś zobaczyć komponent Spinner, przycisk i tekst rozmieszczone w jednej, pionowej kolumnie. Spinner to stosowana w terminologii Androida nazwa określająca rozwijaną listę wartości. Dotknięcie tego komponentu powoduje rozwinięcie listy, tak by można było wybrać jeden z jej elementów.

To jest rozwijana lista, z której użytkownik będzie mógł wybrać ulubiony rodzaj piwa. Użytkownik kliknie przycisk… …a tu zostanie wyświetlona lista dobranych dla niego gatunków piwa.

Pokazaliśmy Ci, jak można dodawać komponenty GUI do układu, i to zarówno przy użyciu graficznego edytora projektu, jak i bezpośrednio w kodzie XML. Ogólnie rzecz biorąc, zamierzone efekty łatwiej można uzyskać, modyfikując kod XML układu niż wybierając opcje w edytorze projektu. Dzieje się tak dlatego, że wprowadzając zmiany w kodzie XML, mamy bardziej bezpośrednią kontrolę nad układem.

48

Rozdział 2.

¨  Utworzenie projektu ¨  Aktualizacja układu ¨  Połączenie aktywności ¨  Implementacja logiki

Komponent Spinner tworzy rozwijaną listę wartości. Pozwala on wybrać jedną wartość ze zbioru określonych wartości. Komponenty GUI, takie jak przyciski, widoki tekstowe oraz listy rozwijane, mają bardzo podobne atrybuty, gdyż wszystkie są typu View. Wszystkie te komponenty dziedziczą bowiem po wspólnej klasie View.

Tworzenie interaktywnych aplikacji

¨  Utworzenie projektu ¨  Aktualizacja układu

Weź swoją aplikację na jazdę próbną

¨  Połączenie aktywności ¨  Implementacja logiki

Wciąż mamy jeszcze sporo do zrobienia w naszej aplikacji, sprawdźmy jednak, jak ona wygląda w swojej obecnej postaci. Zapisz wszystkie wprowadzone zmiany, wybierając z menu głównego Android Studio opcję File/Save All, a następnie wybierz opcję Run ‘app’ z menu Run. Kiedy zostaniesz o to poproszony, wybierz opcję uruchomienia emulatora. Poczekaj cierpliwie na uruchomienie aplikacji, w końcu pojawi się w emulatorze. Spróbuj dotknąć rozwijaną listę. Choć obecnie nie będzie to takie oczywiste, dotknięcie tego komponentu powoduje wyświetlenie rozwijanej listy wartości — na razie jednak jest ona pusta.

Oto co udało się nam już zrobić Poniżej zamieściliśmy krótkie podsumowanie wszystkiego, co udało się nam do tej pory zrobić:

1

Stworzyliśmy układ określający wygląd aplikacji.

2

Aktywność określa, jak aplikacja powinna prowadzić interakcję z użytkownikiem.

To jest rozwijana lista, choć na razie jest jeszcze pusta.

Przycisk i pole tekstowe znajdują się poniżej rozwijanej listy i są wyśrodkowane w poziomie.

Nasz układ zawiera rozwijaną listę, przycisk oraz widok tekstowy.

Android Studio utworzyło dla nas prostą aktywność, na razie jednak jeszcze nic z nią nie zrobiliśmy. Nie istnieją

1



Układ

głupie pytania

P: Mój układ wyświetlony

w wirtualnym urządzeniu wygląda nieco inaczej niż w edytorze projektu. Dlaczego tak się dzieje?

O: Edytor projektu robi, co tylko może, 2 Aktywność

by pokazać, jak układ będzie wyglądał na urządzeniu, jednak nie zawsze jest on dokładny, przy czym zależy to od używanej wersji Android Studio. Wygląd układu w AVD odzwierciedla to, jak będzie on wyglądał na rzeczywistym urządzeniu.

Kolejną rzeczą, jaką się zajmiemy, będzie zastąpienie podanych na stałe łańcuchów znaków, których na razie używamy w widoku tekstowym i na przycisku.

jesteś tutaj 

49

Stosowanie łańcuchów znaków

Podawanie tekstów na stałe utrudnia lokalizację

¨  Utworzenie projektu ¨  Aktualizacja układu ¨  Połączenie aktywności ¨  Implementacja logiki

Jak na razie wszystkie teksty, które miały się pojawiać w widokach tekstowych oraz na przyciskach, podawaliśmy na stałe, używając właściwości android:text: Wyświetla tekst…

„Siemka, stary!”.

android:text=”Siemka, stary!”

Choć w zupełności wystarcza to podczas nauki, to jednak takie podawanie tekstów na stałe nie jest najlepszym rozwiązaniem. Załóżmy, że napisałeś aplikację, która stała się wielkim hitem w krajowej wersji sklepu Google Play. Jednak nie chcesz ograniczać się tylko do jednego kraju bądź języka — chcesz udostępnić ją w wielu krajach i w wielu językach. Jeśli jednak podałeś teksty na stałe w plikach układów, to udostępnianie aplikacji w wielu krajach będzie znacząco utrudnione.

Wartości łańcuchowe należy umieszczać w pliku strings.xml,

Co więcej, taki sposób określania tekstów także bardzo utrudnia wprowadzanie globalnych zmian. Wyobraź sobie, że szef zażyczył sobie wprowadzenia globalnych zmian w słownictwie stosowanym w aplikacji ze względu na zmianę nazwy firmy. Jeśli wszystkie teksty zostaną podane na stałe, będzie to oznaczać konieczność wprowadzania modyfikacji w potencjalnie bardzo wielu plikach.

stałe. Strings.xml to

Umieszczaj teksty w plikach zasobów tekstowych

par nazwa-łańcuch

Lepszym rozwiązaniem jest podawanie wszystkich tekstów w pliku zasobów tekstowych o nazwie strings.xml. Utworzenie i stosowanie tego pliku znacząco ułatwia tworzenie wielojęzycznych wersji aplikacji. Zamiast wprowadzać zmiany w tekstach podanych na stałe we wszystkich plikach aktywności i układów, wystarczy zastąpić plik strings.xml jego odpowiednią wersją językową. Takie rozwiązanie także znacząco ułatwia wprowadzenie globalnych zmian w tekstach używanych w całej aplikacji, gdyż wystarczy w tym celu zmodyfikować tylko jeden plik — strings.xml.

a nie podawać na plik zasobów służący do przechowywania znaków. Pliki układów oraz aktywności mogą odwoływać się do wartości łańcuchowych na podstawie ich nazw.

Jak można używać zasobów łańcuchowych? Aby skorzystać z zasobów łańcuchowych w tworzonym układzie, należy wykonać dwie czynności:

1

Utworzyć zasób łańcuchowy poprzez dodanie go do pliku strings.xml.

2

Zastosować zasób łańcuchowy w układzie.

Zobaczmy zatem, jak to zrobić.

50

Rozdział 2.

Tworzenie interaktywnych aplikacji

Utworzenie zasobu łańcuchowego

¨  Utworzenie projektu ¨  Aktualizacja układu ¨  Połączenie aktywności ¨  Implementacja logiki

Planujemy utworzyć dwa zasoby łańcuchowe: pierwszy dla tekstu wyświetlanego na przycisku oraz drugi dla domyślnego tekstu wyświetlanego w widoku tekstowym. W tym celu skorzystaj z eksploratora Android Studio i odszukaj w nim plik strings.xml, umieszczony w katalogu app/src/main/res/values. Następnie kliknij go dwukrotnie, aby go otworzyć.

DoradcaPiwny app/src/main

res

Doradca Piwny

values

strings.xml

Na razie plik strings.xml zawiera tylko jeden zasób, o nazwie ”app_name”, który ma wartość Doradca Piwny. Android Studio utworzyło ten zasób automatycznie podczas tworzenia projektu. zi Ten znacznik informuje, że chod o zasób łańcuchowy. Doradca Piwny Ten zasób łańcuchowy ma nazwę „app_name” i wartość „Doradca Piwny”.

W pierwszej kolejności dodamy nowy zasób, o nazwie ”find_beer”, którego wartością będzie Odszukaj piwo!. Aby to zrobić, otwórz plik strings.xml, a następnie dodaj do niego nowy wiersz, taki jak ten wyróżniony w poniższym przykładzie:

Doradca Piwny Odszukaj piwo!

Ten wiersz dodaje nowy zasób łańcuchowy, o nazwie „find_beer”.

Następnie dodaj kolejny zasób łańcuchowy, o nazwie ”brands” i wartości Nie wybrano żadnego piwa.

Doradca Piwny Odszukaj piwo! Nie wybrano żadnego piwa

Ten tekst będzie domyślnie wyświetlany w widoku tekstowym.

Po wprowadzeniu zmian w pliku wybierz z menu głównego Android Studio opcję File/Save All, aby zapisać modyfikacje. Teraz zastosujemy nowe zasoby łańcuchowe w pliku układu.

jesteś tutaj 

51

Stosowanie łańcuchów znaków

Zastosowanie zasobu łańcuchowego w układzie

¨  Utworzenie projektu ¨  Aktualizacja układu ¨  Połączenie aktywności ¨  Implementacja logiki

Zasobu łańcuchowego można używać w układzie, korzystając z kodu takiego jak ten przedstawiony poniżej: android:text=”@strings/find_beer” />

Fragment android:text widziałeś już wcześniej; określa on tekst, który ma zostać wyświetlony. Ale co oznacza zapis ”@string/find_beer”? Zacznijmy od jego pierwszej części, czyli @string. To po prostu sposób, by poinformować Androida, że tekstu należy szukać w pliku zasobów tekstowych. W naszym przykładzie będzie to zmodyfikowany przed chwilą plik strings.xml. Drugi fragment zapisu, find_beer, nakazuje systemowi odszukać zasób o nazwie find_beer. Innymi słowy, zapis ”@string/find_beer” oznacza: „odszukaj zasób łańcuchowy o nazwie find_beer i użyj jego wartości”. Wyświetl tekst…

zasobu łańcuchowego o nazwie find_beer.

android:text=”@string/find_beer” />

Chcemy zmienić przycisk oraz widok tekstowy umieszczone w kodzie XML układu w taki sposób, by używały dwóch dodanych przed chwilą zasobów tekstowych. Wróć zatem do pliku układu activity_find_beer.xml i wprowadź następujące zmiany w jego kodzie: Zmień wiersz:

android:text=”Przycisk” na

android:text=”@string/find_beer” Zmień wiersz:

android:text=”To jest widok tekstowy” na

android:text=”@string/brands” Cały kod układu przedstawimy na następnej stronie.

52

Rozdział 2.

Android Studio wyświetla Obejrzyj to! czasami wartości odwołań zamiast faktycznego kodu. Na przykład może się zdarzyć, że Android Studio wyświetli tekst ”Odszukaj piwo!” zamiast faktycznego kodu ”@string/find_ beer”. W edytorze kodu wszystkie takie podstawienia będą wyróżniane. Jeśli któreś z nich klikniemy lub wskażemy myszką, to zostanie wyświetlony faktyczny kod.

Tworzenie interaktywnych aplikacji

Kod pliku activity_find_beer.xml Poniżej przedstawiliśmy zaktualizowany kod pliku układu activity_find_beer.xml (zmiany zostały wyróżnione pogrubioną czcionką); zaktualizuj swoją wersję tego pliku, by była identyczna z tą. ...

DoradcaPiwny

Tego elementu nie musieliśmy zmieniać. Na kilku następnych stronach dowiesz się, jak można określić wyświetlane w nim wartości.

res layout

activity_find_beer.xml

Usuń tekst podany na stałe.

Ten zapis sprawi, że na przycisku zostanie wyświetlona wartość zasobu tekstowego find_beer.



Ten zapis sprawi, że w widoku tekstowym zostanie wyświetlona wartość zasobu tekstowego brands.

Usuń tekst podany na stałe.

Kiedy wprowadzisz zmiany, zapisz plik. Na następnej stronie zamieściliśmy podsumowanie informacji dotyczących dodawania i stosowania zasobów łańcuchowych.

jesteś tutaj 

53

Pod lupą

Pliki zasobów łańcuchowych pod lupą Plik strings.xml jest domyślnym plikiem zasobów używanym do przechowywania par nazwa – wartość, do których następnie można się odwoływać w innych miejscach aplikacji. Oto jego format: Element

informuje, że zawartością pliku są zasoby.

Element określa, że para nazwa – wartość jest łańcuchem znaków.

Doradca Piwny Odszukaj piwo! Nie wybrano żadnego piwa

Istnieją dwa czynniki, dzięki którym Android jest w stanie rozpoznać, że plik strings.xml jest plikiem zasobów łańcuchowych:



Został on zapisany w katalogu app/src/main/res/values.

Pliki XML umieszczone w tym katalogu zawierają proste wartości, takie jak łańcuchy znaków lub kolory.



Plik zawiera element , który z kolei zawiera jeden lub więcej elementów .

Sam format, w jakim została zapisana zawartość pliku, wskazuje, że zostały w nim podane łańcuchy znaków. Element informuje system, że plik zawiera zasoby, a element — że tymi zasobami są łańcuchy znaków. To wszystko oznacza, że zasobów łańcuchowych wcale nie musimy przechowywać w pliku o nazwie strings.xml — ten plik może nosić dowolną inną nazwę, można także używane łańcuchy znaków zapisać w kilku różnych plikach. Każda para nazwa – wartość ma następującą postać: wartość_łańcucha

gdzie nazwa_łańcucha jest identyfikatorem danego łańcucha znaków, a wartość_łańcucha określa sam łańcuch. W pliku układu można odwołać się do konkretnego łańcucha znaków, używając zapisu o postaci: ”@string/nazwa_łańcucha” Fragment „string” informuje system, że ma odszukać zasób łańcuchowy o podanej nazwie.

54

Rozdział 2.

To jest nazwa łańcucha, którego wartość chcemy zwrócić.

Tworzenie interaktywnych aplikacji

Czas na jazdę próbną Zobaczmy, jak teraz wygląda aplikacja. Zapisz wprowadzone zmiany i wybierz z menu Run opcję Run ‘app’. Kiedy zostaniesz poproszony, wybierz opcję pozwalającą uruchomić aplikację w emulatorze. Tym razem po uruchomieniu aplikacji teksty wyświetlane na przycisku oraz w widoku tekstowym zostaną zastąpione łańcuchami podanymi w pliku strings.xml. Na przycisku pojawi się tekst „Odszukaj piwo!”, a w widoku tekstowym napis „Nie wybrano żadnego piwa”.

Oto co już zrobiliśmy

Napisy wyświetlone na przycisku i w widoku tekstowym zostały zmienione.

Oto krótkie podsumowanie tego, co udało się nam zrobić:

1

Stworzyliśmy układ określający wygląd aplikacji.

Nasz układ zawiera komponent Spinner, przycisk oraz widok tekstowy.

2

1



Układ

Plik strings.xml zawiera zasoby łańcuchowe używane przez aplikację.

Aktywność określa, jak aplikacja powinna prowadzić interakcję z użytkownikiem.

Android Studio utworzyło dla nas aktywność, na razie jednak jeszcze nic z nią nie zrobiliśmy.



strings.xml

Dodaliśmy do niego etykietę przycisku oraz domyślny tekst listy sugerowanych gatunków piwa.

3

2

3

Aktywność

Teraz zobaczysz, w jaki sposób można dodać do komponentu Spinner listę piw. Nie istnieją

głupie pytania

P: Czy umieszczanie zasobów

P: W jaki sposób wyodrębnianie

O: Nie ma takiej konieczności, jednak

O: Załóżmy, że chcemy, by w aplikacji

łańcuchowych w plikach takich jak strings.xml jest absolutnie konieczne?

w przypadku podawania wartości tekstowych na stałe Android wyświetla komunikaty ostrzegawcze. Stosowanie pliku zasobów łańcuchowych może się początkowo wydawać niepotrzebnym wysiłkiem, ale znacząco ułatwia ono inne zadania, takie jak lokalizowanie aplikacji. Co więcej, łatwiej jest od samego początku stosować plik zasobów tekstowych, niż modyfikować aplikację i tworzyć go później.

wartości łańcuchowych ułatwia lokalizowanie aplikacji?

domyślnie był stosowany język polski, lecz po zmianie języka wybranego w urządzeniu na angielski jej interfejs także zmieniał się na anglojęzyczny. Zamiast podawać teksty w tych dwóch językach na stałe w aplikacji, można utworzyć plik zasobów łańcuchowych z tekstami w języku polskim oraz drugi taki plik — z tekstami w języku angielskim.

P: Skąd aplikacja wie, którego pliku zasobów łańcuchowych ma używać?

O: Umieść domyślne, polskie zasoby

łańcuchowe standardowo w katalogu app/ src/main/res/values, a teksty w języku angielskim w katalogu app/src/main/res/ values-en. Jeśli język wybrany w urządzeniu zostanie zmieniony na angielski, to aplikacja zacznie używać wartości z katalogu app/ src/main/res/values-en. Jeśli język wybrany na urządzeniu zostanie zmieniony na jakiś inny, to ponownie będzie ona używać wartości z katalogu app/src/main/res/ values.

jesteś tutaj 

55

Zasoby tablicowe

Dodanie wartości do komponentu Spinner Obecnie nasz układ zawiera rozwijaną listę, lecz jeszcze jest ona pusta. Za każdym 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ć. Listę wartości do komponentu Spinner można przekazać mniej więcej w taki 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 pojedynczych wartości łańcuchowych. Tym razem musimy natomiast określić tablicę takich łańcuchów i odwołać się do niej w komponencie.

Tworzenie zasobu tablicowego 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: wartość_łańcucha

W kodzie tym nazwa_łańcucha jest identyfikatorem, a wartość_łańcucha jest wartością zasobu. Aby dodać tablicę łańcuchów, należy użyć kodu o następującej postaci: To jest nazwa tablicy.

łańcuch_wartość1 To są wartości umieszczone w tabli łańcuch_wartość2 Można dodawać dowolną ich liczb cy. ę. łańcuch_wartość3 ...

W powyższym kodzie nazwa_tablicy_łańcuchów jest nazwą tablicy, a łańcuch_ wartość1, łańcuch_wartość2 oraz łańcuch_wartość3 to poszczególne łańcuchy znaków zapisane w tej tablicy. Dodajmy zatem zasób string-array do naszej aplikacji. Otwórz plik strings.xml i dodaj do niego poniższy, wyróżniony fragment kodu:

56

Rozdział 2.

¨  Utworzenie projektu ¨  Aktualizacja układu ¨  Połączenie aktywności ¨  Implementacja logiki

Zasoby to różnego rodzaju używane przez aplikację dane i pliki, które nie są kodem.

Tworzenie interaktywnych aplikacji

¨  Utworzenie projektu ¨  Aktualizacja układu

Dodanie elementu string-array do pliku strings.xml Aby dodać zasób tablicowy string-array, otwórz plik strings.xml i dodaj do niego tablicę, taką jak ta wyróżniona w poniższym przykładzie: ...

¨  Połączenie aktywności ¨  Implementacja logiki

DoradcaPiwny

Nie wybrano żadnego piwa

app/src/main jasne bursztynowe Dodaj ten element string-array do res strings.xml. Definiuje on tablicę pliku brązowe łańcuchów znaków o nazwie beer_colors, ciemne która zawiera następujące wartości: values jasne, bursztynowe, brązowe i ciemne.

...



strings.xml

Dodanie do komponentu Spinner odwołania do string-array W kodzie układu można odwołać się do zasobu string-array w bardzo podobny sposób jak do zasobów łańcuchowych. W tym przypadku zamiast zapisu: ”@string/nazwa_łańcucha”

musimy użyć zapisu:

Użyj zapisu @string, aby odwołać się do zasobu łańcuchowego, i zapisu @array, by odwołać się do tablicy.

”@array/nazwa_tablicy”

gdzie nazwa_tablicy jest nazwą zasobu tablicowego. Użyjmy zatem naszego nowego zasobu w układzie. Przejdź do pliku activity_layout_beer.xml i dodaj do elementu Spinner atrybut entries: DoradcaPiwny

...

...

activity_find_beer.xml

Ten zapis oznacza: „elementy listy w komponencie Spinner pochodzą z tablicy beer_colors”.

To już wszystkie zmiany, które należało wprowadzić, by wyświetlić w komponencie Spinner listę wartości. Zobaczmy, jak teraz prezentuje się nasza aplikacja.

jesteś tutaj 

57

Jazda próbna

Jazda próbna komponentu Spinner

¨  Utworzenie projektu ¨  Aktualizacja układu

Sprawdźmy zatem, jakie efekty wywołały te wszystkie zmiany w wyglądzie naszej aplikacji. Zapisz wszystkie zmodyfikowane pliki i uruchom aplikację.

Domyślnie w komponencie wyświetlana jest pierwsza wartość.

Kliknij komponent, aby rozwinąć listę wartości.

¨  Połączenie aktywności ¨  Implementacja logiki

Kiedy klikniesz wartość, zostanie ona wybrana.

Co udało się nam zrobić Oto krótkie podsumowanie tego, co udało się nam zrobić:

1

Stworzyliśmy układ określający wygląd aplikacji.

Nasz układ zawiera komponent Spinner, przycisk oraz widok tekstowy.

2

1



Plik strings.xml zawiera zasoby łańcuchowe używane przez aplikację.

Układ

Dodaliśmy do niego etykietę przycisku oraz domyślny tekst listy sugerowanych gatunków piwa.

3

Aktywność określa, jak aplikacja powinna prowadzić interakcję z użytkownikiem.

Android Studio utworzyło dla nas aktywność, na razie jednak jeszcze nic z nią nie zrobiliśmy.

Co zatem dalej?

58

Rozdział 2.

2



strings.xml

3

Aktywność

Tworzenie interaktywnych aplikacji

¨  Utworzenie projektu ¨  Aktualizacja układu ¨  Połączenie aktywności

Musimy zadbać o to, by przycisk coś robił 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:

1

Użytkownik wybiera z rozwijanej listy rodzaj piwa.

2

Użytkownik klika przycisk Odszukaj piwo, a układ określa, którą metodę aktywności należy wywołać.

3

Metoda zdefiniowana w aktywności pobiera wartość wybraną z listy określającą ulubiony rodzaj piwa i przekazuje ją do metody getBrands() napisanej przez nas klasy o nazwie BeerExpert.

¨  Implementacja logiki

4

Metoda getBrands() odnajduje pasujące gatunki piwa odpowiadające przekazanemu rodzajowi, po czym zwraca je do aktywności jako listę ArrayList zawierającą łańcuchy znaków.

5

Aktywność pobiera odwołanie do widoku tekstowego umieszczonego w układzie i wyświetla w nim zwróconą listę gatunków piwa.

Po wykonaniu wszystkich tych kroków lista zostaje wyświetlona na ekranie urządzenia.

2

1

3

Urządzenie

5

Układ

Aktywność getBrands("bursztynowe")

"Jack Amber" "Red Moose"

4

BeerExpert

W pierwszej kolejności zadbajmy o to, by kliknięcie przycisku powodowało wywołanie jakiejś metody aktywności.

jesteś tutaj 

59

Atrybut onClick

Niech przycisk wywołuje metodę

¨  Utworzenie projektu ¨  Aktualizacja układu ¨  Połączenie aktywności

Można przypuszczać, że zawsze dodając do układu jakiś przycisk, będziesz chcieć, by jego kliknięcie powodowało wykonanie jakiejś operacji. W tym celu przycisk musi wywoływać metodę zdefiniowaną w aktywności.

¨  Implementacja logiki

Aby kliknięcie przycisku powodowało wywołanie metody aktywności, konieczne jest wprowadzenie zmian w dwóch plikach:

 Musimy zmienić plik układu activity_find_beer.xml. Określimy w nim, która metoda klasy aktywności ma zostać wywołana po kliknięciu przycisku.

 Musimy zmienić plik aktywności FindBeerActivity.java. W tym pliku zdefiniujemy wywoływaną metodę. Zacznijmy od modyfikacji układu.

Użyj onClick, by określić metodę wywoływaną przez przycisk Poinstruowanie systemu, którą metodę należy wywołać po kliknięciu przycisku, wymaga tylko jednego wiersza kodu XML. Musimy w tym celu dodać do elementu atrybut android:onClick i podać w nim nazwę wywoływanej metody: android:onClick=”nazwa_metody”

To oznacza: „kiedy komponent zostanie kliknięty, wywołaj zdefiniowaną w aktywności metodę nazwa_metody”.

Spróbujmy to zrobić. Przejdź do pliku układu, activity_find_beer.xml, i dodaj do elementu nowy wiersz kodu XML, który określi, że kliknięcie przycisku ma spowodować wywołanie metody onClickFindBeer(): ...

...

Kiedy już wprowadzisz te zmiany, zapisz plik. Skoro układ już wie, którą metodę aktywności ma wywołać, nadszedł czas, by ją zaimplementować. Przyjrzyjmy się zatem aktywności.

60

Rozdział 2.

Kiedy przycisk wywołaj zdef zostanie kliknięty, in metodę onClic iowaną w aktywności Implementacj kFindBeer(). ą tej metody się na kilku kolejnych stro zajmiemy nach.



activity_find_beer.xml

Tworzenie interaktywnych aplikacji

Jak wygląda kod aktywności? Kiedy po raz pierwszy tworzyliśmy projekt aplikacji, kazaliśmy kreatorowi wygenerować prostą aktywność o nazwie FindBeerActivity. Jej kod został zapisany w pliku FindBeerActivity.java. Otwórz go teraz — wyświetl zawartość katalogu app/src/main/java i dwukrotnie kliknij ikonę pliku.

DoradcaPiwny

Po wyświetleniu pliku przekonasz się, że Android Studio wygenerowało całkiem sporo kodu napisanego w Javie. Zamiast analizować go teraz krok po kroku, zastąpimy go w całości kodem przedstawionym poniżej. A zatem usuń cały kod zapisany w pliku FindBeerActivity.java i zastąp go poniższym kodem:

app/src/main java com.hfad.doradcapiwny FIndBeerActivity.java

package com.hfad.doradcapiwny; import android.app.Activity; import android.os.Bundle;

Upewnij się, że klasa rozszerza klasę Activity, czyli jedną z klas systemu Android.

public class FindBeerActivity extends Activity {

jest To jest metoda onCreate(), która go wywoływana w momencie pierwsze tworzenia aktywności.

@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_find_beer); }

Metoda setContentView() informuje system o tym, którego układu używa dana aktywność. W tym przypadku 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(). Wszystkie aktywności (a nie tylko ta pokazana powyżej) muszą dziedziczyć po klasie Activity lub którejś z jej klas pochodnych. Klasa ta definiuje grupę metod, które przekształcają zwyczajne klasy Javy w pełnowartościowe aktywności Androida. Oprócz tego wszystkie aktywności muszą implementować metodę onCreate(). Metoda ta jest wywoływana w momencie tworzenia obiektu aktywności i służy do wykonania prostych operacji konfiguracyjnych, takich jak określenie układu skojarzonego z daną aktywnością. Ta konkretna czynność jest wykonywana poprzez wywołanie metody setContentView(). Jej wywołanie użyte w powyższym kodzie, setContentView(R.layout.activity_find_ beer), informuje system, że aktywność używa układu activity_find_beer. Na poprzedniej stronie dodaliśmy do przycisku atrybut onClick i przypisali mu wartość onClickFindBeer. Musimy zatem dodać tę metodę do aktywności, aby można ją było wywołać w momencie kliknięcia przycisku. Dzięki temu aktywność będzie w stanie zareagować, gdy użytkownik dotknie przycisku wyświetlonego w interfejsie aplikacji.

Zrób to sam!

Zastąp kod w swoim pliku FindBeerActivity.java kodem przedstawionym na tej stronie.

jesteś tutaj 

61

onClickFindBeer()

Dodaj do aktywności metodę onClickFindBeer() Metoda onClickFindBeer() musi mieć ściśle określoną sygnaturę, gdyż w przeciwnym razie nie zostanie wywołana po kliknięciu przycisku wyświetlonego w układzie. Metoda musi wyglądać dokładnie tak jak pokazaliśmy poniżej:

¨  Utworzenie projektu ¨  Aktualizacja układu ¨  Połączenie aktywności ¨  Implementacja logiki

public void onClickFindBeer(View view) { } Metoda musi być publiczna.

Metoda musi zwracać wartość typu void.

Metoda musi mieć jeden parametr typu View.

Jeśli metoda nie będzie miała powyższej postaci, to nie zostanie wywołana, gdy użytkownik dotknie przycisku. Stanie się tak dlatego, że za kulisami Android będzie poszukiwał publicznej metody zwracającej wartość typu void, której nazwa odpowiada wartości podanej w kodzie XML układu. Z pozoru można przypuszczać, że parametr View tej metody jest niepotrzebny, niemniej jednak jego istnienie jest uzasadnione. Otóż parametr ten odwołuje się do komponentu GUI, który doprowadził do wywołania metody (w naszym przypadku komponentem tym jest przycisk). Jak już wspominaliśmy, elementy graficznego interfejsu użytkownika, takie jak przyciski lub widoki tekstowe, są obiektami klasy View. Zmodyfikujmy zatem kod naszej aktywności (FindBeerActivity.java). Dodaj do niej metodę onClickFindBeer():

Używamy tej klasy, więc musimy ją zaimportować.

Jeśli chcesz, aby metoda odpowiadała na kliknięcia przycisku, musi być metodą publiczną, zwracać wartość typu void i mieć jeden parametr typu View.

... import android.view.View;

DoradcaPiwny

public class FindBeerActivity extends Activity { ...

Dodaj metodę onClickFindBeer do klasy FindBeerActivity.java.

app/src/main

// Metoda wywoływana, gdy użytkownik kliknie przycisk public void onClickFindBeer(View view) { }

com.hfad.doradcapiwny

}

FIndBeerActivity.java

onClickFindBeer()

activity_find_beer.xml

62

Rozdział 2.

java

FindBeerActivity.java

Tworzenie interaktywnych aplikacji

Metoda onClickFindBeer() musi coś robić Skoro dodaliśmy do aktywności metodę onClickFindBerr(), kolejnym zadaniem, które przed nami stoi, jest sprawienie, by metoda ta coś robiła. Konkretnie rzecz biorąc, naszym celem jest wyświetlenie w aplikacji listy różnych gatunków piwa, które odpowiadają rodzajowi wybranemu przez użytkownika.

¨  Utworzenie projektu ¨  Aktualizacja układu ¨  Połączenie aktywności ¨  Implementacja logiki

Aby to zrobić, w pierwszej kolejności musimy pobrać referencje do dwóch komponentów GUI: rozwijanej listy i widoku tekstowego. Dzięki temu będziemy mogli zarówno pobrać wartość wybraną przez użytkownika na rozwijanej liście, jak i wyświetlić tekst w widoku tekstowym.

Zastosuj metodę findViewById(), by pobrać referencję do widoku Referencje do dwóch interesujących nas komponentów GUI można pobrać przy użyciu metody findViewById(). Metoda ta pobiera jako parametr identyfikator komponentu GUI i zwraca obiekt klasy View. Ten zwrócony obiekt należy następnie rzutować do odpowiedniego typu komponentu (na przykład TextView lub Button). Poniżej pokazaliśmy, w jaki sposób możemy użyć metody findViewById(), żeby pobrać referencję widoku tekstowego o identyfikatorze brands:

Interesuje nas widok o identyfikatorze brands.

TextView brands = (TextView) findViewById(R.id.brands); brands to widok tekstowy, więc musisz rzutować zwrócony wynik do tego typu.

Przyjrzyj się dokładnie, w jaki sposób określiliśmy identyfikator widoku tekstowego. Zamiast przekazywać jego nazwę przekazaliśmy identyfikator o postaci R.id.brands. Ale co to oznacza? I czym jest R? R.java to specjalny plik Javy generowany przez narzędzia Android SDK za każdym razem, gdy jest budowana aplikacja. Plik ten jest zapisywany w katalogu app/build/ generated/source/r/debug projektu i należy do tego samego pakietu co aplikacja. Android używa go do zarządzania wszystkimi zasobami używanymi w aplikacji, a jego zawartość pozwala nam między innymi na odwoływanie się do komponentów GUI z poziomu kodu aktywności. Jeśli wyświetlisz kod pliku R.java, to przekonasz się, że zawiera on grupę klas wewnętrznych — po jednej klasie dla każdego typu zasobów. W każdej z tych klas znajdują się pola odpowiadające wszystkim zasobom danego typu. Na przykład klasa R zawiera klasę wewnętrzną o nazwie id, która z kolei definiuje wartość static final brands. Android dodał ten kod do pliku R.java, kiedy umieściliśmy w układzie odwołanie "@+id/brands". Poniższe wywołanie: (TextView) findViewById(R.id.brands);

używa tej wartości, by odwołać się do komponentu brands.

R jest specjalną klasą Javy umożliwiającą pobieranie odwołań do różnych zasobów aplikacji.

Spokojnie

Plik R.java jest generowany automatycznie.

Sami nie musimy niczego zmieniać w pliku R.java, warto jednak wiedzieć o jego istnieniu.

jesteś tutaj 

63

Metody klasy View

Dysponując obiektem View, można odwoływać się do jego metod

¨  Utworzenie projektu ¨  Aktualizacja układu ¨  Połączenie aktywności ¨  Implementacja logiki

Metoda findViewById() zwraca obiekt reprezentujący komponent GUI. Oznacza to, że można używać metod udostępnianych przez daną klasę Javy do pobierania i ustawiania właściwości komponentu GUI. Przyjrzyjmy się temu nieco dokładniej.

Określanie tekstu wyświetlanego w komponencie TextView Jak wiadomo, referencję do widoku tekstowego można pobrać, używając następującego kodu: TextView brands = (TextView) findViewById(R.id.brands);

Wykonanie tego kodu powoduje utworzenie obiektu TextView o nazwie brands; jednocześnie zyskujemy możliwość wywoływania metod tego obiektu. Załóżmy, że chcesz wyświetlić w widoku tekstowym tekst „Utelka iwa”. Moglibyśmy to zrobić w następujący sposób: brands.setText(”Utelka iwa”);

Ustawiamy tekst w komponencie TextView na „Utelka iwa”.

Pobranie wartości wybranej w komponencie Spinner Referencję do listy rozwijanej można pobrać w taki sam sposób jak do widoku tekstowego. Także w tym przypadku posłużymy się metodą findViewById(), ale zwrócony wynik rzutujemy na typ Spinner: Spinner color = (Spinner) findViewById(R.id.color);

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: String.valueOf(color.getSelectedItem())

Wywołanie o postaci:

To wywołanie pobiera z rozwijanej listy aktualnie wybraną wartość i konwertuje ją na łańcuch znaków.

color.getSelectedItem()

zwraca w rzeczywistości ogólny obiekt Javy. Wynika to z faktu, że zawartością rozwijanych list mogą być nie tylko łańcuchy znaków, lecz także na przykład obrazki. W naszym przypadku wiemy, że są to łańcuchy znaków, więc możemy wywołać metodę String.valueOf(), aby skonwertować Object na String.

64

Rozdział 2.

Tworzenie interaktywnych aplikacji

Aktualizacja kodu aktywności Teraz już wiesz dostatecznie dużo, by napisać kod metody onClickFindBeer(). Zamiast pisać ten kod w jednym wierszu zacznijmy od odczytania wartości wybranej na liście rozwijanej, a następnie wyświetlmy ją w widoku tekstowym.

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? Kod musi pobierać rodzaj piwa wybrany na liście rozwijanej, a następnie wyświetlić go w widoku tekstowym. // Metoda wywoływana, gdy użytkownik kliknie przycisk public void onClickFindBeer(............ view) { // Pobiera referencję komponentu TextView ........ brands = ..........

.............. (.................);

// Pobiera referencję komponentu Spinner Spinner ............. = ............ .................(................); // Pobiera wartość wybraną w komponencie Spinner String ............ = String.valueOf(color. ..................); // Wyświetla wybraną wartość brands. ................(beerType); }

findViewById

TextView

color

(TextView) Button

getSelectedItem()

setText R.view.brands

findView

R.id.color R.id.brands

Nie musisz używać wszystkich magnesików.

R.view.color

findView

View

findViewById

(Spinner) beerType

jesteś tutaj 

65

Magnesiki — rozwiązanie

Magnesiki aktywności. Rozwiązanie 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? Kod musi pobierać rodzaj piwa wybrany na liście rozwijanej, a następnie wyświetlić go w widoku tekstowym. // Metoda wywoływana, gdy użytkownik kliknie przycisk public void onClickFindBeer(............ view) { View // Pobiera referencję komponentu TextView ........ brands = .......... (TextView) View Text

.............. findViewById

(..................); R.id.brands

// Pobiera referencję komponentu Spinner Spinner ............. = ............ findViewById R.id.color (Spinner) .................(................); color // Pobiera wartość wybraną w komponencie Spinner String ............ getSelectedItem() beerType = String.valueOf(color. ..................); // Wyświetla wybraną wartość brands. ................(beerType); setText }

R.view.brands

findView Button

R.view.color findView

66

Rozdział 2.

Tych magnesików nie musiałeś używać.

Tworzenie interaktywnych aplikacji

Pierwsza wersja aktywności

¨  Utworzenie projektu ¨  Aktualizacja układu ¨  Połączenie aktywności ¨  Implementacja logiki

Nasz chytry plan zakłada, by kod aktywności pisać etapami i po każdym z nich go testować. Na samym końcu aktywność będzie pobierać wartość wybraną z rozwijanej listy, wywoływać metodę napisanej przez nas klasy, a następnie wyświetlać dobrane gatunki piwa. W przypadku pierwszej wersji aktywności chcemy jedynie upewnić się, że prawidłowo pobraliśmy wartość wybraną z rozwijanej listy. Poniżej przedstawiliśmy kod aktywności, włącznie z metodą, którą uzupełniłeś na poprzedniej stronie. Wprowadź te zmiany w kodzie pliku FindBeerActivity.java, a następnie go zapisz: package com.hfad.doradcapiwny; DoradcaPiwny

import android.os.Bundle;

app/src/main

import android.app.Activity; import android.view.View; import android.widget.Spinner; import android.widget.TextView;

Używamy tych dodatkowych klas, więc musimy je zaimportować.

java com.hfad.doradcapiwny

public class FindBeerActivity extends Activity {

FIndBeerActivity.java

@Override protected void onCreate(Bundle savedInstanceState) {

Tej metody nie musimy zmieniać.

super.onCreate(savedInstanceState); setContentView(R.layout.activity_find_beer); } // Metoda wywoływana, gdy użytkownik kliknie przycisk public void onClickFindBeer( View view) { // Pobiera referencję komponentu TextView TextView brands = (TextView)

findViewById (R.id.brands);

// Pobiera referencję komponentu Spinner Spinner color = (Spinner) findViewById(R.id.color);

Metoda findViewById() zwraca obiekt View, musimy go zatem rzutować na odpowiedni typ.

// Pobiera wartość wybraną w komponencie Spinner String beerType = String.valueOf(color.getSelectedItem() ); // Wyświetla wybraną wartość brands.setText(beerType); } }

Metoda getSelectedItem() zwraca wynik typu Object, musimy zamienić go na String.

jesteś tutaj 

67

Co się dzieje?

Co ten kod robi?

¨  Utworzenie projektu ¨  Aktualizacja układu ¨  Połączenie aktywności ¨  Implementacja logiki

Zanim weźmiemy nasz kod na jazdę próbną, spróbujmy zrozumieć, co on tak naprawdę robi.

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 .

FindBeerActivity

Układ

2

Aktywność pobiera referencje do komponentów TextView i Spinner, wywołując w tym celu metodę findViewById().

komponent Spinner FindBeerActivity komponent TextView

3

Aktywność odczytuje wartość wybraną w komponencie Spinner (w tym przypadku jest to wartość "bursztynowe") i konwertuje ją na łańcuch znaków.

FindBeerActivity

4

bursztynowe

komponent Spinner

Aktywność określa wartość właściwości text komponentu TextView tak, by odpowiadała ona aktualnie wybranemu rodzajowi piwa. "bursztynowe"

FindBeerActivity

68

Rozdział 2.

komponent TextView

Tworzenie interaktywnych aplikacji

Jazda próbna — test modyfikacji Wprowadź wszystkie zmiany w kodzie aktywności, następnie zapisz plik i uruchom aplikację. Tym razem kiedy klikniesz przycisk Odszukaj piwo!, aplikacja wyświetli wartość, która w danej chwili będzie wybrana z rozwijanej listy. piwa jest Wybrany rodzaj doku wi w y lan wyświet tekstowym.

Nie istnieją

głupie pytania

P

: Dodałem nowy łańcuch znaków do pliku strings.xml, ale nie mogę go znaleźć w pliku R.java. Dlaczego go tam nie ma?

P: W tym przypadku? A nie zawsze? O: Rozwijanych list można używać do bardziej złożonych rzeczy

zmian wprowadzonych w projekcie. Jeśli dodałeś zasoby, lecz nie widzisz ich w pliku R.java, to sprawdź, czy zmiany zostały zapisane.

niż wyświetlanie tekstów. Na przykład można na nich wyświetlać ikonę obok tekstu. Dzięki temu, że metoda getSelectedItem() zwraca obiekt, jest ona bardziej elastyczna niż w przypadku, gdyby zwracała wynik typu String.

Plik R.java jest także aktualizowany w momencie budowania aplikacji. Aplikacja jest budowana przed jej uruchomieniem, a zatem także uruchomienie aplikacji powoduje zaktualizowanie pliku R.java.

P: Czy nazwa metody onClickFindBeer ma znaczenie? O: Znaczenie ma tylko to, by nazwa metody w kodzie aktywności

O: Android Studio generuje plik R.java w momencie zapisywania

P: Wygląda na to, że wartości wyświetlane w rozwijanej liście są statyczne, gdyż są to łańcuchy podane w zasobie string-array. Czy można te wartości modyfikować programowo?

O: Owszem, można, choć jest to bardziej złożone niż użycie

wartości statycznych. W dalszej części książki pokażemy, w jaki sposób można uzyskać pełną kontrolę nad wartościami wyświetlanymi w komponentach, w tym także na rozwijanych listach.

P: Jaki jest typ obiektu zwracanego przez metodę getSelectedItem()?

odpowiadała nazwie podanej w atrybucie onClick przycisku w kodzie układu.

P: Dlaczego zastąpiliśmy kod aktywności wygenerowany przez Android Studio?

O: Zintegrowane środowiska programistyczne, takie jak Android

Studio, zawierają wiele funkcji i narzędzi umożliwiających programistom oszczędzanie czasu. Generują za nas dużo kodu, co czasami jest bardzo użyteczne. Uważamy jednak, że podczas nauki nowego języka lub nowego obszaru zagadnień programistycznych, takich jak tworzenie aplikacji na Androida, lepiej jest poznawać podstawy języka, a nie kod generowany przez IDE. Dzięki temu można lepiej zrozumieć język.

O: Jest on zadeklarowany jako Object. Ponieważ wartości

zostały określone przy użyciu zasobu string-array, w tym przypadku faktycznym typem zwracanej wartości jest String.

jesteś tutaj 

69

Klasa BeerExpert

Tworzenie własnej klasy Javy

Zgodnie z tym, co sygnalizowaliśmy już na samym początku rozdziału, nasza aplikacja Doradca piwny wyświetla rekomendowane gatunki piwa, używając napisanej przez nas niestandardowej klasy języka Java. Jest to najzwyklejsza klasa Javy, która nic nie wie o tym, że jest używana w aplikacji działającej w systemie Android.



Układ

strings.xml

Specyfikacja własnej klasy Javy Nasza klasa musi spełniać następujące wymagania:

Aktywność



Musi należeć do pakietu o nazwie com.hfad.doradcapiwny.



Musi mieć nazwę BeerExpert.



Ma udostępniać jedną metodę, getBrands(), pobierającą preferowany kolor piwa (określony jako łańcuch znaków), i zwracać obiekt typu List, listę zawierającą sugerowane gatunki piwa.

Implementacja i testowanie klasy Klasy Javy mogą być niesłychanie złożone i korzystać z wywołań metod implementujących skomplikowaną logikę aplikacji. Możesz bądź to napisać swoją własną wersję tej klasy, bądź też skorzystać z naszej, bardzo wyszukanej wersji, przedstawionej poniżej: package com.hfad.doradcapiwny;

DoradcaPiwny app/src/main java com.hfad.doradcapiwny

import java.util.ArrayList; import java.util.List;

BeerExpert

Musimy napisać klasę, której aktywność będzie mogła używać do odnajdywania gatunków piwa odpowiadających rodzajowi wybranemu przez użytkownika.

To zwyczajny kod napisany w Javie, nie ma nic wspólnego z Androidem.

BeerExpert.java

public class BeerExpert { List getBrands(String color) { List brands = new ArrayList(); if (color.equals(”bursztynowe”)) { brands.add(”Jack Amber”);

Zrób to sam!

brands.add(”Red Moose”); } else { brands.add(”Jail Pale Ale”); brands.add(”Gout Stout”); } return brands; } }

70

Rozdział 2.

Dodaj do projektu klasę BeerExpert. Zaznacz pakiet com.hfad.doradcapiwny w katalogu app/src/ main/java i wybierz opcję File/New.../Java Class, następnie nadaj plikowi nazwę „BeerExpert”   i upewnij się, że będzie on należał do pakietu „com.hfad.doradcapiwny”. W ten sposób utworzysz plik BeerExpert.java.

Tworzenie interaktywnych aplikacji

Dodaj do aktywności wywołanie metody naszej klasy, aby była wyświetlana FAKTYCZNA porada

¨  Utworzenie projektu ¨  Aktualizacja układu ¨  Połączenie aktywności ¨  Implementacja logiki

W drugiej wersji kodu aktywności musimy rozszerzyć kod metody onClickFindBeer() o wywołanie metody klasy BeerExpert i zastosowanie zwróconych przez nią rekomendacji. Te zmiany wiążą się z zastosowaniem zwyczajnego kodu napisanego w Javie. Możesz spróbować samodzielnie napisać ten kod i wypróbować jego działanie, uruchamiając aplikację, bądź też możesz odwrócić kartkę i przeanalizować nasze rozwiązanie. Jednak zanim pokażemy zmiany, które należy wprowadzić w kodzie, spróbuj wykonać poniższe ćwiczenie; pomoże Ci ono stworzyć niezbędny kod aktywności.

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 import import import import import import

android.os.Bundle; android.app.Activity; android.view.Menu; android.view.View; android.widget.Spinner; android.widget.TextView; java.util.List; Ten wiersz kodu dodaliśmy za Ciebie.

public class FindBeerActivity extends Activity { private BeerExpert expert = new BeerExpert(); ...

Musisz użyć klasy BeerExpert, by pobrać rekomendowane gatunki piwa, zatem dodaliśmy także ten wiersz kodu.

// Metoda wywoływana, gdy użytkownik kliknie przycisk public void onClickFindBeer( View view) { // Pobiera referencję komponentu TextView TextView brands = (TextView) findViewById (R.id.brands); // Pobiera referencję komponentu Spinner Spinner color = (Spinner) findViewById(R.id.color); // Pobiera wartość wybraną w komponencie Spinner String beerType = String.valueOf(color.getSelectedItem() );

}

}

Twoim zadaniem jest zaktualizowa nie kodu metody onClickFindBeer().

jesteś tutaj 

71

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 import import import import import import

android.os.Bundle; android.app.Activity; android.view.Menu; android.view.View; android.widget.Spinner; android.widget.TextView; java.util.List;

public class FindBeerActivity extends Activity { private BeerExpert expert = new BeerExpert(); ... // Metoda wywoływana, gdy użytkownik kliknie przycisk public void onClickFindBeer( View view) { // Pobiera referencję komponentu TextView TextView brands = (TextView) findViewById (R.id.brands); // Pobiera referencję komponentu Spinner Spinner color = (Spinner) findViewById(R.id.color); // Pobiera wartość wybraną w komponencie Spinner String beerType = String.valueOf(color. getSelectedItem() ); // pobranie rekomendacji z klasy BeerExpert Pobiera listę sugerowanych gatunków. Konstruuje łańcuch znaków, w którym zostaną zapisane wartości z listy.

List brandsList = expert.getBrands(beerType); StringBuilder brandsFormatted = new StringBuilder(); for (String brand : brandsList) { brandsFormatted.append(brand).append( ‘\n’); }

Wyświetla każdy gatunek piwa w osobnym wierszu.

// wyświetlenie wyników brands.setText(brandsFormatted);

Wyświetla wyniki w widoku tekstowym.

} }

72

Rozdział 2.

wymaga jedynie zwyczajnego Zastosowanie klasy BeerExpert nie musisz się przejmować, go dlate e, Javi w go sane kodu napi lądać nieco inaczej niż nasze. wyg ie będz nie iąza jeśli Twoje rozw

Tworzenie interaktywnych aplikacji

Kod aktywności, wersja 2 Poniżej zamieściliśmy pełną wersję kodu aktywności. Wprowadź zmiany w swojej wersji pliku FindBeerActivity.java. Upewnij się, że dodałeś do projektu klasę BeerExpert, a następnie zapisz wszystkie zmiany:

¨  Utworzenie projektu ¨  Aktualizacja układu ¨  Połączenie aktywności ¨  Implementacja logiki

package com.hfad.doradcapiwny; import import import import import import import

android.os.Bundle; android.app.Activity; android.view.Menu; android.view.View; android.widget.Spinner; android.widget.TextView; java.util.List; Używamy tej dodatkowej klasy, więc musimy ją zaimportować.

public class FindBeerActivity extends Activity { private BeerExpert expert = new BeerExpert();

DoradcaPiwny app/src/main java com.hfad.doradcapiwny FIndBeerActivity.java

Dodaj instancję klasy BeerExpert jako prywatne pole klasy.

@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_find_beer); }

// Metoda wywoływana, gdy użytkownik kliknie przycisk public void onClickFindBeer( View view) { // Pobiera referencję komponentu TextView TextView brands = (TextView) findViewById (R.id.brands); // Pobiera referencję komponentu Spinner Spinner color = (Spinner) findViewById(R.id.color); // Pobiera wartość wybraną w komponencie Spinner String beerType = String.valueOf(color.getSelectedItem() ); // Pobranie rekomendacji z klasy BeerExpert Użyj klasy BeerExpert do pobrania listy sugerowanych List brandsList = expert.getBrands(beerType); gatunków piwa. StringBuilder brandsFormatted = new StringBuilder(); for (String brand : brandsList) { Skonstruuj łańcuch znaków, brandsFormatted.append(brand).append(‘\n’); który wyświetli każdy gatunek w nowym wierszu. } // Wyświetlenie wyników brands.setText(brandsFormatted); Wyświetl skonstruowany łańcuch znaków brands.setText(beerType); w widoku tekstowym. } }

Usuń ten wiersz.

jesteś tutaj 

73

Co się dzieje?

Co się dzieje podczas wykonywania tego kodu? 1

Kiedy użytkownik klika przycisk Odszukaj piwo!, zostaje wywołana metoda onClickFindBeer() aktywności.

Metoda ta tworzy referencje do listy rozwijanej i widoku tekstowego, a następnie pobiera wartość wybraną w liście. onClickFindBeer()

bursztynowe FindBeerActivity

Układ

komponent Spinner komponent TextView

2

Metoda onClickFindBeer() wywołuje metodę getBrands() klasy BeerExpert, przekazując do niej wartość wybraną w komponencie Spinner.

Metoda getBrands() zwraca listę sugerowanych gatunków piwa.

getBrands("bursztynowe") onClickFindBeer() FindBeerActivity

3

"Jack Amber" "Red Moose"

BeerExpert

Metoda onClickFindBeer() formatuje listę gatunków piwa i zapisuje ją we właściwości text komponentu TextView. "Jack Amber Red Moose" onClickFindBeer() FindBeerActivity

74

Rozdział 2.

komponent TextView

Tworzenie interaktywnych aplikacji

Jazda próbna — test aplikacji Kiedy już wprowadzisz w aplikacji zmiany przedstawione na poprzedniej stronie, uruchom ją i przetestuj. Spróbuj wybierać różne rodzaje piwa i za każdym razem klikaj przycisk Odszukaj piwo!.

¨  Utworzenie projektu ¨  Aktualizacja układu ¨  Połączenie aktywności ¨  Implementacja logiki

Takie wyniki uzyskasz po wybraniu opcji „jasne”. Takie wyniki uzyskasz po wybraniu opcji „bursztynowe”.

Po wybraniu rodzaju piwa i kliknięciu przycisku Odszukaj piwo!, aplikacja użyje klasy BeerExpert do określenia i wyświetlenia sugerowanych gatunków piwa.

jesteś tutaj 

75

Przybornik

Rozdział 2.

Twój przybornik do Androida Opanowałeś już rozdział 2. i dodałeś do swojego przybornika z narzędziami umiejętności tworzenia interaktywnych aplikacji na Androida.

j Pełny kod przykładowe j w  tym ane tow zen pre cji ika apl rać pob z rozdziale możes nictwa z serwera FTP wydaw .pl/ lion .he ftp :// ftp : Helion przyklady/andrr2.zip

CELNE SPOSTRZEŻENIA  





Element pozwala dodawać przyciski. Element umożliwia dodawanie pola, którego kliknięcie powoduje wyświetlenie listy wartości.



"@string/nazwa" 

Wszystkie komponenty GUI (graficznego interfejsu użytkownika) są rodzajami widoków. Wszystkie one dziedziczą po klasie View.

Oprócz tego w aktywności należy zaimplementować metodę o podanej nazwie:

łańcuch1

public void metodaClick(View view) {

...

}



strings.xml to plik zasobów łańcuchowych. Służy on do oddzielania wartości łańcuchowych od kodu układów i aktywności oraz ułatwia lokalizowanie aplikacji. Zasób łańcuchowy można dodać do pliku strings.xml, używając zapisu: wartość

76

Rozdział 2.



Do zasobu string-array można odwołać się w układzie, używając zapisu: ”@array/nazwa_tablicy”



Aby kliknięcie przycisku wywołało metodę, należy dodać do jego kodu w układzie następujący atrybut: android:onClick=”metodaClick”

Tablicę łańcuchów znaków można dodawać, używając następującego zapisu:



Do zasobu łańcuchowego można się odwoływać, używając zapisu:









Plik R.java jest generowany automatycznie. Pozwala on pobierać w kodzie Javy referencje do układów, komponentów GUI, łańcuchów znaków oraz wszelkich innych zasobów. Metoda findViewById() zwraca referencję do widoku. Metoda setText() pozwala określić tekst wyświetlany w widoku. Metoda getSelectedItem() zwraca wartość, która jest aktualnie wybrana z rozwijanej listy. Nowe klasy można dodawać do projektu, wybierając z menu opcję File/New.../Java Class.

3. Wiele aktywności i intencji

Jakie są Twoje intencje? Wysłałam intencję w sprawie obsługi mojej ACTION_CALL i dostałam do wyboru oferty przeróżnych aktywności.

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.

to jest nowy rozdział 

77

Zadania

Aplikacja może zawierać więcej niż jedną aktywność Wcześniej w tej książce napisaliśmy, że aktywność jest jedną, dobrze zdefiniowaną operacją, którą użytkownik może wykonywać w aplikacji, taką jak na przykład wyświetlanie listy receptur. Jeśli aplikacja jest w miarę prosta, to taka jedna aktywność może w zupełności wystarczyć. Jednak w bardzo wielu przypadkach użytkownik będzie chciał robić więcej niż jedną rzecz — na przykład oprócz wyświetlania listy receptur będzie także chciał je dodawać. W takich przypadkach konieczne będzie zastosowanie więcej niż jednej aktywności: jednej do wyświetlania listy receptur i drugiej do dodawania nowych receptur. Najlepszym sposobem, by zrozumieć, o co tu chodzi, jest przeanalizowanie odpowiedniego przykładu w działaniu. W tym rozdziale napiszemy aplikację składającą się z dwóch aktywności. Pierwsza z nich będzie umożliwiała wpisanie wiadomości. Kliknięcie przycisku wyświetlanego w pierwszej aktywności spowoduje uruchomienie drugiej aktywności i przekazanie do niej treści wiadomości. Ta druga aktywność wyświetli wiadomość.

Pierwsza aktywność umożliwia wpisanie wiadomości.

Kiedy klikniesz przycisk Wyślij wiadomość wyświetlony w pierwszej aktywności, treść wiadomości zostanie przekazana do drugiej aktywności. Następnie zostaje uruchomiona druga aktywność, która wyświetla wiadomość.

Oto co zrobimy w tym rozdziale: 1

Utworzysz prostą aplikację z jedną aktywnością i układem.

2

Dodasz do aplikacji drugą aktywność i układ.

3

Sprawisz, że pierwsza aktywność wywoła drugą.

4

Przekażesz dane z pierwszej aktywności do drugiej.

78

Rozdział 3.

Aktywność jest jedną, konkretnie określoną operacją, którą użytkownik może wykonywać. Połączenie większej liczby aktywności w celu wykonania czegoś bardziej złożonego nazywamy zadaniem.

Wiele aktywności i intencji

Oto struktura naszej aplikacji Aplikacja składa się z dwóch aktywności i dwóch układów:

1

Podczas uruchamiania aplikacji zostanie wykonana aktywność CreateMessageActivity,

2

Kiedy użytkownik kliknie przycisk wyświetlony w aktywności CreateMessageActivity, zostaje uruchomiona aktywność ReceiveMessageActivity.

Ta aktywność używa układu o nazwie activity_create_message.xml.

To kliknięcie powoduje uruchomienie aktywności ReceiveMessageActivity, która z kolei używa układu activity_receive_message.xml.





activity_create_message.xml

1

activity_receive_message.xml

Tekst wpisany w aktywności CreateMessageActivity zostanie przekazany do aktywności ReceiveMessageActivity.

2 Urządzenie

CreateMessageActivity.java

Zaczynamy: utworzenie projektu Projekt tej aplikacji możesz utworzyć w dokładnie taki sam sposób, w jaki tworzyłeś projekty w dwóch poprzednich rozdziałach. Utwórz w Android Studio nowy projekt aplikacji o nazwie Komunikator i użyj przy tym domeny firmowej „hfad.com”, dzięki czemu kody aplikacji zostaną umieszczone w pakiecie com.hfad.komunikator. Jako minimalny poziom API wybierz API poziomu 19, tak by aplikacja mogła działać na większości urządzeń. Aby Twoja aplikacja odpowiadała naszej, przedstawionej w tej książce, będziesz także potrzebować pustej aktywności o nazwie CreateMessageActivity i układu o nazwie activity_create_message. Upewnij się, że przed zakończeniem tworzenia aktywności usunięte zostało zaznaczenie z pola wyboru Backwards Compatibility (AppCompat).

ReceiveMessageActivity.java

¨  Utworzenie pierwszej aktywności ¨  Utworzenie drugiej aktywności ¨  Wywołanie drugiej aktywności ¨  Przekazanie danych

Na następnej stronie zajmiemy się aktualizacją układu aplikacji.

jesteś tutaj 

79

Aktualizacja układu

To jest edytowalne pole tekstowe. Kiedy jest puste, prezentuje podpowiedź wyjaśniającą, co należy w nim wpisać.

Aktualizacja układu Poniżej przedstawiliśmy kod XML umieszczony w pliku activity_create_message.xml. Usunęliśmy z niego element umieszczony tam przez Android Studio i zastąpiliśmy go dwoma nowymi elementami: i . Element tworzy pole tekstowe, którego można używać do wpisywania danych. Zmień swój plik activity_create_message.xml, tak by zawierał poniższy kod XML:

layout

Ten element tworzy edytowalne pole tekstowe.





80

Rozdział 3.

To jest zasób łańcuchowy, który musimy utworzyć.

Kliknięcie przycisku spowoduje wywołanie metody onSendMessage() zdefiniowanej w aktywności.

Element definiuje pole tekstowe służące do wpisywania danych. Dziedziczy on po tej samej klasie View co wszystkie inne komponenty GUI, które poznaliśmy do tej pory.

Wiele aktywności i intencji

Aktualizacja pliku strings.xml…

¨  Utworzenie pierwszej aktywności

W układzie przedstawionym na poprzedniej stronie użyliśmy dwóch zasobów łańcuchowych. Tekst prezentowany na przycisku jest pobierany z zasobu @string/ send, a podpowiedź prezentowana w edytowalnym polu tekstowym, informująca o tym, co należy w nim wpisać, jest pobierana z zasobu @string/hint. Oznacza to, że do pliku strings.xml musimy dodać dwa zasoby łańcuchowe, o nazwach ”send” oraz ”hint”, a następnie określić ich wartości. Zrób to teraz:

¨  Utworzenie drugiej aktywności ¨  Wywołanie drugiej aktywności ¨  Przekazanie danych

Komunikator app/src/main

Tekst „Wyślij wiadomość” zostanie wyświetlony na przycisku.

...

res

Wyślij wiadomość Wpisz wiadomość

values

Tekst „Wpisz treść wiadomości” będzie wyświetlany w pustym polu tekstowym jako podpowiedź dla użytkownika.



strings.xml

…i dodanie metody do kodu aktywności Poniższy atrybut umieszczony w elemencie : android:onClick=”onSendMessage”

oznacza, że kliknięcie przycisku spowoduje wywołanie metody onSendMessage() zdefiniowanej w aktywności. Musimy ją zatem dodać. Otwórz plik CreateMessageActivity.java i zastąp kod wygenerowany przez Android Studio poniższym kodem: package com.hfad.komunikator; import android.app.Activity; import android.os.Bundle; import android.view.View;

Upewnij się, że aktywność dziedziczy po klasie Activity.

public class CreateMessageActivity extends Activity {

Komunikator

ana za Metoda onCreate() jest wywoływ aktywność. zona twor jest gdy m, raze pierwszym

@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_create_message); }

}

app/src/main java com.hfad.komunikator

// Metoda onSendMessage() jest wywoływana po kliknięciu przycisku public void onSendMessage(View view) { Ta metoda zostanie wywołana po kliknięciu } przycisku. Jej

CreateMessage Activity.java

kod uzupełnimy w trakcie dalszych prac nad aplikacją.

Skoro załatwiliśmy sprawę pierwszej aktywności, przejdźmy teraz do drugiej.

jesteś tutaj 

81

Utworzenie aktywności

Utworzenie drugiej aktywności i układu Android Studio udostępnia kreator umożliwiający dodawanie do aplikacji 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ść.

¨  Utworzenie pierwszej aktywności ¨  Utworzenie drugiej aktywności ¨  Wywołanie drugiej aktywności ¨  Przekazanie danych

Aby utworzyć nową aktywność, w eksploratorze Android Studio, przejdź do widoku Project, kliknij pakiet com.hfad.komunikator w katalogu app/src/main/java, a następnie wybierz z menu głównego opcję File/New/Activity, a później opcję Empty 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ę, że jest zaznaczone pole wyboru nakazujące wygenerowanie układu oraz że pola wyboru Launcher Activity i Backwards Compatibility (AppCompat) nie są zaznaczone. W końcu potwierdź, że kody aktywności zostaną umieszczone w pakiecie com.hfad. komunikator, a kiedy będziesz gotów, kliknij przycisk Finish.

Niektóre wersje Android Studio mogą także pytać o język, w jakim będzie tworzony kod źródłowy aktywności. Jeśli pojawi się takie pytanie, wybierz Javę.

Upewnij się, że będzie zaznaczone pole wyboru nakazujące wygenerowanie układu.

Aktywności nadaj nazwę „ReceiveMessageActivity”, a układowi „activity_ receive_message”.

82

Rozdział 3.

Usuń zaznaczenia z pól wyboru Launcher Activity oraz Backwards Compatibility (AppCompat).

Wiele aktywności i intencji

¨  Utworzenie pierwszej aktywności ¨  Utworzenie drugiej aktywności

Co się właściwie stało?

¨  Wywołanie drugiej aktywności ¨  Przekazanie danych

Kiedy kliknąłeś przycisk Finish, Andorid Studio wygenerowało dla Ciebie lśniący nowością plik aktywności i równie nowiutki plik układu. Jeśli spojrzysz do eksploratora, przekonasz się, że w katalogu app/src/main/java pojawił się plik ReceiveMessageActivity.java, a w katalogu app/src/main/res/layout — plik activity_receive_message.xml. Twoje okno eksploratora może wyglądać inaczej, gdyż my przełączyliśmy się do widoku Project.

Każda aktywność używa innego układu. Pierwsza aktywność, CreateMessageActivity, używa układu activity_create_ message.xml, natomiast druga, ReceiveMessageActivity — układu activity_receive_message.xml. Android Studio dodało ReceiveMessageActivity.





activity_create_message.xml

activity_receive_message.xml

CreateMessageActivity.java

RecieveMessageActivity.java

Dodało także ten plik układu.

Za kulisami Android Studio wprowadziło także niezbędne zmiany w pliku konfiguracyjnym aplikacji — AndroidMainfest.xml. Przyjrzyjmy się mu zatem nieco dokładniej.

jesteś tutaj 

83

AndroidManifest.xml

¨  Utworzenie pierwszej aktywności ¨  Utworzenie drugiej aktywności

Przedstawiamy plik manifestu aplikacji 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 biblioteki oraz wiele innych deklaracji. Android generuje dla nas ten plik w momencie tworzenia aplikacji. Jeśli przypomnisz sobie ustawienia wybierane podczas tworzenia aplikacji, to niektóre fragmenty kodu pliku manifestu będą wyglądać podobnie.

Komunikator app/src/main

Plik AndoridManifest.xml można znaleźć w tym AndroidManifest.xml katalogu.

Poniżej przedstawiliśmy naszą wersję pliku AndroidManiest.xml:



Motyw graficzny określa wygląd aplikacji. Także o tych zagadnieniach dowiesz się więcej w dalszej części książki.

To jest nasza pierwsza aktywność, Create Message Activity.



Obejrzyj to!

Ten wiersz kodu określa, że to jest główna aktywność aplikacji.



Z kolei ten wiersz informuje, że aktywności można użyć do uruchomienia aplikacji.





84

Jeśli piszesz aplikacje na Androida, nie używając żadnego zintegrowanego środowiska programistycznego, to musisz utworzyć ten plik ręcznie.

Rozdział 3.

To jest druga aktywność, ReceiveMessageActivity. Android Studio dodało ten kod po utworzeniu drugiej aktywności.

Wiele aktywności i intencji

Każdą aktywność należy zadeklarować Wszystkie aktywności należy zadeklarować w pliku AndrodiManifest.xml. Jeśli aktywność nie zostanie zadeklarowana w tym pliku, to system nie będzie o niej wiedział. A jeśli system nie będzie nic widział o aktywności, to nigdy nie zostanie ona wykonana. W pliku manifestu aktywności deklaruje się przy użyciu elementów umieszczanych wewnątrz elementu . Okazuje się, że każdej aktywności w aplikacji musi odpowiadać element . Poniżej przedstawiamy jego ogólną postać:

Każda aktywność musi zostać zadeklarowana wewnątrz elementu .

Jeśli nie zostanę wymieniona w pliku AndroidManifest.xml, to z punktu widzenia systemu nie będę istnieć i nigdy nie zostanę wywołana.

Ten wiersz jest obowiązkowy; po prostu zastąp NazwaKlasyAktywnosci nazwą swojej klasy aktywności.

Aktywność

Aktywność może zawierać także inne właściwości.

...

...

Poniższy wiersz kodu jest obowiązkowy i służy do określenia nazwy klasy aktywności w naszym przykładzie jest to ”.NazwaKlasyAktywnosci”: android:name=”nazwa_klasy_aktywności”

Przy czym nazwa_klasy_aktywności to, jak łatwo się domyślić, nazwa klasy aktywności poprzedzona znakiem kropki (.). Nazwa klasy jest poprzedzona znakiem kropki, gdyż Android łączy nazwę klasy z nazwą pakietu, uzyskując w ten sposób pełną nazwę klasy. Deklaracja aktywności może zawierać także inne właściwości, takie jak uprawnienia niezbędne do wykonania aktywności, oraz informację, czy dana aktywność może być używana przez aktywności należące do innych aplikacji.

Nasza druga aktywność została automatycznie zadeklarowana, gdyż dodaliśmy ją, używając kreatora Android Studio.

Obejrzyj to!

W przypadku samodzielnego dodawania dodatkowych aktywności będziemy musieli ręcznie wprowadzać odpowiednie zmiany w pliku manifestu — AndroidManifest.xml. Podobnie może być w przypadku stosowania innych zintegrowanych środowisk programistycznych niż Android Studio.

jesteś tutaj 

85

intencje

Intencja jest rodzajem komunikatu Dotychczas stworzyliśmy aplikację z dwiema aktywnościami, z których każda używa 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 wywołała drugą aktywność — ReceiveMessageActivity. 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ś”. 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 uruchomić drugą, robi to, przesyłając do systemu Android odpowiednią intencję. W efekcie system uruchomi aktywność i przekaże do niej tę intencję.

¨  Utworzenie pierwszej aktywności ¨  Utworzenie drugiej aktywności ¨  Wywołanie drugiej aktywności ¨  Przekazanie danych

Aktywność uruchamia się, tworząc intencję i używając jej w wywołaniu metody startActivity(). Intencja określa aktywność, do której chcemy ją przesłać. Informację tę można by porównać z adresem umieszczonym na kopercie.

Do utworzenia i przesłania intencji wystarczy jeden lub dwa wiersze kodu. W pierwszej kolejności należy utworzyć intencję, używając poniższego wiersza kodu: Intent intent = new Intent(this, KlasaDocelowa.class);

Pierwszy parametr tego wywołania informuje system, z jakiego obiektu pochodzi 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.

Intencja

Do: InnaAktywnosc

Po utworzeniu intencji można ją przekazać do systemu Android w następujący sposób: startActivity(intent);

Wywołanie metody startActivity() uruchamia aktywność określoną w intencji.

To wywołanie informuje system, że ma uruchomić aktywność określoną przez przekazaną intencję. 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.

„Mój drogi Androidzie, czy mogłabym Cię prosić, żebyś kazał Aktywności 2 wziąć się do roboty? Szczerze oddana, Twoja stara kumpela, Aktywność 1”.

Niech no sprawdzę. Tak, wydaje się, że wszystko jest w porządku. Powiem Aktywności 2, żeby się brała do pracy.

Intencja

Intencja

Do: Aktywność2

Do: Aktywność2 Aktywność1

86

Rozdział 3.

Oo… komunikat. Chyba zacznę działać.

Android

Aktywność2

Wiele aktywności i intencji

Użycie intencji do uruchomienia drugiej aktywności Zastosujmy to rozwiązanie w praktyce i użyjmy intencji do uruchomienia aktywności ReceiveMessageActivity. Ponieważ aktywność chcemy uruchomić w odpowiedzi na kliknięcie przycisku Wyślij wiadomość, do kodu metody onSendMessage() dodamy dwa wiersze kodu opisane na poprzedniej stronie.

¨  Utworzenie pierwszej aktywności ¨  Utworzenie drugiej aktywności ¨  Wywołanie drugiej aktywności ¨  Przekazanie danych

Wprowadź zmiany wyróżnione w poniższym kodzie: package com.hfad.komunikator; import android.app.Activity; import android.content.Intent; import android.os.Bundle;

Musimy zaimportować klasę Intent, android. content.Intent, gdyż będziemy jej używać w metodzie onSendMessage().

import android.view.View;

Komunikator

public class CreateMessageActivity extends Activity {

app/src/main java

@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_create_message); }

com.hfad.komunikator CreateMessage Activity.java

// Metoda onSendMessage() jest wywoływana po kliknięciu przycisku public void onSendMessage(View view) { Intent intent = new Intent(this, ReceiveMessageActivity.class); startActivity(intent); } }

Te dwa wiersze uruchamiają aktywność ReceiveMessageActivity.

Co się wydarzy, kiedy uruchomimy aplikację?

jesteś tutaj 

87

Co się dzieje?

¨  Utworzenie pierwszej aktywności ¨  Utworzenie drugiej aktywności ¨  Wywołanie drugiej aktywności

Co się dzieje po uruchomieniu aplikacji? Zanim weźmiemy naszą nową aplikację na jazdę próbną, jeszcze raz przeanalizujmy, jak ona będzie działać:

¨  Przekazanie danych

1

Po uruchomieniu aplikacji zaczyna działać aktywność CreateMessageActivity.

Po uruchomieniu tej aktywności informuje ona system, że ma być używany układ zapisany w pliku activity_create_message.xml. Ten właśnie układ zostaje wyświetlony w nowym oknie.

2

activity_create_message.xml

Urządzenie

onSendMessage()

Użytkownik wpisuje wiadomość, a następnie klika przycisk.

W odpowiedzi na kliknięcie zostaje wywołana metoda onSendMessage() aktywności CreateMessageActivity.

CreateMessageActivity.java

Urządzenie

CreateMessageActivity

onSendMessage()

3

Metoda onSendMessage() prosi system o uruchomienie aktywności ReceiveMessageActivity, używając do tego intencji.

Intencja CreateMessageActivity

Android upewnia się, że intencja jest prawidłowa, po czym nakazuje uruchomienie aktywności ReceiveMessageActivity.

Do: Receive Message Activity Intencja

Android

Do: ReceiveMessageActivity ReceiveMessageActivity

88

Rozdział 3.

Wiele aktywności i intencji

Historii ciąg dalszy 4

Po uruchomieniu aktywności ReceiveMessageActivity informuje ona system, że używa układu z pliku activity_receive_message.xml, zatem ten układ jest wyświetlany.

CreateMessageActivity

Android

Urządzenie ReceiveMessageActivity

activity_receive_message

Jazda próbna aplikacji Zapisz wszystkie zmiany, a następnie uruchom aplikację. Początkowo zostanie uruchomiona aktywność CreateMessageActivity, kiedy jednak klikniesz przycisk Wyślij wiadomość, aplikacja uruchomi drugą aktywność — ReceiveMessageActivity.

¨  Utworzenie pierwszej aktywności ¨  Utworzenie drugiej aktywności ¨  Wywołanie drugiej aktywności ¨  Przekazanie danych

Wpisz komunikat i kliknij przycisk Wyślij wiadomość.

Kiedy klikniesz przycisk Wyślij wiadomość, zostanie uruchomiona aktywność ReceiveMessageActivity, a używany przez nią układ pojawi się na ekranie. Ta aktywność początkowo będzie pusta, gdyż taką właśnie postać będzie mieć jej domyślny układ wygenerowany przez Android Studio.

jesteś tutaj 

89

Przekazanie tekstu

Przekazanie tekstu do drugiej aktywności Jak na razie skoncentrowaliśmy się na tym, by aktywność CreateMessageActivity uruchamiała drugą aktywność, ReceiveMessageActivity, po kliknięciu przycisku Wyślij wiadomość. Kolejnym krokiem będzie zapewnienie możliwości przekazywania tekstu z aktywności CreateMessageActivity do ReceiveMessageActivity, tak by ta druga mogła go wyświetlić. W tym celu konieczne będzie wykonanie następujących czynności:

1

2

3

Zmienić układ activity_receive_message.xml, tak by można w nim było wyświetlać tekst. Aktualnie ten układ ma domyślną postać wygenerowaną przez kreator. Zaktualizować kod w pliku CreateMessageActivity.java, tak by aktywność pobierała tekst wpisany przez użytkownika w polu tekstowym, a następnie dodawała go do intencji przed jej przesłaniem. Zaktualizować kod w pliku ReceiveMessageAction.java, tak by przesłany w intencji tekst był wyświetlany na ekranie.

¨  Utworzenie pierwszej aktywności ¨  Utworzenie drugiej aktywności ¨  Wywołanie drugiej aktywności ¨  Przekazanie danych



1

activity_create_ message.xml

activity_receive_ message.xml Intencja

2

CreateMessage Activity.java

3

RecieveMessage Activity.java

Zacznijmy od aktualizacji układu Zaczniemy od zmiany kodu układu activity_recive_message.xml, wygenerowanego przez Android Studio. Jego dotychczasową zawartość zastąpimy układem . Zmodyfikuj zatem swoją wersję pliku, tak by była identyczna z tą przedstawioną poniżej:

Komunikator app/src/main

Ćwiczenie

90

Musimy zmienić układ w taki sposób, by zawierał widok tekstowy — element . Element ten musi mieć identyfikator ”message”, tak byśmy mogli odwoływać się do niego w kodzie aktywności. Jak powinien wyglądać zmodyfikowany układ? Spróbuj go przygotować samodzielnie, zanim spojrzysz na następną stronę.

Rozdział 3.

res layout

activity_receive_ message.xml

Wiele aktywności i intencji

Aktualizacja właściwości widoku tekstowego W pierwszej kolejności musimy dodać do układu element , a następnie przypisać jego atrybutowi id wartość ”message”. Takie identyfikatory trzeba dodawać do wszystkich komponentów GUI, do których chcemy się odwoływać w kodzie aplikacji, a do naszego widoku tekstowego musimy się odwoływać, abyśmy mogli zmieniać wyświetlany w nim tekst.

¨  Utworzenie pierwszej aktywności ¨  Utworzenie drugiej aktywności ¨  Wywołanie drugiej aktywności ¨  Przekazanie danych

Obie te sprawy możemy załatwić, wprowadzając zmiany pokazane w poniższym przykładzie:



Nie istnieją

głupie pytania Nie określiliśmy domyślnego tekstu prezentowanego w widoku tekstowym, gdyż jedynym tekstem, jaki kiedykolwiek będziemy chcieli w nim wyświetlać, jest komunikat przesłany z aktywności CreateMessageActivity. Skoro poradziliśmy sobie z układami, możemy zająć się kodem aktywności. Zacznijmy od dowiedzenia się, w jaki sposób można użyć intencji do przekazania treści wiadomości do aktywności ReceiveMessageActivity.

P: Czy muszę używać intencji? Czy nie mogę

utworzyć instancji drugiej aktywności w kodzie pierwszej?

O: To jest dobre pytanie, jednak nie — to nie jest

androidowy sposób uruchamiania aktywności. Jednym z powodów jest to, że przekazując do systemu intencje, Android będzie wiedział, w jakiej kolejności były wykonywane poszczególne aktywności. A to z kolei oznacza, że w razie kliknięcia przycisku Wstecz na urządzeniu Android będzie dokładnie wiedział, gdzie ma wrócić.

jesteś tutaj 

91

Ekstra, ekstra

Metoda putExtra() zapisuje w intencji dodatkowe informacje

¨  Utworzenie pierwszej aktywności ¨  Utworzenie drugiej aktywności ¨  Wywołanie drugiej aktywności ¨  Przekazanie danych

Wiesz już, że nową intencję można utworzyć w następujący sposób:

Metoda putExtra() umożliwia dodawanie do wysyłanych komunikatów dodatkowych informacji.

Intent intent = new Intent(this, KlasaDocelowa.class);

Jednak do takiej intencji można dodać dodatkowe informacje, które następnie będzie można odczytać w aktywności docelowej i odpowiednio na nie zareagować. Do zapisywania takich dodatkowych informacji służy metoda putExtra(): intent.putExtra(”message”, value);

gdzie message jest łańcuchem znaków określającym nazwę przekazywanej wartości, a value jest samą wartością. Metoda putExtra() jest przeciążona, dzięki czemu można jej używać do przekazywania wartości wielu różnych typów. Na przykład mogą to być wartości typów prostych, takich jak boolean lub int, tablice typów prostych bądź łańcuchy znaków — String. Metodę putExtra() można wywoływać wielokrotnie, aby zapisać w intencji więcej danych. W takich sytuacjach należy jednak pamiętać, by każda wartość miała unikalną nazwę.

Jak pobrać dodatkowe informacje z intencji?

Intencja

Do: ReceiveMessageActivity message: “Witam!”

Do intencji można dodawać wartości wielu różnych typów. Wszystkie dostępne typy można poznać, przeglądając dokumentację Androida. Oprócz tego informacje te będą także wyświetlane przez Android Studio podczas wpisywania kodu.

To jednak jeszcze nie koniec historii. Kiedy system nakaże uruchomienie aktywności ReceiveMessageActivity, musi ona dysponować jakąś możliwością pobrania dodatkowych informacji, które aktywność CreateMessageActivity przesłała do niej w intencji. Istnieje kilka przydatnych metod, których można użyć do tego celu. Pierwszą z nich jest: Intencja

getIntent();

Metoda getIntent() zwraca intencję, która została użyta do uruchomienia aktywności. Z kolei tej intencji można użyć do pobrania przekazanych w niej informacji. Konkretny sposób pobierania informacji z intencji zależy od ich typu. Na przykład jeśli wiemy, że intencja zawiera łańcuch znaków o nazwie ”message”, to możemy go pobrać, używając następującego fragmentu kodu: Intent intent = getIntent();

Pobiera intencję.

String string = intent.getStringExtra(“message”);

Nasze możliwości nie ograniczają się jednak do pobierania wyłącznie łańcuchów znaków. Na przykład poniższe wywołanie: int intNum = intent.getIntExtra(”name”, default_value);

pozwala pobrać wartość typu int o nazwie name. Argument default_value określa wartość domyślną.

92

Rozdział 3.

Do: ReceiveMessageActivity message: “Witam!”

Pobiera łańcuch znaków o nazwie „message” przekazany w intencji.

Wiele aktywności i intencji

Zagadkowy basen

package com.hfad.komunikator;

Twoim zadaniem jest powyciąganie z basenu fragmentów kodu i wstawienie ich w odpowiednie puste miejsca pliku CreateMessageActivity.java. Żadnego fragmentu kodu nie można użyć więcej niż raz, lecz nie wszystkie fragmenty będą potrzebne. Twoim celem jest skompletowanie aktywności, która będzie pobierać tekst z widoku tekstowego i zapisywać go w intencji.

import android.os.Bundle; import android.app.Activity; import android.content.Intent; import android.view.View; .............................................

public class CreateMessageActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_create_message); } // Metoda onSendMessage() jest wywoływana po kliknięciu przycisku public void onSendMessage(View view) { ....................................................... ....................................................... Intent intent = new Intent(this, ReceiveMessageActivity.class); ....................................................... startActivity(intent); } }

Uwaga: Każdy fragment z basenu może być użyty tylko raz!

EditText import String

putExtra

messageView

EditText putExtraString

“message”

findViewById getText() messageView R.id.message messageText messageText intent android.widget.EditText

=

(

=

;

(

( ,

toString()

)

.

; . ) ;

)

;

.

jesteś tutaj 

93

Rozwiązanie zagadkowego basenu package com.hfad.komunikator;

Zagadkowy basen. Rozwiązanie

import android.os.Bundle; import android.app.Activity; import android.content.Intent;

Musisz zaimportować klasę EditText.

import android.view.View; import android.widget.EditText .............................................

public class CreateMessageActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);

Twoim zadaniem jest powyciąganie z basenu fragmentów kodu i wstawienie ich w odpowiednie puste miejsca pliku CreateMessageActivity.java. Żadnego fragmentu kodu nie można użyć więcej niż raz, lecz nie wszystkie fragmenty będą potrzebne. Twoim celem jest skompletowanie aktywności, która będzie pobierać tekst z widoku tekstowego i zapisywać go w intencji.

setContentView(R.layout.activity_create_message); } // Metoda onSendMessage() jest wywoływana po kliknięciu przycisku 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("message", messageText); ....................................................... startActivity(intent); } }

Ten wiersz dodaje tekst do intencji, nadając mu nazwę „message”.

Ten fragment kodu nie był potrzebny.

putExtraString

94

Rozdział 3.

Te dwa wiersze pobierają tekst z pola tekstowego o identyfikatorze „message”.

Wiele aktywności i intencji

Aktualizacja kodu aktywności CreateMessageActivity

¨  Utworzenie pierwszej aktywności ¨  Utworzenie drugiej aktywności ¨  Wywołanie drugiej aktywności ¨  Przekazanie danych

Zaktualizowaliśmy kod pliku CreateMessageActivity.java w taki sposób, że aktywność pobiera tekst wpisany przez użytkownika w polu tekstowym i dodaje go do intencji. Poniżej przedstawiliśmy kompletną wersję kodu (koniecznie wprowadź te same modyfikacje, wyróżnione pogrubioną czcionką, w swoim pliku): package com.hfad.komunikator;

Komunikator

import android.os.Bundle; import android.app.Activity; import android.content.Intent; import android.view.View; import android.widget.EditText;

app/src/main

Musisz zaimportować klasę EditText, android.widget.EditText, gdyż jest ona używana w kodzie aktywności.

java com.hfad.komunikator

public class CreateMessageActivity extends Activity {

CreateMessage Activity.java

@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_create_message); } // Metoda onSendMessage() jest wywoływana po kliknięciu przycisku public void onSendMessage(View view) {

Te wiersze pobierają tekst zapisany w komponencie EditText.

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); } }

To wywołanie uruchamia aktywność ReceiveMessageActivity, używając w tym celu intencji.

Teraz, kiedy aktywność CreateMessageActivity zapisuje już dodatkowe informacje w intencji, musimy zająć się ich pobraniem i wyświetleniem.

Ten fragment kodu tworzy intencję, następnie dodaje do niej tekst. Nazwę informacji zapisywanej w intencji określamy przy użyciu stałej, dzięki czemu możemy mieć pewność, że w obu 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, więc nie przejmuj się, jeśli Android Studio stwierdzi, że on nie istnieje.

jesteś tutaj 

95

Metoda getStringExtra()

Zastosowanie informacji przekazanych w intencji w klasie ReceiveMessageActivity

¨  Utworzenie pierwszej aktywności ¨  Utworzenie drugiej aktywności ¨  Wywołanie drugiej aktywności ¨  Przekazanie danych

Skoro zmodyfikowaliśmy już kod aktywności CreateMessageActivity i zaimplementowaliśmy zapisywanie tekstu w intencji, nadszedł czas na wprowadzenie zmian w kodzie klasy ReceiveMessageActivity i zastosowanie przekazanego tekstu. Chcemy sprawić, by aktywność ReceiveMessageActivity, bezpośrednio po jej utworzeniu, wyświetlała przekazany tekst w widoku tekstowym. Ponieważ metoda onCreate()jest wywoływana zaraz po utworzeniu aktywności, to właśnie do niej dodamy niezbędny kod.

Intencja

CreateMessage Activity.java

RecieveMessage Activity.java Musimy zadbać o to, by aktywność ReceiveMessageActivity skorzystała z intencji, która ją uruchomiła.

Aby pobrać tekst z intencji, w pierwszej kolejności musimy pobrać intencję, używając metody getIntent(), a następnie pobrać sam łańcuch znaków, używając metody getStringExtra(). Poniżej przedstawiliśmy pełny kod aktywności zapisany w pliku ReceiveMessageActivity.java (zastąp nim początkowy kod wygenerowany przez Android Studio, a następnie zapisz zmodyfikowany plik): package com.hfad.komunikator;

Komunikator

import android.app.Activity; import android.content.Intent; import android.os.Bundle;

Musimy zaimportować te klasy.

import android.widget.TextView;

app/src/main

Upewnij się, że klasa dziedziczy po klasie Activity.

java com.hfad.komunikator

public class ReceiveMessageActivity extends Activity { public static final String EXTRA_MESSAGE = ”message”; @Override

To nazwa wartości, którą będziemy przekazywali w intencji.

ReceiveMessage Activity.java

protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_receive_message); Intent intent = getIntent(); String messageText = intent.getStringExtra(EXTRA_MESSAGE);

Te dwa wiersze pobierają intencję, a następnie używają metody getStringExtra(), by odczytać przekazany w niej łańcuch znaków.

TextView messageView = (TextView)findViewById(R.id.message); messageView.setText(messageText); } Ten wiersz wyświetla łańcuch znaków w widoku tekstowym.

}

Zanim weźmiemy aplikację na jazdę próbną, przeanalizujmy dokładniej, jak działa ten kod.

96

Rozdział 3.

Wiele aktywności i intencji

Co się dzieje, gdy użytkownik kliknie przycisk Wyślij wiadomość? 1

Kiedy użytkownik klika przycisk, zostaje wywołana metoda onSendMessage().

Kod metody onSendMessage() tworzy nową intencję, żądającą uruchomienia aktywności ReceiveMessageActivity, dodaje do niej wiadomość wpisaną przez użytkownika, po czym przekazuje intencję do systemu wraz z prośbą o uruchomienie aktywności.

2

Android sprawdza, czy z intencją jest wszystko w porządku, a następnie uruchamia aktywność ReceiveMessageActivity.

onSendMessage()

CreateMessageActivity

Intencja Do: ReceiveMessage Activity message:”Cześć!”

Android

CreateMessageActivity Intencja Do: ReceiveMessage Activity message:”Cześć!”

Android

ReceiveMessageActivity

3

Po uruchomieniu aktywność ReceiveMessageActivity określa, że używa układu activity_receive_ message.xml, i to właśnie on jest wyświetlany na ekranie urządzenia.

Aktywność aktualizuje także układ, wyświetlając w nim tekst przekazany w intencji.

CreateMessageActivity Cześć!

Urządzenie ReceiveMessageActivity

activity_receive_message

jesteś tutaj 

97

Jazda próbna

Jazda próbna aplikacji Upewnij się, czy wprowadziłeś zmiany w obu aktywnościach, następnie zapisz wszystkie zmienione pliki i uruchom aplikację. Początkowo zostanie uruchomiona aktywność CreateMessageActivity, lecz po wpisaniu tekstu i kliknięciu przycisku Wyślij wiadomość aplikacja uruchomi aktywność ReceiveMessageActivity. Wpisany wcześniej tekst zostanie wyświetlony w widoku tekstowym.

Obie aktywności zajmują cały obszar ekranu urządzenia, lecz tu pominęliśmy część pustych fragmentów ekranu.

¨  Utworzenie pierwszej aktywności ¨  Utworzenie drugiej aktywności ¨  Wywołanie drugiej aktywności ¨  Przekazanie danych

Oto wpisany wcześniej tekst, który pomyślnie udało się przekazać do drugiej aktywności przy użyciu intencji.

Możemy zmienić aplikację tak, by wiadomości były wysyłane do innych osób Teraz, kiedy nasza aplikacja wysyła już wiadomości do innej aktywności, możemy ją zmienić w taki sposób, by wiadomości były wysyłane do innych osób. Możemy to zrobić, integrując ją z innymi aplikacjami wysyłającymi widomości, już zainstalowanymi na naszym urządzeniu. W zależności od zainstalowanych aplikacji możemy zapewnić możliwość wysyłania wiadomości przy użyciu Messaging, GMaila, Google+, Facebooka, Twittera itd.

Hej, zaczekajcie no chwilkę! Zapewnienie współpracy naszej aplikacji z innymi wymaga pewnie strasznie dużych nakładów pracy. A poza tym skąd możemy wiedzieć, jakie aplikacje będą zainstalowane na urządzeniach użytkowników?

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.

98

Rozdział 3.

Wiele aktywności i intencji

Jak działają aplikacje na Androida? Jak już się przekonaliśmy, wszystkie aplikacje na Androida składają się z jednej lub kilku aktywności oraz z komponentów dodatkowych, takich jak układy. Każda aktywność reprezentuje jedną, dobrze zdefiniowaną operację, którą może wykonywać użytkownik. Na przykład takie aplikacje jak Gmail, Google+, Messaging, Facebook oraz Twitter udostępniają aktywności umożliwiające wysyłanie wiadomości, choć każda z nich może to robić w zupełnie odmienny sposób.

¨  Utworzenie pierwszej aktywności ¨  Utworzenie drugiej aktywności ¨  Wywołanie drugiej aktywności ¨  Przekazanie danych

Urządzenie

Gmail

Google+

Twitter

Każda aplikacja składa się z grupy aktywności. (Poza tym aplikacje zawierają także inne komponenty, lecz na razie koncentrujemy się wyłącznie na aktywnościach).

Intencje mogą uruchamiać aktywności w innych aplikacjach Wiesz już, w jaki sposób można używać intencji do uruchomienia drugiej aktywności w tej samej aplikacji. Pierwsza aktywność przekazuje systemowi intencję, a Android nakazuje uruchomienie drugiej aktywności. Dokładnie to samo dotyczy uruchamiania aktywności należących do innych aplikacji. Aktywność należąca do naszej aplikacji przekazuje systemowi intencję, ten ją sprawdza, a następnie nakazuje uruchomienie drugiej aktywności, nawet jeśli będzie ona należeć do innej aplikacji. Na przykład możemy użyć intencji w celu uruchomienia aktywności służącej do wysyłania wiadomości należącej do aplikacji Gmail i przekazać jej tekst, który chcemy wysłać. Zamiast pisać własne aktywności do wysyłania poczty elektronicznej, możemy skorzystać z już istniejącej aplikacji Gmail. Komunikator To jest aplikacja, którą piszemy w tym rozdziale.

Możemy stworzyć intencję, która uruchomi wybraną aktywność, nawet jeśli ta aktywność będzie należała do innej aplikacji.

Intencja

Intencja

Gmail

Android

Oznacza to, że tworząc sekwencję aktywności dostępnych na urządzeniu, możemy tworzyć aplikacje o znacznie większych możliwościach.

jesteś tutaj 

99

Stosowanie akcji

Ale przecież nie wiemy, jakie aplikacje są dostępne na urządzeniu Zanim będziemy mogli korzystać z aplikacji należących do innych aplikacji, musimy znaleźć odpowiedzi na trzy pytania:

 Skąd mamy wiedzieć, jakie aktywności są dostępne na urządzeniu użytkownika?  Skąd mamy wiedzieć, które z tych aktywności nadają się do wykonania interesującej nas operacji?  Skąd mamy wiedzieć, jak używać tych aktywności? Bardzo dobrą wiadomością jest to, że wszystkie te problemy możemy rozwiązać, używając akcji (ang. actions). Akcje to sposób pozwalający na poinformowanie systemu Android o tym, jakie standardowe operacje może wykonywać dana aktywność. Na przykład Android wie, że wszystkie aktywności zarejestrowane do wykonywania akcji SEND (wysyłania) potrafią wysyłać wiadomości. 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.

Oto co mamy zamiar zrobić 1

Utworzyć intencję określającą akcję.

2

Zapewnić użytkownikowi możliwość wyboru, której aplikacji chce użyć.

100

Intencja ta przekaże Androidowi informację, że chcemy użyć aktywności, która potrafi wysyłać wiadomości. Intencja ta będzie także zawierała tekst tej wiadomości. Istnieje spora szansa, że na urządzeniu będzie dostępnych więcej aplikacji pozwalających na wysyłanie wiadomości, dlatego użytkownik będzie musiał wybrać jedną z nich. Chcemy, aby użytkownik mógł wybrać używaną aplikację po każdym kliknięciu przycisku Wyślij wiadomość.

Rozdział 3.

Wiele aktywności i intencji

Utworzenie intencji określającej akcję

¨  Określenie akcji ¨  Zapewnienie możliwości wyboru

Dotychczas dowiedziałeś się, w jaki sposób można tworzyć intencje uruchamiające konkretną aktywność: Intent intent = new Intent(this, ReceiveMessageActivity.class);

Są to tak zwane intencje jawne (ang. explicit intent), gdyż jawnie informują system, którą klasę ma uruchomić. Jeśli jednak chcemy wykonać określoną czynność, lecz nie interesuje nas, która aktywność to zrobi, to możemy utworzyć intencję niejawną (ang. implicit intent). W takim przypadku określamy akcję, którą chcemy wykonać, pozostawiając Androidowi możliwość doboru odpowiednich aktywności.

Sposób tworzenia intencji Intencję określającą akcję do wykonania można utworzyć w następujący sposób: Intent intent = new Intent(action);

gdzie action określa typ akcji, którą aktywność ma wykonać. Android udostępnia obszerną grupę standardowych akcji. Na przykład akcja Intent.ACTION_DIAL pozwala wybrać numer telefonu, akcja Intent.ACTION_WEB_SEARCH umożliwia wyszukanie frazy w internecie, a za pomocą akcji Intent.ACTION_SEND można wysłać wiadomość. A zatem gdybyśmy chcieli utworzyć intencję określającą, że zależy nam na wysłaniu wiadomości, moglibyśmy to zrobić w następujący sposób: Intent intent = new Intent(Intent.ACTION_SEND);

Dodawanie informacji Po określeniu akcji, której chcemy użyć, możemy dodać do intencji dodatkowe informacje. W naszym przypadku chcemy dodać tekst, który stanie się treścią wysyłanej wiadomości. Do tego celu musimy użyć dwóch poniższych wierszy kodu: intent.setType(”text/plain”); intent.putExtra(Intent.EXTRA_TEXT, messageText);

gdzie messageText jest tekstem, który chcemy wysłać. Powyższy fragment kodu informuje system, że poszukujemy aktywności potrafiącej obsługiwać dane typu MIME ”text/plain”, a dodatkowo określa sam wysyłany tekst.

Przekazaliśmy intencji informację o tym, która klasa nas interesuje. Ale co zrobić w przypadku, gdy nie będziemy znali tej klasy?

Informacje o wszelkich dostępnych akcjach oraz dodatkowych informacjach, które można do nich przekazywać, znajdziesz w dokumentacji Androida na stronie http://tinyurl.com/ n57qb5.

Wszystkie te atrybuty są powiązane z akcją Intent.ACTION_SEND. Nie będą one miały zastosowania we wszystkich dostępnych akcjach.

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 intent.putExtra(Intent.EXTRA_SUBJECT, subject);

gdzie subject jest tekstem określającym temat wiadomości.

umożliwia określania tematu wiadomości, to zostanie on zignorowany. Jednak wszystkie aplikacje, które wiedzą, jak używać tematu, zastosują go.

jesteś tutaj 

101

Zastosowanie akcji

Zmiana intencji w celu użycia akcji

¨  Określenie akcji ¨  Zapewnienie możliwości wyboru

Zmienimy teraz kod w pliku CreateMessageActivity.java i utworzymy w nim intencję niejawną z akcją do wysyłania wiadomości. Wprowadź zatem zmiany przedstawione w poniższym kodzie i zapisz plik: package com.hfad.komunikator; Komunikator

import android.os.Bundle; app/src/main

import android.app.Activity; import android.content.Intent;

java

import android.view.View; import android.widget.EditText;

com.hfad.komunikator CreateMessage Activity.java

public class CreateMessageActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_create_message); } // Metoda onSendMessage() jest wywoływana po kliknięciu przycisku public void onSendMessage(View view) { EditText messageView = (EditText)findViewById(R.id.message); String messageText = messageView.getText().toString();

Usuń te dwa wiersze kodu.

Intent intent = new Intent(this, ReceiveMessageActivity.class); intent.putExtra(ReceiveMessageActivity.EXTRA_MESSAGE, messageText); Intent intent = new Intent(Intent.ACTION_SEND); intent.setType(“text/plain”); intent.putExtra(Intent.EXTRA_TEXT, messageText); startActivity(intent); }

}

Skoro już zaktualizowaliśmy kod, przeanalizujmy teraz krok po kroku, co się stanie, kiedy użytkownik kliknie przycisk Wyślij wiadomość.

102

Rozdział 3.

Zamiast tworzyć intencję, która jawnie żąda uruchomienia aktywności ReceiveMessageActivity, tworzymy intencję, używając akcji wysyłania wiadomości.

Wiele aktywności i intencji

Co się dzieje podczas działania kodu? 1

Po wywołaniu metody onSendMessage() zostaje utworzona intencja. Metoda startActivity() przekazuje tę intencję do systemu Android.

Intencja zawiera określenie akcji ACTION_SEND i określenie typu MIME text/plain.

2

¨  Określenie akcji ¨  Zapewnienie możliwości wyboru

onSendMessage()

CreateMessageActivity

Android dowiaduje się, że intencję może przekazać wyłącznie do aktywności, które potrafią wykonywać akcję ACTION_SEND i operują na danych typu text/plain. Sprawdza zatem wszystkie aktywności na urządzeniu użytkownika, wyszukując spośród nich te, do których może wysłać intencję.

Intencja ACTION_SEND type: “text/plain” messageText:”Cześć!” Android

A… Intencja niejawna. Muszę zatem znaleźć wszystkie aktywności, które są w stanie obsługiwać akcję ACTION_SEND, operują na danych typu text/plain i należą do kategorii DEFAULT.

Jeśli system nie znajdzie aktywności, które mogą obsłużyć intencję, zostanie zgłoszony wyjątek ActivityNotFoundException. CreateMessageActivity

3a

Jeśli tylko jedna aktywność jest w stanie obsługiwać przekazaną intencję, to Android uruchomi ją i przekaże do niej intencję.

Android

CreateMessageActivity Intencja Android Do: Aktywność messageText:”Cześć!” Aktywność

jesteś tutaj  103

Co się dzieje?

Historii ciąg dalszy

3b

Jeśli więcej niż jedna aktywność może odebrać intencję, Android wyświetli okno dialogowe pozwalające wybrać jedną z nich i poprosi użytkownika o dokonanie wyboru.

¨  Określenie akcji ¨  Zapewnienie możliwości wyboru

Hej, użytkowniku! Każda z tych aktywności może wysyłać wiadomości. Której z nich chcesz użyć?

CreateMessageActivity

Android Użytkownik

4

Kiedy użytkownik wybierze aktywność, której chce użyć, Android uruchamia ją i przekazuje do niej intencję.

Aktywność wyświetli dodatkowy tekst przekazany w intencji jako treść wysyłanej wiadomości.

CreateMessageActivity Intencja Android

Wybrana aktywność

Aby przekazać intencję do aktywności, Android musi wiedzieć, które z nich są w stanie odebrać i obsłużyć tę intencję. Na kilku następnych stronach dowiesz się, jak on to zrobi.

104

Rozdział 3.

Do: Wybrana aktywność messageText: „Cześć!”

Użytkownik

Wiele aktywności i intencji

Filtry intencji informują system, które aktywności mogą obsługiwać poszczególne akcje

¨  Określenie akcji ¨  Zapewnienie możliwości wyboru

Kiedy do systemu Android zostanie przekazana intencja, musi on określić, która aktywność (bądź które aktywności) jest w stanie ją obsłużyć. Ten proces jest nazywany wyznaczaniem intencji (ang. intent resolution). W razie zastosowania intencji jawnej wyznaczanie intencji jest banalnie proste. W takim przypadku intencja bowiem jawnie określa, do którego komponentu jest skierowana, zatem Android dysponuje jednoznacznymi instrukcjami dotyczącymi tego, co należy zrobić. Na przykład poniższy kod jawnie informuje system, że należy uruchomić aktywność ReceiveMessageActivity: Intent intent = new Intent(this, ReceiveMessageActivity.class); startActivity(intent);

W przypadku zastosowania intencji niejawnej Android określa komponenty, które mogą ją obsłużyć, na podstawie informacji zawartych w tej intencji. W tym celu system sprawdza filtry intencji umieszczone w plikach AndroidManifest.xml wszystkich zainstalowanych aplikacji. 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:

zym To jedynie przykład; w nas ywności akt nej żad ma nie ie jekc pro Ten wiersz informuje Androida, o nazwie „ShareActivity”. że aktywność jest w stanie

obsługiwać akcję ACTION_SEND.





To są typy danych, które aktywność potrafi obsługiwać.

Ten filtr intencji musi określać kategorię o wartości DEFAULT, gdyż w przeciwnym razie nie będzie mógł obsługiwać intencji niejawnych.

Filtry intencji określają także kategorię. Kategoria określa dodatkowe informacje dotyczące aktywności, na przykład czy dana aktywność może być uruchamiana przez przeglądarkę WWW bądź czy jest ona głównym punktem wejścia aplikacji. Filtr intencji musi określać kategorię o wartości android.intent.category.DEFAULT, jeśli ma zapewniać możliwość obsługi intencji niejawnych. Jeżeli aktywność nie ma żadnego filtra intencji albo nie określa kategorii o wartości android.intent.category. DEFAULT, to oznacza to, że nie będzie jej można uruchamiać przy użyciu intencji niejawnych. Taką aktywność będzie można uruchomić wyłącznie za pomocą intencji jawnej, określającej pełną nazwę komponentu (zawierającą także nazwę pakietu).

jesteś tutaj  105

Filtry intencji

Jak Android korzysta z filtrów intencji?

¨  Określenie akcji ¨  Zapewnienie możliwości wyboru

W przypadku stosowania intencji niejawnych Android porównuje informacje umieszczone w intencji z informacjami z filtrów intencji zapisanych w pliku AndroidManifest.xml aplikacji. W pierwszej kolejności Android bierze pod uwagę filtry określające kategorię typu android.intent.category.DEFAULT:

...

Intencje, które nie określają tej kategorii, zostaną pominięte, gdyż nie mogą one obsługiwać intencji niejawnych. Następnie Android porównuje intencje z filtrami intencji, uwzględniając przy tym podaną w nich akcję i obsługiwany typ MIME. Na przykład jeśli intencja określa akcję Intent.ACTION_SEND, gdyż została utworzona w poniższy sposób: Intent intent = new Intent(Intent.ACTION_SEND);

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:

...

I podobnie jeśli w intencji zostanie określony typ MIME o wartości ”text/plain”: intent.setType(”text/plain”);

to Android uwzględni tylko te aktywności, które potrafią obsługiwać konkretny typ danych:

...

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.

106

Rozdział 3.

Oprócz tego, jeśli w intencji została określona kategoria, to Android sprawdzi także kategorię podaną w filtrze intencji. To rozwiązanie jest jednak stosowane raczej sporadycznie, dlatego nie przedstawimy go tutaj.

Wiele aktywności i intencji

Wczuj się w intencję!

Twoim zadaniem jest wcielenie się w rolę intencji przedstawionej z prawej strony i wskazanie, które z zaprezentowanych poniżej aktywności są zgodne z Twoją akcją i typem danych. Dla każdej z aktywności podaj, dlaczego jest lub dlaczego nie jest zgodna z intencją.

To jest intencja.

Intent intent = new Intent(Intent.ACTION_SEND); intent.setType(”text/plain”); intent.putExtra(Intent.EXTRA_TEXT, ”Witaj”);

















jesteś tutaj  107

Rozwiązanie

Wczuj się w intencję. Rozwiązanie Twoim zadaniem jest wcielenie się Intent intent = new Intent(Intent.ACTION_SEND); w rolę intencji przedstawionej z prawej intent.setType(”text/plain”); strony i wskazanie, które intent.putExtra(Intent.EXTRA_TEXT, ”Witaj”); z zaprezentowanych poniżej aktywności są zgodne z Twoją akcją i typem danych. Dla każdej z aktywności podaj, dlaczego jest lub dlaczego nie jest zgodna z intencją.

Ta aktywność akceptuje akcję ACTION_SEND i potrafi obsługiwać dane dowolnego typu MIME, dlatego może odpowiedzieć na intencję.







Ta aktywność nie zawiera kategorii typu DEFAULT, więc nie może odbierać aktywności niejawnych.







Ta aktywność nie obsługuje intencji z akcją ACTION_SEND, lecz z akcją ACTION_SENDTO (która pozwala wysłać wiadomość do odbiorcy określonego w danych intencji).





108

Rozdział 3.

Wiele aktywności i intencji

Musisz uruchomić aplikację na PRAWDZIWYM urządzeniu

¨  Określenie akcji ¨  Zapewnienie możliwości wyboru

Dotychczas wszystkie nasze aplikacje uruchamialiśmy na emulatorze. Emulator udostępnia niewielką liczbę aplikacji i jest całkiem możliwe, że wśród nich będzie tylko jedna potrafiąca obsługiwać akcję ACTION_SEND. Aby prawidłowo przetestować aplikację, będziemy musieli uruchomić ją na rzeczywistym, fizycznym urządzeniu, mając przy tym pewność, że jest na nim zainstalowanych więcej aplikacji, które potrafią obsługiwać naszą akcję — na przykład aplikacja umożliwiająca wysyłanie e-maili i aplikacja do przesyłania wiadomości. A oto, w jaki sposób możesz uruchomić tworzoną aplikację na fizycznym urządzeniu:

1. Włącz debugowanie USB

Właśnie tak, serio!

Na swoim urządzeniu otwórz opcję Opcje programistyczne (począwszy od Androida 4.0 opcja ta jest domyślnie niedostępna). Aby ją włączyć, wybierz opcję Ustawienia/Informacje o urządzeniu, a następnie siedmiokrotnie kliknij pozycję Numer wersji. Kiedy wrócisz do poprzedniego ekranu, powinna już być na nim widoczna opcja Opcje programisty. Na ekranie Opcje programistyczne zaznacz pole wyboru Debugowanie USB. Musisz włączyć opcję Debugowanie USB.

2. Skonfiguruj system, by wykrył urządzenie Jeśli używasz komputera Mac, możesz pominąć te czynności. Jeśli używasz komputera z systemem Windows, to musisz zainstalować sterownik USB. Jego najnowszą wersję można pobrać ze strony: http://developer.android.com/tools/extras/oem-usb.html Jeśli używasz systemu Ubuntu, musisz utworzyć plik reguł udev. Aktualne informacje o tym, jak to zrobić, można znaleźć na stronie: http://developer.android.com/tools/device.html#setting-up

3. Podłącz urządzenie do komputera kablem USB Twoje urządzenie może zapytać, czy chcesz zaakceptować klucz RSA umożliwiający przeprowadzanie debugowania USB na danym komputerze. Jeśli faktycznie tak się stanie, zaznacz opcję Zawsze zezwalaj temu komputerowi, a następnie kliknij przycisk OK, by podłączyć urządzenie.

jesteś tutaj  109

Uruchamianie na prawdziwym urządzeniu

Uruchamianie aplikacji na prawdziwym urządzeniu (ciąg dalszy)

¨  Określenie akcji ¨  Zapewnienie możliwości wyboru

4. Zatrzymaj aplikację działającą w emulatorze Nim będziesz mógł uruchomić aplikację na innym urządzeniu, musisz ją zatrzymać na urządzeniu, na którym działa obecnie (w naszym przykładzie jest to emulator). W tym celu wybierz z menu głównego Android Studio opcję Run/Stop ‘app’ lub kliknij przycisk Stop ‘app’, umieszczony na pasku narzędzi.

5. Uruchom aplikację na fizycznym urządzeniu Uruchom aplikację, wybierając z menu opcję Run/Run ‘app’. Android Studio zapyta, na którym urządzeniu chcesz uruchomić aplikację; z wyświetlonej listy wybierz swoje urządzenie i kliknij przycisk OK. Android Studio zainstaluje aplikację na urządzeniu i uruchomi ją.

Pierwszym z wyświetlonych urządzeń jest emulator.

To jest nasze fizyczne urządzenie.

A oto aplikacja uruchomiona na fizycznym urządzeniu Zauważysz zapewne, że aplikacja wygląda niemal identycznie jak w przypadku uruchamiania jej na emulatorze. Oprócz tego pewnie przekonasz się także, że na fizycznym urządzeniu aplikacja działa znacznie szybciej niż na emulatorze. Skoro już wiesz, jak możesz uruchamiać własne aplikacje na własnym, fizycznym urządzeniu, możesz już przetestować aplikację Komunikatora.

110

Rozdział 3.

Kliknij ten przycisk umieszczony na pasku narzędzi Android Studio, by zatrzymać aplikację działającą na aktualnie wybranym urządzeniu.

Wiele aktywności i intencji

Jazda próbna aplikacji Spróbuj uruchomić aplikację w emulatorze, a następnie na własnym urządzeniu. Uzyskane wyniki będą zależały od tego, jak dużo będzie dostępnych aktywności obsługujących akcję wysyłania danych tekstowych.

Jeśli będzie jedna aktywność W takim przypadku kliknięcie przycisku Wyślij wiadomość spowoduje przejście bezpośrednio do wybranej aplikacji. Na emulatorze mamy dostępną tylko jedną aktywność, która jest w stanie wysyłać wiadomości z danymi tekstowymi, dlatego kiedy klikniemy przycisk Wyślij wiadomość, Android automatycznie uruchomi tę aktywność.

Jeśli będzie więcej niż jedna aktywność W tym przypadku Android wyświetli okno dialogowe z listą wszystkich odszukanych aktywności i poprosi o wskazanie tej, której chcemy użyć. Oprócz tego zostaniemy zapytani, czy chcemy, by wybrana akacja została wykonana raz, czy też ma już być wykonywana zawsze. W razie wybrania drugiej możliwości każde kolejne kliknięcie przycisku sprawi, że domyślnie zostanie wykonana ta sama akcja.

To jest wiadomość.

Na naszym fizycznym urządzeniu dostępnych jest wiele aktywności spełniających nasze potrzeby. Zdecydowaliśmy się wybrać aplikację Wiadomości. Poza tym wybraliśmy opcję Zawsze — świetne rozwiązanie, jeśli chcemy zawsze używać tej samej aplikacji SMS/MMS, lecz już nie tak dobre, jeśli za każdym razem chcielibyśmy używać innej aplikacji.

jesteś tutaj 

111

Niech wybierze użytkownik

A co, jeśli chcemy, by użytkownik ZAWSZE wybierał aktywność? Przekonałeś się właśnie, że jeśli na urządzeniu jest więcej aktywności, które mogą odebrać i obsłużyć intencję, to Android automatycznie prosi o wybór tej, której chcesz użyć. Co więcej, pyta nawet, czy ta aktywność ma zostać użyta tylko jeden raz, czy też ma być wykonywana już zawsze.

Metoda createChooser()

Z tym domyślnym sposobem działania systemu wiąże się tylko jeden problem: co zrobić w przypadku, gdy chcemy zagwarantować, aby użytkownik mógł wybrać aktywność po każdym kliknięciu przycisku Wyślij wiadomość? Jeśli użytkownik zdecyduje na przykład, że zawsze chce wysyłać wiadomości za pomocą aplikacji Gmail, to w przyszłości nie zostanie już zapytany, czy chce użyć Twittera.

aktywności i jednocześnie

Na szczęście ten problem można łatwo rozwiązać. Okazuje się, że można utworzyć okno dialogowe wyboru aktywności, które nie będzie pytało użytkownika, czy wybrana aktywność ma zostać użyta tylko raz, czy zawsze.

aktywność ma być używana

umożliwia podanie tytułu okna dialogowego wyboru ukrywa w nim opcje pozwalające użytkownikowi określać, czy dana domyślnie. Jednocześnie, jeśli nie uda się znaleźć

Metoda Intent.createChooser() wyświetla okno dialogowe wyboru aktywności

żadnej pasującej aktywności, to użytkownik zostanie

Możliwość wyświetlenia okna dialogowego wyboru aktywności zapewnia metoda Intent.createChooser(). Metoda ta pobiera utworzoną intencję i przekazuje ją do okna dialogowego wyboru aktywności. W przypadku zastosowania tej metody wyświetlone okno dialogowe nie daje możliwości określania aktywności domyślnej — użytkownik będzie proszony o wybór aktywności za każdym razem. Oto, jak wygląda wywołanie metody createChooser():

o tym poinformowany odpowiednim komunikatem.

To jest utworzona wcześniej intencja.

Intent chosenIntent = Intent.createChooser(intent, ”Wysyłanie wiadomości...”);

Metoda createChooser() pobiera dwa parametry: intencję i opcjonalny łańcuch znaków określający tytuł wyświetlanego okna dialogowego. Możemy do niej przekazać utworzoną wcześniej intencję, tę, która korzysta z akcji ACTION_SEND i używa danych tekstowych. Metoda createChooser() zwraca nowiutki obiekt Intent. Ta nowa intencja jawna zostanie skierowana bezpośrednio do aktywności wybranej przez użytkownika. Będzie ona także zawierać wszelkie informacje dodatkowe przekazane w początkowej intencji, w tym także wszystkie łańcuchy znaków. Aby uruchomić wybraną aktywność, wystarczy użyć poniższego wywołania: startActivity(chosenIntent);

Na kilku kolejnych stronach nieco dokładniej przyjrzymy się temu, co się dzieje w momencie wywołania metody createChooser().

112

Rozdział 3.

zać Możemy także przeka tytuł, który zostanie wyświetlony na samej górze ekranu.

Wiele aktywności i intencji

Co się dzieje w momencie wywołania metody createChooser()?

¨  Określenie akcji ¨  Zapewnienie możliwości wyboru

Oto, co się stanie w momencie wykonania dwóch poniższych wierszy kodu: Intent chosenIntent = Intent.createChooser(intent, ”Wysyłanie wiadomości...”); startActivity(chosenIntent);

1

Zostaje wywołana metoda createChooser().

createChooser() Intencja

W jej wywołaniu przekazywana jest intencja określająca akcję, którą chcemy wykonać, oraz informacje na temat typu danych CreateMessageActivity MIME.

2

Android sprawdza, które aktywności mogą obsłużyć tę intencję, sprawdzając ich filtry intencji.

Porównywane są akcja, typ danych oraz kategoria, obsługiwane przez poszczególne aktywności.

ACTION_SEND type: “text/plain” message:”Cześć!”

Widzę, że muszę utworzyć okno dialogowe wyboru aktywności, które obsługują akcję SEND i operują na danych typu text/plain.

CreateMessageActivity

3

Android

Jeśli aktywność może zostać obsłużona przez więcej niż jedną aktywność, to Android wyświetla okno dialogowe i prosi użytkownika o wybór jednej spośród odnalezionych aktywności.

Android

Hej, użytkowniku! Powiedz mi, proszę, której aktywności chciałbyś użyć tym razem?

Tym razem użytkownik nie będzie miał CreateMessageActivity możliwości określenia, że wybrana aktywność ma być używana za każdym razem — w oknie dialogowym zostanie tylko wyświetlony tytuł „Wysyłanie wiadomości…”. Jeśli nie uda się znaleźć żadnych pasujących aktywności, to Android i tak wyświetli okno dialogowe, a dodatkowo także komunikat informujący użytkownika, że nie ma aplikacji, które mogłyby obsłużyć wybraną akcję.

Android

Użytkownik

jesteś tutaj 

113

Co się dzieje?

Historii ciąg dalszy 4

¨  Określenie akcji ¨  Zapewnienie możliwości wyboru Ona chce użyć aktywności WybranaAktywność. Proszę bardzo, to odpowiednia intencja.

Kiedy użytkownik wybierze, której aktywności chce użyć, Android zwraca nową intencję jawną skierowaną do wybranej aktywności.

Ta nowa intencja zawiera wszelkie informacje dodatkowe, które były zapisane w początkowej aktywności, na przykład wszystkie teksty.

Intencja

CreateMessageActivity

5

Aktywność prosi system o uruchomienie aktywności określonej w intencji.

WybranaAktywność message:”Cześć!”

Android

Dzięki za intencję, Androidzie. A czy możesz teraz uruchomić tę intencję? Intencja

CreateMessageActivity

Do: WybranaAktywność message:”Cześć!”

Android

Jasne! Proszę bardzo.

6

Android uruchamia aktywność określoną w intencji i przekazuje do niej tę intencję.

CreateMessageActivity

Intencja

WybranaAktywność

114

Rozdział 3.

Do: WybranaAktywność Android message:”Cześć!”

Użytkownik

Wiele aktywności i intencji

Zmień kod, aby wyświetlać okno dialogowe

¨  Określenie akcji ¨  Zapewnienie możliwości wyboru

Zmienimy teraz kod naszej aktywności w taki sposób, by po każdym kliknięciu przycisku Wyślij wiadomość użytkownik był proszony o wybór aktywności, której chce użyć do wysłania wiadomości. W tym celu dodamy do pliku strings.xml zasób łańcuchowy, który będzie używany jako tytuł okna do wyboru aktywności, a następnie zmodyfikujemy kod metody onSendMessage() w pliku CreateMessageActivity.java, tak by wywoływała ona metodę createChooser(). Komunikator

Zaktualizuj plik strings.xml… Chcemy, by okno dialogowe do wyboru aktywności miało tytuł Wysyłanie 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 o zapisaniu zmienionego pliku).

app/src/main res

...

values

Wysyłanie wiadomości...

Ten tekst będzie wyświetlany w oknie do wyboru aktywności. strings.xml

...

…a następnie kod metody onSendMessage() Musimy zmienić kod metody onSendMessage() w taki sposób, by pobierała z pliku strings.xml zasób określający tytuł okna dialogowego, wywoływała metodę createChooser(), a następnie uruchamiała wybraną aktywność. A zatem zmodyfikuj kod aktywności w następujący sposób:

Komunikator

...

Usuń ten wiersz kodu.

...

app/src/main 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(); CreateMessage Intent intent = new Intent(Intent.ACTION_SEND); Activity.java intent.setType(”text/plain”); 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): getString(R.string.chooser);

Jeśli zajrzysz do pliku R.java, znajdziesz 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 

115

Jazda próbna

Jazda próbna aplikacji

¨  Określenie akcji ¨  Zapewnienie możliwości wyboru

Zapisz zmiany i ponownie uruchom aplikację na urządzeniu lub emulatorze.

Jeśli będzie jedna aktywność W takim przypadku kliknięcie przycisku Wyślij wiadomość spowoduje, jak wcześniej, przejście bezpośrednio do wybranej aplikacji. Tu nic się nie zmieniło — Android bezpośrednio uruchamia wybraną aktywność.

Jeśli będzie więcej niż jedna aktywność

W tym przypadku Android wyświetli okno dialogowe z listą wszystkich odszukanych aktywności, lecz nie zapyta, czy chcemy, by wybrana akacja została wykonana raz, czy też ma już być wykonywana zawsze. Oprócz tego okno ma tytuł określony na podstawie zasobu łańcuchowego.

116

Rozdział 3.

, które To jest okno dialogowe wyboru aktywności er(). utworzyliśmy przy użyciu metody createChoos lającej na Jak widać, nie zawiera ono już opcji pozwa razem. m wykonywanie wybranej aktywności za każdy

Wiele aktywności i intencji

Jeśli nie będzie ŻADNYCH pasujących aktywności Jeśli na naszym urządzeniu nie będzie żadnych aktywności pozwalających wysyłać wiadomości, to metoda createChooser() powiadomi nas o tym, wyświetlając stosowny komunikat.

¨  Określenie akcji ¨  Zapewnienie możliwości wyboru

Jeśli chcesz samodzielnie uzyskać podobne wyniki, to spróbuj uruchomić swoją aplikację w emulatorze i wyłączyć przy tym aplikację wiadomości.

Nie istnieją

głupie pytania

P: A zatem mogę uruchamiać

swoje aplikacje w emulatorze lub na fizycznym urządzeniu. Które z tych rozwiązań jest lepsze?

O: Każde z nich ma swoje wady i zalety. W razie stosowania urządzenia fizycznego testowane aplikacje zazwyczaj wczytują się znacznie szybciej niż na emulatorze. To rozwiązanie jest także przydatne w przypadku pisania kodu, który współpracuje z komponentami sprzętowymi urządzenia. Z kolei emulator pozwala testować aplikację na wielu różnych wersjach Androida, na ekranach o różnych wielkościach oraz na urządzeniach o różnych specyfikacjach. Pozwala zatem uniknąć konieczności kupowania wielu różnych urządzeń. Najważniejsze jest, by dokładnie przetestować aplikację, używając kombinacji urządzeń emulowanych i fizycznych, zanim udostępnimy ją szerszemu gronu odbiorców.

P: Czy należy używać intencji niejawnych czy jawnych?

O: Wszystko sprowadza się do

pytania, czy chcemy wykonać akcję, używając konkretnej aktywności, czy zależy nam wyłącznie na wykonaniu operacji. Załóżmy, że chcemy wysłać e-mail. Jeśli nie interesuje nas, której aplikacji użytkownik użyje, byleby tylko wiadomość została wysłana, to możemy użyć intencji niejawnej. Natomiast jeśli musimy przekazać intencję do konkretnej aktywności w aplikacji, to powinniśmy użyć intencji jawnej, aby jawnie określić aktywność, do której intencja ma zostać przekazana.

P: Wspominaliście, że filtr intencji

aktywności oprócz akcji może także określać kategorię. Czym różni się kategoria od akcji?

O

: Akcja określa, co dana aktywność potrafi robić, natomiast kategoria określa dodatkowe informacje na temat wykonywanej czynności. Nie opisaliśmy tych kategorii szczegółowo, gdyż w praktyce rzadko kiedy pojawia się potrzeba tworzenia intencji z kategoriami.

P: Napisaliście, że w przypadku,

gdy nie ma aktywności, która by mogła obsłużyć intencję, to wywołanie metody createChooser() wyświetli komunikat. Co by się stało, gdybym użył standardowego okna do wyboru aktywności i przekazał intencję niejawną w wywołaniu metody startActivity()?

O: Jeśli nie ma aktywności pasujących

do intencji przekazanej w wywołaniu metody startActivity(), to zostanie zgłoszony wyjątek ActivityNotFoundException. Istnieje możliwość sprawdzenia, czy aktywności na urządzeniu są w stanie odebrać intencję. W tym celu należy wywołać jej metodę resolveActivity() i sprawdzić zwróconą przez nią wartość wynikową. Jeśli jest nią null, to żadna aktywność na urządzeniu nie będzie w stanie obsłużyć intencji; w takim przypadku nie należy wywoływać metody startActivity().

jesteś tutaj 

117

Przybornik

Rozdział 3.

Twój przybornik do Androida Opanowałeś już rozdział 3. i dodałeś do swojego przybornika z narzędziami umiejętności tworzenia aplikacji zawierających wiele aktywności i stosowania intencji.

j Pełny kod przykładowe j ane tow zen aplikacji pre z żes mo ale dzi roz w tym FTP ra we ser z rać pob wydawnictwa Helion: klady/ ftp://ftp.helion.pl/przy p .zi andrr2

CELNE SPOSTRZEŻENIA 

Zadanie to co najmniej dwie aktywności połączone w sekwencję.



Element 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.





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().







 





118

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);.

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);.

Rozdział 3.

4. Cykl życia aktywności

Była sobie aktywność …wtedy mu powiedziałam, że jeśli zaraz nie wykona onStop(), to pokażę mu, co z nim zrobi moja onDestroy().

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 ekranie, a jakie gdy aktywność traci miejsce wprowadzania i jest ukrywana? W jaki sposób można zapisywać i odtwarzać stan aktywności?

to jest nowy rozdział 

119

Jak działają aktywności?

Jak właściwie działają aktywności? Dotychczas dowiedziałeś się jedynie, jak można tworzyć aktywności, które prowadzą interakcję z użytkownikami, i aplikacje używające kilku aktywności do wykonywania akcji. Skoro dodałeś już te podstawowe umiejętności do swojego przybornika z narzędziami, nadszedł czas, by nieco dokładniej przyjrzeć się temu, jak tak naprawdę działają aktywności. Poniżej zamieściliśmy podsumowanie tego, co już wiesz, dodając kilka dodatkowych szczegółów.



Aplikacja jest kolekcją aktywności, układów oraz innych zasobów.

Jedna z tych aktywności jest główną aktywnością aplikacji. Aplikacja

Każda aplikacja ma aktywność główną, wskazaną w pliku manifestu AndroidManifest.xml.

Aktywność główna

Aktywność



Aktywność

Aktywność

Domyślnie każda aplikacja jest uruchamia w odrębnym, własnym procesie.

Dzięki temu łatwiej jest zapewnić bezpieczeństwo aplikacji. Więcej informacji na ten temat można znaleźć w „Dodatku C” (poświęconym środowisku uruchomieniowemu Androida — ART), umieszczonym na końcu książki. Proces 1

Proces 2

Aplikacja 1

Aktywność

Aktywność

Aplikacja 2

Aktywność

Aktywność Aktywność

120

Rozdział 4.

Aktywność

Aktywność

Cykl życia aktywności

Można uruchomić aktywność w innej aplikacji, przekazując, przy użyciu metody startActivity(), odpowiednią intencję.



System Android dysponuje informacjami o wszystkich zainstalowanych aplikacjach i o ich aktywnościach i potrafi użyć intencji do uruchomienia odpowiedniej aktywności. Aplikacja 1

Aplikacja 2 startActivity()

Aktywność Aktywność

Intencja

Intencja

Aktywność

Android

Aktywność



Aktywność

Aktywność Kiedy trzeba uruchomić jakąś aktywność, Android sprawdza, czy istnieje już proces danej aplikacji.

Jeśli taki proces istnieje, to system uruchamia w nim wybraną aktywność. W przeciwnym razie, jeśli procesu nie ma, to system go utworzy. Proces 1 Aplikacja 1

Już wykonuję aktywności tej aplikacji w ramach procesu 1. Zatem tę aktywność także uruchomię w tym procesie. Android



Kiedy Android uruchamia aktywność, wywołuje jej metodę onCreate().

Metoda onCreate() jest zawsze wywoływana w momencie tworzenia aktywności.

onCreate()

Android

Aktywność

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ć?

jesteś tutaj 

121

Stoper

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. Nasza aplikacja będzie się składała z jednej aktywności i jednego układu. Układ będzie zawierał tekst pokazujący, ile czasu upłynęło, oraz przyciski: Start (rozpoczynający pomiar czasu), Stop (zatrzymujący stoper) oraz Kasuj (zerujący licznik czasu).

To jest liczba sekund. Kiedy klikniesz przycisk Start, liczba sekund zaczyna być inkrementowana. Kiedy klikniesz przycisk Stop, liczba sekund przestaje być inkrementowana.

Kiedy klikniesz przycisk Kasuj, liczba sekund zostaje zmniejszona do 0.

Implementacja aplikacji Dysponujesz już dostateczną wiedzą i doświadczeniem, by samodzielnie napisać tę aplikację, bez większej pomocy z naszej strony. Pokażemy tu tylko tyle kodu, ile potrzebujesz, by móc ją w całości zaimplementować, a następnie sprawdzić, co się stanie, kiedy ją uruchomisz. Zacznij od utworzenia nowego projektu aplikacji na Androida o nazwie Stoper, używając przy tym nazwy domeny firmowej o postaci hfad.com, dzięki czemu kody aplikacji zostaną umieszczone w pakiecie com.hfad.stoper. Wybierz minimalny poziom API o wartości 19, tak by aplikacja mogła działać na przeważającej większości urządzeń. Utwórz także pustą aktywność o nazwie StopwatchActivity i układ o nazwie activity_stopwatch. Upewnij się, że pole wyboru Backwards Compatibility (AppCompat) nie będzie zaznaczone.

122

Rozdział 4.



activity_stopwatch.xml Aplikacja składa się z jednej aktywności i jednego układu.

StopwatchActivity.java

Cykl życia aktywności

Dodanie zasobów łańcuchowych W układzie naszej aplikacji zamierzamy zastosować trzy wartości łańcuchowe łańcuchów znaków — każdy z nich określa tekst jednego przycisku. Łańcuchy te zdefiniujemy jako zasoby łańcuchowe, co oznacza, że będziesz musiał je zapisać w pliku strings.xml. A zatem dodaj do tego pliku poniższe wiersze kodu:

Stoper app/src/main

... Start

res

Użyjemy tych zasobów łańcuchowych w układzie aplikacji.

Stop Kasuj

values

...

strings.xml

Kolejną rzeczą, którą się zajmiemy, będzie zaktualizowanie kodu układu.

Aktualizacja kodu układu stopera Poniżej przedstawiliśmy kod XML układu aplikacji. Składa się on z jednego widoku tekstowego używanego do wyświetlania czasu zmierzonego przez stoper oraz trzech przycisków kontrolujących działanie stopera. Zastąp zatem zawartość swojego pliku activity_stopwatch.xml poniższym kodem XML:



Te atrybuty sprawią, że czas wyświetlany przez nasz stoper będzie ładny i duży.

Dalsza część kodu układu znajduje się na następnej stronie.

jesteś tutaj  123

Kod pliku activity_stopwatch.xml

Kod układu (ciąg dalszy)

Zrób to sam!

Kliknięcie tegoprzycisku Stopspowodujewywołanie metody onClickStop().



Układ jest gotowy! Teraz zajmijmy się aktywnością.

124

Rozdział 4.

Kliknięcie tego przycisku Kasuj spowoduje wywołanie metody onClickReset().

Cykl życia aktywności

Jak będzie działał kod aktywności? Układ aplikacji definiuje trzy przyciski, których będziemy używać do obsługi stopera. Każdy z tych przycisków korzysta z atrybutu onClick i przy jego użyciu określa metodę aktywności, którą należy wywołać w momencie kliknięcia danego przycisku. W przypadku kliknięcia przycisku Start zostanie wywołana metoda onClikStart(), kliknięcie przycisku Stop spowoduje wywołanie metody onClickStop(), a rezultatem kliknięcia przycisku Kasuj będzie wywołanie metody onClickReset(). Tych trzech metod będziemy używali do uruchamiania, zatrzymywania oraz zerowania stopera.

Kiedy klikniesz przycisk Start, zostanie wywołana metoda onClickStart(). Kiedy klikniesz przycisk Stop, zostanie wywołana metoda onClickStop().

Kiedy klikniesz przycisk Kasuj, zostanie wywołana metoda onClickReset().

Sam stoper będziemy aktualizować przy użyciu metody o nazwie runTimer(). Będzie ona wywoływana co sekundę, by sprawdzić, czy stoper działa, i inkrementować liczbę sekund przechowywaną w aplikacji i wyświetlaną w widoku tekstowym. Obsługę aplikacji ułatwimy sobie, używając dwóch zmiennych prywatnych, które będą przechowywały stan stopera. Pierwszą z nich będzie zmienna typu int o nazwie seconds, która będzie przechowywała liczbę sekund, jaka upłynęła od momentu włączenia stopera, a drugą zmienna typu boolean o nazwie running, określająca, czy stoper aktualnie działa, czy nie.

runTimer() Aktywność

Zaczniemy od napisania kodu obsługującego przyciski, a następnie zajmiemy się metodą runTimer().

jesteś tutaj  125

Przyciski

Dodanie kodu obsługującego przyciski running=true

Kiedy użytkownik kliknie przycisk Start, przypiszemy zmiennej running wartość true, dzięki czemu stoper zacznie działać. Następnie, gdy użytkownik kliknie przycisk Stop, zmienimy wartość zmiennej running na false, co spowoduje zatrzymanie stopera. Kiedy zaś użytkownik kliknie przycisk Kasuj, przypiszemy zmiennej running wartość false, a zmiennej seconds wartość 0, co spowoduje, że stoper zostanie zatrzymany i wyzerowany.

running=false running=false seconds=0

W tym celu zastąp zawartość swojego pliku StopwatchActivity.java następującym kodem: package com.hfad.stoper; import android.os.Bundle; import android.app.Activity; import android.view.View;

Upewnij się, że Twoja aktywność dziedziczy po klasie Activity.

Stoper app/src/main

public class StopwatchActivity extends Activity { private int seconds = 0; private boolean running;

java

Używamy zmiennych seconds i running do przechowywania liczby sekund, które upłynęły, i informacji, czy stoper jest włączony, czy nie.

com.hfad.stoper Stopwatch Activity.java

@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_stopwatch); } // Metoda uruchamia stoper po kliknięciu przycisku Start public void onClickStart(View view) { running = true; Ta instrukcja uruchamia stoper. }

Ta metoda jest wywoływana po kliknięciu przycisku Start.

// Metoda zatrzymuje stoper po kliknięciu przycisku Stop public void onClickStop(View view) { Ta metoda jest wywoływana po kliknięciu przycisku Stop. running = false; Ta instrukcja zatrzymuje stoper. } // Metoda zeruje stoper po kliknięciu przycisku Kasuj public void onClickReset(View view) { running = false; Te instrukcje zatrzymują stoper i przypisują liczbie seconds = 0; sekund wartość 0. } }

126

Rozdział 4.

Ta metoda jest wywoływana po kliknięciu przycisku Kasuj.

Cykl życia aktywności

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 metody runTimer(). Już niebawem, za kilka stron, dodamy go do pliku StopwatchActivity.java: Pobranie referencji do komponentu TextView.

private void runTimer() {

final TextView timeView = (TextView)findViewById(R.id.time_view); ... int hours = seconds/3600; W tych miejscach pominęliśmy krótkie fragmenty kodu. Przedstawimy je na następnej stronie.

int minutes = (seconds%3600)/60; int secs = seconds%60; String time = String.format(”%d:%02d:%02d”,

Ten fragment formatuje liczbę sekund do postaci godzin, minut i sekund. To zwyczajny kod napisany w Javie.

hours, minutes, secs); timeView.setText(time); if (running) { seconds++; } ...

Ta instrukcja wyświetla tekst w komponencie TextView.

Jeśli zmienna running ma wartość true, to ta instrukcja inkrementuje wartość zmiennej seconds.

}

Musimy zapewnić, aby kod metody runTimer() był wykonywany cyklicznie, tak by co sekundę inkrementował wartość zmiennej seconds i aktualizował wyświetlany czas zmierzony przez stoper. Jednocześnie musimy to zrobić w taki sposób, by nie zablokować głównego wątku aplikacji. W aplikacjach pisanych w innych językach programowania niż Java takie zadania można wykonywać przy użyciu wątków działających w tle. Jednak w Androidowie takie rozwiązania nastręczają nieco problemów, gdyż tylko główny wątek aplikacji może aktualizować jej interfejs użytkownika — jeśli spróbuje to zrobić jakikolwiek inny wątek, to zostanie zgłoszony wyjątek CalledFromWrongThreadException.

Stoper app/src/main java com.hfad.stoper Stopwatch Activity.java

Rozwiązaniem jest zastosowanie obiektu klasy Handler. Przyjrzymy się mu dokładniej na następnej stronie.

jesteś tutaj  127

Obiekty Handler

Obiekty Handler umożliwiają planowanie wykonania kodu Handler to klasa systemu Android umożliwiająca tworzenie kodu, który ma zostać wykonany w przyszłości. Można jej także używać do przekazywania kodu, który ma zostać wykonany w innym wątku. W naszym przypadku zastosujemy tę klasę do utworzenia kodu stopera, który będzie wykonywany co sekundę. By skorzystać z klasy Handler, kod, którego wykonanie chcemy zaplanować, musimy umieścić w obiekcie Runnable, a następnie użyć metody post() lub postDelayed() klasy Handler, by określić, kiedy kod ma zostać wykonany. Przyjrzyjmy się nieco dokładniej każdej z tych metod.

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: final Handler handler = new Handler(); handler.post(Runnable);

Kod, który chcemy wykonać, należy umieścić w metodzie run() obiektu Runnable.

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: final Handler handler = new Handler(); handler.postDelayed(Runnable, long);

Na następnej stronie użyjemy tych metod do cyklicznego aktualizowania stopera.

128

Rozdział 4.

Tej metody można użyć, aby opóźnić wykonanie kodu o określoną liczbę milisekund.

Cykl życia aktywności

Pełny kod metody runTimer() W celu aktualizacji stopera będziemy cyklicznie planowali wykonanie kodu z użyciem obiektu Handler, za każdym razem stosując opóźnienie o długości 1 sekundy. Każde wykonanie kodu będzie powodowało inkrementację wartości zmiennej seconds i aktualizację tekstu wyświetlanego w widoku tekstowym. Poniżej przedstawiamy pełny kod metody runTimer(), który na kilku następnych stronach dodamy do pliku StopwatchActivity.java: private void runTimer() { final TextView timeView = (TextView)findViewById(R.id.time_view); final Handler handler = new Handler(); handler.post(new Runnable() { @Override public void run() {

Tworzy nowy obiekt Handler.

To wywołanie metody post() i przekazanie do niej nowego obiektu typu Runnable(). Metoda post() wykonuje kod bez opóźnienia, zatem kod umieszczony w obiekcie Runnable zostanie wykonany niemal natychmiast.

int hours = seconds/3600; int minutes = (seconds%3600)/60; int secs = seconds%60; String time = String.format(”%d:%02d:%02d”, hours, minutes, secs); timeView.setText(time); if (running) {

Metoda run() obiektu Runnable zawiera kod, który chcemy wykonać — w naszym przypadku jest to kod, który aktualizuje liczbę sekund wyświetloną w widoku tekstowym.

seconds++; } handler.postDelayed(this, 1000); } });

To wywołanie przekazuje kod w obiekcie Runnable i planuje jego wykonanie z opóźnieniem wynoszącym 1000 milisekund. Ponieważ ten wiersz kodu jest umieszczony wewnątrz metody run() obiektu Runnable, będzie on wykonywany cyklicznie.

}

Zastosowanie metod post() i postDelayed() oznacza, że przekazany kod będzie wykonywany najszybciej jak to możliwe, po upływie podanego opóźnienia. W praktyce oznacza to, że będzie on wykonany niemal natychmiast po upływie zadanego okresu czasu. Choć oznacza to, że im dłużej będzie działała aplikacja, tym opóźnienia mierzonego czasu będą większe, to jednak rozwiązanie to będzie wystarczająco dokładne jak na potrzeby zamieszczonej w tym rozdziale prezentacji cyklu życia aktywności. Metodę runTimer() uruchomimy w momencie tworzenia aktywności StopwatchActivity, a to oznacza, że wywołamy ją w metodzie onCreate() aktywności: protected void onCreate(Bundle savedInstanceState) { ... runTimer(); }

Pełny kod aktywności zamieściliśmy na następnej stronie.

jesteś tutaj  129

Kod aktywności StopwatchActivity

Kompletny kod aktywności StopwatchActivity Poniżej przedstawiliśmy kompletny kod pliku StopwatchActivity.java. Zaktualizuj ten plik w swojej aplikacji, by miał tę samą zawartość. package com.hfad.stoper; Stoper

import android.app.Activity; import android.os.Bundle;

app/src/main

import android.view.View;

Używamy tych trzech dodatkowych klas, więc musimy je zaimportować.

import java.util.Locale; import android.os.Handler;

java

import android.widget.TextView;

com.hfad.stoper

public class StopwatchActivity extends Activity {

Stopwatch Activity.java

// Liczba sekund wyświetlana przez stoper private int seconds = 0; // Czy stoper działa? private boolean running;

Zmiennych seconds i running używamy odpowiednio do przechowywania liczby zmierzonych sekund i informacji, czy aktualnie stoper działa, czy nie.

@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.

// Metoda uruchamia stoper po kliknięciu przycisku Start public void onClickStart(View view) { running = true;

Ta instrukcja uruchamia stoper.

Ta metoda jest wywoływana po kliknięciu przycisku Start.

} // Metoda zatrzymuje stoper po kliknięciu przycisku Stop public void onClickStop(View view) { running = false; }

130

Rozdział 4.

Ta instrukcja zatrzymuje stoper.

Ta metoda jest wywoływana po kliknięciu przycisku Stop.

Cykl życia aktywności

Kod aktywności (ciąg dalszy) // Metoda zeruje stoper po kliknięciu przycisku Kasuj public void onClickReset(View view) { running = false; seconds = 0; }

Te instrukcje zatrzymują stoper i zerują liczbę zmierzonych sekund.

Ta metoda jest wywoływana po kliknięciu przycisku Kasuj.

Stoper app/src/main java

Ta instrukcja pobiera referencję do widoku tekstowego.

// Wyświetla w stoperze liczbę sekund

com.hfad.stoper

private void runTimer() { final TextView timeView = (TextView)findViewById(R.id.time_view);

Stopwatch Activity.java

final Handler handler = new Handler(); handler.post(new Runnable() { @Override

Tu używamy obiektu Handler do przekazania kodu.

public void run() { int hours = seconds/3600; int minutes = (seconds%3600)/60; int secs = seconds%60;

Ten fragment formatuje liczbę sekund do postaci godzin, minut i sekund.

String time = String.format(”%d:%02d:%02d”, hours, minutes, secs); timeView.setText(time); if (running) { seconds++; }

Ta instrukcja wyświetla tekst w komponencie TextView.

Jeśli zmienna running ma wartość true, to ta instrukcja inkrementuje wartość zmiennej seconds.

handler.postDelayed(this, 1000); }

To wywołanie ponownie przekazuje kod do wywołania z opóźnieniem 1 sekundy.

}); } }

Zrób to sam!

Zobaczmy, co się stanie, kiedy wykonamy ten kod.

Upewnij się, że zaktualizowałeś kod swojej aktywności, wprowadzając w nim wszystkie pokazane zmiany.

jesteś tutaj 

131

Co się dzieje?

Co się dzieje w trakcie działania aplikacji? 1

Użytkownik decyduje się uruchomić aplikację.

Klika zatem ikonę aplikacji.

Urządzenie Użytkownik

2

Aplikacja tworzy intencję w celu uruchomienia tej aktywności przy użyciu metody startActivity(intent).

Plik manifestu, AndroidManifest.xml, określa, którą aktywność należy wykonać podczas uruchamiania aplikacji.



AndroidManifest.xml

3

Android

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

132

Rozdział 4.

Cykl życia aktywności

Historii ciąg dalszy 4

Zostaje wywołana metoda onCreate() aktywności.

Kod onCreate() wywołuje metodę setContnetView(), określając używany układ, a następnie uruchamia stoper, wywołując w tym celu metodę runTimer().



runTimer() StopwatchActivity

5

Układ

Po zakończeniu wykonywania metody onCreate() na ekranie urządzenia zostaje wyświetlony układ.

Metoda runTimer() określa tekst, który należy wyświetlić w widoku tekstowym na podstawie wartości zmiennej seconds. Natomiast zmienna running pozwala jej określić, czy należy inkrementować liczbę sekund, czy nie. Ponieważ zmienna running ma początkowo wartość false, liczba sekund nie jest inkrementowana.

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

O: Ze względów bezpieczeństwa i dla zapewnienia stabilności.

O: Nie. Wykonywanie metody onCreate() musi się zakończyć

procesie?

Uniemożliwia to aplikacji uzyskanie dostępu do danych innych aplikacji. Oprócz tego oznacza to, że jeśli aplikacja ulegnie awarii, nie będzie to miało zgubnych konsekwencji dla innych aplikacji.

P: Do czego służy metoda onCreate()? Nie prościej by

było umieszczać kod w konstruktorze?

O: Android musi przygotować środowisko na potrzeby

aktywności już po jej utworzeniu. Kiedy aktywność jest już gotowa, Android wywołuje jej metodę onCreate(). To właśnie dlatego kod przygotowujący ekran aplikacji jest wykonywany w metodzie onCreate(), a nie w konstruktorze aktywności.

pętli, która aktualizowałaby stoper?

przed wyświetleniem ekranu aplikacji. Umieszczenie w niej nieskończonej pętli uniemożliwiłoby zatem wyświetlenie układu.

P: Metoda runTimer() wygląda na naprawdę złożoną.

Czy naprawdę trzeba wykonywać te wszystkie operacje?

O: Fakt, jest trochę skomplikowana, jednak za każdym razem, gdy będziemy musieli zaplanować wykonanie kodu, używane rozwiązanie będzie wyglądało podobnie do tego z metody runTimer().

jesteś tutaj  133

Jazda próbna

Jazda próbna aplikacji Kiedy uruchomimy tę aplikację w emulatorze, będzie działała doskonale. Możemy uruchomić stoper, zatrzymać go, wyzerować, a wszystko to bez najmniejszych problemów. Innymi słowy: aplikacja działa dokładnie według oczekiwań. Przyciski działają zgodnie z oczekiwaniami. Kliknięcie przycisku Start rozpoczyna działanie stopera, kliknięcie przycisku Stop zatrzymuje go, a kliknięcie przycisku Kasuj powoduje wyzerowanie stopera.

Ale jest jeden problem… Kiedy uruchomimy aplikację na fizycznym urządzeniu, będzie ona działała prawidłowo, dopóki nie obrócimy urządzenia. Gdy to zrobimy, stoper zostanie wyzerowany.

Stoper działał, lecz po obróceniu urządzenia został wyzerowany.

W androidowych aplikacjach wyjątkowo często występują problemy po obróceniu urządzenia. Zanim zajmiemy się rozwiązaniem tego problemu, przyjrzymy się dokładniej jego przyczynie.

134

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? Przyjrzyjmy się nieco dokładniej temu, co się w niej stało.

1

Użytkownik uruchamia aplikację i klika przycisk Start, by rozpocząć działanie stopera. Metoda runTimer() rozpoczyna inkrementację liczby sekund wyświetlanych w widoku tekstowym o identyfikatorze time_view, wykorzystując przy tym obie zmienne: seconds i running.

seconds=8 StopwatchActivity Urządzenie

2

running=true

Użytkownik obraca urządzenie.

Android zauważa, że zmieniła się orientacja urządzenia i wielkość ekranu, i w efekcie usuwa działającą aktywność, włącznie ze zmiennymi używanymi przez metodę runTimer().

Urządzenie

3

Aktywność StopwatchActivity zostaje ponownie utworzona.

Metoda onCreate() aktywności zostaje ponownie wykonana, co powoduje wywołanie metody runTimer(). Ponieważ aktywność została ponownie utworzona, zmienne seconds i running przyjmują wartości domyślne.

seconds=0 StopwatchActivity Urządzenie

Zmiennej seconds zostaje przypisana wartość 0, a zmiennej running wartość false. Ta zmiana jest wynikiem usunięcia i ponownego utworzenia aktywności, spowodowanych obróceniem urządzenia.

running=false

jesteś tutaj  135

Konfiguracje urządzenia

Obrót ekranu zmienia konfigurację urządzenia Kiedy Android uruchamia aplikację i rozpoczyna wykonywanie aktywności, uwzględnia przy tym konfigurację urządzenia. Konfiguracja ta obejmuje zarówno konfigurację fizycznego urządzenia (czyli takie jego aspekty jak wielkość ekranu, orientacja ekranu, dostępność podłączonej klawiatury), jak i opcje konfiguracyjne określane przez użytkownika (takie jak wybrane ustawienia lokalne). Android musi znać konfigurację urządzenia w momencie uruchamiania aktywności, gdyż może ona mieć wpływ na zasoby niezbędne dla działania aplikacji. Na przykład inny układ może być używany, gdy urządzenie jest trzymane w pionie, a inny gdy jest trzymane w poziomie, i podobnie inne zasoby łańcuchowe mogą być używane w przypadku, kiedy na urządzeniu zostaną wybrane francuskie ustawienia lokalne.

Konfiguracja urządzenia obejmuje opcje podane przez użytkownika (takie jak ustawienia lokalne), jak również opcje związane z fizycznym

Każda aplikacja na Androida może zawierać wiele plików zasobów, przechowywanych w katalogu /app/src/ main/res. Na przykład jeśli w urządzeniu zostaną wybrane francuskie ustawienia lokalne, to aplikacja będzie używała pliku strings.xml pobranego z katalogu values-fr.

Kiedy zmienia się konfiguracja urządzenia, trzeba zaktualizować wszystko, co prezentuje interfejs użytkownika, tak by odpowiadał on nowej konfiguracji. Jeśli obrócimy urządzenie, Android zauważy zmianę orientacji i wielkości ekranu i uzna to za zmianę konfiguracji urządzenia. W efekcie system usunie aktualną aktywność i ponownie ją utworzy, aby można było pobrać i zastosować zasoby odpowiadające bieżącej konfiguracji urządzenia.

136

Rozdział 4.

urządzeniem (takie jak jego orientacja i wielkość ekranu). Zmiana dowolnej spośród tych opcji spowoduje usunięcie i ponowne odtworzenie aktywności.

Cykl życia aktywności

Stany aktywności Kiedy Android tworzy i usuwa aktywność, zmienia się jej stan: początkowo jest uruchomiona, potem działa, a w końcu zostaje zniszczona. Podstawowym stanem aktywności jest stan działania lub aktywności. Aktywność działa, kiedy jest wyświetlona na ekranie (znajduje się na pierwszym planie), dysponuje miejscem wprowadzania (ang. input focus), a użytkownik może z nią prowadzić interakcje. Większość swojego życia aktywność spędza właśnie w tym stanie. Aktywność zaczyna działać po wcześniejszym uruchomieniu, a potem, pod koniec swojego istnienia, zostanie usunięta (lub zniszczona).

Aktywność uruchomiona

Obiekt aktywności został już utworzony, ale jeszcze nie działa.

Aktywność działająca

Większość swojego życia aktywność spędza w tym stanie.

Aktywność usunięta

Na tym etapie aktywność już nie istnieje.

Kiedy stan aktywności zmienia się z utworzonej na usuniętą, wykonywane są dwie kluczowe metody cyklu życia aktywności: onCreate() i onDestroy(). Są to metody cyklu życia każdej aplikacji, które są dziedziczone i które, w razie takiej potrzeby, można przesłonić. Metoda onCreate() jest wywoływana bezpośrednio po uruchomieniu aktywności. To właśnie w niej można wykonywać wszystkie standardowe czynności związane z przygotowaniem działania aktywności, takie jak wywołanie metody setContentView(). Tę metodę zawsze należy przesłaniać — w przeciwnym razie nie będziemy w stanie poinstruować systemu, którego układu używa aktywność. Z kolei onDestroy() to ostatnia metoda aktywności, która zostaje wywołana przed jej ostatecznym usunięciem. Istnieje kilka różnych sytuacji, które mogą skutkować usunięciem aplikacji — na przykład gdy nakazano jej zakończenie, gdy trzeba ją ponownie utworzyć z powodu zmiany konfiguracji urządzenia albo gdy Android zdecyduje się usunąć aktywność, aby zaoszczędzić miejsce. Na następnej stronie przyjrzymy się powiązaniom tych dwóch metod ze stanami aktywności.

Aktywność działa, kiedy znajduje się na pierwszym planie, czyli jest widoczna na ekranie. Metoda onCreate() jest wywoływana w momencie pierwszego tworzenia aktywności i stanowi miejsce, w którym aktywność jest przygotowywana do działania. Metoda onDestroy() jest wywoływana przed usunięciem aktywności. jesteś tutaj  137

Od narodzin do śmierci

Cykl życia aktywności: od utworzenia do usunięcia Poniżej przedstawiony został cykl życia aktywności, od jej narodzin do śmierci. Jak się przekonasz w dalszej części rozdziału, pominęliśmy tu kilka szczegółów, lecz na razie koncentrujemy się wyłącznie na metodach onCreate() i onDestroy().

Aktywność uruchomiona

1

Aktywność zostaje uruchomiona.

2

Bezpośrednio po uruchomieniu aktywności zostaje wywołana jej metoda onCreate().

Metoda onCreate() jest miejscem, w którym należy umieścić cały kod inicjujący działanie aktywności, gdyż jest ona zawsze wywoływana po uruchomieniu aktywności, lecz zanim zacznie ona działać.

onCreate()

Aktywność działająca

System tworzy obiekt aktywności i zostaje wywołany jej konstruktor.

3

Aktywność działa, kiedy jest widoczna na ekranie i użytkownik może prowadzić z nią interakcję.

To właśnie w tym stanie aktywność spędza przeważającą większość swego życia.

onDestroy()

4

Metoda onDestroy() jest wywoływana bezpośrednio przed usunięciem aktywności.

Metoda onDestroy() pozwala wykonać ostateczne porządki po aktywności, na przykład zwolnić używane przez nią zasoby.

Aktywność usunięta

5

onCreate() oraz onDestroy() to dwie metody cyklu życia aktywności. A skąd się biorą te metody?

138

Rozdział 4.

Po wykonaniu metody onDestroy() aktywność jest usuwana.

W tym momencie aktywność przestaje istnieć.

mało Jeśli na urządzeniu jest bardzo pamięci, to może się zdarzyć, że metoda onDestroy() nie zostanie wności. wywołana przed usunięciem akty

Cykl życia aktywności

Twoja aktywność dziedziczy metody cyklu życia Jak już wspomnieliśmy, wszystkie aktywności rozszerzają klasę android.app.Activity. To właśnie ta klasa zapewnia naszym aktywnościom dostęp do metod cyklu życia. Poniższy rysunek przedstawia diagram hierarchii klas. Context startActivity(Intent)

ContextWrapper startActivity(Intent)

ContextThemeWrapper startActivity(Intent)

Activity onCreate(Bundle) onCreateOptionsMenu(Menu) onStart()

Abstrakcyjna klasa Context (android.content.Context)

Interfejs do globalnych informacji na temat środowiska aplikacji. Zapewnia dostęp do zasobów aplikacji, klas oraz operacji na poziomie aplikacji.

Klasa ContextWrapper

(android.content.ContextWrapper)

Implementacja pośrednika do klasy Context.

Klasa ContextThemeWrapper

(android.view.ContextThemeWrapper)

Klasa ContextThemeWrapper umożliwia modyfikowanie motywu z poziomu zawartości obiektu ContextWrapper.

Klasa Activity

(android.app.Activity)

Klasa Activity zawiera domyślne implementacje metod cyklu życia aktywności. Oprócz tego definiuje także takie metody jak findViewById(Int) i setContentView(View).

onRestart() onResume() onPause() onStop()

To są metody cyklu życia aktywnoś W dalszej części rozdziału znajdzieci. sz więcej informacji na ich temat.

onDestroy() onSaveInstanceState() startActivity(Intent) findViewById(Int) setContentView(View)

MojaAktywnosc onCreate(Bundle) mojaMetoda()

i tak To nie są metody cyklu życia, leczz nich są bardzo przydatne. Większości ziałach. używałeś już w poprzednich rozd

Klasa MojaAktywnosc

(com.hfad.foo)

Większość zachowań aktywności jest obsługiwana przez metody klasy bazowej dziedziczone przez nasze aktywności. Nam pozostaje jedynie przesłonięcie tych metod, które będą nam potrzebne.

Skoro już dowiedziałeś się nieco więcej na temat metod cyklu życia aktywności, zobaczmy, w jaki sposób można sobie radzić ze zmianami konfiguracji urządzenia.

jesteś tutaj  139

Zapisywanie stanu

Zapisanie bieżącego stanu… Jak można było łatwo zauważyć, w naszej aplikacji wystąpił problem, kiedy obróciliśmy ekran urządzenia. Aktywność została usunięta i ponownie utworzona, co oznaczało utratę wszystkich używanych w niej zmiennych lokalnych. Jak możemy sobie z tym poradzić?

Aktywność uruchomiona

Najlepszym sposobem obsługi zmian konfiguracji jest zapisywanie bieżącego stanu aktywności, a następnie odtwarzanie go w metodzie onCreate(). Aby zapisać bieżący stan aktywności, konieczne jest zaimplementowanie metody onSaveInstanceState(). Metoda ta jest wywoływana przed usunięciem aktywności, co oznacza, że zapewnia nam możliwość zapisania wszelkich wartości, których nie chcemy utracić.

onCreate()

Metoda onSaveInstanceState() ma jeden parametr — obiekt klasy Bundle. Klasa Bundle pozwala gromadzić w jednym obiekcie dane różnych typów:

Aktywność działająca

public void onSaveInstanceState(Bundle savedInstanceState) { }

Obiekt klasy Bundle jest także przekazywany jako parametr do metody onCreate(). Oznacza to, że jeśli zapiszemy wartości zmiennych seconds i running w obiekcie Bundle, to metoda onCreate() będzie w stanie je odczytać i zastosować podczas odtwarzania aktywności. Do umieszczania informacji w obiekcie Bundle służą metody, które zapisują w nim pary nazwa – wartość. Metody te mają następującą postać:

Metoda onSaveInstanceState() jest wywoływana onSaveInstanceState() przed metodą onDestroy(). Zapewnia nam ona możliwość zapisania stanu aktywności przed jej onDestroy() usunięciem.

bundle.put*(”nazwa”, wartość)

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 przechowywanej w zmiennej seconds, należałoby użyć następującego wywołania:

Aktywność usunięta

bundle.putInt(”seconds”, seconds);

W jednym obiekcie typu Bundle można zapisać wiele takich par danych. Poniżej przedstawiliśmy kod metody onSaveInstanceState() używanej w naszej aplikacji (dodamy ją do pliku StopwatchActivity.java kilka stron dalej): @Override

Stoper app/src/main

public void onSaveInstanceState(Bundle savedInstanceState) {

java

savedInstanceState.putInt(”seconds”, seconds); savedInstanceState.putBoolean(”running”, running); }

Po zapisaniu wartości naszych zmiennych w obiekcie Bundle możemy z nich skorzystać w metodzie onCreate().

140

Rozdział 4.

com.hfad.stoper

Te wywołania zapisują wartości zmiennych seconds i running w obiekcie Bundle.

Stopwatch Activity.java

Cykl życia aktywności

…a następnie odtworzenie go w onCreate() Jak już wspominaliśmy wcześniej, metoda onCreate() ma jeden parametr — obiekt Bundle. Jeśli aktywność jest tworzona po raz pierwszy, parametr ten będzie miał wartość null. Jeśli jednak aktywność jest odtwarzana i wcześniej została wywołana metoda onSaveInstanceState(), to w wywołaniu metody onCreate() zostanie przekazany ten sam obiekt Bundle, który wcześniej został użyty w metodzie onSaveInstanceState(): protected void onCreate(Bundle savedInstanceState) { ...

Stoper app/src/main java com.hfad.stoper

} Stopwatch Activity.java

Dane z obiektu Bundle można pobierać, używając metod o postaci: bundle.get*(”nazwa”);

Zamiast * wstaw Int, String i tak dalej, określając w ten sposób typ danej, którą chcesz pobrać.

gdzie bundle to nazwa zmiennej typu Bundle, * to określenie typu pobieranej wartości, a nazwa nazwa pary, którą zapisaliśmy na poprzedniej stronie. Na przykład aby pobrać z obiektu Bundle liczbę całkowitą (typu int) o nazwie seconds, należałoby użyć następującego wywołania: int seconds = bundle.getInt(”seconds”);

Łącząc to wszystko w jedną całość, uzyskamy naszą nową metodę onCreate(), która będzie miała następującą postać (na następnej stronie dodamy ją do pliku StopwatchActivity.java): protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_stopwatch); if (savedInstanceState != null) { seconds = savedInstanceState.getInt(“seconds”); running = savedInstanceState.getBoolean(“running”); }

bierają Te instrukcje po wartości le nd Bu tu iek ob z s i running. zmiennych second

runTimer(); }

Na następnej stronie przedstawimy kompletny kod służący do zapisu i odtwarzania stanu aktywności StopwatchActivity.

jesteś tutaj 

141

Kod aktywności StopwatchActivity

Zaktualizowany kod aktywności StopwatchActivity Zaktualizowaliśmy kod aktywności StopwatchActivity w taki sposób, że w przypadku obrócenia urządzenia stan aktywności zostaje zapisany w metodzie onSaveInstanceState(), a następnie odtworzony w metodzie onCreate(). Zaktualizuj swoją wersję pliku StopwatchActivity.java tak, by była identyczna z naszą (zmiany zostały wyróżnione pogrubioną czcionką): ... public class StopwatchActivity extends Activity { // Liczba sekund wyświetlana przez stoper

Stoper app/src/main

private int seconds = 0; java

// Czy stoper działa? private boolean running;

com.hfad.stoper

@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);

Stopwatch Activity.java

setContentView(R.layout.activity_stopwatch); if (savedInstanceState != null) { seconds = savedInstanceState.getInt(“seconds”); running = savedInstanceState.getBoolean(“running”); } runTimer(); }

Zapisujemy stan zmiennych w metodzie onSaveInstanceState() aktywności.

@Override public void onSaveInstanceState(Bundle savedInstanceState) { savedInstanceState.putInt(“seconds”, seconds); savedInstanceState.putBoolean(“running”, running); } ...

Część kodu aktywności została pominięta, gdyż nie trzeba wprowadzać w nim żadnych zmian.

A jak te modyfikacje spisują się w praktyce?

142

Rozdział 4.

Odtwarzamy stan aktywności, pobierając wartości z obiektu Bundle.

Cykl życia aktywności

Co się stanie po uruchomieniu aplikacji? 1

Użytkownik uruchamia aplikację i klika przycisk Start, by rozpocząć działanie stopera.

Metoda runTimer() zaczyna inkrementować liczbę sekund wyświetlanych na ekranie w widoku tekstowym o identyfikatorze time_view.

seconds=8 StopwatchActivity running=true

Urządzenie

2

Użytkownik obraca urządzenie.

Android uznaje to za zmianę konfiguracji i przygotowuje się do usunięcia aktywności. Zanim to jednak zrobi, wywołuje metodę onSaveInstanceState(). Metoda ta zapisuje wartości zmiennych seconds i running w obiekcie Bundle.

Zaraz mnie usuną, muszę was zapisać…

seconds=8 StopwatchActivity Urządzenie running=true

bundle “seconds”=8 “running”=true

jesteś tutaj  143

Co się dzieje?

Historii ciąg dalszy 3

Android usuwa aktywność, a następnie ją odtwarza.

Zostaje wywołana metoda onCreate(), a do niej przekazywany jest obiekt typu Bundle.

Urządzenie seconds=0 StopwatchActivity bundle “seconds”=8 “running”=true

4

running=false

Obiekt Bundle zawiera wartości zmiennych seconds i running, zapisane przed usunięciem aktywności.

Kod metody onCreate() przywraca wcześniejsze wartości zmiennych seconds i running, odczytując je z obiektu Bundle.

seconds=8 StopwatchActivity Urządzenie running=true bundle “seconds”=8 “running”=true

5

Zostaje wywołana metoda runTimer(), a stoper rozpoczyna działanie w miejscu, w którym wcześniej zostało ono przerwane.

Czas zmierzony przez stoper zostaje wyświetlony na ekranie urządzenia, a odliczanie jest kontynuowane.

144

Rozdział 4.

Cykl życia aktywności

Jazda próbna aplikacji Wprowadź zmiany w kodzie swojej aktywności, a następnie uruchom aplikację. Kiedy klikniesz przycisk Start, stoper zacznie mierzyć czas i nie zatrzyma się nawet po obróceniu urządzenia.

Kiedy obrócimy urządzenie, stoper wciąż będzie działał.

Nie istnieją

głupie pytania

P: Dlaczego Android chce

odtwarzać aktywność tylko dlatego, że obróciliśmy ekran?

O: Metoda onCreate() jest normalnie

używana do przygotowywania ekranu. Jeśli jej kod jest zależny od konfiguracji ekranu (na przykład jeśli używamy odmiennych układów dla urządzeń działających w orientacji pionowej i poziomej), to będziemy chcieli, by metoda onCreate() była wywoływana po każdej zmianie konfiguracji. Co więcej, możemy także chcieć zmieniać teksty wyświetlane w interfejsie użytkownika aplikacji, kiedy użytkownik zmieni wybrane ustawienia lokalne.

P: Dlaczego Android nie zapisuje

automatycznie wartości wszystkich zmiennych? Dlaczego muszę ten kod pisać ręcznie?

O: Może się zdarzyć, że nie będziesz

chciał zapisywać wartości wszystkich zmiennych. Na przykład możesz używać zmiennej zawierającej bieżącą szerokość ekranu. Zapewne nie chciałbyś odtwarzać wartości tej zmiennej podczas ponownego wywołania metody onCreate().

P: Czy Bundle to jakiś rodzaj mapy zaimplementowanej w Javie?

O: Nie, ale ta klasa została

zaprojektowana, by działać podobnie do klasy java.util.Map. Obiekty klasy Bundle mają dodatkowe możliwości, którymi nie dysponują mapy. Na przykład można je przekazywać pomiędzy procesami. To naprawdę bardzo przydatna możliwość, gdyż pozwala Androidowi mieć dostęp do bieżącego stanu aktywności.

jesteś tutaj  145

Uruchamianie i zatrzymywanie

Tworzenie i usuwanie to nie cały cykl życia aktywności Dotychczas przyjrzeliśmy się wyłącznie dwóm etapom cyklu życia aktywności, a mianowicie tworzeniu i usuwaniu, oraz pokazaliśmy, jak można obsługiwać zmiany konfiguracji urządzenia, takie jak zmiana jego orientacji. Cykl życia aplikacji zawiera jednak także inne zdarzenia, które mogą się nam przydać do zapewnienia prawidłowego działania aplikacji. Na przykład załóżmy, że w trakcie pomiaru czasu za pomocą naszej aplikacji stopera zadzwoni telefon. W takim przypadku, pomimo tego, że stoper nie będzie widoczny, wciąż będzie działał. A co zrobić, gdybyśmy chcieli, aby niewidoczny stoper był zatrzymywany i wznawiał pomiar czasu po jego ponownym wyświetleniu?

Nawet jeśli wcale nie chcesz, by aplikacja stopera działała w taki sposób, to bądź tak miły i udaj, że właśnie o to Ci chodziło. Jest to bowiem doskonały pretekst do przedstawienia kolejnych metod cyklu życia aktywności.

Uruchamianie, zatrzymywanie i restartowanie Na szczęście dzięki metodom cyklu życia aplikacji obsługa akcji związanych z widzialnością aplikacji jest całkiem łatwa. Oprócz metod onCreate() i onDestroy(), związanych z ogólnie pojętym cyklem życia aktywności, zawiera on także grupę metod związanych z jej widzialnością. Istnieją trzy podstawowe metody wywoływane, gdy aktywność staje się widoczna lub gdy znika z ekranu. Są to: onStart(), onStop() oraz onRestart(). Wszystkie te metody, podobnie jak onCreate() i onDestroy(), są dziedziczone po klasie Activity. Metoda onStart() jest wywoływana, gdy aktywność staje się widoczna dla użytkownika. Metoda onStop() jest wywoływana, gdy aktywność przestaje być widoczna dla użytkownika. Może się to zdarzyć na przykład, kiedy aktywność zostanie całkowicie przesłonięta przez inną aktywność wyświetloną na ekranie albo kiedy aktywność ma zostać usunięta. Jeśli metoda onStop() jest wywoływana w wyniku planowanego usunięcia aktywności, to przed nią zostanie wywołana metoda onSaveInstanceState(). Metoda onRestart() jest wywoływana, gdy aktywność jest już niewidoczna, ale ma zostać ponownie wyświetlona Na następnej stronie przyjrzymy się dokładniej, jak te nowe metody cyklu życia aktywności pasują do przedstawionych wcześniej metod onCreate() i onDestroy().

146

Rozdział 4.

Aktywność jest zatrzymana, gdy jest całkowicie przesłonięta przez inną aktywność i nie jest widoczna dla użytkownika. Taka aktywność wciąż istnieje w tle i zachowuje wszystkie informacje o swoim stanie.

Cykl życia aktywności

Cykl życia aktywności: widzialny czas życia Spróbujmy teraz narysować ten sam diagram cyklu życia aktywności, który pokazaliśmy już wcześniej, uwzględniając na nim metody onStart(), onStop() oraz onRestart() (te elementy diagramu, na których masz się skoncentrować, zostały wytłuszczone):

1

Aktywność uruchomiona

Wykonywany jest kod inicjalizujący aktywność, umieszczony w jej metodzie onCreate(). Na tym etapie aktywność jeszcze nie jest widoczna i nie wywołano jeszcze jej metody onStart().

1 onCreate()

2 2 onStart()

Aktywność działająca

Aktywność jest uruchamiana i zostaje wywołana jej metoda onCreate().

Po wykonaniu metody onStart() użytkownik może już zobaczyć aktywność wyświetloną na ekranie.

4 onRestart()

Po wywołaniu metody onCreate() zostaje wywołana metoda onStart(). To wywołanie jest wykonywane, gdy aktywność ma zostać wyświetlona.

3

Metoda onStop() jest wywoływana, gdy aktywność przestaje być widoczna dla użytkownika.

Po wykonaniu metody onStop() aktywność nie będzie już widoczna.

3 onStop()

4

onDestroy()

Aktywność może wielokrotnie przejść ten cykl, o ile wielokrotnie będzie ukrywana i ponownie wyświetlana.

5

Aktywność usunięta

Jeśli aktywność ponownie ma być wyświetlona, to zostaje wywołana metoda onRestart(), a po niej metoda onStart().

5

I w końcu aktywność jest usuwana.

Zazwyczaj metoda onStop() jest wywoływana przed wywołaniem metody onDestroy(), jeśli jednak w urządzeniu jest wyjątkowo mało dostępnej pamięci, to wywołanie onStop() może zostać pominięte.

jesteś tutaj  147

Metoda onStop

Musimy zaimplementować dwie dodatkowe metody cyklu życia Aby zaktualizować naszą aplikację stopera, musimy teraz wykonać dwie modyfikacje. Przede wszystkim musimy zaimplementować metodę onStop(), która zatrzyma stoper, gdy stanie się on niewidoczny. Kiedy to już zrobimy, będziemy musieli zaimplementować metodę onStart(), która uruchomi stoper, gdy tylko aplikacja ponownie zostanie wyświetlona. Zacznijmy od metody onStop().

Stoper app/src/main java

Implementacja metody onStop() stopera

com.hfad.stoper

Metodę onStop() klasy Activity można przesłonić, dodając do własnej klasy aktywności następujący fragment kodu:

Stopwatch Activity.java

@Override protected void onStop() { super.onStop(); }

Poniższy wiersz kodu:

Ten wiersz kodu wywołuje meto dę onStop() klasy bazowej aktywnoś ci — android.app.Activity.

super.onStop();

wywołuje metodę onStop() klasy bazowej Activity. Podczas przesłaniania metody onStop() zawsze należy do niej dodawać to wywołanie, aby zapewnić, że aktywność wykona wszelkie czynności realizowane w metodzie onStop() klasy bazowej. W przypadku pominięcia tego kroku Android zgłosi wyjątek. Dotyczy to także wszystkich pozostałych metod cyklu życia aktywności. Jeśli we własnym kodzie przesłaniamy którąkolwiek z metod cyklu życia aktywności, to koniecznie musimy wywoływać w nich odpowiednią metodę klasy bazowej, gdyż jeśli tego nie zrobimy, Android zgłosi wyjątek. W przypadku naszej aplikacji chcemy, by wywołanie metody onStop() spowodowało zatrzymanie stopera. W tym celu wystarczy, że przypiszemy zmiennej running wartość false: @Override 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.

148

Rozdział 4.

W przypadku przesłaniania metod cyklu życia naszych aktywności koniecznie musimy wywoływać metody klasy bazowej Activity. Jeśli tego nie zrobimy, Android zgłosi wyjątek.

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. Wskazówka: być może będziesz musiał wprowadzić nową zmienną. public class StopwatchActivity extends Activity { private int seconds = 0; private boolean running; @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 adzić zaimplementować metodę onStart() i wprow . drobne modyfikacje w kodzie innych metod

@Override public void onSaveInstanceState(Bundle savedInstanceState) { savedInstanceState.putInt(”seconds”, seconds); savedInstanceState.putBoolean(”running”, running); } @Override protected void onStop() { super.onStop(); running = false; }

jesteś tutaj  149

Zaostrz ołówek — rozwiązanie

Zaostrz ołówek Rozwiązanie

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. Wskazówka: być może będziesz musiał wprowadzić nową zmienną.

public class StopwatchActivity extends Activity { private int seconds = 0; Dodaliśmy nową zmienną, wasRunning, w której private boolean running; będziemy zapisywali informację, czy stoper działał przed wywołaniem metody onStop(), żebyśmy w razie potrzeby private boolean wasRunning; mogli go ponownie uruchomić, gdy aktywność znowu zostanie wyświetlona.

@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”); wasRunning = savedInstanceState.getBoolean(“wasRunning”); } runTimer(); Jeśli aktywność jest ponownie tworzona, to odtwarzamy stan zmiennej wasRunning. } @Override public void onSaveInstanceState(Bundle savedInstanceState) { savedInstanceState.putInt(”seconds”, seconds); savedInstanceState.putBoolean(”running”, running); savedInstanceState.putBoolean(“wasRunning”, wasRunning); } @Override protected void onStop() { super.onStop(); wasRunning = running; running = false; } @Override protected void onStart() { super.onStart(); if (wasRunning) { running = true; } }

150

Rozdział 4.

Zapisujemy stan zmiennej wasRunning.

Rejestrujemy, czy w momencie wywoływania metody onStop() stoper działał, czy nie.

To jest implementacja metody onStart(). Jeśli stoper działał, to niech dalej działa.

Cykl życia aktywności

Zaktualizowany kod aktywności StopwatchActivity Zmodyfikowaliśmy kod naszej aktywności w taki sposób, że jeśli stoper działał, zanim aktywność zniknęła z ekranu, to będzie kontynuował działanie po jej ponownym wyświetleniu. Stoper Wprowadź zmiany wyróżnione pogrubioną czcionką do kodu swojej aktywności: public class StopwatchActivity extends private int seconds = 0; private boolean running; private boolean wasRunning;

app/src/main

Activity { Nowa zmienna, wasRunning, przechowuje informację, czy przed wywołaniem metody onStop() stoper działał, czy nie.

java com.hfad.stoper

@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”); wasRunning = savedInstanceState.getBoolean(“wasRunning”); } runTimer(); Ta instrukcja odtwarza stan zmiennej wasRunning po ponownym utworzeniu aktywności. } @Override public void onSaveInstanceState(Bundle savedInstanceState) { savedInstanceState.putInt(”seconds”, seconds); savedInstanceState.putBoolean(”running”, running); savedInstanceState.putBoolean(“wasRunning”, wasRunning); } @Override protected void onStop() { super.onStop(); wasRunning = running; running = false; } @Override protected void onStart() { super.onStart(); if (wasRunning) { running = true; } } ...

Stopwatch Activity.java

Tu zapisujemy stan zmiennej wasRunning.

Ta instrukcja zapisuje, czy w momencie wywoływania metody onStop() stoper działał, czy nie.

To jest implementacja metody onStart(). Jeśli wcześniej stoper działał, to tu go ponownie uruchamiamy.

Pominęliśmy fragmenty kodu aktywności, gdyż nie zostały zmienione.

jesteś tutaj 

151

Co się dzieje?

Co się dzieje podczas działania aplikacji? 1

Użytkownik uruchamia aplikację i klika przycisk Start, by rozpocząć działanie stopera. Metoda runTimer() zaczyna inkrementować liczbę sekund wyświetlanych na ekranie w widoku tekstowym o identyfikatorze time_text.

seconds=16

Stopwatch Activity Urządzenie

2

running=true

W metodzie onStart() zmiennej running jest przypisywana wartość true.

wasRunning=false

Użytkownik przechodzi do ekranu głównego urządzenia, przez co nasza aplikacja przestaje być widoczna.

W efekcie zostaje wywołana metoda onStop(), zmiennej wasRunning zostaje przypisana wartość true, zmiennej running wartość false, a aktywność przestaje inkrementować liczbę sekund.

seconds=16

Aktywność wciąż istnieje, choć nie jest widoczna.

Stopwatch Activity Urządzenie

3

running=false

W metodzie onStop() zmiennej running zostaje przypisana wartość false.

wasRunning=true

Użytkownik ponownie wyświetla naszą aplikację.

Powoduje to wywołanie metody onStart(), która przypisuje zmiennej running wartość true, dzięki czemu aktywność ponownie będzie inkrementować liczbę sekund.

seconds=16

Stopwatch Activity Urządzenie

152

Rozdział 4.

running=true

wasRunning=true

W metodzie onStart() zmiennej running przypisywana jest wartość true.

Cykl życia aktywności

Jazda próbna aplikacji Zapisz zmiany wprowadzone w kodzie aktywności, a następnie uruchom aplikację. Kiedy klikniesz przycisk Start, stoper zacznie mierzyć upływający czas; kiedy aplikacja będzie niewidoczna, pomiar zostanie zatrzymany, a ponowne wyświetlenie aplikacji spowoduje jego wznowienie.

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 stosować metodę onRestart(),

moglibyśmy wznawiać działanie stopera, używając metody onStart()?

O: Metoda onRestart() jest używana w sytuacjach, w których

chcemy wykonać jakiś kod, gdy aplikacja zostaje wyświetlona po jej wcześniejszym ukryciu. Nie jest ona natomiast wywoływana podczas wyświetlania aktywności po raz pierwszy. W naszym przypadku chcieliśmy, by aplikacja cały czas działała według założeń, nawet po obróceniu urządzenia.

P: A czym różni się ta sytuacja? O: W przypadku obrócenia urządzenia dotychczasowa

aktywność jest usuwana, a na jej miejsce jest tworzona nowa. Gdybyśmy zatem umieścili kod w metodzie onRestart() zamiast w metodzie onStart(), to nie zostałby on wykonany po ponownym utworzeniu aktywności. Metoda onStart() jest wywoływana w obu tych przypadkach.

jesteś tutaj  153

Cykl życia na pierwszym planie

A co się dzieje, jeśli aplikacja jest tylko częściowo widoczna? Wiesz już, co się stanie, kiedy aktywność zostanie utworzona i usunięta, a także co się dzieje, gdy aktywność jest wyświetlana i ukrywana. Jednak jest jeszcze jedna sytuacja, którą musimy wziąć pod uwagę: kiedy aktywność jest widoczna, lecz nie dysponuje miejscem wprowadzania. 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. Kiedy to się stanie, aktywność zostanie wstrzymana.

To jest aktywność należąca do innej aplikacji, wyświetlona na naszym stoperze.

Aktywność zmienia stan na wstrzymaną, gdy nie dysponuje miejscem wprowadzania, lecz cały czas jest widoczna dla użytkownika. Taka aktywność cały czas żyje i zachowuje wszystkie 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. Na następnej stronie zobaczysz, jak te dwie metody pasują do pozostałych, przedstawionych wcześniej metod cyklu życia aplikacji.

154

Rozdział 4.

Cykl życia aktywności

Cykl życia aktywności: życie na pierwszym planie Kontynuujmy tworzenie przedstawionego już wcześniej diagramu metod cyklu życia aktywności, a konkretnie dodajmy do niego dwie metody: onPause() i onResume() (te nowe elementy schematu zostały wyróżnione pogrubieniem):

1

Aktywność uruchomiona

W tym momencie aktywność jest już widoczna, ale jeszcze nie dysponuje miejscem wprowadzania.

2

1 onCreate()

3

2 onResume()

Aktywność działająca

onPause()

3

Kiedy aktywność przestaje być wyświetlana na pierwszym planie, zostaje wywołana metoda onPause().

Po wywołaniu metody onPause() aktywność wciąż jest widoczna, lecz nie dysponuje miejscem wprowadzania.

4 onRestart()

Jeśli aktywność zostanie ponownie przeniesiona do pierwszego planu, to zostanie wywołana metoda onResume().

Ten cykl może się powtarzać wielokrotnie, jeśli tylko aktywność będzie na przemian tracić i ponownie odzyskiwać miejsce wprowadzania.

6

5

Kiedy aktywność przestanie być widoczna dla użytkownika, zostanie wywołana metoda onStop().

Po wywołaniu metody onStop() aktywność nie jest już widoczna dla użytkownika.

5 onStop() 6 onDestroy()

7

Aktywność usunięta

Po metodzie onStart() zostaje wywołana metoda onResume(). Jest ona wywoływana przed przeniesieniem aktywności na pierwszy plan.

Po wywołaniu metody onResume() aktywność dysponuje już miejscem wprowadzania i użytkownik może prowadzić z nią interakcję.

onStart()

4

Aktywność jest uruchamiana i zostają wywołane jej metody onCreate() i onStart().

Jeśli aktywność ponownie stanie się widoczna, zostanie wywołana jej metoda onRestart(), a następnie dwie kolejne metody, onStart() i onResume().

Także ten cykl wywołań może być wykonywany wielokrotnie.

7

I w końcu aktywność zostaje usunięta.

Kiedy aktywność ma zostać usunięta, zazwyczaj zanim to się stanie, zostają wywołane metody onPause() oraz onStop().

jesteś tutaj  155

Obroty Wcześniej opisywaliście, jak aktywność jest usuwana i ponownie tworzona w efekcie obrócenia ekranu urządzenia. A co się stanie, kiedy urządzenie zostanie obrócone w momencie, gdy aplikacja będzie wstrzymana? Czy wówczas wykonywane będą te same metody cyklu życia aktywności? To jest świetne pytanie, dlatego zanim wrócimy do naszej aplikacji, przyjrzymy się temu zagadnieniu dokładniej. W początkowej aktywności wywoływane są wszystkie metody cyklu jej życia, zaczynając od onCreate(), a kończąc na onDestroy(). Nowa aktywność jest tworzona po usunięciu początkowej. Ponieważ ta nowa aktywność nie jest jeszcze wyświetlona na pierwszym planie, zostają wywołane tylko dwie metody cyklu jej życia, a mianowicie onCreate() i onStart(). Poniższy diagram pokazuje, co się dzieje, kiedy użytkownik odwróci urządzenie, gdy aktywność nie ma miejsca wprowadzania:

Początkowa aktywność

1

Aktywność uruchomiona 2 1 onCreate() onStart()

3

onResume()

Aktywność działająca 2 onPause() 3

onStop() onDestroy()

Aktywność usunięta

4

Użytkownik uruchamia aktywność.

Zostają wywołane metody onCreate(), onStart() oraz onResume() cyklu życia aktywności. Nasza aktywność jest przesłaniana przez inną.

Zostaje wywołana metoda onPause() aktywności. Użytkownik obraca urządzenie.

Android zauważa zamianę konfiguracji urządzenia. Zostają wywołane metody onStop() i onDestroy(), po czym system usuwa aktywność. Na jej miejsce jest tworzona nowa aktywność. Aktywność jest widoczna, lecz nie na pierwszym planie.

Zostają wywołane metody onCreate() i onStart(). Ponieważ aktywność jest widoczna, lecz nie dysponuje miejscem wprowadzania, metoda onResume() nie zostanie wywołana.

Aktywność zastępcza

Aktywność uruchomiona 4 onCreate() onStart()

156

Rozdział 4.

Cykl życia aktywności Widzę, że aktywność zastępcza nie przeszła do stanu „działająca”, gdyż nie została wyświetlona na pierwszym planie. Ale co by się stało, gdybyśmy całkowicie przeszli do innej aktywności, tak że ta nasza w ogóle nie byłaby widoczna? Jeśli aktywność została zatrzymana, to czy przed metodą onStop() są wywoływane metody onResume() i onPause()?

Aktywności mogą przechodzić bezpośrednio od wywołania onStart() do wywołania onStop(), pomijając przy tym wywołania metod onPause() i onResume(). Jeśli aktywność jest widoczna, lecz nigdy nie znalazła się na pierwszym planie ani nie uzyskała miejsca wprowadzania, to jej metody onPause() i onResume() nigdy nie zostaną wywołane. Metoda onResume() jest wywoływana, kiedy aktywność zostaje wyświetlona na pierwszym planie i uzyskuje miejsce wprowadzania. Jeśli aktywność jest widoczna, lecz przesłonięta innymi aktywnościami, to jej metoda onResume() nie zostanie wywołana. I podobnie metoda onPause() jest wywoływana, kiedy aktywność przestaje być widoczna na pierwszym planie. A zatem jeśli aktywność nigdy nie była wyświetlona na pierwszym planie, to jej metoda onPause() nie zostanie wywołana. Jeśli aktywność zostanie zatrzymana lub usunięta, zanim pojawi się na pierwszym planie, to po wywołaniu metody onStart() zostaje wywołana metoda onStop(). W takim przypadku wywołania metod onResume() i onPause() są pomijane.

Aktywność uruchomiona

onCreate()

onStart()

onResume()

Aktywność działająca

onRestart()

onPause()

onStop()

onDestroy()

Aktywność usunięta jesteś tutaj  157

Zmiana kodu metod

Zatrzymanie stopera w razie wstrzymania aktywności Wróćmy do naszej aplikacji stopera. Na razie zatrzymujemy stoper, kiedy aplikacja nie jest widoczna, a następnie wznawiamy jego działanie, gdy aplikacja ponownie pojawi się na ekranie. Robimy to, używając dwóch metod, przedstawionych poniżej: onStop() oraz onStart(): @Override protected void onStop() { super.onStop(); wasRunning = running; running = false; }

Stoper app/src/main java

@Override protected void onStart() {

com.hfad.stoper

super.onStart(); if (wasRunning) { running = true; } }

Spróbujmy zmodyfikować aplikację tak, by działała w identyczny sposób, nawet jeśli będzie częściowo przesłonięta. Zadbamy o to, by stoper był zatrzymywany, kiedy aktywność zostanie wstrzymana, i by ponownie zaczynał mierzyć upływ czasu po jej wznowieniu. Jakie zmiany musimy w tym celu wprowadzić w metodach cyklu życia aktywności? Chcemy zatrzymać aplikację, kiedy aktywność zostanie wstrzymana, i ponownie ją uruchomić (o ile wcześniej działała), kiedy działanie aktywności zostanie wznowione. Innymi słowy, chcemy, by aplikacja zachowywała się tak samo jak podczas zatrzymywania i ponownego uruchamiania aktywności. Oznacza to, że zamiast powielania kodu, który już umieściliśmy w kilku metodach, możemy zastosować jedną metodę do obsługi wstrzymywania i zatrzymywania aktywności oraz drugą metodę do obsługi jej uruchamiania i wznawiania.

158

Rozdział 4.

Stopwatch Activity.java

Cykl życia aktywności

Implementacja metod onPause() oraz onResume() Zaczniemy od implementacji obsługi wznawiania oraz uruchamiania aktywności. Kiedy działanie aktywności jest wznawiane, wywoływana jest metoda cyklu życia onResume(). Kiedy aktywność jest uruchamiana, metoda onResume() jest wywoływana po wcześniejszym wywołaniu metody onStart(). Metoda onResume() jest zatem wywoływana niezależnie od tego, czy aktywność jest uruchamiana, czy wznawiana; a to oznacza, że jeśli kod umieszczony wcześniej w metodzie onStart() przeniesiemy do metody onResume(), aplikacja będzie zachowywać się identycznie niezależnie od tego, czy aktywność jest uruchamiana po raz pierwszy, czy jej działanie jest wznawiane. Innymi słowy, możemy usunąć metodę onStart() i zastąpić ją metodą onResume(), jak pokazaliśmy w poniższym przykładzie: @Override protected void onStart() { super.onStart(); if (wasRunning) { running = true; } } @Override protected void onResume() { super.onResume(); if (wasRunning) { running = true; } }

Metoda onResume() jest wywoływana, kiedy aktywność jest uruchamiana lub gdy jej działanie zostaje wznowione. Ponieważ chcemy, by nasza aplikacja robiła dokładnie to samo niezależnie od tego, czy jest uruchamiana, czy wznawiana, wystarczy, że zaimplementujemy jedynie metodę onResume().

onStart() Usuń metodę onStart().

Stoper

onResume()

app/src/main java

I dodaj metodę onResume().

com.hfad.stoper

Aktywność działająca

Stopwatch Activity.java

onPause()

W podobny sposób możemy poradzić sobie z obsługą wstrzymywania i zatrzymywania aktywności. Kiedy działanie aktywności jest wstrzymywane, wywoływana jest metoda cyklu życia onPause(). W przypadku zatrzymywania aktywności wywoływana jest najpierw metoda onPause(), a następnie onStop(). Metoda onPause() jest wywoływana niezależnie od tego, czy aktywność jest wstrzymywana, czy zatrzymywana; a to oznacza, że kod umieszczony wcześniej w metodzie onStop() możemy przenieść do onPause(): @Override protected void onStop() { super.onStop(); wasRunning = running; running = false; } @Override protected void onPause() { super.onPause(); wasRunning = running; running = false; }

Usuń metodę onStop().

Stoper app/src/main

onStop()

Metoda onPasue() jest wywoływana, kiedy aktywność jest wstrzymywana, jak również gdy jest zatrzymywana. Oznacza to, że wystarczy, byśmy zaimplementowali jedynie metodę onPause().

java

Dodaj metodę onPause().

com.hfad.stoper Stopwatch Activity.java

jesteś tutaj  159

Kod aktywności StopwatchActivity

Kompletny kod aktywności Poniżej przedstawiliśmy kompletny kod aktywności zapisany w pliku StopwatchActivity.java (zmiany zostały wyróżnione pogrubioną czcionką): package com.hfad.stoper; Stoper

import android.app.Activity; import android.os.Bundle;

app/src/main

import android.view.View; import java.util.Locale;

java

import android.os.Handler; import android.widget.TextView;

com.hfad.stoper

public class StopwatchActivity extends Activity { // Liczba sekund wyświetlana przez stoper private int seconds = 0; Zmiennych seconds, running i wasRunning // Czy stoper działa? używamy odpowiednio do przechowywania liczby zmierzonych sekund, określenia, czy stoper działa, private boolean running; i określenia, czy stoper działał w momencie private boolean wasRunning; wstrzymywania aktywności.

Stopwatch Activity.java

@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Ten fragment kodu odtwarza stan setContentView(R.layout.activity_stopwatch); stopera w razie usunięcia i ponownego utworzenia aktywności. if (savedInstanceState != null) { seconds = savedInstanceState.getInt(”seconds”); running = savedInstanceState.getBoolean(”running”); wasRunning = savedInstanceState.getBoolean(”wasRunning”); } runTimer(); } metoda zapisuje stan stopera, Ta jeśli aktywność ma zostać usunięta.

@Override public void onSaveInstanceState(Bundle savedInstanceState) { savedInstanceState.putInt(”seconds”, seconds); savedInstanceState.putBoolean(”running”, running); savedInstanceState.putBoolean(”wasRunning”, wasRunning); } Dalsza część kodu aktywności została zamieszczona na następnej stronie.

160

Rozdział 4.

Cykl życia aktywności

Kod aktywności (ciąg dalszy) @Override protected void onStop() { super.onStop();

Stoper

wasRunning = running; app/src/main

running = false; } Usuń te dwie metody.

java

@Override com.hfad.stoper

protected void onStart() { super.onStart();

Stopwatch Activity.java

if (wasRunning) { running = true; } } @Override

Jeśli aktywność zostaje wstrzymana, to zatrzymujemy stoper.

protected void onPause() { super.onPause(); wasRunning = running; running = false; } @Override Jeśli działanie

aktywności protected void onResume() { zostaje wznow

super.onResume(); if (wasRunning) {

ione i podczas jej wstrzymywania stoper działał, to teraz go uruchamiamy.

running = true; } } // Metoda uruchamia stoper po kliknięciu przycisku Start public void onClickStart(View view) { running = true; }

Ta metoda jest wywoływana w odpowiedzi na kliknięcie przycisku Start.

Dalsza część kodu aktywności została zamieszczona na następnej stronie.

jesteś tutaj 

161

Kod aktywności StopwatchActivity, ciąg dalszy

Kod aktywności (ciąg dalszy) // Metoda zatrzymuje stoper po kliknięciu przycisku Stop public void onClickStop(View view) {

Ta metoda jest wywoływana w odpowiedzi na kliknięcie przycisku Stop.

running = false; }

// Metoda zeruje stoper po kliknięciu przycisku Kasuj public void onClickReset(View view) { Ta metoda jest wywoływana w odpowiedzi na kliknięcie przycisku Kasuj.

running = false; seconds = 0; }

Metoda runTimer() używa obiektu Handler do inkrementacji liczby sekund i wyświetlania ich w widoku tekstowym.

// Wyświetla w stoperze liczbę sekund private void runTimer() { final TextView timeView = (TextView)findViewById(R.id.time_view); final Handler handler = new Handler(); handler.post(new Runnable() { @Override public void run() { int hours = seconds/3600; int minutes = (seconds%3600)/60; int secs = seconds%60; String time = String.format(”%d:%02d:%02d”, hours, minutes, secs); timeView.setText(time); if (running) { seconds++; } handler.postDelayed(this, 1000);

Stoper

} }); }

app/src/main java

}

com.hfad.stoper

A teraz sprawdźmy, co się stanie po uruchomieniu tego kodu.

162

Rozdział 4.

Stopwatch Activity.java

Cykl życia aktywności

Co się stanie po uruchomieniu aplikacji? 1

Użytkownik uruchamia aplikację i klika przycisk Start, by rozpocząć mierzenie czasu. Metoda runTimer() zaczyna inkrementować liczbę sekund, która jest wyświetlana w widoku tekstowym o identyfikatorze time_view.

seconds=15

Stopwatch Activity Urządzenie

2

running=true

wasRunning=false

Inna aktywność zostaje wyświetlona na pierwszym planie, przez co nasza aktywność StopwatchActivity staje się tylko częściowo widoczna.

Zostaje wywołana metoda onPause(), zmiennej wasRunning zostaje przypisana wartość true, zmiennej running wartość false, a zmienna seconds przestaje być inkrementowana.

seconds=15 Aktywność zostaje wstrzymana, gdyż jest widoczna, ale nie na pierwszym planie.

Stopwatch Activity Urządzenie

3

W metodzie onPause() zmiennej running zostaje przypisana wartość false.

running=false

wasRunning=true

Kiedy aktywność StopwatchActivity ponownie zostaje wyświetlona na pierwszym planie, wywoływana jest jej metoda onResume(), zmiennej running jest przypisywana wartość true, a zmienna seconds ponownie zaczyna być inkrementowana.

seconds=15

Stopwatch Activity Urządzenie

running=true

W metodzie onResume() zmiennej running zostaje przypisana wartość true.

wasRunning=true

jesteś tutaj  163

Jazda próbna

Jazda próbna aplikacji Zapisz zmiany wprowadzone w kodzie aktywności, a następnie uruchom aplikację. Kiedy klikniesz przycisk Start, stoper zacznie mierzyć upływające sekundy; pomiar zostanie zatrzymany po częściowym przesłonięciu naszej aktywności przez jakąś inną aktywność, a po jej ponownym wyświetleniu na pierwszym planie pomiar zostanie wznowiony.

Uruchomiliśmy nasz stoper.

Jego działanie zostało wstrzymane po częściowym przesłonięciu aktywności przez inną.

Nasz stoper wznowił działanie, kiedy aktywność ponownie została wyświetlona na pierwszym planie.

164

Rozdział 4.

Cykl życia aktywności

Bądź aktywnością

Po prawej stronie przedstawiliśmy kod pewnej aktywności. Twoim zadaniem jest wcielić się w rolę tej aktywności i podać, który kod zostanie wykonany w każdej z poniższych sytuacji. Kod, który masz brać pod uwagę, zaznaczyliśmy literami w kółkach. Aby ułatwić Ci rozwiązanie ćwiczenia, podaliśmy odpowiedź na pierwsze pytanie.

... class MyActivity extends Activity{ protected void onCreate( Bundle savedInstanceState) { // Wykonujemy kod A A ... } protected void onPause() { // Wykonujemy kod B B ... } protected void onRestart() { // Wykonujemy kod C C ... }

Użytkownik uruchamia aktywność i zaczyna jej używać. Zostają wykonane fragmenty kodu A, G i D. Aktywność jest tworzona, następnie zostaje wyświetlona na pierwszym planie i uzyskuje miejsce wprowadzania.

protected void onResume() { // Wykonujemy kod D D ... }

Użytkownik uruchamia aktywność, zaczyna jej używać, a następnie przechodzi do innej aplikacji.

protected void onStop() { // Wykonujemy kod E E ... } protected void onRecreate() { // Wykonujemy kod F F ... }

dne. To jest tru

Użytkownik uruchamia aktywność, zaczyna jej używać, obraca urządzenie, przechodzi do innej aktywności, a następnie wraca do początkowej.

protected void onStart() { // Wykonujemy kod G G ... } protected void onDestroy() { // Wykonujemy kod H H ... } }

jesteś tutaj  165

Rozwiązanie

Bądź aktywnością. Rozwiązanie

Po prawej stronie przedstawiliśmy kod pewnej aktywności. Twoim zadaniem jest wcielić się w rolę tej aktywności i podać, który kod zostanie wykonany w każdej z poniższych sytuacji. Kod, który masz brać pod uwagę, zaznaczyliśmy literami w kółkach. Aby ułatwić Ci rozwiązanie ćwiczenia, podaliśmy odpowiedź na pierwsze pytanie.

... class MyActivity extends Activity{ protected void onCreate( Bundle savedInstanceState) { // Wykonujemy kod A A ... } protected void onPause() { // Wykonujemy kod B B ... }

Użytkownik uruchamia aktywność i zaczyna jej używać.

protected void onRestart() { // Wykonujemy kod C C ... }

Zostają wykonane fragmenty kodu A, G i D. Aktywność jest tworzona, następnie zostaje wyświetlona na pierwszym planie i uzyskuje miejsce wprowadzania.

protected void onResume() { // Wykonujemy kod D D ... }

Użytkownik uruchamia aktywność, zaczyna jej używać, a następnie przechodzi do innej aplikacji. Zostaną wykonane fragmenty kodu A, G, D, B i E. Aktywność jest tworzona, następnie zostaje wyświetlona i uzyskuje miejsce wprowadzania. Potem użytkownik przechodzi do innej aplikacji, więc aktywność traci miejsce wprowadzania i nie jest już dłużej widoczna dla użytkownika.

protected void onStop() { // Wykonujemy kod E E ... W cyklu życia aktywności } nie ma metody o nazwie onRecreate().

Użytkownik uruchamia aktywność, zaczyna jej używać, obraca urządzenie, przechodzi do innej aktywności, a następnie wraca do początkowej.

protected void onRecreate() { // Wykonujemy kod F F ... }

Zostaną wykonane fragmenty kodu A, G, D, B, E, H, A, G, D, B, E, C, G i D. Aktywność jest tworzona, następnie zostaje wyświetlona i uzyskuje miejsce wprowadzania. Kiedy urządzenie zostaje obrócone, aktywność traci miejsce wprowadzania, przestaje być widoczna i zostaje usunięta. Potem jest ponownie tworzona, staje się widoczna i uzyskuje miejsce wprowadzania. Gdy użytkownik przechodzi do innej aplikacji, a następnie wraca do tej, aktywność traci miejsce wprowadzania, jest ukrywana, po czym zostaje ponownie wyświetlona i uzyskuje miejsce wprowadzania.

protected void onStart() { // Wykonujemy kod G G ... } protected void onDestroy() { // Wykonujemy kod H H ... } }

166

Rozdział 4.

Cykl życia aktywności

Wygodny przewodnik po metodach cyklu życia aktywności Metoda

Kiedy jest wywoływana?

Następna metoda

onCreate()

Gdy aktywność jest tworzona po raz pierwszy. Można jej używać 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()

onRestart()

Gdy aktywność została wcześniej zatrzymana, bezpośrednio przed jej ponownym uruchomieniem.

onStart()

onStart()

Gdy aktywność staje się widoczna. Po niej jest wywoływana metoda onResume(), jeśli aktywność ma zostać wyświetlona na pierwszym planie, lub metoda onStop(), jeśli aktywność ma zostać ukryta.

onResume() lub onStop()

onResume()

Gdy aktywność jest wyświetlana na pierwszym planie.

onPause()

onPause()

Gdy aktywność nie jest już wyświetlana na pierwszym planie, ponieważ ma zostać wznowione działanie innej aktywności. 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().

onResume() lub onStop()

onStop()

Gdy aktywność nie jest już widoczna. Przyczyną może być przesłonięcie danej aktywności przez inną bądź też planowane 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().

onRestart() lub onDestroy()

onDestroy()

Gdy aktywność ma zostać usunięta lub gdy kończy działanie.

żadna

jesteś tutaj  167

Przybornik

Rozdział 4.

Twój przybornik do Androida Opanowałeś już rozdział 4. i dodałeś do swojego przybornika z narzędziami znajomość metod tworzących cykl życia aktywności.

CELNE SPOSTRZEŻENIA 



















168

j Pełny kod przykładowe j ane tow zen aplikacji pre z żes mo ale dzi roz w tym P FT ra we ser z rać pob wydawnictwa Helion: klady/ ftp://ftp.helion.pl/przy p .zi andrr2

Aktywność 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 Activity. W razie przesłaniania którejkolwiek z tych metod należy wywołać przesłanianą metodę klasy bazowej.

onResume()

Metoda onSaveInstanceState(Bundle) pozwala zapisać stan aktywności przed jej usunięciem. Tego samego obiektu Bundle można użyć w metodzie onCreate() do odtworzenia stanu aktywności.

Aktywność działająca

Do zapisywania wartości w obiekcie Bundle służą 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ą odpowiednio narodziny i śmierć aktywności. Metody onRestart(), onStart() oraz onStop() są związane ze zmianami widzialności aktywności. Metody onResume() i onPause() są związane z uzyskiwaniem i traceniem przez aktywność miejsca wprowadzania.

Rozdział 4.

onPause()

onStop()

onDestroy()

Aktywność usunięta

onRestart()

5. Widoki i grupy widoków

Podziwiaj widoki …a to układ, który przygotowałem wcześniej.

Zobaczyłeś już, jak można rozmieszczać elementy GUI, używając układu LinearLayout, ale to jedynie wierzchołek góry lodowej. W tym rozdziale nieco dokładniej poznamy rzeczywisty sposób działania układu liniowego. Przedstawimy także FrameLayout, nazywany też układem ramki, będący prostym układem używanym do rozmieszczania widoków jeden na drugim 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ć.

to jest nowy rozdział  169

Interfejs użytkownika

Interfejs użytkownika aplikacji składa się z układów i komponentów GUI Jak już wiesz, układ definiuje, jak wygląda ekran aplikacji, a postać układu definiuje się, używając kodu XML. Układy zazwyczaj zawierają komponenty użytkownika, takie jak przyciski i pola tekstowe. Użytkownik prowadzi z nimi interakcje i sprawia, że aplikacja wykonuje jakieś operacje. Wszystkie aplikacje przedstawione w poprzednich rozdziałach książki wykorzystywały układy liniowe,

w których komponenty GUI są rozmieszczane w pojedynczej kolumnie lub pojedynczym wierszu. Jednak by w pełni wykorzystać możliwości układów tego typu, trzeba poświęcić nieco czasu na dokładne poznanie sposobów ich działania oraz nauczenie się efektywnego ich używania. To wszystko są przykłady układów LinearLayout.

W tym rozdziale przyjrzymy się dokładniej układom liniowym, LinearLayout, przedstawimy ich bliskiego krewnego — układ ramki, FrameLayout — oraz

parę dodatkowych komponentów GUI, dzięki którym Twoje aplikacje staną się bardziej interaktywne. Zacznijmy od układów typu LinearLayout.

170

Rozdział 5.

Widoki i grupy widoków

¨  Układ LinearLayout

Układ LinearLayout wyświetla widoki w jednym wierszu lub jednej kolumnie

¨  Układ FrameLayout

Jak już wiesz, układ liniowy wyświetla umieszczone w nim widoki jeden przy drugim, rozmieszczając je w pionie lub poziomie. Jeśli widoki będą rozmieszczane w pionie, to utworzą pojedynczą kolumnę; jeśli natomiast będą umieszczane w poziomie — powstanie z nich jeden wiersz. Układ liniowy definiuje się przy użyciu elementu , takiego jak ten przedstawiony poniżej:

...

W elemencie można także stosować inne atrybuty.

Atrybut orientation określa, czy widoki mają być rozmieszczane w pionie, czy w poziomie.

Atrybut xmlns:android służy do określania przestrzeni nazw Androida, a jego wartością zawsze musi być ”http://schemas.android.com/apk/ res/android”.

Określenie szerokości i wysokości układu jest KONIECZNE Atrybuty layout_width oraz layout_height określają, odpowiednio, jak szeroki i wysoki ma być definiowany układ. Oba te atrybuty są obowiązkowe dla wszystkich układów i widoków. Atrybutom layout_width oraz layout_height można przypisywać takie wartości jak ”wrap_content”, ”match_parent” bądź też konkretne wymiary, na przykład 8dp — czyli 8 pikseli niezależnych od gęstości ekranu. Wartość ”wrap_content” oznacza, że wielkość układu ma być jedynie na tyle duża, by można w nim było wyświetlić wszystkie umieszczone w nim widoki; z kolei użycie wartości ”match_parent” nakazuje dostosowanie wymiarów układu do wielkości jego elementu nadrzędnego — w tym przypadku będą to wymiary ekranu pomniejszone o ewentualne wypełnienie (więcej informacji na temat wypełnień zamieściliśmy w dalszej części rozdziału). Podczas definiowania układów zazwyczaj stosuje się wartości ”match_parent”. Czasami można znaleźć układy, w których atrybutom layout_width oraz layout_height przypisana jest wartość ”fill_parent”. Była ona stosowana we wcześniejszych wersjach systemu Android, lecz teraz została zastąpiona wartością ”match_parent”. Wartość ”fill_parent” została wycofana.

Dla maniaków Co to są piksele niezależne od gęstości? Niektóre urządzenia mają bardzo małe piksele, dzięki czemu mogą wyświetlać bardzo ostre obrazy. Z kolei inne urządzenia są tańsze w produkcji, gdyż ich piksele są większe i jest ich mniej. Pikseli niezależnych od gęstości (dp) można używać do tworzenia interfejsów użytkownika, które nie będą zbyt małe na jednych urządzeniach, a zbyt duże na innych. Wielkości wyrażone w pikselach niezależnych od gęstości są w przybliżeniu takie same na wszystkich urządzeniach.

jesteś tutaj 

171

Atrybut orientation

¨  Układ LinearLayout

Orientacja układu może być pionowa lub pozioma

¨  Układ FrameLayout

Kierunek w którym będą rozmieszczane widoki w układzie, określamy, przy użyciu atrybutu android:orientation. Jak już się przekonałeś w poprzednich rozdziałach, aby widoki były rozmieszczane pionowo, należy użyć atrybutu o następującej postaci: android:orientation=”vertical” W przypadku zastosowania orientacji pionowej widoki są rozmieszczane w jednej kolumnie.

W tym przypadku widoki będą wyświetlane w jednej kolumnie. Aby widoki były rozmieszczane w jednym wierszu, należy użyć atrybutu orientation o następującej postaci: android:orientation=”horizontal”

W przypadku zastosowania orientacji poziomej widoki są domyślnie rozmieszczane od lewej do prawej. Rozwiązanie to świetnie się sprawdza w przypadku języków, w których tekst jest zapisywany od lewej do prawej; jednak co się dzieje, gdy użytkownik wybierze na urządzeniu jeden z języków, w których tekst jest zapisywany od prawej do lewej? W przypadku aplikacji, w których minimalnym poziomem API jest poziom 17. lub wyższy, można nakazać, by rozmieszczanie widoków było zgodne z ustawieniami językowymi wybranymi na urządzeniu. Jeśli w wybranym języku tekst jest zapisywany od prawej do lewej, to także widoki mogą być rozmieszczane w tym samym kierunku. Aby skorzystać z tej możliwości, w pliku manifestu AndroidManifest.xml musimy zadeklarować, że aplikacja ma wspierać takie języki. Poniżej pokazaliśmy, jak należy to zrobić:

app/src/main

...

Android Studio może automatyczni ten wiersz kodu. Należy go umie e dodawać szczać wewnątrz znacznika .



AndroidManifest.xml

supportsRtl oznacza „obsługuje języki, w których tekst jest zapisywany od prawej do lewej”.

To jest układ o orientacji poziomej w przypadku wybrania języka, w którym tekst jest zapisywany od lewej do prawej.

172

Rozdział 5.

To jest układ o orientacji poziomej w przypadku wybrania języka, w którym tekst jest zapisywany od prawej do lewej.

Widoki i grupy widoków

¨  Układ LinearLayout

Wypełnienie dodaje puste miejsce Jeśli chcesz, by wokół krawędzi układu zostało nieco pustego miejsca, to możesz skorzystać z atrybutów padding. Atrybuty te informują system Android, jak duże ma być wypełnienie pomiędzy krawędziami układu a jego elementem nadrzędnym. Poniżej pokazaliśmy, w jaki sposób można dodać wypełnienie o wielkości 16dp do wszystkich krawędzi układu:

...

¨  Układ FrameLayout

padding padding Układ

Ten atrybut dodaje wypełnienie o tej samej wielkości do wszystkich krawędzi układu.

Jeśli do poszczególnych krawędzi układu chcesz dodawać wypełnienia o różnej wielkości, to należy je podać osobno dla każdej z nich. Poniżej pokazaliśmy, w jaki sposób można dodać wypełnienie o wielkości 32dp do górnej krawędzi układu oraz wypełnienie o wielkości 16dp do wszystkich pozostałych krawędzi:

padding

padding paddingTop

paddingLeft

Układ

Te atrybuty dodają wypełnienie do poszczególnych krawędzi układu.

...

Jeśli tworzona aplikacja obsługuje języki, w których tekst jest zapisywany od prawej do lewej, to można także użyć atrybutów:

paddingBottom

paddingRight

android:paddingStart=”16dp”

oraz android:paddingEnd=”16dp”

aby dodać wypełnienie, odpowiednio, do początkowej i końcowej krawędzi układu, a nie do krawędzi lewej i prawej. Atrybut android:paddingStart określa wypełnienie przy początkowej krawędzi układu. W przypadku języków, w których tekst jest zapisywany od lewej do prawej, krawędź początkowa znajduje się po lewej stronie; natomiast w przypadku języków, w których tekst jest zapisywany od prawej do lewej — po prawej. Atrybut android:paddingEnd określa wypełnienie przy końcowej krawędzi układu. W przypadku języków, w których tekst jest zapisywany od lewej do prawej, krawędź końcowa znajduje się po prawej stronie; natomiast w przypadku języków w których tekst jest zapisywany od prawej do lewej — po lewej.

Właściwości start   i end można używać wyłącznie w aplikacjach korzystających   z API poziomu 17. lub wyższego.

Obejrzyj to!

Jeśli tworzona aplikacja ma działać w starszych wersjach Androida, konieczne jest użycie atrybutów określających lewe i prawe wypełnienie.

jesteś tutaj  173

Zasoby wymiaru

Dodawanie pliku zasobów wymiaru w celu zapewnienia spójnych wypełnień w układach W przykładzie przedstawionym na poprzedniej stronie wypełnienie podaliśmy na stałe, przypisując mu wartość 16dp. Alternatywnym rozwiązaniem jest podawanie wartości wypełnień w pliku zasobów wymiaru. Ułatwia ono zachowanie spójnych wielkości wypełnień we wszystkich układach w aplikacji. Aby skorzystać z pliku zasobów wymiaru, w pierwszej kolejności trzeba go dodać do projektu. W tym celu w Android Studio zaznacz katalog app/src/ main/res/values, a następnie wybierz z menu opcję File/New/Values resource file. Kiedy zostaniesz poproszony o nazwę, wpisz dimens i kliknij przycisk OK. Spowoduje to utworzenie nowego pliku zasobów, o nazwie dimens.xml.

¨  Układ LinearLayout ¨  Układ FrameLayout

Może się zdarzyć, że Android Studio utworzy ten plik już podczas tworzenia projektu, lecz to zależy od używanej wersji IDE.

Po utworzeniu pliku zasobów wymiaru konkretne wymiary dodaje się do niego, używając elementu . W ramach przykładu poniżej pokazaliśmy, w jaki sposób można dodać do pliku dimens.xml wymiary dla pionowego i poziomego marginesu:

...

app/src/main

16dp 16dp

Tak zdefiniowanych wymiarów można używać w układach, podając nazwy zasobów w odpowiednich atrybutach, tak jak to pokazaliśmy w poniższym przykładzie:

W razie zastosowania takiej konfiguracji, podczas działania aplikacji, Android poszukuje wartości atrybutów w pliku zasobów wymiaru i stosuje odnalezione wartości.

174

Rozdział 5.

Te dw a wiersz kodu t e dwa z worzą a wymia soby ru.

res values

dimens.xml

Atrybutom paddingLeft oraz paddingRight została przypisana wartość wymiaru @dimen/ activity_horizontal_margin. Atrybutom paddingTop oraz paddingBottom została przypisana wartość wymiaru @dimen/activity_vertical_margi n.

Widoki i grupy widoków

¨  Układ LinearLayout

Układ liniowy wyświetla widoki w kolejności ich występowania w kodzie XML

¨  Układ FrameLayout

Podczas definiowania układu liniowego poszczególne widoki należy dodawać do niego w takiej kolejności, w jakiej chcemy, aby były wyświetlane. A zatem jeśli chcemy, by komponent TextView był umieszczony nad przyciskiem, to musimy dodać go do układu jako pierwszy:

Jeśli w kodzie XML zdefiniujesz komponent TextView przed przyciskiem, to po wyświetleniu układu będzie on widoczny nad przyciskiem.



Tworząc układ liniowy, identyfikatory widoków trzeba określać wyłącznie w przypadku, gdy będziemy się do nich jawnie odwoływać w kodzie aktywności. Wynika to z faktu, że układ liniowy określa, gdzie należy umieścić poszczególne widoki, na podstawie ich kolejności w kodzie XML. Widoki nie muszą odwoływać się do siebie nawzajem w celu określenia miejsca, w którym należy je wyświetlić. Wymiary poszczególnych widoków można określać, używając atrybutów android:layout_width i android:layout_height. Poniższy kod: android:layout_width=”wrap_content”

oznacza, że szerokość widoku ma jedynie wystarczyć do wyświetlenia jego zawartości — na przykład aby wyświetlić tekst na przycisku lub w komponencie TextView. Z kolei ten kod: android:layout_width=”match_parent”

oznacza, że widok ma zająć całą dostępną szerokość układu nadrzędnego. Jeśli trzeba odwoływać się do widoku w innym miejscu kodu, to musimy określić jego identyfikator. Na przykład aby przypisać komponentowi TextView identyfikator ”text_view”, należałoby użyć poniższego fragmentu kodu: ...

Atrybuty android:layout_width i android:layout_height trzeba obowiązkowo stosować we wszystkich widokach, niezależnie od typu używanego układu. Mogą one przyjmować wartości warp_content, match_parent lub konkretny rozmiar, na przykład 16dp.

...

jesteś tutaj  175

Marginesy

Stosowanie marginesów do oddalania widoków od siebie

¨  Układ LinearLayout ¨  Układ FrameLayout

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. Na przykład załóżmy, że chcemy wyświetlić w układzie dwa widoki i jednocześnie zapewnić, aby były one oddalone od siebie o 48dp. W tym celu możemy dodać margines o wielkości 48dp u góry dolnego widoku:



Dodanie marginesu powyżej dolnego przycisku spowodowało pojawienie się odstępu między przyciskami.

Poniżej przedstawiliśmy listę marginesów, których można używać w celu dodawania do układu odstępów między widokami. Te atrybuty dodaje się do widoku, a następnie przypisuje wartość określającą wielkość marginesu: android:atrybut=”8dp”

176

Atrybut

Działanie

android:layout_marginTop

Dodaje dodatkowy obszar nad widokiem.

android:layout_marginBottom

Dodaje dodatkowy obszar pod widokiem.

android:layout_marginLeft layout_marginStart

Dodaje dodatkowy obszar z lewej (lub początkowej) strony widoku.

android:layout_marginRight layout_marginEnd

Dodaje dodatkowy obszar z prawej (lub końcowej) strony widoku.

layout_margin

Dodaje identyczny obszar przy każdej z krawędzi widoku.

Rozdział 5.

48dp

Widoki i grupy widoków

Zmieńmy nieco prosty układ liniowy

¨  Układ LinearLayou ¨  Układ FrameLayout

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. Nasz układ składa się z dwóch pól tekstowych i przycisku. W początkowej wersji układu wszystkie te widoki są wyświetlone w pionie, jeden pod drugim, jak pokazaliśmy na poniższym rysunku:

Każdy widok przyjmuje możliwie jak najmniejszą wysokość.

Zmienimy ten początkowy układ w taki sposób, by przycisk był wyświetlony w końcowym dolnym rogu układu, a cały pozostały dostępny obszar zajmowało jedno z pól tekstowych.

Pole tekstowe Treść wiadomości zostało znacznie powiększone.

Przycisk Wyślij jest teraz wyświetlany w prawym dolnym rogu ekranu, o ile na urządzeniu jest używany język, w którym tekst zapisywany jest od strony lewej do prawej. .

jesteś tutaj  177

Zaczynamy

Początkowa wersja naszego układu liniowego

¨  Układ LinearLayout ¨  Układ FrameLayout

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:

Szerokość pól tekstowych jest równa szerokości układu nadrzędnego.

Wartości tych zasobów łańcuchowych zostały standardowo zdefiniowane w pliku strings.xml.

Atrybut android:hint określa tekst podpowiedz i, sugerujący, co użytkownik powinien wpisa ć w danym polu.



Każdy z widoków umieszczonych w układzie będzie miał jak najmniejszą wysokość — niezbędną do wyświetlenia jego zawartości. A zatem w jaki sposób możemy zwiększyć wysokość pola do podania treści wiadomości?

178

Rozdział 5.

Wyświetlane tu wartości tekstowe pochodzą z pliku strings.xml.

Widoki i grupy widoków

Rozciągaaaaamy widok, zwiększając jego wagę

¨  Układ LinearLayout ¨  Układ FrameLayout

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.

Chcemy, by pole Treść wiadomoś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. Do określania wagi widoku służy następujący atrybut: android:layout_weight=”liczba”

gdzie liczba to liczba większa od 0. W przypadku określenia wagi jakiegoś widoku układ w pierwszej kolejności upewnia się, czy każdy widok dysponuje dostatecznie dużym obszarem, koniecznym do wyświetlenia jego zawartości. Innymi słowy: upewnia się, czy każdy przycisk ma miejsce na wyświetlenie umieszczonego na nim tekstu, każde pole tekstowe — na wyświetlenie tekstu podpowiedzi, i tak dalej. Następnie układ określa wielkość pozostałego obszaru i dzieli go proporcjonalnie, rozdzielając pomiędzy elementy, których waga jest większa od 1.

jesteś tutaj  179

Atrybut layout_weight

Dodawanie wagi do widoków

¨  Układ LinearLayout ¨  Układ FrameLayout

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:



Do tych elementów i eight. ut_w layo butu atry y liśm nie doda sca, Zajmą one zatem tylko tyle miej ile jest konieczne do wyświetlenia ich zawartości.

obszar układu, który nie został zużyty przez pozostałe

Przypisanie polu tekstowemu do podania treści wiadomości wagi 1 oznacza, że ma ono zająć cały dostępny obszar ekranu, który nie został zajęty przez inne widoki. Dzieje się tak dlatego, że do żadnego innego widoku nie dodaliśmy atrybutu layout_weight. 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.

180

Rozdział 5.

Domyślnie tekst podpowiedzi, Treść wiadomości, jest wyświetlany w połowie wysokości pola tekstowego. Zajmiemy się nim już niebawem.

Widoki i grupy widoków

Dodawanie wagi do większej liczby widoków

¨  Układ LinearLayout ¨  Układ FrameLayout

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? Załóżmy, że pierwszemu polu tekstowemu, Do, przypiszemy wagę 1, a drugiemu, Treść wiadomości, przypiszemy wagę 2:

...

...

Aby określić, ile dodatkowego miejsca zajmie każdy z widoków, musimy zacząć od zsumowania wartości atrybutów layout_weight wszystkich widoków. W tym przypadku suma ta wyniesie 1+2=3. Wielkość dodatkowego miejsca przydzielonego każdemu widokowi będzie odpowiadała jego wadze podzielonej przez sumę wszystkich wag. Pole Do ma wagę 1, zatem zostanie mu przydzielona 1/3 pozostałego miejsca w układzie. Pole Treść wiadomości ma wagę 2, więc zostaną mu przydzielone 2/3 pozostałego miejsca.

Pole Do ma wagę 1, zatem zostanie mu przydzielona 1/3 pozostałego miejsca w układzie.

Pole Treść wiadomości ma wagę 2, więc zostaną mu przydzielone 2/3 pozostałego miejsca w układzie.

jesteś tutaj 

181

Atrybut gravity

Atrybut gravity kontroluje położenie zawartości widoku Kolejną zmianą, którą musimy wprowadzić, jest przesunięcie tekstu podpowiedzi wyświetlanego w polu tekstowym do wpisywania treści wiadomości. Obecnie jest on wyśrodkowany w pionie. Chcemy to jednak zmienić, tak aby był on wyświetlany u góry pola. Taki efekt możemy uzyskać, stosując atrybut android:gravity. Atrybut android:gravity służy do określania, gdzie wewnątrz widoku ma być wyświetlana jego zawartość — na przykład gdzie ma być wyświetlany tekst w polu tekstowym. Jeśli chcemy, by tekst był wyświetlany u góry pola, to cała sztuka sprowadza się do zastosowania poniższego atrybutu:

¨  Układ LinearLayout ¨  Układ FrameLayout

Musimy przesunąć tekst podpowiedzi ze środka na samą górę widoku.

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:

...

...

Teraz tekst podpowiedzi jest widoczny u góry pola.

Jazda próbna Dodanie atrybutu android:gravity do pola tekstowego służącego do wpisywania treści wiadomości powoduje, że prezentowany w nim tekst podpowiedzi jest widoczny u góry widoku — dokładnie tak jak chcieliśmy. Na następnej stronie podaliśmy listę innych wartości, których można używać w atrybucie android:gravity.

182

Rozdział 5.

Widoki i grupy widoków

Wartości atrybutu android:gravity Poniżej przedstawiliśmy kilka kolejnych wartości, których można używać w atrybucie android:gravity. Wystarczy dodać ten atrybut do wybranego widoku i przypisać mu jedną z poniższych wartości: android:gravity=”wartosc”

¨  Układ LinearLayout ¨  Układ FrameLayout

Atrybut android:gravity pozwala określić, w którym miejscu widoku ma być wyświetlana jego zawartość.

Wartość

Działanie

top

Zawartość jest wyświetlona u góry widoku.

bottom

Zawartość jest wyświetlona u dołu widoku.

left

Zawartość jest wyświetlona z lewej strony widoku.

right

Zawartość jest wyświetlona z prawej strony widoku.

start

Zawartość jest wyświetlana przy początkowej krawędzi widoku.

end

Zawartość jest wyświetlana przy końcowej krawędzi widoku.

center_vertical

Zawartość widoku jest wyśrodkowana w pionie.

center_horizontal

Zawartość widoku jest wyśrodkowana w poziomie.

center

Zawartość widoku jest wyśrodkowana w pionie i w poziomie.

fill_vertical

Zawartość wypełnia całą dostępną wysokość widoku.

fill_horizontal

Zawartość wypełnia całą dostępną szerokość widoku.

fill

Zawartość wypełnia cały dostępny obszar widoku.

Wartości start i end są dostępne wyłącznie w przypadku stosowania API poziomu 17. lub wyższego.

W atrybucie android:gravity można podawać więcej niż jedną wartość, przy czym należy je oddzielać od siebie znakiem pionowej kreski („|”). Na przykład, aby przesunąć zawartość widoku do jego prawego dolnego wierzchołka, należałoby użyć atrybutu o poniższej postaci: android:gravity=”bottom|end”

jesteś tutaj  183

Atrybut layout_gravity

Przesunięcie przycisku w prawo za pomocą atrybutu layout_gravity Jest jeszcze jedna modyfikacja, którą musimy wprowadzić w naszym układzie. Przycisk Wyślij jest obecnie umieszczony w jego lewym dolnym rogu. Musimy przesunąć go do końca (tak aby w przypadku języków, w których tekst jest zapisywany od lewej do prawej, był on wyświetlony w prawym dolnym rogu układu). Do tego celu zastosujemy atrybut android:layout_gravity. Atrybut android:layout_gravity pozwala określać, w którym miejscu obszaru otaczającego widok w układzie liniowym ma być wyświetlany dany widok. Możemy go zastosować, by na przykład przesunąć widok w prawo lub wyśrodkować w poziomie. Aby przesunąć przycisk w prawo, wystarczy użyć atrybutu o następującej postaci: android:layout_gravity=”end”

Chwila. Sądziłam, że ciężkość — atrybut andorid:gravity — jest używana do określania rozmieszczenia zawartości widoku, a nie samego widoku.

Układy liniowe udostępniają dwa atrybuty, które wyglądają całkiem podobnie: gravity oraz layout_gravity. Kilka stron wcześniej użyliśmy atrybutu android:gravity, by rozmieścić tekst Treść wiadomości w widoku tekstowym. Zrobiliśmy tak, gdyż atrybut ten pozwala nam określić, w którym miejscu widoku ma być umieszczona jego zawartość. Z kolei atrybut android:layout_gravity określa rozmieszczenie samego widoku i daje nam możliwość kontroli nad tym, gdzie będzie umieszczony widok w dostępnym dla niego obszarze. W naszym przykładzie chcemy, by widok był umieszczony na końcu dostępnego obszaru, dlatego też użyjemy atrybutu o następującej postaci: android:layout_gravity=”end”

Na następnej stronie zamieściliśmy listę innych wartości, których można używać w atrybucie android:layout_gravity.

184

Rozdział 5.

¨  Układ LinearLayout ¨  Układ FrameLayout

Przesuniemy przycisk do końca, tak że w językach, w których tekst jest zapisywany od lewej do prawej, będzie on umieszczony po prawej, a w językach, w których tekst jest zapisywany od prawej do lewej, po lewej.

Widoki i grupy widoków

Inne wartości, których można używać w atrybucie android:layout_gravity

¨  Układ LinearLayout ¨  Układ FrameLayout

Poniżej przedstawiliśmy inne wartości, których można używać w atrybucie android:layout_gravity. Wystarczy dodać ten atrybut do widoku, W atrybucie layout_gravity można podać wiele wartości, przy czym należy je rozdzielać znakiem a następnie przypisać mu jedną z poniższych wartości: android:layout_gravity=”wartosc”

pionowej kreski („|”). Na przykład użycie atrybutu android:layout_gravity=„bottom|end” spowoduje umieszczenie widoku w końcowym, dolnym wierzchołku dostępnego obszaru.

Wartość

Działanie

top, bottom, left, right

Umieszcza widok u góry, u dołu, z lewej bądź z prawej strony dostępnego dla niego obszaru.

start, end

Umieszcza widok na początku bądź na końcu dostępnego dla niego obszaru.

center_vertical, center_horizontal

Wyśrodkowuje widok w dostępnym dla niego obszarze, w pionie lub w poziomie.

center

Wyśrodkowuje widok w dostępnym dla niego obszarze, w pionie i w poziomie.

fill_vertical, fill_horizontal

Rozszerza widok, tak by zajmował on całą dostępną wysokość lub szerokość dostępnego dla niego obszaru.

fill

Rozszerza widok, tak by zajmował cały dostępny dla niego obszar w pionie i poziomie.

Atrybut android:layout_gravity pozwala określać, w którym miejscu dostępnego obszaru ma być wyświetlany widok. Atrybut android:layout_gravity określa rozmieszczenie samego widoku, natomiast atrybut android:gravity informuje, w którym miejscu widoku należy wyświetlać jego zawartość.

jesteś tutaj  185

Kompletny kod

Kompletny układ liniowy

¨  Układ LinearLayout ¨  Układ FrameLayout

Oto kompletny kod naszego układu liniowego:



żni się oid:gravity ró Atrybut andr droid:layout_gravity. od atrybutu anch odnosi się do Pierwszy z ni oku, drugi zaś do id zawartości w u. ok id w o eg m sa

186

Rozdział 5.

Zawartość pola do wprowadzania treści wiadomości będzie wyświetlana na samej górze tego widoku. Dzięki temu będziemy mieć naprawdę dużo miejsca do wpisywania.

Przycisk Wyślij jest wyświetlony w końcowym dolnym rogu układu.

Widoki i grupy widoków

LinearLayout — podsumowanie

¨  Układ LinearLayout ¨  Układ FrameLayout

Poniżej zamieściliśmy podsumowanie informacji o tworzeniu układów liniowych.

Sposób określania układów liniowych Układ liniowy tworzymy, używając elementu . Musimy przy tym określić szerokość, wysokość oraz orientację układu; podawanie wypełnienia jest opcjonalne:

...

Widoki są wyświetlane w kolejności, w jakiej podano je w kodzie Podczas definiowania układu liniowego widoki dodajemy do niego w takiej kolejności, w jakiej chcemy, aby były wyświetlane.

Widoki można rozciągać, używając wagi Domyślnie wszystkie widoki zajmują tylko tyle miejsca, ile jest niezbędne do wyświetlenia ich zawartości. Jeśli chcemy, by któryś z widoków zajął więcej miejsca, możemy go rozciągnąć, używając atrybutu layout_weight: android:layout_weight=”1”

Położenie zawartości w widoku można określać, używając ciężkości Położenie zawartości widoku wewnątrz danego widoku możemy określać za pomocą atrybutu android:gravity. W ten sposób możemy określić na przykład, gdzie wyświetlić tekst wewnątrz pola tekstowego.

Do określania położenia widoku w dostępnym dla niego obszarze służy atrybut layout_gravity Atrybut android:layout_gravity pozwala określać, w którym miejscu obszaru otaczającego widok w układzie liniowym ma zostać wyświetlony dany widok. Na przykład można go używać, by przesunąć widok do końcowej krawędzi obszaru lub wyśrodkować w poziomie. To już wszystko, co chcieliśmy napisać na temat układów liniowych. Kolejnym układem, który opiszemy, będzie FrameLayout — układ ramki.

jesteś tutaj  187

Układ FrameLayout

¨  Układ LinearLayout ¨  Układ FrameLayout

Układy FrameLayout rozmieszczają widoki jeden na drugim Jak już się przekonałeś, układy LinearLayout rozmieszczają widoki w pojedynczym wierszu lub pojedynczej kolumnie. Każdemu widokowi jest przydzielany odrębny fragment powierzchni ekranu, a same widoki nie nakładają się na siebie.

Układy FrameLayout pozwalają nakładać jedne widoki na inne. To bardzo przydatne w przypadkach, gdy chcemy stworzyć interfejs użytkownika, w którym, na przykład, tekst jest wyświetlany na tle obrazu.

Czasami jednak może się zdarzyć, że będziemy chcieli, by widoki nakładały się na siebie. Na przykład wyobraźmy sobie, że chcemy wyświetlić jakiś tekst na tle zdjęcia. Stosując układ LinearLayout, nie bylibyśmy w stanie uzyskać takiego efektu. Jeśli potrzebujemy układu, w którym poszczególne widoki mogą się na siebie nakładać, to najprostszym rozwiązaniem będzie użycie układu FrameLayout, nazywanego także układem ramki. Układ FrameLayout nie wyświetla widoków w jednym wierszu lub jednej kolumnie, lecz zamiast tego umieszcza je na stosie — jeden na drugim. Dzięki temu można, na przykład, wyświetlić tekst na obrazie.

Definiowanie układu ramki Układ ramki definiuje się przy użyciu elementu , jak przedstawiliśmy to w poniższym przykładzie:

...

W tym miejscu należy dodawać lone widoki, które mają być wyświet . dzie w ukła

Podobnie jak w przypadku układów liniowych, atrybuty android:layout_width oraz android:layout_height są obowiązkowe i określają, odpowiednio, szerokość i wysokość układu.

Utworzenie nowego projektu Aby zobaczyć, jak działa układ FrameLayout, zastosujemy go w celu wyświetlenia tekstu na tle obrazka. Utwórz zatem nowy projekt aplikacji, o nazwie „Kaczusia”, używając nazwy firmy hfad.com, przez co kody aplikacji będą umieszczone w pakiecie com.hfad.kaczusia. Minimalnym poziomem API powinno być API 19 lub wyższe, tak by aplikacja działała na przeważającej większości urządzeń. Aby Twój projekt odpowiadał naszemu, w aplikacji utwórz jedną pustą aktywność (Empty Activity), o nazwie MainActivity, korzystającą z układu o nazwie activity_main. Podczas tworzenia aktywności koniecznie zadbaj o to, by pole wyboru Backwards Compatibility (AppCompat) nie było zaznaczone.

188

Rozdział 5.

Widoki i grupy widoków

¨  Układ LinearLayout ¨  Układ FrameLayout

Dodanie obrazka do projektu W naszym układzie planujemy zastosować obrazek o nazwie duck.jpg, dlatego musimy go dodać do projektu. W tym celu, w pierwszej kolejności utwórz katalog zasobów drawable (o ile Android Studio nie zrobiło tego podczas tworzenia projektu). To domyślny katalog przeznaczony do przechowywania zasobów graficznych. A zatem w eksploratorze Android Studio przejdź do widoku Project, wybierz z menu głównego opcję File/ New, a następnie wybierz opcję pozwalającą na utworzenie katalogu zasobów. Kiedy zostaniesz poproszony o wybranie typu katalogu, wybierz opcję drawable, następnie wpisz jego nazwę — drawable — po czym kliknij przycisk OK. Po utworzeniu katalogu drawable odszukaj w przykładach dołączonych do książki plik duck.jpg (znajdziesz go w analogicznym podkatalogu drawable w katalogu Rozdział_05/Kaczusia/) i skopiuj do katalogu app/src/main/res/drawable.

duck.jpg

Zmienimy teraz kod pliku układu activity_main.xml tak, by korzystał z układu FrameLayout zawierającego widoki ImageView (w którym będzie prezentowany obrazek) oraz TextView. W tym celu zastąp zawartość swojej wersji pliku activity_main.xml kodem przedstawionym poniżej:

Kaczusia app/src/main

activity_main.xml w dalszej Ten atrybut informuje system, że ma użyć części rozdziału. obrazka o nazwie „duck” umieszczonego

Kiedy wprowadzisz zmiany, uruchom aplikację; na następnej stronie sprawdzimy, jak działa zastosowany kod.

W rzeczywistej aplikacji ten teks lepiej zdefiniować jako zasób łańct byłoby uchowy.

jesteś tutaj  189

Układ ramki nakłada widoki

Widok ramki wyświetla widoki na stosie w kolejności, w jakiej podano je w kodzie XML Dodając poszczególne widoki do układu FrameLayout, dodajemy je w takiej kolejności, w jakiej mają być wyświetlane i w jakiej będą się na siebie nakładać. Pierwszy widok jest wyświetlany jako pierwszy, drugi zostanie umieszczony na pierwszym i tak dalej. W naszej przykładowej aplikacji dodaliśmy najpierw widok graficzny, a po nim tekst, dzięki czemu zostanie on wyświetlony na tle obrazka:



Użycie atrybutu layout_gravity do rozmieszczania widoków w układzie Domyślnie wszystkie widoki dodawane do układu FrameLayout będą wyświetlane w jego lewym górnym wierzchołku. To domyślne rozmieszczenie widoków można zmieniać przy użyciu atrybutu android:layout_gravity, dokładnie tak samo jak w układach LinearLayout. Poniższy przykład pokazuje, jak można przesunąć tekst do prawego dolnego wierzchołka układu. ...

190

Rozdział 5.

Ten wiers przesuwa z końcoweg tekst do o wierzchołk dolnego a układu.

¨  Układ LinearLayout ¨  Układ FrameLayout

Widoki i grupy widoków

Układy można zagnieżdżać

¨  Układ LinearLayout ¨  Układ FrameLayout

Jedną z wad korzystania z widoku FrameLayout jest to, że łatwo można doprowadzić do efektu nakładania się widoków w sytuacji, gdy wcale tego nie chcemy. Na przykład, załóżmy, że będziemy chcieli wyświetlić w prawym, dolnym wierzchołku układu dwa widoki tekstowe, jeden nad drugim:

Jeśli nie zachowamy ostrożności, to widoki mogą się przesłaniać, jak w tym przykładzie.

Problem ten można rozwiązać, dodając do widoków marginesy lub wypełnienia. Jednak lepszym rozwiązaniem będzie umieszczenie takich widoków w układzie liniowym, a następnie dodanie tego układu do układu ramki. W ten sposób można rozmieścić dwa widoki tekstowe liniowo, a następnie umieścić je jako jedną grupę w układzie ramki:

W tym przypadku układ ramki zawiera obrazek oraz układ liniowy. To jest układ liniowy, który zawiera dwa widoki tekstowe elegancko rozmieszczone w jednej kolumnie.

Kompletny kod takiego układu przedstawimy na następnej stronie.

jesteś tutaj 

191

Kompletny kod układu

Kompletny kod układu

¨  Układ LinearLayout ¨  Układ FrameLayout

To jest kompletny kod pokazujący, jak można zagnieździć układ liniowy w układzie ramki. Zaktualizuj swoją wersję pliku activity_main.xml, by była identyczna z naszą, a następnie uruchom aplikację.

Kaczusia





activity_main.xml





192

Rozdział 5.

app/src/main

Ten wiersz przesuwa układ liniowy do końcowego, dolnego wierzchołka układu ramki.

Widoki i grupy widoków

Układy FrameLayout: podsumowanie

¨  Układ LinearLayout ¨  Układ FrameLayout

Poniżej zamieściliśmy podsumowanie dotyczące tworzenia układów typu FrameLayout.

Definiowanie układu FrameLayout Do definiowania układu FrameLayout służy element . Konieczne jest przy tym określenie szerokości i wysokości układu:

...

Widoki są wyświetlane w kolejności występowania w kodzie Podczas definiowania układu FrameLayout poszczególne widoki są do niego dodawane w takiej kolejności, w jakiej chcemy, by były wyświetlane. Pierwszy z widoków jest wyświetlany na samym dole stosu, drugi nad nim i tak dalej.

Położenie widoków można określać przy użyciu atrybutu layout_gravity Do określania tego, w którym miejscu układu chcemy wyświetlać umieszczone w nim widoki, należy używać atrybutu android:layout_gravity. Na przykład możemy go zastosować, by przesunąć widok na końce obszaru zajmowanego przez układ bądź też do końcowego, dolnego wierzchołka. Teraz, kiedy już wiesz, jak używać dwóch prostych układów, liniowego i ramki, możesz się zmierzyć z ćwiczeniem zamieszczonym na następnej stronie.

jesteś tutaj  193

Ćwiczenie

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. 2

1

5 4

194

Rozdział 5.

Widoki i grupy widoków A



B



C



jesteś tutaj  195

Rozwiązanie

Bądź układem. Rozwiązanie

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.

Żaden z układów nie pozwala wygenerować ekranu o takiej postaci.

1 A



Ten układ

zawiera tylko jeden przycisk zajmujący cały obszar ekranu.

3

196

B

zostawiając

Rozdział 5.

Widoki i grupy widoków 4

C

W obu przyciskach atrybutom layout_width oraz layout_height

przypisano wartość „wrap_content”, dzięki czemu zajmują one tylko tyle miejsca, ile trzeba na wyświetlenie ich zawartości.

Układy i komponenty GUI mają wiele wspólnego Być może zauważyłeś, że układy wszystkich typów mają kilka wspólnych atrybutów. Niezależnie od typu używanego układu musimy określić jego szerokość i wysokość, używając do tego celu atrybutów android:layout_width i android:layout_height. Wymóg określania wymiarów nie ogranicza się jednak do układów — dokładnie te same dwa atrybuty, android:layout_width i android:layout_height, muszą być dodawane do wszystkich komponentów GUI. Dzieje się tak dlatego, że wszystkie układy i komponenty GUI są klasami dziedziczącymi po klasie View. Przyjrzyjmy się temu zagadnieniu nieco dokładniej.

jesteś tutaj  197

Widoki

Komponenty GUI są typami pochodnymi klasy View Przekonałeś się już, że komponenty GUI są typami widoków — okazuje się, że wszystkie one są klasami pochodnymi klasy android.view.View. Oznacza to, że wszystkie komponenty GUI, których używamy w interfejsie użytkownika, mają wspólne atrybuty i zachowania. Wszystkie one mogą być wyświetlane na ekranie i wszystkie pozwalają określać swoją szerokość i wysokość. Wszystkie komponenty GUI, których używamy do tworzenia interfejsów użytkownika, dziedziczą te podstawowe funkcjonalności i je rozszerzają. android.view.View

Spinner to typ widoku, który poznałeś w rozdziale 2. Przyjrzymy mu się bliżej w dalszej części tego rozdziału.

...

android.widget.TextView jest bezpośrednią klasą pochodną klasy View.

android.widget.TextView ...

Klasa android.view.View jest klasą bazową wszystkich komponentów GUI używanych do tworzenia interfejsów użytkownika aplikacji.

android.widget.Spinner ...

android.widget.Button

android.widget.EditText ...

...

Układy dziedziczą po klasie ViewGroup Nie tylko komponenty GUI są widokami — klasami pochodnymi klasy View. Okazuje się, że także układy są wyspecjalizowanymi typami widoków, a nazywamy je grupami widoków. Wszystkie widoki są klasami pochodnymi klasy android.view.ViewGroup. Grupa widoków to wyspecjalizowany widok, który może zawierać inne widoki.

...

obiektem zajmującym

Układ jest obiektem Układy są typami pochodnymi klasy ViewGroup. ViewGrup jest klasą pochodną klasy View.

android.view.ViewGroup ...

android.widget. GridLayout

android.widget. LinearLayout

android.widget. RelativeLayout

...

...

...

Rozdział 5.

jest typem widoku, miejsce na ekranie.

android.view.View

198

Komponent GUI

określanym jako grupa widoków — to widok specjalnego typu, który może zawierać inne widoki.

Widoki i grupy widoków

Co nam daje bycie widokiem? Obiekt View zajmuje prostokątny obszar na ekranie. Zawiera on wszystkie funkcjonalności, których potrzebują widoki, by móc prowadzić szczęśliwy i użyteczny żywot w Androidowie. Poniżej wymieniliśmy kilka spośród tych funkcjonalności, które według nas są najważniejsze.

Odczytywanie i ustawianie właściwości Za kulisami każdy widok jest obiektem Javy. Oznacza to, że w kodzie aktywności możemy odczytywać i ustawiać wartości jego właściwości. Na przykład możemy odczytać wartość wybraną z listy rozwijanej lub zmienić tekst prezentowany w widoku tekstowym. Konkretne właściwości i metody, jakie są dostępne, zależą od typu widoku. Aby umożliwić wykonywanie takich operacji, z każdym widokiem można skojarzyć identyfikator, dzięki któremu będzie można odwoływać się do danego widoku w kodzie aktywności.

Wielkość i położenie Możemy określać szerokość i wysokość widoku, tak by Android wiedział, jaką ma on mieć wielkość. Oprócz tego możemy określić, czy widok ma mieć jakieś wypełnienie, czy nie. Po wyświetleniu widoku możemy odczytać jego położenie i faktyczne wymiary na ekranie.

Ten schemat przedstawia kilka metod klasy View, których możemy używać w kodzie aktywności. Ponieważ zostały one zdefiniowane w klasie bazowej View, są dostępne także we wszystkich widokach i grupach widoków.

android.view.View getId() getHeight() getWidth() setVisibility(int) findViewById(int) isClickable() isFocused() requestFocus()

Obsługa miejsca wprowadzania

...

Android obsługuje przenoszenie miejsca wprowadzania w zależności od czynności wykonywanych przez użytkownika. Obejmuje to odpowiednie reakcje na widoki ukryte, usuwane lub wyświetlane.

Obsługa zdarzeń i obiekty nasłuchujące Każdy z widoków może odpowiadać na zdarzenia. Możemy także tworzyć obiekty nasłuchujące, które reagują na to, co się dzieje z widokiem. Na przykład wszystkie widoki mogą reagować na utratę miejsca wprowadzania, a przyciski (i ich klasy pochodne) mogą reagować na kliknięcia. Ponieważ grupa widoków sama jest typem widoku, wszystkie układy i komponenty GUI dysponują tymi samymi, podstawowymi możliwościami funkcjonalnymi.

jesteś tutaj  199

Hierarchia widoków

Układ jest tak naprawdę hierarchią widoków Układy, które definiujemy w kodzie XML, dają nam w rzeczywistości hierarchiczne drzewo widoków i grup widoków. Na przykład poniżej przedstawiliśmy układ liniowy zawierający przycisk i pole tekstowe. Układ liniowy jest grupą widoków, natomiast przycisk i pole tekstowe są widokami. Grupa widoków jest elementem nadrzędnym, widoki zaś są jego elementami podrzędnymi:

W tym przykładzie pominęliśmy duże fragmenty kodu XML. Kluczowe znaczenie mają tu widoki umieszczone w grupie widoków.

Układ liniowy

Przycisk



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 EditText to dwie klasy pochodne klasy View.

Układ liniowy



ViewGroup

layout.xml

Pole tekstowe

Przycisk

View

To właśnie dzięki temu możemy operować na widokach w kodzie Javy. Za kulisami wszystkie widoki są bowiem przekształcane do postaci obiektów typu View.

200

Rozdział 5.

View

View

Widoki i grupy widoków

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 Widok tekstowy, komponent TextView, służy do wyświetlania fragmentów tekstu na ekranie.

Definiowanie w kodzie XML

android.view.View ...

android.widget.TextView ...

Ten typ komponentów definiuje się w kodzie XML za pomocą elementu . 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:

Interfejs API kontrolki TextView zawiera wiele atrybutów pozwalających kontrolować jej wygląd, na przykład określać wielkości prezentowanego tekstu. By zmienić wielkość tekstu wyświetlonego w widoku tekstowym, należy użyć atrybutu android:textSize w sposób pokazany poniżej: android:textSize=”16sp”

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 16sp 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.

Stosowanie w kodzie aktywności Aby zmienić tekst wyświetlany w komponencie TextView, należy użyć fragmentu kodu o następującej postaci: TextView textView = (TextView) findViewById(R.id.text_view); textView.setText(”Jakiś inny tekst”);

jesteś tutaj  201

Edytowalne pola tekstowe

Pola tekstowe

android.view.View ...

Przypominają widoki tekstowe, lecz pozwalają edytować wyświetlany w nich tekst.

android.widget.TextView

Definiowanie w kodzie XML

...

Pola tekstowe definiuje się w kodzie XML za pomocą elementu . Atrybut android:hint pozwala określić tekst podpowiedzi, dzięki któremu użytkownik będzie mógł się zorientować, co ma wpisać w danym polu.

android.widget.EditText ...

Atrybut android:inputType pozwala zdefiniować, jakiego typu dane użytkownik ma wpisać w polu, dzięki czemu system będzie mu w stanie pomóc. Na przykład jeśli użytkownik ma wpisać liczbę, to możemy użyć atrybutu o postaci: android:inputType=”number”

a Android wyświetli klawiaturę numeryczną. Oto kilka naszych ulubionych wartości, które można przypisać temu atrybutowi: Kompletną listę dostępnych wartości tego atrybutu można

znaleźć w dokumentacji Androida dla programistów dostępnej w internecie na stronie https://developer.android.com/reference/ android/widget/TextView.html#attr_android:inputType. .

Wartość

Działanie

phone

Wyświetla klawiaturę do wybierania numeru telefonu.

textPassword

Wyświetla klawiaturę do wpisywania tekstu, przy czym zawartość pola jest maskowana.

textCapSentences

Sprawia, że pierwsze słowo każdego zdania zaczyna się od wielkiej litery.

textAutoCorrect

Automatycznie poprawia błędy we wpisywanym tekście.

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”

Stosowanie w kodzie aktywności Tekst wpisany w polu tekstowym można pobrać, używając następującego fragmentu kodu: EditText editText = (EditText) findViewById(R.id.edit_text); String text = editText.getText().toString();

202

Rozdział 5.

Widoki i grupy widoków

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 . Tekst wyświetlany na przycisku jest określany w atrybucie android: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”

Tę metodę należy zdefiniować w kodzie aktywności w następujący sposób: /** Metoda wywoływana po kliknięciu przycisku */ public void onButtonClicked(View view) { // Robimy coś w odpowiedzi na kliknięcie przycisku }

onButtonClicked()

Układ

Aktywność

jesteś tutaj  203

Przycisk przełącznika

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 jest wyłączony.

Kiedy klikniesz przycisk, zostanie on włączony.

Definiowanie w kodzie XML

android.widget.TextView ...

Przycisk przełącznika definiuje się w kodzie XML za pomocą elementu . Dwa atrybuty, android:textOn i android:textOff, służą do określania tekstów wyświetlanych na przycisku w zależności od jego stanu:

android.widget.Button ...

CompoundButton to przycisk, który ma dwa stany, zaznaczon y i niezaznaczony. Implementacją komponentu tego typu jest przycisk przełącznika.

android.widget. CompoundButton ...

Stosowanie w kodzie aktywności Jeśli chcemy, by przycisk przełącznika odpowiadał na kliknięcia, do jego kodu XML musimy dodać atrybut android:onClick. Wartością tego atrybutu musi być nazwa funkcji zdefiniowanej w kodzie aktywności: android:onClick=”onToggleButtonClicked”

/** Metoda wywoływana po kliknięciu przycisku przełącznika */ public void onToggleButtonClicked(View view) { // Pobieramy stan przycisku przełącznika boolean on = ((ToggleButton) view).isChecked(); if (on) { } else { // Przycisk wyłączony } }

204

Rozdział 5.

...

To jest dokładnie to samo co wywoływanie metody w odpowiedzi na kliknięcie normalnego przycisku.

Metoda zdefiniowana w kodzie aktywności może mieć następującą postać:

// Przycisk włączony

android.widget. ToggleButton

To wywołanie zwraca wartość true, jeśli przycisk jest włączony, i wartość false, jeśli jest on wyłączony.

Widoki i grupy widoków

Przełącznik Przełącznik to rodzaj małego suwaka, który działa bardzo podobnie do przycisku omawianego wcześniej przełącznika. A tak wygląda przełącznik, gdy jest włączony.

Tak wygląda przełącznik, kiedy jest wyłączony.

Definiowanie w kodzie XML Przełączniki definiuje się w kodzie XML za pomocą elementów . 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.widget.TextView ...

Stosowanie w kodzie aktywności Jeśli chcemy, by przełącznik odpowiadał na kliknięcia, do jego kodu XML musimy dodać atrybut android:onClick. Wartością tego atrybutu musi być nazwa funkcji zdefiniowanej w kodzie aktywności:

android.widget.Button ...

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 onSwitchClicked(View view) { // Czy przełącznik jest włączony? boolean on = ((Switch) view).isChecked(); if (on) { // Przełącznik włączony

Ten kod jest bardzo podobny do metody obsługującej kliknięcia przycisku przełącznika.

android.widget.Switch ...

} else { // Przełącznik wyłączony } }

jesteś tutaj  205

Pola wyboru

Pola wyboru Pola wyboru umożliwiają prezentowanie użytkownikom grupy opcji. Użytkownicy mogą następnie wybierać z tej grupy dowolne opcje. Każde pole wyboru można zaznaczyć niezależnie od pozostałych.

Oto dwa pola wyboru. Użytkownik może zaznaczyć mleko, cukier lub oba te pola albo może nie zaznaczać żadnego z nich.

android.view.View ...

android.widget.TextView ...

android.widget.Button ...

Definiowanie w kodzie XML Pola wyboru definiuje się w kodzie XML za pomocą elementów . Tekst wyświetlany obok danego pola można określić, używając atrybutu android:text:

android.widget. CompoundButton ...

...

Stosowanie w kodzie aktywności Do określenia, czy konkretne pole wyboru jest zaznaczone, czy nie, służy metoda isChecked(). Jeśli pole jest zaznaczone, jej wywołanie zwróci wartość true: CheckBox checkbox = (CheckBox) findViewById(R.id.checkbox_milk); boolean checked = checkbox.isChecked(); if (checked) { // Pole jest zaznaczone — reagujemy odpowiednio }

206

Rozdział 5.

Widoki i grupy widoków

Pola wyboru (ciąg dalszy) Podobnie jak w przypadku przycisków, istnieje także możliwość reagowania na kliknięcie pola wyboru. W tym celu należy dodać do kodu XML układu atrybut android:onClick i przypisać mu nazwę metody zdefiniowanej w kodzie aktywności, którą należy wywołać:

Metoda obsługująca kliknięcia pól tekstowych mogłaby zostać zdefiniowana w następujący sposób: public void onCheckboxClicked(View view) { // Czy kliknięte pole wyboru jest zaznaczone? boolean checked = ((CheckBox) view).isChecked(); // Określamy, które pole zostało kliknięte switch(view.getId()) { case R.id.checkbox_milk: if (checked) // Kawa z mlekiem else // Czarna jak niebo w bezksiężycową noc break; case R.id.checkbox_sugar: if (checked) // Słodziutka else // Lepiej niech będzie gorzka break; } }

jesteś tutaj  207

Przyciski opcji

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. Zastosuj przyciski opcji, aby ograniczyć możliwości użytkownika do wyboru tylko jednej spośród dostępnych opcji.

Definiowanie w kodzie XML

android.view.View ...

android.widget.TextView ...

Definiowanie przycisków opcji należy zacząć od dodania specjalnego typu grupy widoków: . Wewnątrz elementu można następnie definiować poszczególne przyciski opcji, używając w tym celu elementów : ...

android.widget.Button

Możemy wybrać, czy przyciski opcji mają być wyświetlane w układzie pionowym czy poziomym.



Stosowanie w kodzie aktywności Identyfikator zaznaczonego przycisku opcji można określić za pomocą metody getCheckedRadioButtonId(): RadioGroup radioGroup = (RadioGroup)findViewById(R.id.radioGroup); int id = radioGroup.getCheckedRadioButtonId(); if (id == -1){ // Nie wybrano żadnej opcji } else{ RadioButton radioButton = (RadioButton)findViewById(id); }

208

Rozdział 5.

android.widget. RadioButton ...

Widoki i grupy widoków

Przyciski opcji (ciąg dalszy) Aby odpowiadać na kliknięcia przycisku opcji, należy dodać do kodu XML układu atrybut android:onClick i podać w nim nazwę metody, którą należy wywołać, zdefiniowaną w kodzie aktywności:



Metodę obsługującą kliknięcia przycisku można zdefiniować w następujący sposób: public void onRadioButtonClicked(View view) { RadioGroup radioGroup = (RadioGroup)findViewById(R.id.radioGroup); int id = radioGroup.getCheckedRadioButtonId(); switch(id) { case R.id.radio_cavemen: // Wygrali jaskiniowcy break; case R.id.radio_astronauts: // Wygrali astronauci break; } }

jesteś tutaj  209

Listy rozwijane

Lista rozwijana Jak już wiesz, komponent Spinner tworzy rozwijaną listę wartości, spośród których użytkownik może wybrać jedną.

Listy rozwijanej używaliśmy w rozdziale 2.

android.view.View ...

android.view.ViewGroup AdapterView to widok, który potrafi korzystać z adaptera. Więcej informacji na temat adapterów znajdziesz w dalszej części książki.

Definiowanie w kodzie XML

To abstrakcyjna klasa bazowa komponentów typu Spinner.

...

android.widget. AdapterView ...

Listy rozwijane tworzy się za pomocą elementu . Statyczną tablicę danych, używanych jako opcje listy, określa się przy użyciu android:entries, w którym należy podać nazwę tablicy łańcuchów znaków.

Tablicę łańcuchów znaków można zdefiniować w pliku strings.xml w następujący sposób:

jasne bursztynowe brązowe ciemne

Stosowanie w kodzie aktywności Wartość opcji aktualnie wybranej na liście można pobrać za pomocą metody getSelectedItem(), a następnie skonwertować do łańcucha znaków w następujący sposób: Spinner spinner = (Spinner) findViewById(R.id.spinner); String string = String.valueOf(spinner.getSelectedItem());

210

Rozdział 5.

android.widget.Spinner ...

Widoki i grupy widoków

Widoki obrazów Jak sama nazwa wskazuje, ten typ widoków służy do wyświetlania obrazów.

android.view.View ...

android.widget.ImageView

Widok obrazu przedstawia wskazany obraz.

... ImageView jest bezpośrednią klasą pochodną klasy View.

Dodawanie obrazu do projektu W pierwszej kolejności musimy utworzyć katalog o nazwie drawable, jest on bowiem domyślnym miejscem przechowywania zasobów graficznych w aplikacji. W tym celu zaznacz katalog app/src/main/res projektu, wybierz z menu głównego opcję File/New..., a następnie wybierz opcję pozwalającą na utworzenie nowego katalogu zasobów. Kiedy zostaniesz zapytany o typ zasobów, wybierz opcję drawable, po czym kliknij przycisk OK. Teraz będziesz musiał dodać obraz do utworzonego właśnie katalogu app/src/main/res/drawable. Możemy także używać różnych plików graficznych zależnie od gęstości ekranu urządzenia. Oznacza to, że możemy wyświetlać obrazy o większej rozdzielczości na urządzeniach, których ekrany mają większą gęstość, i obrazy o mniejszej rozdzielczości na urządzeniach z ekranami o mniejszej gęstości. W tym celu w katalogu app/src/main/res projektu należy utworzyć kolejne katalogi drawable dla poszczególnych gęstości ekranów. Nazwy tych katalogów odpowiadają gęstościom ekranów: android-ldpi

Ekrany o niskiej gęstości, około 120 dpi.

android-mdpi

Ekrany o średniej gęstości, około 160 dpi.

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.

W zależności od tego, jaka wersja Android Studio jest używana, IDE może automatycznie utworzyć dla nas niektóre z tych katalogów.

Następnie wystarczy umieścić obrazy o różnej rozdzielczości w odpowiednich katalogach drawable*, upewniając się przy tym, że obrazy będą miały takie same nazwy. Android określi, którego z obrazów użyć podczas działania aplikacji, na podstawie gęstości ekranu używanego urządzenia. Na przykład jeśli urządzenie jest wyposażone w ekran o bardzo wysokiej gęstości, system użyje obrazów z katalogu drawable-xhdpi. Jeżeli plik obrazu zostanie dodany tylko do jednego katalogu, to Android będzie go używał zawsze — na wszystkich urządzeniach. Jeśli chcemy, by aplikacja używała tego samego pliku obrazu niezależnie od gęstości ekranu urządzenia, to zazwyczaj będziemy go umieszczać w katalogu drawable.

jesteś tutaj 

211

Widoki obrazów

Widoki obrazów: kod XML układu Widok obrazu dodaje się do kodu XML układu, używając elementu . Do określenia obrazu, który należy w danym widoku wyświetlić, służy atrybut android:src, a atrybut android:contentDescription pozwala dodać łańcuch znaków opisujący obraz i poprawić w ten sposób dostępność aplikacji:

Wartość atrybutu android:src przyjmuje następującą postać: ”@drawable/nazwa_ obrazu”, gdzie nazwa_obrazu to nazwa pliku obrazu bez rozszerzenia. Zasoby graficzne są poprzedzane prefiksem @drawable, który informuje system, że zasób jest umieszczony w jednym lub kilku katalogach drawable.

Stosowanie w kodzie aktywności Zarówno źródło obrazu, jak i jego opis można ustawić programowo w kodzie aktywności, używając w tym celu odpowiednio metod setImageResource() i setContentDescription(): ImageView photo = (ImageView)findViewById(R.id.photo); int image = R.drawable.starbuzz_logo; String description = ”To jest logo.”; photo.setImageResource(image); photo.setContentDescription(description);

Ten fragment kodu odnajduje zasób graficzny o nazwie starbuzz_logo przechowywany w którymś z katalogów drawable, a następnie używa go jako źródła dla widoku o identyfikatorze photo. Aby odwołać się do zasobu graficznego w kodzie aktywności, należy użyć zapisu R.drawable.nazwa_obrazu, gdzie nazwa_obrazu to nazwa pliku obrazu (bez rozszerzenia).

212

Rozdział 5.

Widoki i grupy widoków

Dodawanie obrazów do przycisków Oprócz wyświetlania obrazów w widokach typu ImageView można je także wyświetlać na przyciskach.

Wyświetlanie na przycisku tekstu i obrazu Aby wyświetlić na przycisku tekst, a po jego prawej stronie obraz, należy skorzystać z atrybutu android:drawableRight, podając w nim nazwę obrazu:

Aby wyświetlić obraz po lewej stronie tekstu, należy użyć atrybutu android:drawableLeft:

Używając atrybutu android:drawableBottom, można wyświetlić obraz poniżej tekstu na przycisku:

I w końcu atrybut android:drawableTop pozwala wyświetlić obraz nad tekstem na przycisku:

jesteś tutaj  213

Przyciski z obrazami

Przyciski z obrazami Przyciski tego typu przypominają normalne przyciski, z tym, że zamiast tekstu są na nich wyświetlane wyłącznie obrazy.

Definiowanie w kodzie XML

android.view.View

Przycisk z obrazem tworzy się, dodając do kodu XML układu element . Źródło obrazu wyświetlanego na przycisku określa się za pomocą atrybutu android:src:

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: android:onClick=”onButtonClicked”

Tę metodę można zdefiniować w kodzie aktywności w następujący sposób: /** Metoda wywoływana po kliknięciu przycisku */ public void onButtonClicked(View view) { // Robimy coś w odpowiedzi na kliknięcie przycisku }

onButtonClicked()

Układ

214

Rozdział 5.

Aktywność

Klasa ImageButton rozszerza klasę ImagView, a nie Button.

Widoki i grupy widoków

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.

Układ liniowy nie udostępnia pasków przewijania. Kiedy spróbowaliśmy wyświetlić w takim układzie siedem przycisków na naszym urządzeniu, nie mogliśmy zobaczyć ich wszystkich.

Aby dodać pionowy pasek przewijania do układu, należy umieścić ten układ wewnątrz elementu , jak w poniższym przykładzie:

Przenieś te atrybuty z początko wego układu do elementu , gdyż to obecnie głównym elementem pliku on jest .

...

Umieszczenie układu wewnątrz elementu spowodowało dodanie fajnego, pionowego paska przewijania. Teraz użytkownik może już wyświetlić wszystkie widoki.

Aby dodać do układu poziomy pasek przewijania, należy cały układ umieścić wewnątrz elementu .

jesteś tutaj  215

Wyświetlanie komunikatów

Krótkie komunikaty Jest jeszcze jeden widżet, który chcielibyśmy przedstawić w tym rozdziale, 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. Takie komunikaty mają wyłącznie charakter informacyjny, a użytkownik nie może prowadzić z nimi żadnych interakcji. Podczas prezentowania tych komunikatów aktywność jest cały czas widoczna i zachowuje pełne możliwości interakcji. Komunikaty są ukrywane automatycznie po określonym czasie.

Stosowanie w kodzie aktywności Komunikaty są wyświetlane w kodzie aktywności. Nie można zdefiniować ich w układzie. Do ich tworzenia używana jest metoda Toast.makeText(), do której przekazywane są trzy parametry: obiekt Context (zazwyczaj jest to referencja this odwołująca się do bieżącej aktywności), obiekt CharSequence zawierający treść wyświetlanego komunikatu oraz liczba typu int określająca czas prezentowania komunikatu. Po utworzeniu obiektu Toast możemy wyświetlić komunikat na ekranie, używając metody show().

java.lang.Object ...

android.widget.Toast ... Jak widać, klasa Toast nie dziedziczy po klasie View, więc nie jest widokiem. Ale ponieważ zapewnia nam ona bardzo przydatną możliwość wyświetlania użytkownikom krótkich komunikatów, przemyciliśmy ją do tego rozdziału.

Poniższy fragment kodu pokazuje, jak można utworzyć komunikat i wyświetlić go na krótki okres czasu: CharSequence text = ”Uwielbiam tosty!”; int duration = Toast.LENGTH_SHORT; Toast toast = Toast.makeText(this, text, duration); toast.show();

Domyślnie komunikaty są wyświetlane u dołu ekranu.

216

Rozdział 5.

Tost to wiadomość, która wyskakuje na ekran jak kromka z tostera.

Widoki i grupy widoków To doskonały moment, żeby przećwiczyć stosowanie widoków poznanych w tym rozdziale. Utwórz zatem układ, który pozwoli wyświetlić ekran o następującej postaci: Ćwiczenie Pewnie nie będzie Ci się chciało pisać kodu układu tutaj, ale może io? Stud poeksperymentujesz w Android

jesteś tutaj  217

Rozwiązanie

Oto jeden z wielu sposobów, na które można utworzyć przedstawiony układ. Nie przejmuj się, jeśli Twój kod wygląda inaczej, gdyż to ćwiczenie ma wiele rozwiązań. Ćwiczenie Rozwiązanie



218

Rozdział 5.

Widoki i grupy widoków



ć inaczej Pamiętaj, Twój kod może wyglądau możliwych od naszego. To tylko jeden z wiel du. sposobów stworzenia takiego ukła

jesteś tutaj  219

Przybornik

Rozdział 5.

Twój przybornik do Androida Opanowałeś już rozdział 5. i dodałeś do swojego przybornika z narzędziami znajomość widoków i grup widoków.

j Pełny kod przykładowe j ane tow zen aplikacji pre z żes mo ale dzi roz w tym P FT ra we ser z rać pob wydawnictwa Helion: klady/ ftp://ftp.helion.pl/przy p .zi andrr2

CELNE SPOSTRZEŻENIA 

















220

Wszystkie komponenty GUI są rodzajami widoków. Są to klasy dziedziczące po kasie android.view.View.



Wszystkie układy są klasami dziedziczącymi po android.view.ViewGroup. Grupa widoków to widok specjalnego typu, który może zawierać inne widoki.



Plik XML układu jest konwertowany do postaci obiektu ViewGroup zawierającego hierarchiczne drzewo widoków.

 

Układ liniowy wyświetla widoki w pionie lub w poziomie. Orientację układu można określić za pomocą atrybutu android:orientation. Układ ramki pozwala umieszczać widoki jeden na drugim. Atrybuty android:padding* pozwalają określać, jak duże wypełnienie pozostanie przy poszczególnych krawędziach widoku. Aby wybrany element zajął więcej miejsca w układzie liniowym, można do niego dodać atrybut android:layout_weight. Za pomocą atrybutu android:layout_gravity można określać, w którym miejscu dostępnego obszaru ma zostać wyświetlony dany widok. Atrybut android:gravity określa, w którym miejscu widoku ma zostać wyświetlona jego zawartość.

Rozdział 5.









Element definiuje przyciski przełączników, które udostępniają dwa stany wybierane kliknięciem. Element definiuje kontrolkę przełącznika, która działa analogicznie do przycisku przełącznika. Wymaga ona użycia API poziomu 14 lub wyższego. Element definiuje pole wyboru. Aby zdefiniować grupę przycisków opcji, w pierwszej kolejności trzeba zdefiniować ich grupę, używając elementu . Wewnątrz tej grupy można dodawać poszczególne przyciski opcji, definiowane za pomocą elementów . Aby wyświetlić obraz, należy użyć elementu . Element definiuje przycisk, który zamiast tekstu przedstawia obraz. Paski przewijania można dodawać do układów za pomocą elementów i . Klasa Toast pozwala wyświetlać komunikaty tekstowe.

6. Układy z ograniczeniami

Rozmieszczaj rzeczy w odpowiednich miejscach Oczywiście, że jestem pewny, właśnie patrzę na szkic dokumentu. Napisano w nim, że duży palec łączy się z kością śródstopia, ta z kolei z kością skokową.

Spójrzmy prawdzie w oczy: musisz wiedzieć, jak tworzyć piękne układy. Jeśli piszesz aplikacje i chcesz, by inni ich używali, to musisz mieć pewność, że będą one wyglądać dokładnie tak, jak sobie tego życzysz. Jak na razie dowiedziałeś się jedynie, jak używać układów LineraLayout oraz FrameLayout, ale co zrobić, jeśli projekt interfejsu aplikacji będzie bardziej złożony? Aby pokazać Ci, jak należy radzić sobie w takich sytuacjach, przedstawimy nowy układ dodany do Androida — układ z ograniczeniami (ConstraintLayout) — używany do wizualnego konstruowania układów na podstawie szkicu graficznego. Pokażemy Ci także, w jaki sposób ograniczenia pozwalają na określanie położenia widoków, niezależnie od wielkości oraz orientacji ekranu. I w końcu dowiesz się także, jak oszczędzać czas, korzystając z możliwości automatycznego przewidywania i dodawania ograniczeń, jaką dysponuje Android Studio.

to jest nowy rozdział  221

Zagnieżdżanie układów

Zagnieżdżone układy mogą być nieefektywne Dowiedziałeś się już jak tworzyć proste interfejsy użytkownika korzystając przy tym z układów liniowych oraz układów ramki. Jednak co począć w sytuacji kiedy będziesz musiał stworzyć coś bardziej złożonego? Choć takie bardziej złożone interfejsy użytkowników można tworzyć zagnieżdżając odpowiednio dużo układów liniowych i układów ramek, to jednak takie rozwiązanie spowalnia aplikację i utrudnia analizę oraz utrzymanie jej kodu. W ramach przykładu załóżmy, że chcemy stworzyć układ składający się z dwóch wierszy, z których każdy zawiera dwa elementy. Jednym z rozwiązań byłoby utworzenie takiego układu z trzech układów liniowych: jednego głównego oraz dwóch zagnieżdżonych określających wygląd każdego z wierszy. Układy liniowe pozwalają jedynie wyświetlać widoki w pojedynczym wierszu lub kolumnie; dlatego nie możemy użyć jednego takiego ukła do stworzenia interfejsu użytkown du takiego jak ten przedstawiony na ika rysunku. Możemy jednak zagnieźd kilka takich układów, by uzyskać zić zamierzony efekt.

W przypadku tego interfejsu, można by użyć jednego układu głównego oraz dwóch układów zagnieżdżonych — po jednym dla każdego wiersza.

Podczas wyświetlania układu na ekranie urządzenia, Android tworzy hierarchię widoków na podstawie komponentów układów, co pomaga mu określać miejsca, w których mają być wyświetlane poszczególne widoki. Jeśli układ zawiera układy zagnieżdżone, to hierarchia widoków stanie się bardziej złożona i może się okazać, że Android będzie musiał ją przetwarzać w kilku przebiegach: To jest główny układ liniowy.

To jest układ pierwszego wiersza.

LinearLayout

LinearLayout

TextView

EditText

To jest układ drugiego wiersza.

LinearLayout

TextView

Każdy widok i układ trzeba zainicjować, zmierzyć, rozmieścić i narysować. To całkiem sporo roboty jeśli interfejs użytkownika składa się z wielu zagnieżdżonych układów; dlatego może spowolnić działanie aplikacji.

EditText

Choć powyższa hierarchia wciąż jest względnie prosta, to jednak możemy sobie wyobrazić co by się stało, gdybyśmy potrzebowali więcej widoków, więcej zagnieżdżonych układów o głębszej hierarchii. Mogłoby to doprowadzić do powstawia wąskich gardeł w wydajności działania aplikacji oraz zagmatwanego kodu, który będzie trudy do analizy i utrzymania. Jeśli zatem nasza aplikacja ma mieć bardziej złożony interfejs użytkownika, którego utworzenie wymaga zagnieżdżenia kilku układów, to zazwyczaj lepszym rozwiązaniem będzie zastosowanie układu innego typu.

222

Rozdział 6.

Układy z ograniczeniami

Przedstawiamy układy z ograniczeniami W tym rozdziale skoncentrujemy się na stosowaniu układów nowego typu: układów z ograniczeniami. Układy tego typu są bardziej złożone od układów liniowych lub układów ramek, lecz jednocześnie są bardziej elastyczne. Co więcej, są one także znacznie bardziej efektywne w przypadku tworzenia złożonych interfejsów użytkownika, gdyż pozwalają na tworzenie bardziej płaskiej hierarchii widoków, a to z kolei oznacza, że Android będzie miał mniej do przetwarzania podczas działania aplikacji.

Układy z ograniczeniami projektuje się WIZUALNIE Kolejną zaletą wynikającą ze stosowania układów z ograniczeniami jest to, że zostały one zaprojektowane specjalnie pod kątem współpracy z narzędziami projektowymi Android Studio. W odróżnieniu od układów liniowych oraz układów ramek, w których przypadku zazwyczaj bezpośrednio modyfikujemy kod XML, układy z ograniczeniami są projektowane w sposób wizualny. Można więc przeciągać i upuszczać komponenty GUI bezpośrednio w obszarze szkicu i wydawać instrukcje określające, jak mają być wyświetlane poszczególne widoki.

Do tworzenia układów z ograniczeniami konieczne jest zainstalowanie Android Studio w wersji 2.3 lub nowszej. W razie stosowania starszej wersji trzeba będzie ją zaktualizować.

Aby przekonać się, jak to wszystko wygląda w praktyce, najpierw zrobimy sobie krótką wycieczkę po układach z ograniczeniami, następnie użyjemy takiego układu do stworzenia interfejsu użytkownika przedstawionego na poniższym rysunku:

To jest kontrolka TextView. To są kontrolki EditText wypełniające dostępny obszar na całą jego szerokość. Kontrolka EditText Treść wiadomości wypełnia dostępny obszar na całą jego szerokość i wysokość.

Przycisk Wyślij jest wyświetlany pośrodku u dołu ekranu.

Utwórz nowy projekt Zaczniemy od utworzenia w Android Studio nowego projektu aplikacji o nazwie „Układ z ograniczeniami”, używając nazwy domeny „hfad.com”, co oznacza, że kod będzie umieszczony w pakiecie com.hfad.ukladzograniczeniami. Wybierz minimalny poziom SDK 19, by aplikacja działała na większości dostępnych urządzeń. Będziesz potrzebował pustej aktywności o nazwie „MainActivity” korzystającej z układu „activity_main.xml”; dzięki temu wygenerowany kod będzie taki sam jak nasz. Podczas tworzenia aktywności nie możesz zapomnieć o usunięciu zaznaczenia z pola wyboru Backwards Compatibility (AppCompat).

jesteś tutaj  223

Dodaj bibliotekę

Nie zapomnij dołączyć do projektu biblioteki Constraint Layout Library W odróżnieniu od wszystkich układów przedstawionych wcześniej układ z ograniczeniami jest umieszczony w odrębnej bibliotece, którą przed zastosowaniem układu trzeba będzie dodać do zależności projektu. Dodawanie biblioteki do projektu jako zależności oznacza, że biblioteka ta zostanie dołączona do aplikacji i skopiowana na urządzenie użytkownika. Jest wysoce prawdopodobne, że Android Studio automatycznie doda bibliotekę Constraint Layout Library do projektu, jednak lepiej to sprawdzić. W tym celu w Android Studio wybierz z menu głównego opcję File/Project Structure, a następnie kliknij moduł aplikacji i przejdź na kartę Dependencies.

To jest biblioteka Constraint Layout Library.

Jeśli Android Studio już dodało bibliotekę Constraint Layout Library, to zostanie ona wyświetlona w oknie dialogowym jako com.android.support.constraints:constraint-layout, jak pokazano na powyższym rysunku. Jeśli jednak biblioteka nie została dodana, to będzie trzeba dodać ją samodzielnie. W tym celu kliknij przycisk „+”, umieszczony u dołu lub po prawej stronie okna Project Structure. Następnie wybierz opcję Library Dependency i na wyświetlonej liście zaznacz opcję Constraint Layout Library. Jeśli taka pozycja nie będzie dostępna na liście, to w polu wyszukiwania wpisz: com.android.support.constraint:constraint-layout:1.0.2

Po kliknięciu przycisku OK biblioteka Constraint Layout Library powinna zostać dodana do listy zależności. Ponownie kliknij przycisk OK, aby zapisać zmiany i zamknąć okno Project Structure. Teraz, kiedy już mamy pewność, że projekt zawiera bibliotekę Constraint Layout Library, można dodać do jego zasobów łańcuchy znaków, które będą używane w układzie.

224

Rozdział 6.

ać Ten tekst będziesz musiał wpis wyłącznie w przypadku, gdy biblioteka Constraint Layout e Library nie została automatyczni ć. dodana do projektu jako zależnoś

Układy z ograniczeniami

Dodanie zasobów do strings.xml Każdy z widoków umieszczonych w układzie będzie prezentował wartości tekstowe lub podpowiedzi, dlatego dodamy je teraz do pliku z zasobami łańcuchowymi. Dodaj poniższe wartości do pliku strings.xml:

UkladZOgraniczeniami app/src/main

... Do:

res

Podaj adres e-mail Temat

values

Treść wiadomości



Wyślij

strings.xml

...

Teraz, po dodaniu łańcuchów do pliku zasobów, możemy zająć się zaktualizowaniem układu.

Zmiana activity_main.xml i użycie układu z ograniczeniami Mamy zamiar skorzystać z układu z ograniczeniami. W tym celu (jak również po to, by mieć pewność, że tworzony układ będzie odpowiadał temu przedstawionemu w książce) zaktualizuj kod pliku activity_main.xml tak, by był identyczny z tym zaprezentowanym poniżej (różnice zostały wyróżnione pogrubioną czcionką):

UkladZOgraniczeniami app/src/main



Jeśli Android Studio dodało tu automatycznie jakieś widoki, to

je usuń.

Powyższy kod definiuje układ z ograniczeniami, ConstraintLayout, do którego można dodawać kolejne widoki. Używamy do tego narzędzia do tworzenia szkicu układów — Blueprint — udostępnianego przez edytor projektu Android Studio.

jesteś tutaj  225

Stosowanie narzędzia do tworzenia szkiców

Zastosowanie narzędzia do tworzenia szkicu Aby skorzystać ze specjalnego narzędzia do tworzenia szkicu układu, w pierwszej kolejności w edytorze układów trzeba przejść do trybu projektowania; aby to zrobić, należy kliknąć kartę Design. W celu uruchomienia narzędzia do tworzenia szkicu układu wystarczy kliknąć przycisk Show Blueprint, umieszczony na pasku narzędzi. Teraz będziesz już mógł przeciągnąć widżet Button z przybornika edytora do obszaru szkicu. Przedstawione poniżej czynności pozwalają dodać do układu przycisk. by wyświetlić projekt Kliknij przycisk Show Blueprint, aby był duży i ładny. go, z ięks pow e ępni nast układu, a

To jest widżet przycisku umieszczony w przyborniku edytora.

Pr

ze

gn

ij

pr

zy

cis

k

na

pr

oje

kt.

A tu jest nasz przycisk.

Ten obszar reprezentuje miejsce, w którym u góry aplikacji będą wyświetlane wszelkie paski.

Widoki możesz przeciągać i upuszczać w dowolnym miejscu głównego obszaru szkicu.

Wielkość przybornika można zwiększyć, klikając ten przycisk i przeciągając go w dół.

226

cią

Ten fragment oznacza obszar przeznaczony na główne przyciski aplikacji.

Rozdział 6.

Układy z ograniczeniami

Rozmieszczanie widoków przy wykorzystaniu ograniczeń W przypadku układu z ograniczeniami, położenie, w którym dany widok ma być wyświetlany, nie jest określane na podstawie miejsca, w którym zostanie on upuszczony w szkicu. Zamiast tego rozmieszczenie widoków jest określane na podstawie ograniczeń (ang. constraints). To ograniczenie jest połączeniem lub dowiązaniem informującym o tym, gdzie w układzie ma być wyświetlony dany widok. Na przykład ograniczenie pozwala dowiązać widok do początkowej krawędzi układu bądź też umieścić go poniżej innego widoku.

Dodamy do przycisku ograniczenie w poziomie Aby się przekonać, jak to wszystko działa, dodamy do przycisku ograniczenie w poziomie, które dowiąże go do lewej krawędzi układu. Najpierw kliknij przycisk, aby się upewnić, że jest wybrany. Po wybraniu widoku wyświetlany jest wokół niego prostokąt ograniczający, na którego krawędziach są umieszczone uchwyty. Kwadratowe uchwyty na wierzchołkach prostokąta ograniczającego pozwalają zmieniać wielkość widoku, a umieszczone na krawędziach uchwyty okrągłe służą do dodawania ograniczeń. Po wybraniu widoku rysowany jest wokół niego prostokąt ograniczający.

Użyj kwadratowych uchwytów na wierzchołkach do zmiany wielkości widoku. Użyj okrągłych uchwytów umieszczonych na krawędziach, aby dodawać ograniczenia.

Aby dodać ograniczenie, należy kliknąć jeden z okrągłych uchwytów ograniczeń i przeciągnąć je do dowolnego elementu układu, do którego chcemy dowiązać dany widok. W naszym przykładzie chcemy dowiązać lewą krawędź przycisku do lewej krawędzi; a zatem należy kliknąć lewy uchwyt ograniczenia i przeciągnąć go do lewej krawędzi szkicu: Kliknij okrągły uchwyt umieszczony pośrodku lewej krawędzi przycisku i przeciągnij go do lewej krawędzi szkicu.

Wykonanie tej czynności spowoduje dodanie ograniczenia, a przycisk zostanie przesunięty do lewej krawędzi układu: Po dodaniu ograniczenia przycisk zostanie przesunięty do krawędzi układu.

W taki sposób dodajemy do widoku ograniczenie w poziomie. Na następnej stronie dodamy do niego ograniczenie w pionie.

jesteś tutaj  227

Ograniczenia w pionie

Dodawanie ograniczenia w pionie Dodajmy teraz do przycisku drugie ograniczenie, aby dowiązać go do górnej krawędzi układu. W tym celu kliknij uchwyt ograniczenia wyświetlony na górnej krawędzi przycisku i przeciągnij go do górnej krawędzi głównego obszaru szkicu. W ten sposób dodasz do przycisku drugie ograniczenie, a przycisk zostanie przesunięty do górnej krawędzi głównego obszaru układu. Każdy widok umieszczony w układzie ConstraintLayout musi mieć przynajmniej dwa ograniczenia — jedno w pionie i jedno w poziomie — gdyż są one niezbędne do wyznaczenia jego położenia. Jeśli pominiemy ograniczenie w poziomie, to po uruchomieniu aplikacji widok zostanie wyświetlony przy początkowej krawędzi układu. Jeśli pominiemy ograniczenie w pionie, to widok zostanie wyświetlony przy górnej krawędzi układu. I stanie się tak niezależnie od tego, w którym miejscu szkicu umieścimy widok.

Zmiana marginesów widoku

Kliknij okrągły uchwyt umieszczony na górnej krawędzi przycisku i przeciągnij go do górnej krawędzi szkicu.

Przycisk zostanie przesunięty do górnej krawędzi głównego obszaru szkicu.

W momencie dodania do widoku ograniczenia edytor projektu automatycznie doda do widoku margines, umieszczając go po tej samej stronie, po której zostało ustawione ograniczenie. Wielkość domyślnego marginesu można określać na pasku narzędzi edytora projektu — wystarczy zmienić liczbę podaną w polu Default Margin:

Aby określić wielkość domyślnego marginesu, zmień liczbę (wyrażoną w dpi) zapisaną w tym polu.

Zmiana wielkości domyślnego marginesu spowoduje określenie wielkości każdego nowego marginesu dodawanego do widoków. Jednak wielkości wszystkich już istniejących marginesów nie zostaną zmienione — te marginesy można jednak zmieniać, korzystając z okna właściwości. Okno właściwości jest wyświetlane w odrębnym panelu, umieszczonym obok edytora projektu. Po wybraniu widoku w oknie właściwości jest wyświetlany jego schemat, przedstawiający ustawione ograniczenia oraz wielkości marginesów. Aby zmienić wielkość marginesu, wystarczy zmienić liczbę wyświetloną obok odpowiedniej krawędzi widoku. Wielkości marginesów widoku można także zmieniać, przeciągając ten widok w oknie szkicu. To rozwiązanie daje dokładnie takie same efekty co podawanie wielkości marginesów w oknie właściwości, jednak stosując je, znacznie trudniej precyzyjnie ustawić konkretną wartość. Teraz, zanim przejdziesz na następną stronę, spróbuj pozmieniać wielkości marginesów, używając każdej z metod opisanych powyżej.

228

Rozdział 6.

To jest okno właściwości.

To jest ograniczenie dotyczące lewej krawędzi widoku.

To jest wielkość marginesu po lewej stronie widoku, w tym przypadku wynosi ona 8.

Ten kwadrat reprezentuje widok.

Układy z ograniczeniami

Zmiany szkicu są uwzględniane w kodzie XML Dodawanie widoków do szkicu oraz ustawianie ograniczeń powoduje zmiany w niewidocznym zazwyczaj kodzie XML układu. Aby się o tym przekonać, należy przejść do widoku kodu. Twój kod powinien wyglądać podobnie do tego przedstawionego poniżej (nie przejmuj się jednak, jeśli pojawią się jakieś nieznaczne różnice):

ektu. Przycisk dodany w edytorze proj





activity_main.xml

Jak widać, kod układu obecnie zawiera przycisk. Przeważająca część kodu tego przycisku będzie już wyglądać znajomo, gdyż określa właściwości opisane w rozdziale 5. Wysokość, szerokość oraz marginesy przycisku są określane dokładnie tak samo jak wcześniej. Nowy kod mieści się tylko w dwóch wierszach, określających ograniczenia dodane do lewej i górnej krawędzi przycisku:

... app:layout_constraintLeft_toLeftOf=”parent” app:layout_constraintTop_toTopOf=”parent” />

enia Te wiersze kodu opisują ogranicz dodane do lewej i górnej krawędzi przycisku.

Dodanie ograniczeń do pozostałych krawędzi przycisku spowoduje wygenerowanie analogicznego kodu. Przełącz się ponownie do widoku projektu, gdyż na następnej stronie przedstawimy inną technikę określania położenia widoków w układach z ograniczeniami.

jesteś tutaj  229

Nie ma to jak być w centrum

Jak wyśrodkowywać widoki Jak na razie pokazaliśmy jedynie, jak można używać ograniczeń do dowiązywania widoku do jednej z krawędzi układu. Takie rozwiązanie sprawdzi się, kiedy będziemy chcieli umieścić widok na przykład w lewym górnym rogu układu. Ale jak można wyśrodkować widok? Otóż aby umieści widok pośrodku układu, należy dodać ograniczenia do obu jego przeciwległych krawędzi. Zobaczymy, jak to zrobić, wyśrodkowując dodany wcześniej przycisk w poziomie. Upewnij się, że przycisk jest zaznaczony, a następnie kliknij prawy uchwyt ograniczenia i przeciągnij go do prawej krawędzi układu:

szczony Kliknij uchwyt ograniczenia umie u, cisk przy ędzi kraw na prawej ej a następnie przeciągnij go do praw krawędzi szkicu.

Wykonanie tej czynności spowoduje dodanie ograniczenia odnoszącego się do prawej krawędzi widoku. Ponieważ widok będzie teraz dysponować już dwoma ograniczeniami w poziomie, zostanie przesunięty na środek, a dwa przeciwległe ograniczenia zostaną wyświetlone jako sprężyny: Przycisk zostaje przesunięty na środek.

Ograniczenia dodane do przeciwległych krawędzi widoku są wyświetlane jako sprężyny.

Przycisk jest teraz dowiązany do obu krawędzi układu, a zatem niezależnie od wielkości i orientacji ekranu będzie zawsze wyświetlany pośrodku. Można to sprawdzić uruchamiając aplikację bądź zmieniając wymiary szkicu poprzez przeciągnięcie jego prawego dolnego wierzchołka.

Wielkość szkicu można zmienić, klikając i przeciągając jego prawy dolny wierzchołek.

230

Rozdział 6.

Układy z ograniczeniami

Zmiana położenia widoku poprzez określanie przesunięcia Po dodaniu ograniczeń do przeciwległych krawędzi widoku można kontrolować jego położenie w stosunku do obu tych krawędzi przy wykorzystaniu przesunięcia (ang. bias). Przesunięcie to określa, jaka powinna być proporcjonalna długość ograniczenia po obu stronach widoku. Aby się przekonać, jak rozwiązanie to działa w praktyce, zmienimy teraz przesunięcie w poziomie, tak by nasz przycisk nie był wyświetlany pośrodku układu. W pierwszej kolejności upewnij się, że przycisk jest zaznaczony, a następnie spójrz na okno właściwości widoku. Poniżej schematu widoku powinien się pojawić suwak z wyświetloną na nim liczbą. Liczba ta reprezentuje procentową wartość przesunięcia widoku w poziomie:

Okno właściwości widoku pokazuje, że dodaliśmy do przycisku ograniczenia po jego lewej i prawej stronie. Ten suwak określa przesunięcie widoku w poziomie. Obecnie jest na nim wyświetlona liczba 50, określająca, że widok będzie ci wyświetlany w połowie odległoś o pomiędzy oboma dodanymi do nieg ograniczeniami w poziomie.

Aby zmienić wartość przesunięcia, wystarczy przesunąć suwak. Na przykład, jeśli przesuniemy suwak w lewo, to także przycisk widoczny na szkicu zostanie przesunięty w lewo:

Przesuwając suwak…

…przesuwamy także przycisk.

Widok zachowuje swoje względne położenie niezależnie od wielkości i orientacji ekranu. Przesunięcie dodawane do widoku w edytorze projektu jest uwzględniane w kodzie XML układu. Na przykład jeśli przesunięcie widoku w poziomie zmienimy do wartości 25%, to do kodu XML widoku zostanie dodany poniższy wiersz kodu:

Można także kliknąć i przeciągnąć sam przycisk, jednak takie rozwiązanie zapewnia mniejszą dokładność.

app:layout_constraintHorizontal_bias=”0.25”

Skoro już wiemy, jak określać przesunięcie, możemy zająć się określaniem wielkości widoku.

jesteś tutaj  231

Zmiana wielkości

Jak zmieniać wielkość widoku? W przypadku stosowania układów z ograniczeniami wielkość widoków można określać na kilka różnych sposobów:



Podając jego konkretne wymiary: szerokość i wysokość.



Używając wartości wrap_content, by wielkość widoku została dostosowana do jego zawartości.



Nakazując dopasowanie wymiarów widoku do wielkości jego ograniczeń (o ile zostały one dodane do obu przeciwległych krawędzi widoku).



Określając stosunek wysokości i szerokości widoku, na przykład by szerokość widoku była dwa razy większa od jego wysokości.

Teraz opiszemy każdą z tych możliwości nieco dokładniej.

1. Stała wielkość widoku W edytorze projektu stałe wymiary widoku można określać na kilka sposobów. Pierwszym z nich jest zmiana wielkości widoku w szkicu poprzez kliknięcie i przesunięcie jednego z kwadratowych uchwytów widocznych w wierzchołkach ramki ograniczającej widok. Drugim sposobem jest natomiast podanie konkretnych wartości liczbowych w polach layout_width oraz layout_height w oknie właściwości widoku: Wielkość widoku można zmieniać przy użyciu tych kwadratowych uchwytów umieszczonych przy jego wierzchołkach.

Jednak ogólnie rzecz biorąc, podawanie stałych wymiarów widoku jest złym rozwiązaniem, gdyż oznacza to, że nie będą one dostosowywane do zawartości widoku ani wymiarów ekranu.

Można także podać konkretne wartości liczbowe w polach szerokości i wysokości w oknie właściwości widoku.

2. Widok wystarczająco duży Aby stworzyć widok, którego wielkość będzie dostosowana do zawartości, należy zmienić wartości w polach layout_width oraz layout_height na wrap_content. Można to zrobić w oknie właściwości widoku, jak pokazano na poniższym rysunku: Ustawienie szerokości i wysokości widoku na wrap_content sprawi, że będzie on na tyle duży, by można w nim było wyświetlić jego zawartość, dokładnie tak samo jak w innych układach.

232

Rozdział 6.

Układy z ograniczeniami

3. Dopasowanie do ograniczeń widoku Jeśli do obu przeciwległych krawędzi widoku zostały dodane ograniczenia, to można zażądać, by wielkość widoku była dostosowana do tych ograniczeń. W tym celu należy przypisać szerokości lub wysokości widoku wielkość 0dp — przypisanie tej wartości szerokości widoku sprawi, że zostanie ona dopasowana do wielkości ograniczeń widoku w poziomie, i analogicznie przypisanie wartości 0dp wysokości widoku sprawi, że wysokość ta zostanie dopasowana do wielkości ograniczeń widoku w pionie. W naszym przykładzie dodaliśmy ograniczenia do lewego i prawego boku przycisku, dlatego też możemy dostosować jego szerokość do wielkości ograniczeń w poziomie. W tym celu należy przejść do okna właściwości widoku i w polu layout_width wpisać wartość 0dp. W efekcie w szkicu układu przycisk powinien zostać powiększony na całą dostępną szerokość (z uwzględnieniem stosowanych marginesów):

Ustaw szerokość na 0dp, a szerokość przycisku zostanie dostosowana do jego ograniczeń.

4. Określenie proporcji szerokości i wysokości I w końcu można także określić proporcję pomiędzy szerokością i wysokością widoku. W tym celu w polu layout_width lub layout_height należy wpisać wartość 0dp (tak jak zrobiliśmy wcześniej), a następnie kliknąć lewy górny wierzchołek schematu widoku wyświetlonego w oknie właściwości. W efekcie w oknie właściwości powinno zostać wyświetlone pole ratio, którego zawartość można następnie zmodyfikować: Kliknij tutaj, aby przełączyć wyświetlenie proporcji widoku. W tym przypadku proporcja wynosi 1:1, co oznacza, że wysokość i szerokość widoku będą równe.

Skoro już się dowiedziałeś, w jaki sposób można zmieniać wymiary widoków, zanim przejdziesz do ćwiczeń zamieszczonych na następnej stronie, spróbuj trochę poeksperymentować z opisanymi technikami.

jesteś tutaj  233

Ćwiczenie

Bądź ograniczeniem

Twoim zadaniem jest wcielić się w rolę układu z ograniczeniami niezbędnego do utworzenia każdego z przedstawionych poniżej układów. Musisz także określić wartości layout_width, layout_height oraz przesunięcia (jeśli takie jest) dla każdego z widoków. Dla ułatwienia zrobiliśmy pierwsze ćwiczenie. Do każdego szkicu musisz dodać widoki i ograniczenia.

A Tak ma wyglądać ekran aplikacji.

Przycisk jest umieszczony w prawym górnym rogu.

B

Przycisk jest wyświetlony u dołu ekranu i wyśrodkowany w poziomie.

234

Rozdział 6.

layout_width: wrap_content layout_height: wrap_content

Układy z ograniczeniami Przycisk wypełnia cały dostępny obszar.

C

D

Przycisk numer 1 jest wyświetlany w lewym górnym rogu układu, a przycisk numer 2 wypełnia cały dostępny obszar obok niego w poziomie.

jesteś tutaj  235

Rozwiązanie

Bądź ograniczeniem — rozwiązanie

Twoim zadaniem jest wcielić się w rolę układu z ograniczeniami niezbędnego do utworzenia każdego z przedstawionych poniżej układów. Musisz także określić wartości layout_width, layout_height oraz przesunięcia (jeśli takie jest) dla każdego z widoków. Dla ułatwienia zrobiliśmy pierwsze ćwiczenie. Do każdego szkicu musisz dodać widoki i ograniczenia.

A Tak ma wyglądać ekran aplikacji.

Przycisk jest umieszczony w prawym górnym rogu.

layout_width: wrap_content layout_height: wrap_content

B

Aby wyśrodkować przycisk w poziomie, należy dodać ograniczenie do obu jego pionowych boków i dodatkowo określić przesunięcie na 50%.

Przycisk jest wyświetlony u dołu ekranu i wyśrodkowany w poziomie.

236

Rozdział 6.

layout_width: wrap_content layout_height: wrap_content bias: 50%

Układy z ograniczeniami Przycisk wypełnia cały dostępny obszar.

C C Przycisk musi zostać powiększony we wszystkich kierunkach, więc ograniczenia trzeba dodać do wszystkich jego boków, a jego szerokość i wysokość należy ustawić na 0dp.

layout_width: 0dp layout_height: 0dp

D D Przycisk 1. layout_width: wrap_content layout_height: wrap_content Przycisk numer 1 jest wyświetlany w lewym górnym rogu układu, a przycisk numer 2 wypełnia cały dostępny obszar obok niego w poziomie.

Przycisk 2. layout_width: 0dp layout_height: wrap_content

Aby Przycisk 2. wypełnił cały dostępny obszar w poziomie, trzeba dodać ograniczenia do obu jego pionowych krawędzi i dodatkowo ustawić jego szerokość na 0dp. Lewa krawędź Przycisku 2. musi zostać dowiązana do prawej krawędzi Przycisku 1.

jesteś tutaj  237

Wyrównywanie widoków

Jak wyrównywać widoki? Jak na razie dowiedziałeś się, w jaki sposób można rozmieszczać widoki w układzie oraz określać ich wielkość. Kolejnym zagadnieniem, któremu się przyjrzymy, będzie wyrównywanie widoków względem innych widoków w układzie. Przede wszystkim kliknij przycisk Show Constraints, umieszczony na pasku narzędzi edytora projektu, aby wyświetlić wszystkie ustawione ograniczenia (a nie jedynie te dodane do obecnie wybranego widoku). Następnie przeciągnij drugi przycisk z przybornika na szkic i umieść go poniżej pierwszego.

Dodaj do szkicu drugi przycisk i umieść go poniżej pierwszego.

Aby po uruchomieniu aplikacji drugi przycisk został wyświetlony poniżej pierwszego, do górnej krawędzi drugiego przycisku należy dodać ograniczenie, które dowiąże go do dolnej krawędzi pierwszego przycisku. W tym celu wybierz drugi przycisk i narysuj ograniczenie prowadzące od jego górnej krawędzi do dolnej krawędzi pierwszego przycisku:

, które W ten sposób dodasz ograniczenie cisku przy ego jedn ędź kraw ą dowiąże górn u do dolnej krawędzi innego przycisk umieszczonego w projekcie.

Aby wyrównać lewe krawędzie obu przycisków, najpierw musisz je oba zaznaczyć. W tym celu naciśnij klawisz Shift, a następnie kliknij kolejno oba przyciski. Później kliknij przycisk Align Left Edges, umieszczony na pasku narzędzi edytora projektu: Kliknięcie tego przycisku daje dostęp do różnych opcji wyrównywania widoków.

Wyrównanie lewych krawędzi widoków powoduje utworzenie kolejnego ograniczenia.

Wykonanie tych operacji spowoduje dodanie ograniczenia wiążącego lewą krawędź drugiego przycisku z lewą krawędzią przycisku pierwszego — ograniczenia powodującego wyrównanie obu krawędzi.

238

Rozdział 6.

To jest przycisk Show Constraints. Jego kliknięcie wyświetla lub ukrywa wszystkie ograniczenia układu.

Układy z ograniczeniami

Stwórzmy prawdziwy układ Teraz już wiesz na tyle dużo na temat układów z ograniczeniami, że możesz stworzyć prawdziwy układ tego typu. Poniższy rysunek przedstawia wygląd układu, którym się zaraz zajmiemy:

To jest kontrolka TextView. To są kontrolki EditText, wypełniające w poziomie cały dostępny obszar układu.

Kontrolka EditText Wiadomość wypełnia cały dostępny obszar zarówno w poziomie, jak i w pionie.

Przycisk Wyślij jest umieszczony u dołu ekranu i wyśrodkowany w poziomie.

Układ stworzymy od samego początku w pliku activity_main.xml, a zatem zanim zaczniemy, usuń wszystkie widoki, które do niego dodałeś, tak by obszar szkicu był zupełnie pusty, a kod XML układu miał następującą postać:



UkladZOgraniczeniami app/src/main res layout

activity_main.xml

jesteś tutaj  239

Górny wiersz

Zacznij od dodania widoków do górnego wiersza Zaczniemy od dodania widoków, które mają być widoczne w górnym wierszu układu: prostej kontrolki prezentującej tekst oraz pola tekstowego.

Górny wiersz układu zawiera etykietę (kontrolkę TextView) oraz pole tekstowe (kontrolkę EditText) do podawania adresu e-mail.

Aby dodać te kontrolki, przełącz się do widoku projektu (oczywiście, jeśli to konieczne), a następnie przeciągnij z przybornika kontrolkę TextView i upuść ją w lewym górnym rogu projektu. Później przeciągnij komponent E-mail i upuść go na szkicu tak, by znajdował się z prawej strony dodanego wcześniej widoku tekstowego. Komponent E-mail to zwyczajne pole tekstowe, które do wprowadzania danych używa klawiatury ekranowej Androida przeznaczonej do wpisywania adresów poczty elektronicznej. Następnie ręcznie dostosuj ten komponent tak, aby był wyrównany w poziomie z widokiem tekstowym i zajmował cały dostępny obszar obok niego.

Umieść komponenty TextView oraz E-mail na szkicu tak, by ich wielkości i rozmieszczenie odpowiadały ich wyglądowi w układzie.

Warto zwrócić uwagę, że jeszcze nie dodaliśmy do widoków żadnego ograniczenia, choć rozmieściliśmy je w taki sposób, w jaki mają wyglądać po uruchomieniu aplikacji. Zrobiliśmy tak celowo: chcieliśmy zaoszczędzić sobie trochę pracy, ponieważ chcemy, by to edytor projektu określił ograniczenia za nas.

Zastosowanie narzędzia wnioskowania ograniczeń Jak już na pewno wiesz, układ z ograniczeniami określa rozmieszczenie widoków w układzie na podstawie ograniczeń. A najlepsze jest to, że edytor projektu udostępnia przycisk Infer Constraints, służący do uruchamiania mechanizmu, który stara się określić, jak powinny wyglądać ograniczenia, a następnie dodaje je do układu. Aby skorzystać z tego narzędzia, wystarczy kliknąć przycisk Infer Constraints, umieszczony na pasku narzędzi edytora projektu: To jest przycisk Infer Constraints. Kliknij go teraz.

240

Rozdział 6.

Układy z ograniczeniami

Mechanizm wnioskowania odgaduje, jakie ograniczenia należy dodać Po kliknięciu przycisku Infer Constraints edytor projektu spróbuje określić, jakie ograniczenia będą niezbędne, a następnie doda je do układu. Mechanizm ten nie działa bezbłędnie, gdyż (z tego co wiemy) nie potrafi się telepatycznie dowiedzieć, jak chcemy, by projektowany układ wyglądał na urządzeniu — dlatego jedynie ogranicza się do zgadywania na podstawie rozmieszczenia i wymiarów widoków umieszczonych na szkicu. Poniżej przedstawiliśmy modyfikacje, które edytor projektu wprowadził do naszego układu po kliknięciu przycisku Infer Constraints (jeśli rozmieściłeś widoki inaczej, może się zdarzyć, że Twoje wyniki także będą nieco inne): Szczegółowe informacje o poszczególnych ograniczeniach możesz wyświetlić, zaznaczając kolejno każdy z widoków i przeglądając wartości wyświetlone w oknie właściwości.

Kliknięcie przycisku Infer Constraints spowodowało dodanie ograniczeń do obu widoków.

Jeśli efekty działania mechanizmu nie będą zadowalające, można je odrzucić, wybierając z menu Edit opcję Undo Infer Constraints; można także zmodyfikować poszczególne ograniczenia. Zanim dodamy do szkicu kolejne widoki, zmodyfikujemy jeszcze nieco te dwa, które dodaliśmy przed chwilą. W pierwszej kolejności w edytorze projektu zaznacz widok tekstowy, a następnie, w oknie właściwości, ustaw jego identyfikator na to_label, a w polu text wpisz: @string/to_lable. Czynności te dadzą ten sam efekt co dodanie poniższych dwóch wierszy kodu do elementu w kodzie XML układu: android:id=”@+id/to_label” android:text=”@string/to_label”

e Nie przejmuj się, kiedy w momenci aktualizacji identyfikatora kontrolkiat Android Studio wyświetli komunik informujący o tym, że zostaną Chcemy, wprowadzone zmiany w kodzie. niamy zmie gdyż , zone wad wpro ały by zost ku. wido identyfikator

Android Studio doda te dwa wiersze kodu, kiedy zmienimy identyfikator widoku oraz określimy wyświetlany w nim tekst.

Zmodyfikuj wartość tej właściwości, by zmienić tekst wyświetlany w kontrolce TextView.

Następnie zaznacz komponent EditText E-mail, zmień jego identyfikator na email_address, w polu layout_height wybierz wartość wrap_content, a w polu hint wpisz @string/email_hint. Wykonanie tych czynności będzie równoznaczne z dodaniem poniższych wierszy do elementu w kodzie XML układu: android:id=”@+id/email_address” android:layout_height=”wrap_content” android:hint=”@string/email_hint”

Android Studio doda te wiersze kodu, kiedy zmienisz wartości w polach layout_height i hint kontrolki.

Skoro pomyślnie dodaliśmy do układu pierwszy wiersz kontrolek, możemy się zająć pozostałą zawartością naszego interfejsu użytkownika.

jesteś tutaj  241

Kolejne wiersze

Dodaj do szkicu kolejny wiersz… Kolejny wiersz układu ma zawierać pole tekstowe służące do podawania tematu wiadomości. Aby je dodać, przeciągnij komponent Plain Text z przybornika na szkic i umieść go poniżej dwóch pól dodanych wcześniej. Wykonanie tej czynności spowoduje dodanie do szkicu kontrolki EditText. Teraz dostosuj rozmiar i położenie komponentu tak, by był on wyrównany z widokami dodanymi wcześniej i zajmował całą szerokość układu:

Kolejną kontrolkę EditText dodamy poniżej pierwszych dwóch widoków.

Wybranie komponentu Plain Text spowoduje dodanie do układu kontrolki EditText.

Teraz ponownie kliknij przycisk Infer Constraints — w efekcie edytor projektu doda kolejne ograniczenia, które tym razem będą określać położenie i wielkość nowego komponentu:

Po kliknięciu przycisku Infer Constraints edytor projektu doda ograniczenia do nowej kontrolki EditText.

Zaznacz nowy widok na szkicu, a następnie zmień jego identyfikator na subject, w polu layout_height wybierz opcję wrap_content, w polu hint wpisz @string/subject_hint, po czym usuń zawartość pola text, jeśli edytor projektu automatycznie ją określił.

…następnie dodaj przycisk Teraz zajmiemy się dodaniem przycisku, który będzie umieszczony u dołu układu. Przeciągnij komponent Button, umieść go u dołu układu i wyśrodkuj w poziomie. Kiedy tym razem klikniesz przycisk Infer Constraints, edytor projektu doda ograniczenia do nowego przycisku: Przycisk ma być umieszczony u dołu układu i wyśrodkowany w poziomie.

Pamiętaj, że kiedy klikniesz przy Infer Constraints w swoim ukła cisk dzie, możesz uzyskać inne wyniki niż te, które przedstawiliśmy w książce.

Zmień także identyfikator przycisku na send_button oraz wyświetlany w nim tekst na @string/send_button.

242

Rozdział 6.

Układy z ograniczeniami

I w końcu dodaj widok na treść wiadomości Do tworzonego układu musimy dodać jeszcze jeden widok — pole tekstowe, które ma zostać rozszerzone na cały dostępny obszar układu. Aby je dodać, przeciągnij z przybornika komponent Plain Text i umieść go pośrodku projektu. Następnie zmodyfikuj wielkość kontrolki tak, by zajmowała ona cały dostępny obszar układu, i kliknij przycisk Infer Constraints:

Tym razem kliknięcie przycisku Infer Constraints spowodowało dodanie ograniczeń do nowej kontrolki EditText.

Zaznacz nowy komponent w projekcie, a następnie zmień jego identyfikator na message, w polu hint wpisz @string/ message_hint, w polu gravity wybierz opcję top i ewentualnie usuń jakikolwiek tekst, który edytor projektu dodał do pola text.

Chcemy, by pole na treść wiadomości wypełniało cały dostępny obszar układu.

Warto zwrócić uwagę na to, że widoki można było dodać te wszystkie m za jednym razem i dopiero pote ts. kliknąć przycisk Infer Constrain takie że ak, jedn y liśm waży Zau daje tworzenie układów krok po kroku o najlepsze efekty. Ale może wart się poeksperymentować i przekonać o tym samemu?

Być może będziesz musiał kliknąć przycisk „View all properties”, aby uzyskać dostęp do właściwości gravity.

A teraz weźmy aplikację na jazdę próbną i przekonajmy się, jak wygląda przygotowany układ.

jesteś tutaj  243

Jazda próbna

Jazda próbna aplikacji Kiedy uruchomimy aplikację, układ aktywności MainActivity będzie wyglądał niemal dokładnie tak, jak chcieliśmy. Kiedy obrócimy urządzenie, przycisk pozostanie wyśrodkowany, pola tekstowe na adres e-mail oraz temat wiadomości rozszerzą się i zajmą całą dostępną szerokość ekranu, a pole tekstowe na treść wiadomości zostanie powiększone i zajmie cały pozostały dostępny obszar:

Przetestuj utworzony układ zej z ograniczeniami na jak najwięks że liczbie urządzeń, by się upewnić, różnej działa prawidłowo na ekranach o okaże, wielkości i orientacji. Jeśli się być to , lemy prob ś jakie ją tępu że wys ś może będziesz musiał zmienić jakiech właściwości i ograniczenia niektóry widoków.

Pola tekstowe powiększają się, by zająć cały dostępny obszar.

Przycisk jest wyśrodkowany u dołu ekranu niezależnie od wielkości i orientacji ekranu.

Musisz pamiętać, że Twój układ może wyglądać i działać inaczej niż ten przedstawiony w książce, zależnie od tego, jakie ograniczenia zostały do niego dodane przez edytor projektu po kolejnych kliknięciach przycisku Infer Constraints. Mechanizm wnioskowania ograniczeń nie jest doskonały, jednak zazwyczaj zapewnia niemal dokładnie takie efekty, jakie chcieliśmy uzyskać; a nawet jeśli mu się to nie uda, to zawsze można cofnąć wprowadzone przez niego modyfikacje lub samodzielnie je poprawić.

244

Rozdział 6.

Układy z ograniczeniami

Twój przybornik do Androida

CELNE SPOSTRZEŻENIA 





Układy z ograniczeniami zostały opracowane z myślą o tworzeniu ich przy użyciu edytora układów Android Studio. Są umieszczone w odrębnej bibliotece i można ich używać jedynie w aplikacjach zgodnych z API poziomu 9. lub nowszym. Widoki są rozmieszczane poprzez dodawanie ograniczeń. Każdy widok musi dysponować przynajmniej jednym ograniczeniem w poziomie oraz jednym w pionie. Widoki można wyśrodkowywać, dodając ograniczenia po obu, przeciwległych stronach. Korzystając z przesunięcia, można zmieniać położenie widoku pomiędzy ograniczeniami.







Wielkość widoku można dostosować do jego ograniczeń, o ile tylko zostały one dodane do obu, przeciwległych boków widoku. Wielkość widoku można także określać poprzez podanie stosunku jego szerokości do wysokości. Kliknięcie przycisku Infer Constraints powoduje dodanie do widoków ograniczeń, określonych na podstawie położenia i wielkości widoków w projekcie.

Nie istnieją

głupie pytania

P: Czy stosowanie układów

P: Dlaczego układy

O: Istnieją także inne rodzaje układów,

O: W porównaniu z innymi układami,

z ograniczeniami to jedyna opcja, jeśli trzeba tworzyć złożone układy?

takie jak układy względne czy układy siatki, jednak układy z ograniczeniami zapewniają takie same możliwości. Co więcej, układy z ograniczeniami zostały zaprojektowane pod kątem używania w edytorze projektu Android Studio, dzięki czemu tworzenie tych układów jest znacznie prostsze. Jeśli chciałbyś dowiedzieć się czegoś więcej o układach względnych oraz układach siatek, to sięgnij do Dodatku A, umieszczonego na końcu książki.

z ograniczeniami są umieszczone w odrębnej bibliotece?

układy z ograniczeniami stanowią stosunkowo nowy dodatek do Androida. Umieszczono je w odrębnej bibliotece, żeby można je było dodawać do starszych aplikacji działających także w starszych wersjach systemu Android. Więcej informacji na temat zgodności ze starszymi wersjami systemu można znaleźć w dalszych rozdziałach.

P: Czy wciąż mogę edytować kod XML układów z ograniczeniami?

O: Owszem, choć zostały one

zaprojektowane tak, by można je było tworzyć w sposób wizualny; dlatego skoncentrowaliśmy się na budowaniu tych układów z użyciem edytora projektu.

P: Spróbowałem użyć przycisku

Infer Constraints, jednak nie dało to oczekiwanych rezultatów. Dlaczego?

O: Mechanizm przycisku Infer Constraints określa ograniczenia na podstawie rozmieszczenia widoków w projekcie, dlatego generowane przez niego efekty nie zawsze będą odpowiadać naszym oczekiwaniom. Jednak zawsze można te efekty poprawić samodzielnie.

jesteś tutaj  245

Rozdział 6.

A zatem opanowałeś już rozdział 6. i dodałeś do swojego przybornika z narzędziami znajomość tworzenia układów z ograniczeniami.

j Pełny kod przykładowe j ane tow zen pre cji aplika z żes w tym rozdziale mo P FT ra we pobrać z ser wydawnictwa Helion: klady/ ftp://ftp.helion.pl/przy andrr2.zip

246

Rozdział 6.

7. Widoki list i adaptery

Zorganizuj się Rany! Mam tyle pomysłów… Tylko czy uda mi się je zmienić w najpopularniejszą aplikację roku?

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.

to jest nowy rozdział  247

Pomysły

Każda aplikacja zaczyna się od pomysłu Kiedy po raz pierwszy zaświta Ci myśl o napisaniu aplikacji, będziesz zapewne mieć wiele pomysłów dotyczących tego, co powinna zawierać. Na przykład szefowie kafeterii Coffeina chcieliby mieć nową aplikację, by przyciągnąć liczniejszą klientelę do swoich lokali. Poniżej przedstawiliśmy kilka ich pomysłów na zawartość aplikacji:

łowe Szczegó żdym cje o ka informa napoju Lista wszystkich naszych lokali Menu zawierając e wszystko, co można u nas zjeś ć

Adresy i godz iny otwarcia wszystkich na szych lokali

Lista oferowanych napojów e Szczegółow każdej o je informac nu pozycji me

To wszystko są pomysły, które użytkownicy aplikacji na pewno uznają za przydatne. Ale w jaki sposób możemy je wszystkie zebrać i przekształcić w intuicyjną, dobrze zorganizowaną aplikację?

248

Rozdział 7.

Ekran początkowy z listą opcji

Widoki list i adaptery

Skategoryzuj swoje pomysły — aktywności: poziom główny, kategoria i szczegóły/edycja Przydatnym sposobem, który pomoże zaprowadzić porządek w naszych pomysłach, będzie podzielenie ich na trzy różne typy aktywności: aktywności poziomu głównego, aktywności kategorii oraz aktywności szczegółów/edycji.

Aktywności poziomu głównego Aktywność poziomu głównego zawiera te rzeczy, które są najważniejsze dla użytkownika, i pozwala mu na łatwe przechodzenie do nich. W większości przypadków pierwszą aktywnością, którą użytkownik zobaczy po uruchomieniu aplikacji, będzie właśnie aktywność poziomu głównego.

Aktywności kategorii Aktywności kategorii prezentują dane należące do konkretnej kategorii i często przybierają postać list. Aktywności tego typu zazwyczaj zapewniają użytkownikom możliwość przejścia do kolejnej aktywności — szczegółów/ edycji. Przykładem aktywności kategorii może być lista wszystkich napojów oferowanych przez kafeterię Coffeina.

Aktywności szczegółów/edycji Aktywności typu szczegółów/edycji wyświetlają szczegółowe informacje o konkretnym rekordzie i pozwalają użytkownikom dodawać nowe rekordy. Przykładem aktywności tego typu może być aktywność wyświetlająca szczegółowe informacje o konkretnym napoju.

Ekran początkowy z listą opcji

Menu zawierające wszystko, co można u nas zjeść

Lista wszystkich naszych lokali Lista oferowanych napojów

łowe Szczegó cje informa m Szczegółowe y o każd informacje napoju zycji o każdej po menu

Adresy i godz iny otwarcia wszystkich naszych lokali.

Określiwszy, do której z tych kategorii należą poszczególne aktywności, możemy ich użyć do utworzenia hierarchii pokazującej, jak użytkownik będzie nawigował po aplikacji i jej poszczególnych aktywnościach.

Ćwiczenie

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.

jesteś tutaj  249

Zorganizuj swoje pomysły

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. Ekran początkowy z listą opcji

Aktywności poziomu głównego są na samej górze To właśnie te aktywności użytkownik zobaczy jako pierwsze.

Aktywności kategorii znajdują się pomiędzy aktywnościami poziomu głównego i aktywnościami szczegółów/edycji

Lista oferowanych napojów

Menu zawierające wszystko, co można u nas zjeść

Lista wszystkich naszych lokali

Szczegółowe informacje o każdym napoju

Szczegółowe informacje o każdej pozycji menu

Adresy i godziny otwarcia wszystkich naszych lokali

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.

Aktywności szczegółów/edycji Tworzą one dolną warstwę hierarchii aktywności. Użytkownicy będą do nich docierali z aktywności kategorii.

W ramach przykładu załóżmy, że użytkownik chce wyświetlić szczegółowe informacje o jednym z napojów dostępnych w kafeteriach Coffeina. W tym celu uruchamia aplikację, która najpierw wyświetla aktywność poziomu głównego — ekran powitalny z listą opcji. Następnie użytkownik naciska opcję wyświetlenia listy napojów. W końcu, aby wyświetlić szczegółowe informacje o konkretnym napoju, użytkownik musi odnaleźć go na liście i kliknąć.

250

Rozdział 7.

Widoki list i adaptery

Użyj widoku listy do nawigowania po danych W przypadku zastosowania takiej struktury aplikacji będziemy potrzebowali sposobu pozwalającego na przechodzenie do poszczególnych aktywności. Popularnym rozwiązanie stosowanym w takich sytuacjach jest widok listy. Widok listy pozwala wyświetlić listę danych, których następnie można użyć do nawigowania po aplikacji. A teraz mały przykład. Na poprzedniej stronie napisaliśmy, że w skład aplikacji dla kafeterii Coffeina wchodzi aktywność kategorii prezentująca listę napojów. Poniżej pokazaliśmy, jak ta aktywność może wyglądać.

To jest widok ListView zawierający listę napojów.

Aktywność wykorzystuje widok listy do wyświetlania wszystkich napojów oferowanych w kafeteriach Coffeina. Aby przejść do konkretnego napoju, użytkownik musi kliknąć odpowiednią opcję listy, a w efekcie zostaną wyświetlone szczegółowe informacje na jego temat.

Jeśli klikniesz opcję Latte na liście ListView, to aplikacja wyświetli szczegółowe informacje na temat latte.

Całą pozostałą część rozdziału poświęcimy na pokazanie, jak używać widoków list, by zaimplementować aplikację działającą w opisany powyżej sposób.

jesteś tutaj  251

Witamy w kafeterii Coffeina

część Napiszemy aplikacjęi dla kafeterii Coffeina

Zamiast tworzyć wszystkie aktywności kategorii i szczegółów/edycji składające się na całą aplikację dla kafeterii Coffeina, skoncentrujemy się wyłącznie na napojach. Napiszemy aktywność poziomu głównego wyświetlaną bezpośrednio po uruchomieniu aplikacji, aktywność kategorii prezentującą listę napojów oraz aktywność szczegółów/edycji przedstawiającą szczegółowe informacje o wybranym napoju.

Aktywność poziomu głównego Kiedy użytkownik uruchamia aplikację, zostanie wyświetlona aktywność poziomu głównego stanowiąca punkt wejścia do całej aplikacji. Aktywność ta będzie zawierała logo kafeterii Coffeina i listę elementów nawigacyjnych: Napoje, Przekąski oraz Kafeterie. Gdy użytkownik kliknie któryś z elementów tej listy, aplikacja odczyta klikniętą opcję i na jej podstawie uruchomi inną, wybraną aktywność. Na przykład jeśli użytkownik klinie element Napoje, to zostanie uruchomiona aktywność prezentująca listę napojów.

Logo kafeterii Coffeina i lista opcji. My zajmiemy się zaimplementowaniem opcji Napoje.

Aktywność kategorii z listą napojów Ta aktywność będzie uruchamiana, kiedy użytkownik kliknie opcję Napoje na liście nawigacyjnej prezentowanej przez aktywność poziomu głównego. Jej zadaniem jest wyświetlanie listy wszystkich napojów, które są dostępne w kafeteriach Coffeina. Użytkownik może kliknąć jeden z napojów, aby wyświetlić szczegółowe informacje na jego temat.

252

Rozdział 7.

Wyświetlimy tylko trzy napoje, choć w ofercie kafeterii Coffeina jest ich na pewno znacznie więcej.

Widoki list i adaptery

Aktywność szczegółów napoju Aktywność szczegółów napoju jest uruchamiana, kiedy użytkownik kliknie jedną z opcji listy napojów wyświetlanej przez aktywność kategorii. Ta aktywność odpowiada za wyświetlenie szczegółowych informacji o napoju wybranym przez użytkownika. Do tych informacji będzie należało zdjęcie napoju, jego nazwa oraz opis. Ta aktywność wyświetla szczegółowe informacje dotyczące konkretnego napoju.

Jak użytkownik porusza się po aplikacji? Użytkownik zaczyna nawigowanie po aplikacji od aktywności poziomu głównego, a następnie, klikając opcję Napoje, przechodzi do aktywności kategorii. Gdy kliknie wybrany napój, może przejść do kolejnej aktywności, prezentującej szczegółowe informacje o napoju.

Użytkownik klika element Napoje, co powoduje wyświetlenie listy napojów.

Kiedy użytkownik kliknie wybrany napój, zostaną wyświetlone informacje o tym napoju.

jesteś tutaj  253

Struktura aplikacji

Struktura aplikacji dla kafeterii Coffeina Nasza aplikacja składa się z trzech aktywności. Aktywność głównego poziomu nosi nazwę TopLevelActivity i umożliwia użytkownikowi poruszanie się po aplikacji. Aktywność DrinkCategoryActivity to aktywność kategorii, która wyświetla listę napojów. A ostatnia aktywność, DrinkActivity, służy do wyświetlania szczegółowych informacji o wybranym napój. Na razie wszystkie informacje dotyczące napoju będą przechowywane w klasie Javy. W jednym z kolejnych rozdziałów przeniesiemy je do bazy danych, a teraz chcemy się skoncentrować na implementacji innych aspektów aplikacji, nie zawracając sobie głowy bazami danych. Oto w jaki sposób aplikacja będzie działać.

1

Podczas uruchamiania aplikacji zostaje wyświetlona aktywność TopLevelActivity.

2

Na liście w aktywności TopLevelActivity użytkownik klika opcję Napoje, co powoduje uruchomienie aktywności DrinkCategoryActivity.

Ta aktywność korzysta z układu activity_top_level.xml. Jej działanie sprowadza się do wyświetlenia listy opcji: Napoje, Przekąski oraz Kafeterie.

Aktywność ta korzysta z układu activity_drink_category.xml i prezentuje listę napojów. Dane napojów są pobierane z pliku Drink.java.

3

Użytkownik klika jeden z napojów wyświetlony na liście w aktywności DrinkCategoryActivity, co powoduje wyświetlenie aktywności DrinkActivity.

Aktywność ta używa pliku układu o nazwie activity_drink.xml. Szczegółowe informacje o wybranym napoju są pobierane z pliku Drink.java.





activity_top_level.xml

1

Urządzenie

254

Rozdział 7.

TopLevelActivity.java

activity_drink_category.xml



activity_drink.xml

Drink.java

2

3

DrinkCategoryActivity.java

DrinkActivity.java

Widoki list i adaptery

Oto co mamy zamiar zrobić W ramach prac nad naszą aplikacją musimy wykonać kilka czynności:

1

Dodać do projektu klasę Drink i zasoby graficzne.

2

Utworzyć klasę TopLevelActivity i jej układ.

3

Utworzyć aktywność DrinkCategoryActivity i jej układ.

4

Ta klasa zawiera szczegółowe informacje o wszystkich dostępnych napojach. Jeśli chodzi o zasoby graficzne, to należy do nich logo kafeterii Coffeina i zdjęcia poszczególnych napojów.

To jest punkt wejścia do aplikacji. Aktywność musi wyświetlić logo kafeterii Coffeina i listę z opcjami do nawigowania po aplikacji. Oprócz tego po kliknięciu opcji Napoje aktywność TopLevelActivity musi spowodować uruchomienie aktywności DrinkCategoryActivity.

Aktywność DrinkCategoryActivity zawiera listę wszystkich dostępnych napojów. Po kliknięciu któregoś z napojów ma ona uruchomić aktywność DrinkActivity. Utworzyć aktywność DrinkActivity.

Aktywność DrinkActivity wyświetla szczegółowe informacje o napoju klikniętym przez użytkownika na liście w aktywności DrinkCategoryActivity.

Utworzenie projektu Projekt naszej nowej aplikacji możesz utworzyć w dokładnie taki sam sposób, w jaki tworzyłeś projekty w poprzednich rozdziałach. Utwórz projekt aplikacji o nazwie Coffeina, użyj nazwy domeny firmowej hfad.com, tak by kody zostały umieszczone w pakiecie com.hfad.coffeina. Jako minimalną wersję SDK wybierz API poziomu 19. W aplikacji utwórz pustą aktywność TopLevelActivity korzystającą z układu o nazwie activity_top_level. Upewnij się, że pole wyboru Backward Comaptibility (AppCompat) nie jest zaznaczone.

¨  Dodanie zasobów ¨  TopLevelActivity ¨  DrinkCategoryActivity ¨  DrinkActivity

jesteś tutaj  255

Klasa Drink

Klasa Drink Zaczniemy od dodania do aplikacji klasy Drink. Drink.java to zupełnie zwyczajny plik zawierający klasę napisaną w Javie, z której aktywności będą pobierać dane o napojach. Klasa Drink definiuje tablicę trzech napojów, przy czym każdy z nich będzie zawierał informacje o nazwie napoju, jego opis oraz identyfikator zasobu graficznego z jego zdjęciem. W eksploratorze Android Studio przejdź do widoku Project, zaznacz pakiet com.hfad.coffeina, umieszczony w katalogu app/src/main/java i wybierz z menu opcję File/New.../Java Class. Jako nazwę klasy wpisz Drink i upewnij się, że zostanie ona dodana do pakietu com.hfad.Coffeina. Następnie zastąp kod klasy wygenerowany przez Android Studio tym podanym poniżej: package com.hfad.coffeina; public class Drink { private String name; private String description; private int imageResourceId;

Każdy Drink ma nazwę i opis oraz identyfikator zasobu graficznego. zawiera Te identyfikatory zasobów określają zdjęcia napojów, do projektu na następnej stronie. które dodamy drinks to tablica trzech obiektów klasy Drink.

// drinks to tablica obiektów klasy Drink public static final Drink[] drinks = { new Drink(”Latte”, ”Czarne espresso z gorącym mlekiem i mleczną pianką.”, To są zdjęcia R.drawable.latte), napojów. 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. }; // Każdy Drink ma nazwę, opis oraz zasób graficzny private Drink(String name, String description, int imageResourceId) { this.name = name; this.description = description; this.imageResourceId = imageResourceId; }

Konstruktor klasy Drink

Coffeina

public String getDescription() { return description; } public String getName() { return name; }

app/src/main

To są metody get, akcesory, do pobierania informacji ze zmiennych prywatnych.

com.hfad.coffeina

public int getImageResourceId() { return imageResourceId; } public String toString() { return this.name; } }

256

Rozdział 7.

java

Łańcuchową reprezentacją obiektu Drink jest nazwa napoju.

Drink.java

Widoki list i adaptery

Pliki graficzne

¨  Dodanie zasobów ¨  TopLevelActivity ¨  DrinkCategoryActivity ¨  DrinkActivity

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. Te trzy pliki graficzne musimy teraz dodać do projektu, wraz z logo kafeterii Coffeina, które będzie używane w aktywności głównego poziomu. Kiedy dodasz już do projektu katalog drawable (o ile jeszcze nie istnieje), będziesz mógł skopiować do niego pliki starbuzz-logo.png, cappuccino.png, filter.png oraz latte.png, które znajdziesz w przykładach dołączonych do książki, w analogicznym podkatalogu drawable w katalogu Rozdzial_07/Coffeina/....

Android Studio mogło już dodać ten katalog podczas tworzenia projektu aplikacji. Jeśli tak się stało, nie będziesz musiał tworzyć go jeszcze raz.

Po dodaniu obrazów do projektu musimy zdecydować, czy chcemy wyświetlać różne obrazy na ekranach o różnej gęstości. My mamy zamiar zawsze wyświetlać obrazy o tej samej rozdzielczości, niezależnie od gęstości ekranu, dlatego do projektu dodaliśmy tylko jeden egzemplarz każdego z niezbędnych pików i umieściliśmy je wszystkie w jednym katalogu. Jeśli we własnych aplikacjach zdecydujesz się używać różnych wersji obrazów dla różnych gęstości ekranów, będziesz musiał umieszczać odpowiednie wersje plików graficznych w odpowiednich katalogach drawable* (zgodnie z informacjami podanymi w rozdziale 5.). Oto cztery pliki graficzne. Dodałeś je do Android Studio, przeciągając je do katalogu drawable.

Po dodaniu plików do projektu Android przypisuje każdemu z nich identyfikator o postaci R.drawable.nazwa_pliku (gdzie nazwa_ pliku to nazwa pliku, w którym jest zapisany obrazek). Na przykład plikowi latte.png zostanie nadany identyfikator R.drawable.latte, który będzie odpowiadał identyfikatorowi zasobu obrazka latte podanemu w kodzie klasy Drink. name: „Latte” description: „Czarne espresso z gorącym mlekiem i mleczną pianką.” Drink

imageResourceId: R.drawable.latte

Skoro dodaliśmy już do projektu klasę Drink i pliki graficzne, zajmijmy się implementacją aktywności. Zaczniemy od aktywności głównego poziomu.

Plikowi latte.png został nadany identyfikator R.drawable.latte.

R.drawable.latte

jesteś tutaj  257

TopLevelActivity

Układ aktywności głównego poziomu składa się z obrazka i listy

¨  Dodanie zasobów ¨  TopLevelActivity ¨  DrinkCategoryActivity ¨  DrinkActivity

Podczas tworzenia projektu nadaliśmy naszej aktywności głównego poziomu nazwę TopLevelActivity, a układ, z którego ona korzysta, zapisaliśmy w pliku activity_top_level.xml. Musimy teraz zmienić ten układ w taki sposób, by prezentował obrazek i listę.

To jest logo kafeterii Coffeina. Ten obrazek dodaliśmy do projektu na poprzedniej stronie.

Wszystkie te elementy dodamy jako statyczną listę opcji, a następnie sprawimy, by każdą z tych opcji można było kliknąć.

Sposób wyświetlania obrazów za pomocą komponentów ImageView znasz z poprzedniego rozdziału. W tym przypadku chcemy wyświetlić logo kafeterii Coffeina, więc utworzymy komponent ImageView, dla którego źródłem danych będzie plik graficzny starbuzz_logo.png. Oto kod definiujący ten komponent w pliku układu: Te elementy dodamy do pliku activity_ top_level.xml. Już niebawem przedstawimy kompletny kod tego pliku.

Stosując w aplikacjach komponenty ImageView, należy dodawać do nich atrybut android:contentDescription zawierający opis prezentowanego obrazu, takie rozwiązanie poprawia bowiem dostępność aplikacji. W naszym przypadku atrybut ten zawiera odwołanie do zasobu łańcuchowego, ”@string/starbuzz_logo”. Musimy zatem dodać ten zasób do pliku strings.xml:

... Logo kafeterii Coffeina

To już wszystko, co trzeba zrobić, by dodać obraz do układu, zajmijmy się więc listą.

258

Rozdział 7.

Dodanie opisu zawartości poprawi dostępność aplikacji.

Coffeina app/src/main res values

strings.xml

Widoki list i adaptery

Użycie widoku listy do wyświetlania listy opcji Jak już wspominaliśmy wcześniej, widok listy umożliwia wyświetlenie pionowej listy danych, której można użyć do nawigowania po aplikacji. Dlatego też dodamy do układu widok listy prezentujący trzy opcje, których później użyjemy do przechodzenia do innych aktywności.

Jak zdefiniować widok listy w kodzie XML?

android.view.View ...

android.view.ViewGroup ...

Widok listy możemy dodać do układu, używając elementu . Zawartość tak utworzonego widoku listy określamy za pomocą atrybutu android:entries, zawierającego identyfikator tablicy łańcuchów znaków. Poszczególne łańcuchy z tej tablicy zostaną następnie wyświetlone w widoku listy jako lista widoków tekstowych. Poniżej pokazaliśmy, w jaki sposób można dodać do układu widok listy, którego zawartość zostanie określona na podstawie tablicy łańcuchów znaków o nazwie options:

android.widget. AdapterView ...

Ten element definiuje widok listy.

Na następnej

Wartości wyświetlane na liście zostały zdefiniowane w tablicy options.

android.widget.ListView ...

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:

Coffeina

....

app/src/main

Napoje Przekąski

res

Kafeterie

values

....



W ten sposób w widoku listy zostaną wyświetlone trzy wartości: Napoje, Przekąski oraz Kafeterie. @array/options

ListView

Napoje Przekąski Kafeterie

strings.xml

strings.xml

Atrybut entries wypełnia komponent ListView wartościami pobranymi z tablicy options. Każdy z elementów komponentu ListView jest widokiem tekstowym.

jesteś tutaj  259

Kod układu

Kompletny kod układu aktywności głównego poziomu Oto pełny kod układu activity_top_level.xml (upewnij się, czy Twój układ będzie dokładnie taki sam jak nasz):

¨  Dodanie zasobów ¨  TopLevelActivity ¨  DrinkCategoryActivity ¨  DrinkActivity



layout



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 — wynika to z prostego faktu, że jeszcze nie kazaliśmy widokowi listy reagować na kliknięcia. Teraz mamy zamiar pokazać, jak sprawić, by widok listy reagował na kliknięcia i uruchamiał inną aktywność.

260

Rozdział 7.

To są wartości zapisane w tablicy options.

Widoki list i adaptery

Zapewnianie reakcji ListView na kliknięcia za pomocą obiektu nasłuchującego

Komponent ListView musi wiedzieć, że aktywność obchodzi to, co się z nim dzieje.

Elementy widoku listy mogą reagować na kliknięcia poprzez zaimplementowanie obiektu nasłuchującego (ang. event listener). Obiekt nasłuchujący pozwala nam oczekiwać na zdarzenia zachodzące w aplikacji, takie jak kliknięcie widoku, utrata lub uzyskanie miejsca wprowadzania, a nawet naciśnięcie przycisku sprzętowego na urządzeniu. Jeśli zaimplementujemy taki obiekt nasłuchujący, możemy określić, kiedy użytkownik wykonał określoną czynność — taką jak kliknięcie widoku — i zareagować na nią.

ListView

Aktywność

OnItemClickListener oczekuje na kliknięcia

Komponent ListView informuje aktywność o tym, że jeden z jego elementów został kliknięty, aby ta mogła na to zdarzenie zareagować.

Jeśli chcemy, aby elementy listy reagowały na kliknięcia, musimy zaimplementować obiekt typu OnItemClickListener i jego metodę onItemClick(). Obiekt nasłuchujący typu OnItemClickListener oczekuje na zdarzenia związane z kliknięciem, a jego metoda onItemClick() pozwala nam określić, jak należy na te kliknięcia zareagować. Metoda ta ma kilka parametrów, których możemy użyć do określenia, który element został kliknięty, na przykład pobrać referencję do klikniętego widoku, jego położenie na liście (liczone od 0) czy też identyfikator prezentowanych na nim danych. W przypadku naszej aplikacji chcemy, by po kliknięciu pierwszego elementu listy, elementu w położeniu o indeksie 0, została uruchomiona aktywność DrinkCategoryActivity. Jeśli zostanie kliknięty ten element, musimy utworzyć intencję odwołującą się do aktywności DrinkCategoryActivity. OnItemClickListener jest klasą Poniżej przedstawiliśmy kod obiektu nasłuchującego; na następnej stronie zagnieżdżoną, która jest umieszczona dodamy go do pliku TopLevelActivity: wewnątrz klasy AdapterView. ListView jest klasą pochodną klasy AdapterView.

AdapterView.OnItemClickListener itemClickListener =

new AdapterView.OnItemClickListener(){ public void onItemClick(AdapterView listView, Napoje to pierwszy element wyświetlony w widoku listy, co oznacza, że znajduje się on w miejscu o indeksie 0.

View itemView, int position, long id) {

if (position == 0) {

To widok, który został kliknięty (w naszym przypadku jest to widok listy).

Te parametry przekazują nam informacje o tym, który element listy został kliknięty i jakie jest jego położenie na liście.

Intent intent = new Intent(TopLevelActivity.this, DrinkCategoryActivity.class); startActivity(intent); } }

Intencja pochodzi z aktywności TopLevelActivity.

Ma ona spowodować uruchomienie aktywności DrinkCategoryActivity.

};

Po utworzeniu obiektu nasłuchującego musimy go dodać do komponentu ListView.

jesteś tutaj  261

setOnItemClickListener()

Dodanie obiektu nasłuchującego do widoku listy

¨  Dodanie zasobów ¨  TopLevelActivity ¨  DrinkCategoryActivity ¨  DrinkActivity

Po utworzeniu obiektu OnItemClickListener musimy powiązać go z widokiem listy. Służy do tego metoda ListView.setOnItemClickListener(). Ma ona tylko jeden argument — sam obiekt nasłuchujący: AdapterView.OnItemClickListener itemClickListener = new AdapterView.OnItemClickListener(){ public void onItemClick(AdapterView listView, ... } };

kod Dodamy ten kod do aktywności TopLevelActivity. Jej kompletny zamieściliśmy na kilku następnych stronach, dzięki czemu będziesz mógł zobaczyć ten fragment we właściwym kontekście.

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. Teraz wiesz już wszystko, co trzeba, by sprawić, że widok listy w aktywności TopLevelActivity będzie reagował na kliknięcia.

Kompletny kod pliku TopLevelActivity.java Poniżej przedstawiliśmy kompletny kod umieszczony w pliku TopLevelActivity.java. Zastąp nim oraz kodem zamieszczonym na następnej stronie kod wygenerowany podczas tworzenia projektu; następnie zapisz plik. package com.hfad.coffeina;

Coffeina

import android.app.Activity;

app/src/main

import android.os.Bundle; import android.content.Intent; import android.widget.AdapterView;

Używamy tych klas, więc musisz je zaimportować.

import android.widget.ListView;

com.hfad.coffeina

import android.view.View; public class TopLevelActivity extends Activity { Upewnij się, że aktywność dziedziczy po klasie Activity.

262

Rozdział 7.

java

TopLevel Activity.java

Widoki list i adaptery

Kod pliku TopLevelActivity.java (ciąg dalszy)

¨  Dodanie zasobów ¨  TopLevelActivity ¨  DrinkCategoryActivity

}

@Override ¨  DrinkActivity 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, Coffeina View v, int position, Implementujemy jego app/src/main long id) { metodę onItemClick(). if (position == 0) { Intent intent = new Intent(TopLevelActivity.this, java DrinkCategoryActivity.class); startActivity(intent); com.hfad.coffeina Jeśli użytkownik kliknie element Napoje, to } uruchamiamy aktywność DrinkCategoryActivity. } Utworzymy ją już za chwilę, więc nie przejmuj TopLevel }; się, jeśli Android Studio zacznie Cię ostrzegać, Activity.java ma. nie jej że // 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.

Co się stanie po uruchomieniu kodu aktywności TopLevelActivity? 1

Metoda onCreate() aktywności TopLevelActivity tworzy obiekt OnItemClickListener i dodaje go do widoku listy.

TopLevelActivity

2

ListView

onItemClickListener

Kiedy użytkownik klika jeden z elementów widoku listy, zostaje wywołana metoda onItemClick() obiektu OnItemClickListener.

Jeśli użytkownik kliknie opcję Napoje, to obiekt nasłuchujący OnItemClickListener utworzy intencję, która uruchomi aktywność DrinkCategoryActivity. onItemClick()

ListView

onItemClickListener

Intencja Musimy utworzyć tę aktywność

DrinkCategoryActivity

jesteś tutaj  263

Jesteś tutaj

Dokąd dotarliśmy?

¨  Dodanie zasobów ¨  TopLevelActivity ¨  DrinkCategoryActivity ¨  DrinkActivity

Dotychczas utworzyliśmy plik Drink.java oraz zaimplementowaliśmy aktywność TopLevelActivity i używany przez nią układ.

Ten plik dodaliśmy w pierwszej kolejności.

Utworzyliśmy aktywność TopLevelActivity i jej układ.





activity_top_level.xml

Urządzenie

activity_drink_category.xml



Drink.java

DrinkCategoryActivity.java

TopLevelActivity.java

activity_drink.xml

DrinkActivity.java

Tą aktywnością zajmiemy się w następnej kolejności.

Kolejną rzeczą, którą musimy się zająć, będzie utworzenie aktywności DrinkCategoryActivity i jej układu, tak by aplikacja mogła ją uruchomić po kliknięciu opcji Napoje w aktywności TopLevelActivity.

Nie istnieją

głupie pytania

P: Dlaczego musieliśmy tworzyć obiekt nasłuchujący,

żeby elementy komponentu ListView zaczęły reagować na kliknięcia? Czy nie wystarczyłoby użyć atrybutu android:onClick w kodzie XML układu?

O

: Atrybutu android:onClick można używać w kodzie XML układów wyłącznie w przyciskach lub w innych widokach, które są klasami pochodnymi klasy Button, takich jak CheckBox lub RadioButton.

ListView nie jest klasą pochodną klasy Button, zatem w jej przypadku zastosowanie atrybutu android:onClick nic by nie dało. To właśnie dlatego musimy zaimplementować własny obiekt nasłuchujący.

264

Rozdział 7.

Widoki list i adaptery

Ćwiczenie

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 (komponent TextView ma identyfikator text_view, a widok listy — list_view). Czy przedstawiony kod działa zgodnie z założeniami? A jeśli nie działa, to dlaczego?

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; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); final TextView textView = (TextView) findViewById(R.id.text_view); AdapterView.OnItemClickListener itemClickListener = new AdapterView.OnItemClickListener(){ public void onItemClick(AdapterView listView, View v, int position, long id) { TextView item = (TextView) v; textView.setText(item.getText()); } }; ListView listView = (ListView) findViewById(R.id.list_view); } }

jesteś tutaj  265

Rozwiązanie ćwiczenia

Ćwiczenie Rozwiązanie

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 (komponent TextView ma identyfikator text_view, a widok listy — list_view). Czy przedstawiony kod działa zgodnie z założeniami? A jeśli nie działa, to dlaczego?

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; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); final TextView textView = (TextView) findViewById(R.id.text_view); AdapterView.OnItemClickListener itemClickListener = new AdapterView.OnItemClickListener(){ public void onItemClick(AdapterView listView, View v, int position, To jest kliknięty elementy listy. To komponent TextView, więc możemy odczytać wyświetlany w nim tekst przy użyciu metody getText().

long id) { TextView item = (TextView) v; textView.setText(item.getText()); }

}; ListView listView = (ListView) findViewById(R.id.list_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.

266

Rozdział 7.

Widoki list i adaptery

Aktywność kategorii wyświetla dane jednej kategorii Jak już zaznaczyliśmy wcześniej, DrinkCategoryActivity jest przykładem aktywności kategorii. Aktywności tego rodzaju prezentują dane należące do jednej kategorii, przy czym bardzo często wyświetlają je w formie listy. Takiej aktywności można użyć, by przejść do aktywności prezentującej informacje szczegółowe.

¨  Dodanie zasobów ¨  TopLevelActivity ¨  DrinkCategoryActivity ¨  DrinkActivity

W naszym przykładzie użyjemy aktywności DrinkCategoryActivity, by wyświetlić listę napojów. Kiedy użytkownik kliknie jeden z tych napojów, zostaną wyświetlone szczegółowe informacje o tym napoju.

Kiedy użytkownik kliknie element Napoje, zostanie uruchomiona aktywność DrinkCategoryActivity.

Aktywność DrinkCategoryActivity wyświetla listę napojów. Kiedy użytkownik kliknie jeden z nich, zostanie uruchomiona aktywność DrinkActivity, która wyświetli szczegółowe informacje o wybranym napoju.

Utworzenie aktywności DrinkCategoryActivity Aby móc rozpocząć prace nad kolejnym zadaniem z naszej listy, musimy utworzyć aktywność prezentującą jeden widok listy z nazwami wszystkich dostępnych napojów. A zatem w katalogu app/src/main/java zaznacz pakiet com.hfad.coffeina, a następnie wybierz z menu głównego Android Studio opcję File/New…/Activity/Empty Activity. Jako nazwę aktywności wpisz DrinkCategoryActivity, a w polu nazwy układu: activity_drink_category, ponadto upewnij się, że kod aktywności zostanie umieszczony w pakiecie com.hfad.coffeina, i usuń zaznaczenie z pola wyboru Backwards Compatibility (AppCompat).

W niektórych wersjach Android Studio możesz także zostać zapytany o to, jaki powinien być język źródłowy aktywności. Jeśli faktycznie pojawi się takie pytanie, wybierz Javę.

Na następnej stronie zaktualizujemy kod układu tej nowej aktywności.

jesteś tutaj  267

Adaptery

Aktualizacja układu activity_drink_category.xml Oto kod pliku activity_drink_category.xml(kursywa). Jak widać jest to prosty układ liniowy zawierający jedynie widok ListView. Zaktualizuj swoją wersję pliku activity_ drink_category.xml tak by była identyczna z kodem zamieszczonym poniżej:

¨  Dodanie zasobów ¨  TopLevelActivity ¨  DrinkCategoryActivity ¨  DrinkActivity



Istnieje jedna kluczowa różnica pomiędzy widokiem ListView tworzonym w tym pliku, oraz tym, który utworzyliśmy w pliku activity_top_activity.xml: tutaj nie ma atrybutu android:entries. Ale dlaczego? W pliku activity_top_level.xml do powiązania danych z widokiem listy zastosowaliśmy atrybut android:entries. Rozwiązanie to działało, gdyż dane były przechowywane w formie zasobu, jako statyczna tablica łańcuchów znaków. Tablica ta została zdefiniowana w pliku strings.xml, dzięki czemu bez problemów mogliśmy się do niej odwołać w następujący sposób: android:entries=”@array/options”

gdzie options było nazwą tablicy łańcuchów znaków. Jednak atrybut android:entries można stosować wyłącznie w przypadku, gdy dane pochodzą ze statycznej tablicy zdefiniowanej w pliku strings.xml. A co we wszystkich innych sytuacjach? Co zrobić, gdy dane pochodzą z tablicy utworzonej programowo w kodzie Javy albo z bazy danych? W takim przypadku zastosowanie atrybutu android:entries nie zadziała. Jeśli musimy powiązać widok listy z danymi pochodzącymi z innego źródła niż zasób będący tablicą łańcuchów znaków, to będziemy musieli zastosować inne rozwiązanie — będziemy musieli napisać w aktywności kod, który odpowiednio powiąże te dane z widokiem listy. W naszym przypadku musimy powiązać widok listy z tablicą drinks w klasie Drink.

268

Rozdział 7.

java

Ten układ m tylko jeden usi zawierać widok ListV iew

.

com.hfad.coffeina TopLevel Activity.java

Widoki list i adaptery

W przypadku danych statycznych należy użyć adaptera

¨  Dodanie zasobów ¨  TopLevelActivity ¨  DrinkCategoryActivity

Jeśli w widoku listy musimy wyświetlić dane, które pochodzą ze źródła innego niż plik strings.xml (na przykład z tablicy zdefiniowanej w kodzie Java lub z bazy danych), to musimy ¨  DrinkActivity użyć adaptera. Adapter działa jak swoisty most łączący źródło danych z widokiem listy: Naszym źródłem danych będzie tablica, ale równie dobrze moglibyśmy użyć bazy danych lub usługi sieciowej.

ListView

Adapter

Źródło danych

Adapter stanowi most łączący widok listy ze źródłem danych. Adaptery pozwalają widokom list wyświetlać dane pochodzące z wielu różnych źródeł.

Dostępnych jest kilka różnych rodzajów adapterów. Na razie skoncentrujemy się na adapterze ArrayAdapter. ArrayAdapter to typ adaptera, który służy do wiązania tablic z widokami. Można go używać z wieloma klasami pochodnymi klasy AdapterView, czyli na przykład z widokami list i listami rozwijanymi. W naszym przykładzie zastosujemy adapter ArrayAdapter do wyświetlenia w widoku listy danych pochodzących z tablicy Drink.drinks. To jest nasza tablica.

Drink. drinks

Utworzymy adapter ArrayAdapter, aby powiązać widok listy z naszą tablicą.

Array Adapter

To jest nasz widok listy.

ListView

Adapter działa jak most łączący widok, obiekt typu View, ze źródłem danych. ArrayAdapter jest typem adaptera wyspecjalizowanego do operowania na tablicach.

Aby skorzystać z adaptera ArrayAdapter, należy go najpierw zainicjować i dołączyć do widoku listy.

jesteś tutaj  269

Adapter ArrayAdapter

Łączenie widoków ListView z tablicami przy użyciu adaptera

¨  Dodanie zasobów ¨  TopLevelActivity ¨  DrinkCategoryActivity ¨  DrinkActivity

Aby zastosować adapter ArrayAdapter, należy go zainicjować, a następnie dołączyć do widoku listy. Inicjalizacja adaptera ArrayAdapter wymaga w pierwszej kolejności określenia typu danych przechowywanych w tablicy, którą chcemy powiązać z widokiem listy. W wywołaniu konstruktora adaptera ArrayAdapter można podać trzy parametry: obiekt Context (którym zazwyczaj jest bieżąca aktywność), zasób układu określającego, jak mają być prezentowane poszczególne elementy tablicy, oraz samą tablicę. Poniżej przedstawiliśmy kod, który tworzy adapter do wyświetlania danych typu Drink przechowywanych w tablicy Drink.drinks (na następnej stronie dodasz ten kod do pliku DrinkCategoryActivity.java): ArrayAdapter listAdapter = new ArrayAdapter( this odwołuje się do bieżącej aktywności. Activity jest klasą pochodną klasy Context.

this, android.R.layout.simple_list_item_1, Drink.drinks);

Tablica

To jest wbudowany zasób układu. Informuje on adapter, że poszczególne elementy tablicy mają być wyświetlane w pojedynczych widokach tekstowych.

Po utworzeniu adaptera musimy go powiązać z widokiem listy; służy do tego metoda setAdapter() klasy ListView: ListView listDrinks = (ListView) findViewById(R.id.list_drinks); listDrinks.setAdapter(listAdapter);

Za kulisami adapter odczyta każdy element tablicy, skonwertuje elementy do postaci łańcuchów znaków za pomocą metody toString(), a następnie umieści je w widokach tekstowych. Na koniec wszystkie utworzone w ten sposób widoki tekstowe zostaną wyświetlone jako odrębne wiersze widoku ListView.

To są napoje pochodzące z tablicy drinks. Każdy wiersz widoku listy jest jednym widokiem tekstowym i każdy z tych widoków wyświetla nazwę odrębnego napoju.

270

Rozdział 7.

Widoki list i adaptery

Dodanie adaptera ArrayAdapter do aktywności DrinkCategoryActivity

¨  Dodanie zasobów ¨  TopLevelActivity ¨  DrinkCategoryActivity ¨  DrinkActivity

Zmienimy teraz kod aktywności zapisany w pliku DrinkCategoryActivity.java w taki sposób, by używał adaptera ArrayAdapter do pobierania danych o napojach zdefiniowanych w klasie Drink. Ten kod umieścimy w metodzie onCreate(), tak by widok listy był wypełniany danymi podczas tworzenia aktywności. Poniżej przedstawiliśmy kompletny kod aktywności (zaktualizuj swój plik, tak aby był taki sam jak nasz, a następnie zapisz zmiany): package com.hfad.coffeina; import android.app.Activity;

Używamy tych klas, więc musimy je zaimportować.

import android.os.Bundle; import android.widget.ArrayAdapter; import android.widget.ListView;

Coffeina app/src/main

Upewnij się, że Twoja aktywność dziedziczy po klasie Activity.

public class DrinkCategoryActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) {

java com.hfad.coffeina

DrinkCategory Activity.java

super.onCreate(savedInstanceState); setContentView(R.layout.activity_drink_category); ArrayAdapter listAdapter = new ArrayAdapter( this, Ten fragment kodu wypełnia android.R.layout.simple_list_item_1, widok listy Drink.drinks); danymi pochodzącymi ListView listDrinks = (ListView) findViewById(R.id.list_drinks); z tablicy drinks. listDrinks.setAdapter(listAdapter);

}

To wszystkie zmiany, które musisz wprowadzić w kodzie aktywności, by w widoku listy były wyświetlane dane napojów pochodzących z tablicy Drink.drinks. Przeanalizujmy teraz, co się stanie po uruchomieniu aplikacji.

jesteś tutaj  271

Co się dzieje?

Co się stanie po wykonaniu kodu? 1

Kiedy użytkownik klika opcję Napoje, zostaje uruchomiona aktywność DrinkCategoryActivity. Jej układ to układ liniowy — LinearLayout — który zawiera widok ListView.

DrinkCategoryActivity

2

ListView

Aktywność DrinkCategoryActivity tworzy adapter ArrayAdapter — adapter, który operuje na tablicy obiektów Drink.

DrinkCategoryActivity

3

Linear-Layout

ArrayAdapter

Adapter ArrayAdapter pobiera dane z tablicy drinks zdefiniowanej w klasie Drink.

Adapter używa metody Drink.toString(), by zwrócić nazwę każdego z napojów. Drink.toString()

DrinkCategoryActivity

4

Drink.drinks

ArrayAdapter

Aktywność DrinkCategoryActivity wywołuje metodę setAdapter(), określając w ten sposób, że widok listy ma używać utworzonego wcześniej adaptera ArrayAdapter.

Widok listy używa adaptera do wyświetlenia listy nazw napojów.

ListView

setAdapter()

Drink.toString()

DrinkCategoryActivity ArrayAdapter

272

Rozdział 7.

Drink.drinks

Widoki list i adaptery

Jazda próbna aplikacji Po uruchomieniu aplikacji wyświetlana jest aktywność TopLevelActivity, zupełnie tak samo, jak było wcześniej. Po kliknięciu elementu Napoje zostaje uruchomiona aktywność DrinkCategoryActivity. Wyświetla ona listę nazw wszystkich napojów zdefiniowanych w klasie Drink.

¨  Dodanie zasobów ¨  TopLevelActivity ¨  DrinkCategoryActivity ¨  DrinkActivity

Kliknij element Napoje, by wyświetlić tablicę napojów.

Na następnej stronie przyjrzymy się temu, co już zrobiliśmy w aplikacji i co jeszcze nam pozostało do zrobienia.

jesteś tutaj  273

Jesteś tutaj

¨  Dodanie zasobów ¨  TopLevelActivity ¨  DrinkCategoryActivity

Przegląd aplikacji, czyli dokąd dotarliśmy Do tej pory dodaliśmy do aplikacji plik Drink.java i utworzyliśmy aktywności TopLevelActivity i DrinkCategoryActivity.

¨  DrinkActivity Zaimplementowaliśmy plik Drink.java.



activity_top_level.xml





activity_drink_category.xml

Drink.java

activity_drink.xml

3 1

2

TopLevelActivity.java

Urządzenie

DrinkCategoryActivity.java

Utworzyliśmy te aktywności oraz ich układy.

Oto jak obecnie działa nasza aplikacja:

1

Po uruchomieniu aplikacji zostaje wyświetlona aktywność TopLevelActivity.

Aktywność ta wyświetla listę opcji: Napoje, Przekąski, Kafeterie.

2

Na liście w aktywności TopLevelActivity użytkownik klika opcję Napoje.

W efekcie zostaje uruchomiona aktywność DrinkCategoryActivity, która wyświetla listę napojów.

3

Wartości używane do wyświetlenia na liście w aktywności DrinkCategoryActivity pochodzą z klasy Drink.java.

Kolejnym zadaniem, którym się zajmiemy, będzie zaimplementowanie w kodzie aktywności DrinkCategoryActivity uruchamiania aktywności DrinkActivity i przekazywanie do niej szczegółowych danych o tym, który napój został kliknięty.

274

Rozdział 7.

DrinkActivity.java Klasy DrinkActivity jeszcze nie napisaliśmy.

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.

Pamiętasz? Listy rozwijane przedstawiliśmy w Rozdziale 5.

Ta aktywność nie jest używana w naszej aplikacji.

... public class MainActivity extends Activity {

String[] colors = new String[] {”czerwony”, ”pomarańczowy”, ”żółty”, ”zielony”, ”niebieski”}; @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);

Ten układ wyświetla każdą wartość z tablicy jako pojedynczy wiersz listy rozwijanej.

spinner. ...........(adapter); } } Uwaga: Każdego fragmentu kodu z basenu można użyć tylko jeden raz!

colors setAdapter

String

this colors

Spinner

String

Odpowiedź znajdziesz na stronie 287. jesteś tutaj  275

Obsługa kliknięć

Jak obsługiwaliśmy kliknięcia w aktywności TopLevelActivity?

¨  Dodanie zasobów ¨  TopLevelActivity ¨  DrinkCategoryActivity ¨  DrinkActivity

We wcześniejszej części rozdziału musieliśmy zadbać o to, by aktywność TopLevelActivity reagowała na kliknięcia pierwszego elementu listy — opcji Napoje — uruchomieniem aktywności DrinkCategoryActivity. W tym celu musieliśmy utworzyć obiekt typu OnItemClickListener, zaimplementować jego metodę onItemClick(), a następnie przypisać go do widoku listy. Poniżej przypominamy ten fragment kodu: Tworzymy obiekt nasłuchujący.

AdapterView.OnItemClickListener itemClickListener =

new AdapterView.OnItemClickListener(){ public void onItemClick(AdapterView listView,

To jest widok listy.

View itemView, int position, long id) {

Widok klikniętego elementu, jego położenie na liście oraz identyfikator wiersza danych, na których lista operuje.

if (position == 0) { Intent intent = new Intent(TopLevelActivity.this, DrinkCategoryActivity.class); startActivity(intent); } } }; ListView listView = (ListView) findViewById(R.id.list_options); listView.setOnItemClickListener(itemClickListener);

Musieliśmy utworzyć obiekt nasłuchujący w taki sposób, gdyż w odróżnieniu na przykład od przycisków widoki listy nie są domyślnie przygotowywane, by odpowiadać na kliknięcia. A zatem w jaki sposób możemy sprawić, aby nasza aktywność DrinkCategoryActivity obsługiwała kliknięcia?

276

Rozdział 7.

Dodajemy odbiorcę zdarzeń do widoku listy.

Widoki list i adaptery

Przekazanie identyfikatora klikniętego elementu poprzez dodanie go do intencji

¨  Dodanie zasobów ¨  TopLevelActivity ¨  DrinkCategoryActivity ¨  DrinkActivity

W razie stosowania aktywności listy do wyświetlania kategorii zazwyczaj będziemy także używali metody onItemClick() do uruchamiania innej aktywności prezentującej szczegółowe informacje o elemencie klikniętym przez użytkownika. W tym celu musimy stworzyć intencję, która uruchomi tę drugą aktywność. Następnie musimy dodać do intencji identyfikator klikniętego elementu, tak by druga aktywność, kiedy zostanie już uruchomiona, mogła z niego skorzystać. W naszym przypadku chcemy uruchomić aktywność DrinkActivity i przekazać do niej identyfikator wybranego napoju. Aktywność DrinkActivity będzie mogła skorzystać z tych informacji, by wyświetlić szczegółowe informacje o wybranym napoju. Poniżej przedstawiliśmy kod metody onListItemClick():

Intencja drinkId DrinkCategoryActivity

DrinkActivity

Aktywność DrinkCategoryActivity musi uruchomić aktywność DrinkActivity.

Intent intent = new Intent(DrinkCategoryActivity.this, DrinkActivity.class); intent.putExtra(DrinkActivity.EXTRA_DRINKID, (int) id); startActivity(intent); Nazwę informacji dodatkowej zapisywanej w intencji zdefiniowaliśmy jako stałą, tak aby obie aktywności, DrinkCategoryActivity i DrinkActivity, używały tego samego łańcucha znaków. Tę stałą dodamy do klasy DrinkActivity już niedługo, kiedy się nią zajmiemy.

Do intencji dodajemy identyfikator klikniętego elementu listy. To indeks napoju w tablicy drinks.

Przekazywanie identyfikatora klikniętego elementu jest często stosowanym rozwiązaniem, gdyż jednocześnie jest to identyfikator nieprzetworzonych danych, na których operuje widok listy. Jeśli te dane pochodzą z tablicy, to ten indeks będzie jednocześnie indeksem elementu tej tablicy. Jeśli dane pochodzą z bazy danych, to zazwyczaj będzie to identyfikator rekordu tabeli. Przekazywanie identyfikatora klikniętego elementu w taki sposób zapewnia drugiej aktywności możliwość prostego pobrania i wyświetlenia danych. To już wszystko, czego nam potrzeba, by sprawić, aby aktywność DrinkCategoryActivity uruchomiła aktywność DrinkActivity i przekazała jej informację o tym, który napój został kliknięty. Pełny kod aktywności zamieściliśmy na następnej stronie.

jesteś tutaj  277

Kod DrinkCategoryActivity

Kompletny kod aktywności DrinkCategoryActivity Oto kompletny kod aktywności DrinkCategoryActivity, umieszczony w pliku DrinkCategoryActivity.java (dodaj do niego nową metodę, a następnie zapisz zmiany).

¨  Dodanie zasobów ¨  TopLevelActivity ¨  DrinkCategoryActivity ¨  DrinkActivity

package com.hfad.coffeina; import import import import import import import

android.app.Activity; android.os.Bundle; android.widget.ArrayAdapter; android.widget.ListView; android.view.View; android.content.Intent; android.widget.AdapterView;

Coffeina app/src/main

Używamy tych dodatkowych klas, więc musimy je zaimportować.

java com.hfad.coffeina

public class DrinkCategoryActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_drink_category); ArrayAdapter listAdapter = new ArrayAdapter( this, android.R.layout.simple_list_item_1, Drink.drinks); ListView listDrinks = (ListView) findViewById(R.id.list_drinks); listDrinks.setAdapter(listAdapter);

DrinkCategory Activity.java

Tworzymy obiekt, który będzie nasł uchiwać kliknięć elementów listy.

// Tworzymy obiekt nasłuchujący. AdapterView.OnItemClickListener itemClickListener = new AdapterView.OnItemClickListener(){ public void onItemClick(AdapterView listDrinks, View itemView, Ta metoda zostanie wywołana int position, . listy po kliknięciu elementu long id) { //Przekazujemy kliknięty napój do DrinkActivity. Kiedy użytkownik kliknie napój, przekazujemy Intent intent = new Intent(DrinkCategoryActivity.this, jego identyfikator do DrinkActivity.class); aktywności DrinkActivity intent.putExtra(DrinkActivity.EXTRA_DRINKID, (int) id); i uruchamiamy ją. startActivity(intent); do Aktywność DrinkActivity dodamy nie więc lę, } chwi za już aplikacji io przejmuj się jeśli Android Stud }; uskarża się, że jej nie ma.

//Przypisujemy obiekt nasłuchujący do widoku listy. listDrinks.setOnItemClickListener(itemClickListener); } }

278

Rozdział 7.

Widoki list i adaptery

¨  Dodanie zasobów ¨  TopLevelActivity ¨  DrinkCategoryActivity ¨  DrinkActivity

Aktywność szczegółów wyświetla informacje o jednym rekordzie 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. Nasza aktywność DrinkActivity będzie wyświetlała szczegółowe informacje o napoju klikniętym przez użytkownika. Obiekty klasy Drink przechowują nazwę napoju, jego opis oraz identyfikator zasobu graficznego. I to właśnie te dane wyświetlimy w naszym układzie. Umieścimy w nim obraz napoju określony przez identyfikator zasobu oraz dwa widoki tekstowe prezentujące odpowiednio nazwę i opis napoju. Aby utworzyć aktywność, w katalogu /app/src/main/java zaznacz pakiet com.hfac.coffeina, a następnie wybierz z menu głównego Android Studio opcję File/New…/Activity/Empty Activity. Tworzonej aktywności nadaj nazwę DrinkActivity, a jej plikowi układu nazwę activity_ drink; upewnij się, że kody aktywności zostaną umieszczone w pakiecie com.hfad.coffeina, i usuń zaznaczenie z pola wyboru Backwards Compatibility (AppCompat). Następnie zastąp wygenerowaną zawartość pliku activity_drink.xml poniższym kodem:



Nie zapomnij utworzyć nowej aktywności. Jeśli zostaniesz zapytany o język źródłowy aktywności, wybierz Javę.

Coffeina app/src/main res layout

activity_drink.xml



Skoro już utworzyliśmy układ dla naszej aktywności szczegółów, możemy się zająć wyświetleniem informacji w jego widokach.

jesteś tutaj  279

Pobranie napoju

Pobranie danych z intencji Jak się przekonałeś, aby sprawić, że aktywność kategorii będzie uruchamiać aktywność szczegółów, zadbaliśmy o to, by elementy widoku listy w aktywności kategorii odpowiadały na kliknięcia. Kiedy któryś z elementów tej listy zostanie kliknięty, tworzymy intencję, która spowoduje uruchomienie aktywności szczegółów. W tej intencji, jako informacja dodatkowa, przekazywany jest jednocześnie identyfikator klikniętego elementu.

¨  Dodanie zasobów ¨  TopLevelActivity ¨  DrinkCategoryActivity ¨  DrinkActivity

Po uruchomieniu aktywności szczegółów aktywność ta może pobrać te dodatkowe informacje z intencji i użyć ich do pobrania danych i wyświetlenia ich w widokach używanego układu. W naszym przypadku możemy użyć informacji przekazanych w intencji, która doprowadziła do uruchomienia aktywności DrinkActivity, aby pobrać szczegółowe informacje o napoju klikniętym przez użytkownika. Pisząc kod aktywności DrinkCategoryActivity, identyfikator klikniętego napoju dodaliśmy do intencji jako informacje dodatkowe. Nadaliśmy mu przy tym nazwę DrinkActivity.EXTRA_DRINKID, którą teraz musimy zdefiniować w klasie DrinkActivity jako stałą: public static final String EXTRA_DRINKID = ”drinkId”;

Jak się dowiedziałeś w rozdziale 3., intencję, która doprowadziła do uruchomienia aktywności, można pobrać za pomocą metody getIntent(). Jeśli taka intencja zawiera dodatkowe informacje, to można je pobrać, używając jednej z metod get*(). Poniżej przedstawiliśmy kod, który pozwala pobrać wartość EXTRA_DRINKNO: int drinkId = (Integer)getIntent().getExtras().get(EXTRA_DRINKID);

Po pobraniu informacji z intencji możemy ich użyć do pobrania danych, które chcemy wyświetlić. W naszym przypadku użyjemy zmiennej drinkId, by pobrać informacje o napoju wybranym przez użytkownika. Zmiana drinkId zawiera identyfikator napoju — indeks informacji o napoju zapisanych w tablicy drinks. Oznacza to, że szczegółowe dane napoju klikniętego przez użytkownika możemy pobrać w następujący sposób: Drink drink = Drink.drinks[drinkId];

Ta instrukcja zwróci obiekt Drink zawierający wszystkie informacje, których potrzebujemy do zaktualizowania widoku w aktywności: name=”Latte” description=”Czarne espresso z gorącym mlekiem i mleczną pianką.” imageResourceId=R.drawable.latte drink

280

Rozdział 7.

Widoki list i adaptery

Wypełnienie widoków danymi Aktualizując widoki w aktywności szczegółów, musimy się upewnić, że wyświetlane przez nie dane będą odpowiadać tym, które zostały przekazane w intencji. Nasza aktywność szczegółów zawiera dwa widoki tekstowe i jeden ImageView. Musimy się zatem upewnić, że każdy z tych widoków zostanie zaktualizowany i będzie prezentował szczegółowe informacje o wybranym napoju.

name description imageResourceId drink

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.

... // Pobieramy identyfikator napoju z intencji int drinkId = (Integer)getIntent().getExtras().get(EXTRA_DRINKID); Drink drink = Drink.drinks[drinkId];

// Wyświetlamy nazwę napoju TextView name = (TextView)findViewById(R.id.name); name. ............ (drink.getName());

// Wyświetlamy opis napoju

setText

setContentDescription

TextView description = (TextView)findViewById(R.id.description); description. ............... (drink.getDescription());

setContent

... setImageResourceId

// Wyświetlamy zdjęcie napoju ImageView photo = (ImageView)findViewById(R.id.photo); photo. ................ (drink.getImageResourceId());

setImageResource setText

photo. ..................... (drink.getName());

jesteś tutaj  281

Rozwiązanie magnesików

Magnesiki z napojami. Rozwiązanie 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.

... // Pobieramy identyfikator napoju z intencji int drinkNo = (Integer)getIntent().getExtras().get(EXTRA_DRINKID); Drink drink = Drink.drinks[drinkId];

// Wyświetlamy nazwę napoju TextView name = (TextView)findViewById(R.id.name); setText name. ............ (drink.getName());

Użyj metody setText(), by ustawić tekst wyświetlany w widokach tekstowych.

// Wyświetlamy opis napoju TextView description = (TextView)findViewById(R.id.description); description. ............... (drink.getDescription()); setText ...

Źródło obrazka określiliśmy, używając metody setImageResource().

// Wyświetlamy zdjęcie napoju ImageView photo = (ImageView)findViewById(R.id.photo);

To wywołanie jest potrzebne, żeby poprawić dostępność aplikacji.

setImageResource photo. ..................... (drink.getImageResourceId());

setContentDescription (drink.getName()); photo. ......................... Te magnesiki nie były potrzebne.

setImageResourceId

setContent

282

Rozdział 7.

Widoki list i adaptery

Kod aktywności DrinkActivity

¨  Dodanie zasobów ¨  TopLevelActivity ¨  DrinkCategoryActivity ¨  DrinkActivity

Poniżej przedstawiliśmy kod zapisany w pliku DrinkActivity.java (zastąp cały kod wygenerowany przez kreator kodem przedstawionym poniżej, a następnie zapisz plik): package com.hfad.coffeina; import android.app.Activity; import android.os.Bundle;

Używamy tych klas, więc musimy je zaimportować.

app/src/main

import android.widget.ImageView; import android.widget.TextView;

Coffeina

Upewnij się, że aktywność dziedziczy po klasie Activity.

java com.hfad.coffeina

public class DrinkActivity extends Activity { public static final String EXTRA_DRINKID = ”drinkId”; @Override

DrinkActivity.java

Dodaj EXTRA_DRINKID jako stałą.

protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_drink); // Pobieramy identyfikator napoju z intencji int drinkId = (Integer)getIntent().getExtras().get(EXTRA_DRINKID); Drink drink = Drink.drinks[drinkId]; // Wyświetlamy nazwę napoju

Użyj zmiennej drinkId do pobrania szczegółowych informacji o napoju klikniętym przez użytkownika.

TextView name = (TextView)findViewById(R.id.name); name.setText(drink.getName()); // Wyświetlamy opis napoju TextView description = (TextView)findViewById(R.id.description);

Wypełnij widoki danymi o napoju.

description.setText(drink.getDescription()); // Wyświetlamy zdjęcie napoju ImageView photo = (ImageView)findViewById(R.id.photo); photo.setImageResource(drink.getImageResourceId()); photo.setContentDescription(drink.getName()); }

}

jesteś tutaj  283

Co się dzieje?

¨  Dodanie zasobów ¨  TopLevelActivity ¨  DrinkCategoryActivity ¨  DrinkActivity

Co się stanie po uruchomieniu aplikacji? 1

Kiedy użytkownik uruchamia aplikację, zostaje wyświetlona aktywność TopLevelActivity.

TopLevelActivity Urządzenie

2

Metoda onCreate() aktywności TopLevelActivity tworzy obiekt OnItemClickListener i dodaje go do komponentu ListView aktywności.

TopLevelActivity

3

ListView

OnItemClickListener

Kiedy użytkownik klika jeden z elementów na liście ListView, 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

4

OnItemClickListener

DrinkCategoryActivity

DrinkCategoryActivity wyświetla pojedynczą kontrolkę ListView.

Widok listy aktywności DrinkCategoryActivity używa adaptera ArrayAdapter do wyświetlenia listy nazw napojów.

ListView

DrinkCategoryActivity ArrayAdapter

284

Rozdział 7.

Drink.drinks

Widoki list i adaptery

Ciąg dalszy historii 5

Kiedy użytkownik wybiera jeden z napojów wyświetlony w widoku ListView, w aktywności DrinkCategoryActivity zostaje wywołana metoda onItemClick() obiektu OnItemClickedListener. onItemClick()

DrinkCategoryActivity

6

onItemClickListener

ListView

Metoda onItemClick() aktywności DrinkCategoryActivity tworzy intencję, która uruchamia aktywność DrinkActivity, dodając do niej identyfikator napoju jako informację dodatkową. Intencja drinkId=0 DrinkCategoryActivity

7

DrinkActivity

Zostaje uruchomiona aktywność DrinkActivity.

Aktywność ta pobiera identyfikator napoju przekazany w intencji, pobiera informacje o tym napoju z klasy Drink, a następnie używa ich do zaktualizowania widoków.

drinks[0]? Latte, świetny wybór. To wszystko, co wiem o latte.

drinks[0]

DrinkActivity

Latte

Drink

jesteś tutaj  285

Jazda próbna

Jazda próbna aplikacji Kiedy uruchomisz aplikację, tak jak wcześniej zostanie uruchomiona aktywność TopLevelActivity. Zaimplementowaliśmy wyłącznie część aplikacji dotyczącą napojów. Klikanie pozostałych opcji tej listy nie da żadnych rezultatów.

Gdy klikniesz opcję Napoje, zostanie uruchomiona aktywność DrinkCategoryActivity. Wyświetla ona listę wszystkich napojów opisanych w klasie Drink.

Kiedy klikniesz któryś z wyświetlonych napojów, zostanie uruchomiona aktywność DrinkActivity, a aplikacja wyświetli szczegółowe informacje na temat wybranego napoju.

Te trzy aktywności pokazują, w jaki sposób można tworzyć strukturę aplikacji, dzieląc ją na aktywność głównego poziomu, aktywność kategorii oraz aktywność szczegółów/edycji. W dalszej części książki, a konkretnie w rozdziale 15., wrócimy jeszcze do aplikacji kafeterii Coffeina i pokażemy, jak można pobierać napoje z bazy danych.

286

Rozdział 7.

Kliknęliśmy opcję Latte…

…i oto szczegółowe informacje o latte.

Widoki list i adaptery

Zagadkowy basen. Rozwiązanie 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. ... public class MainActivity extends Activity { String[] colors = new String[] {„czerwony”, „pomarańczowy”, „żółty”, „zielony”, „niebieski”}; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);

Używamy tablicy typu String.

setContentView(R.layout.activity_main); Spinner ) findViewById(R.id.spinner); Spinner spinner = ( ........ ArrayAdapter< ...... String > adapter = new 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

jesteś tutaj  287

Przybornik

Rozdział 7.

Twój przybornik do Androida Opanowałeś już rozdział 7. i dodałeś do swojego przybornika z narzędziami znajomość widoków list i projektowania aplikacji.

j Pełny kod przykładowe j ane tow zen aplikacji pre z żes mo ale dzi roz w tym P FT ra we ser z rać pob wydawnictwa Helion: klady/ ftp://ftp.helion.pl/przy p .zi andrr2

CELNE SPOSTRZEŻENIA 





288

Podziel pomysły dotyczące aplikacji na aktywności głównego poziomu, aktywności kategorii oraz aktywności szczegółów/ edycji. Zastosuj aktywność kategorii, by przechodzić z aktywności głównego poziomu do aktywności szczegółów/edycji. Komponent ListView wyświetla elementy w formie listy. Dodajemy go do układów, używając elementu . Do określania źródła elementów listy w kodzie 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.

Rozdział 7.









Adaptery pełnią rolę mostów łączących widoki typu AdapterView ze źródłami danych. Widoki ListView i Spinner są przykładami klas dziedziczących po AdapterView. ArrayAdapter to typ adaptera, który operuje na przekazanej tablicy. Sposób obsługi kliknięć przycisków można określać w kodzie układu, używając atrybutu android:onClick. W pozostałych przypadkach obsługa kliknięć wymaga utworzenia obiektu nasłuchującego i zaimplementowania jego odpowiedniej metody.

8. Biblioteki wsparcia i paski aplikacji

Na skróty Widzisz, Kasztanko, mówiłam, że kiedyś dojedziemy do domu.

Bylibyśmy tu już kilka godzin temu, gdyby wiedziała o przycisku Strona główna. Iiiiha!

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. Jednocześnie przedstawimy Ci potężne biblioteki wsparcia Androida, mające kluczowe znaczenie dla zapewniania nowoczesnego wyglądu aplikacji na starszych wersjach systemu.

to jest nowy rozdział  289

Struktura aplikacji

Świetne aplikacje mają przejrzystą strukturę W poprzednim rozdziale pokazaliśmy, jak określać strukturę aplikacji, by zapewnić użytkownikowi maksymalne doznania. Pamiętaj, że tworząc aplikację, będziesz korzystać z trzech rodzajów ekranów, które opisaliśmy poniżej.

Ekrany głównego poziomu To zazwyczaj pierwsza aktywność aplikacji, która jest wyświetlana zaraz po jej uruchomieniu.

Włoskie Co Nieco Pizze Makarony Restauracje Złóż zamówienie

Ekrany kategorii

To jest orientacyjny szkic aplikacji o. dla restauracji Włoskie Co Niec macje Aplikacja będzie zawierała infor ma o daniach i lokalach. Oprócz tego e umożliwiać użytkownikom składani zamówień.

Pizze

Makarony

Restauracje

Diavolo

Spaghetti bolognese

Wrocław

Ekrany tego typu przedstawiają dane należące do określonej kategorii, często używając do tego celu list. Zapewniają także użytkownikom możliwość przejścia do ekranów szczegółów/edycji.

Ekrany szczegółów/edycji Te ekrany wyświetlają szczegółowe informacje o wybranym rekordzie, umożliwiają użytkownikom edycję wybranego rekordu lub dodanie nowego.

Mają także świetne skróty Jeśli użytkownik ma zamiar często korzystać z Twojej aplikacji, to zapewne będzie chciał mieć możliwość szybkiego poruszania się po niej. Przyjrzymy się zatem widokom nawigacyjnym, służącym jako skróty do poruszania się po aplikacji, dzięki którym możliwe będzie przeznaczenie większej powierzchni ekranu na prezentowanie faktycznych treści. Zacznijmy od uważniejszego spojrzenia na główny ekran naszej aplikacji WłoskieCoNieco.

290

Rozdział 8.

Złóż zamówienie

Biblioteki wsparcia i paski aplikacji

Różne typy nawigacji Na ekranie najwyższego poziomu naszej aplikacji dla Włoskiego Co Nieco znajduje się lista opcji zawierająca te miejsca aplikacji, do których może przejść użytkownik.

Włoskie Co Nieco

aplikacji. To jest ekran głównego poziomu

Pizze Makarony

To są odnośniki do ekranów kate gorii.

Restauracje Złóż zamówienie

Pierwsze trzy opcje stanowią połączenia z aktywnościami kategorii: pierwsza powoduje wyświetlenie listy pizz, druga — listy dań z makaronu, a trzecia — listy lokali. Te aktywności kategorii można sobie wyobrażać jako pasywne. Ich działanie polega na zapewnianiu użytkownikom możliwości poruszania się po aplikacji. Czwarta opcja stanowi połączenie z aktywnością szczegółów/edycji, która pozwala użytkownikowi złożyć zamówienie. Ta opcja umożliwia użytkownikom wykonywanie akcji.

ekranu Ta opcja przenosi użytkownika do on szczegółów/edycji, na którym może złożyć zamówienie.

Przypominaj opcje nawig ą one nieco przedstawio acyjne w rozdziale ne 7.

W aplikacjach na Androida akcje można dodawać do paska aplikacji. Pasek aplikacji to element wyświetlany zazwyczaj na samej górze aktywności; czasami jest on także nazywany paskiem akcji. Zazwyczaj na pasku aplikacji umieszcza się najczęściej wykonywane akcje, tak by były wyraźnie widoczne u góry ekranu. W naszej aplikacji dla restauracji Włoskie Co Nieco możemy ułatwić użytkownikom składanie zamówień z dowolnego miejsca w aplikacji — musimy w tym celu wyświetlić pasek aplikacji u góry każdej aktywności. Ten pasek będzie zawierał przycisk Złóż zamówienie, dzięki czemu użytkownik zawsze będzie miał do niego dostęp.

To jest pasek aplikacji.

Zobaczmy teraz, jak się tworzy paski aplikacji. To jest przycisk Złóż zamówienie.

jesteś tutaj  291

Etapy

Oto co zamierzamy zrobić To jest pasek aplikacji, który dodamy.

W tym rozdziale mamy zamiar zająć się kilkoma zagadnieniami.

1

2

3

Dodamy do aplikacji prosty pasek aplikacji.

Utworzymy aktywność o nazwie MainActivity i poprzez zastosowanie odpowiedniego motywu dodamy do niej prosty pasek aplikacji. Zastąpimy prosty pasek aplikacji paskiem narzędzi.

Aby móc korzystać z najnowszych możliwości pasków aplikacji, musisz zastąpić prosty pasek aplikacji paskiem narzędzi. Wygląda on tak samo jak prosty pasek aplikacji, jednak ma więcej zastosowań. Dodamy akcję do składania zamówień.

Utworzymy nową aktywność, o nazwie OrderActivity, i dodamy do paska aplikacji ikonę, która będzie tę aktywność uruchamiać.

Pasek aplikacji aktywności MainActivity

4

OrderActivity

Zaimplementujemy przycisk W górę.

Zaimplementujemy przycisk W górę wyświetlany na pasku aplikacji aktywności OrderActivity, aby użytkownicy mogli w prosty sposób wrócić do aktywności głównej. Przycisk W górę jest prezentowany jako strzałka w lewo (co może być nieco mylące).

MainActivity

5

Pasek aplikacji aktywności OrderActivity

Dodamy dostawcę akcji udostępniania.

Do paska aplikacji aktywności MainActivity dodamy dostawcę akcji udostępniania, tak by użytkownicy mogli przekazywać tekst do innych aplikacji i zapraszać znajomych na wspólne wyjście na pizzę. Intencja

Pasek aplikacji aktywności MainActivity

ACTION_SEND type: „text/plain” messageText: „Cześć!”

Zacznijmy od dowiedzenia się, jak można dodać prosty pasek aplikacji.

292

Rozdział 8.

InnaAktywnosc

O tym, czym są dostawcy akcji, dowiesz się w dalszej części rozdziału.

Biblioteki wsparcia i paski aplikacji

Zacznijmy od paska akcji

¨  Prosty pasek aplikacji

Pasek akcji ma kilka zastosowań:



Służy do wyświetlania nazwy aplikacji lub aktywności, tak by użytkownik wiedział, w którym miejscu aplikacji aktualnie się znajduje. Na przykład aplikacja do obsługi poczty elektronicznej może używać paska akcji do wyświetlania, czy użytkownik znajduje się w skrzynce odbiorczej, czy w koszu ze spamem.



Umożliwia umieszczanie kluczowych czynności w widocznym i przewidywalnym miejscu — chodzi o takie operacje jak udostępnianie lub wyszukiwanie treści.



Służy jako element nawigacyjny pozwalający przechodzić do innych aktywności w celu wykonywania jakichś akcji.

¨  Pasek narzędzi ¨  Akcja ¨  Przycisk W górę ¨  Akcja udostępniania

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. Android zawiera sporo wbudowanych motywów, których możemy używać w tworzonych aplikacjach. Niektóre z nich, takie jak Holo, zostały wprowadzone już we wczesnych wersjach systemu, natomiast inne, takie jak Material, pojawiły się znacznie później, by umożliwić tworzenie aplikacji o nowoczesnym wyglądzie. Motywy Holo były dostępne w systemie Android już od API poziomu 11.

Motywy Material zostały wprowadzone w API poziomu 21.

Te motywy wyglądają nieco inaczej niż te przedstawione na poprzedniej stronie, gdyż nie zostały w nich użyte dodatkowe style. Dodawanie stylów zostanie opisane w dalszej części rozdziału.

Jest jednak pewien problem. Chcemy, by nasza aplikacja wyglądała możliwie jak najbardziej nowocześnie, ale możemy używać tylko motywów dostępnych w wersji Androida, dla której została przygotowana aplikacja. Na przykład nie można używać rodzimych motywów Material na urządzeniach działających pod kontrolą Androida w wersji wcześniejszej od Lollipop; wynika to z tego, że motywy te zostały wprowadzone w API poziomu 21. Ten problem nie ogranicza się bynajmniej jedynie do motywów. Każda nowa wersja Androida oferuje nowe możliwości, które programiści i użytkownicy chcą wykorzystywać w swoich aplikacjach. Przykładami mogą tu być nowe komponenty GUI. Jednak nie wszyscy aktualizują swoje urządzenia i instalują na nich najnowsze wersje Androida, gdy tylko się one pojawią. W rzeczywistości większość osób używa systemu o przynajmniej jedną wersję starszego niż najnowsza dostępna wersja Androida. Zatem w jaki sposób można korzystać w tworzonych aplikacjach z najnowszych możliwości i motywów Androida, jeśli większość użytkowników nie ma najnowszej wersji systemu operacyjnego? Jak możemy zapewnić naszym użytkownikom spójny wygląd i spójne sposoby obsługi aplikacji niezależnie od używanej przez nich wersji systemu Android, i to bez tworzenia aplikacji, które wyglądałyby staro?

jesteś tutaj  293

Biblioteki wsparcia

Biblioteki wsparcia pozwalają na wykorzystywanie nowych możliwości w starszych wersjach Androida

¨  Prosty pasek aplikacji

Zespół twórców Androida rozwiązał ten problem poprzez wprowadzenie pojęcia bibliotek wsparcia (ang. Support Libraries).

¨  Pasek narzędzi ¨  Akcja ¨  Przycisk W górę ¨  Akcja udostępniania

Biblioteki wsparcia Androida zapewniają zgodność wstecz ze starszymi wersjami systemu. Są one ulokowane poza główną wersją systemu operacyjnego i zawierają implementacje nowych możliwości, z których programiści mogą korzystać w tworzonych aplikacjach. Dzięki bibliotekom wsparcia można zapewniać użytkownikom starszych urządzeń te same doznania, jakie mają użytkownicy nowoczesnych urządzeń, i to nawet jeśli używają oni innych wersji Androida. Poniższy rysunek przedstawia wybrane biblioteki pakietu Support Libraries.

Biblioteka wsparcia v4

Biblioteka układu z ograniczeniami

Obejmuje największy zestaw możliwości, takich jak obsługa komponentów aplikacji i różnych możliwości interfejsu użytkownika.

Biblioteka appcompat v7

Zawiera wsparcie dla pasków akcji w systemach obsługujących API poziomu 7 i wyższego oraz dla tworzenia i stosowania Material Design.

Umożliwia tworzenie układów z ograniczeniami. Korzystaliśmy z tych możliwości w Rozdziale 6.

ia arc p s ki w e t lio Bib

Bibolioteka recycleview v7

Zapewnia wsparcie dla widżetu RecyclerView.

Biblioteka cardview v7

Biblioteka wsparcia wzornictwa

Dodaje wsparcie dla widżetu CardView, pozwalającego prezentować informacje na kartach.

Zapewnia wsparcie dla dodatkowych komponentów, takich jak karty i szuflady nawigacyjne.

To są tylko wybrane spośród wszystkich bibliotek wsparcia.

Każda z bibliotek oferuje unikalny zestaw możliwości. Biblioteka appcompat v7 zawiera zestaw aktualnych motywów, których można używać w starszych wersjach Androida; w praktyce można je stosować na niemal wszystkich urządzeniach, gdyż większość użytkowników korzysta z Androida zgodnego z API poziomu 19. lub wyższego. My zastosujemy tę bibliotekę wsparcia, gdyż użyjemy w aplikacji jednego z udostępnianych przez nią motywów. Motyw ten pozwoli nam dodać pasek aplikacji zapewniający naszej aplikacji nowoczesny wygląd i działający tak samo na wszystkich wersjach Androida, na których ma działać nasza aplikacja. Zawsze gdy chcemy zastosować jedną z bibliotek wsparcia, w pierwszej kolejności musimy dodać ją do aplikacji. Zobaczmy, jak to zrobić po utworzeniu projektu.

294

Rozdział 8.

Biblioteki wsparcia i paski aplikacji

Utwórz aplikację Włoskie Co Nieco Zaczniemy od utworzenia prototypu aplikacji Włoskie Co Nieco. A zatem utwórz nowy projekt aplikacji na Androdia, noszącej nazwę „Włoskie Co Nieco” i należącej do domeny „hfad.com”, co sprawi, że jej kody będą umieszczane w pakiecie com.hfad.wloskieconieco. Minimalnym obsługiwanym poziomem API powinien być poziom 19., tak by aplikacja działała na większości urządzeń. Dodatkowo w aplikacji będą nam potrzebne pusta aktywność o nazwie „MainActivity” oraz układ „activity_main.xml”. Koniecznie należy także zaznaczyć pole wyboru Backwards Compatibility (AppCompat) (już niebawem dowiesz się, dlaczego jest to ważne).

¨  Prosty pasek aplikacji ¨  Pasek narzędzi ¨  Akcja ¨  Przycisk W górę ¨  Akcja udostępniania

Inaczej niż w przykładach prezentowanych w poprzednich rozdziałach teraz musisz się upewnić, że pole Backwards Compatibility (AppCompat) będzie zaznaczone.

Teraz zajmiemy się dodawaniem do projektu bibliotek wsparcia.

jesteś tutaj  295

Biblioteka wsparcia AppCompat

Dodaj bibliotekę wsparcia AppCompat v7 W aplikacji zastosujemy jeden z tematów udostępnianych przez bibliotekę AppCompat v7, dlatego też musimy dodać tę bibliotekę do tworzonego projektu jako jego zależność. Oznacza to, że biblioteka zostanie dołączona do aplikacji i pobrana wraz z nią na urządzenie użytkownika. Aby zarządzać plikami bibliotek wsparcia dodanymi do projektu, należy wybrać opcję File/Project structure. Następnie należy kliknąć moduł app i wyświetlić kartę Dependencies.

oteki Opcja Dependencies pokazuje bibli oid Studio wsparcia dodane do projektu. Andr e doda parę najprawdopodobniej automatyczni ci. żnoś zale

Może się zdarzyć, że Android Studio już automatycznie doda do projektu bibliotekę wsparcia AppCompat; w takim przypadku pojawi się ona na karcie Dependencies jako opcja appcompat-7, jak to pokazano na powyższym rysunku. Jeśli się jednak okaże, że biblioteka wsparcia AppCompat nie została dodana, będziesz to musiał zrobić samemu. W tym celu kliknij przycisk „+”, umieszczony u dołu lub z prawej strony okna Project Structure. Kliknij przycisk Library Dependency, wybierz bibliotekę appcompat-v7 i kliknij przycisk OK. Następnie ponownie kliknij przycisk OK, aby zapisać wprowadzone zmiany i zamknąć okno dialogowe Project Structure. Po dodaniu biblioteki wsparcia AppCompat do projektu będzie już można korzystać z jej zasobów w aplikacji. My chcemy zastosować jeden z motywów udostępnianych przez tę bibliotekę, aby wyświetlić w aktywności MainActivity pasek aplikacji. Zanim to jednak zrobimy, musimy przyjrzeć się typowi aktywności MainActivity.

296

Rozdział 8.

¨  Prosty pasek aplikacji ¨  Pasek narzędzi ¨  Akcja ¨  Przycisk W górę ¨  Akcja udostępniania

Biblioteki wsparcia i paski aplikacji

AppCompatActivity pozwala stosować motywy z biblioteki AppCompat Jak na razie wszystkie aktywności, które tworzyliśmy we wcześniejszych przykładach, dziedziczyły po klasie Activity. To klasa bazowa wszystkich aktywności i to właśnie dziedziczenie po tej klasie sprawia, że klasa jest aktywnością. Jednakże w celu skorzystania z motywów AppCompat konieczne jest zastosowanie aktywności specjalnego typu — AppCompatActivity. AppCompatActivity jest klasą pochodną klasy Activity. Została ona zdefiniowana w bibliotece wsparcia AppCompat, a jej podstawowym przeznaczeniem jest umożliwianie stosowania motywów dostępnych w tej bibliotece. Zawsze gdy chcemy skorzystać z paska aplikacji zapewniającego zgodność z wcześniejszymi wersjami systemu Android, tworzone aktywności muszą rozszerzać klasę AppCompatActivity, a nie Activity. Ponieważ klasa AppCompatActivity dziedziczy po klasie Activity, odnosi się do niej wszystko, co wcześniej napisaliśmy na temat klasy Activity. Klasa AppCompatActivity korzysta z układów tak samo jak klasa Activity i dysponuje dokładnie takimi samymi metodami cyklu życia. Podstawowa różnica pomiędzy nimi polega na tym, że klasa AppCompatActivity dysponuje dodatkowymi możliwościami pozwalającymi na korzystanie z motywów dostępnych w bibliotece wsparcia AppCompat. Poniższy diagram przedstawia hierarchię klasy AppCompatActivity: Activity onCreate(Bundle) onStart() onRestart() onResume() onPause() onStop() onDestroy() onSaveInstanceState() FragmentActivity

Klasa Activity

(android.app.Activity) Klasa Activity implementuje domyślne metody cyklu życia aktywności.

Klasa FragmentActivity

(android.support.v4.app.FragmentActivity) Klasa bazowa aktywności, które muszą korzystać z fragmentów. Więcej informacji na temat stosowania fragmentów można znaleźć w następnym rozdziale.

AppCompatActivity

Klasa AppCompatActivity

(android.support.v7.app.AppCompatActivity) Klasa bazowa aktywności, które chcą korzystać z paska aplikacji dostępnego w bibliotece wsparcia.

TwojaKlasaActivity onCreate(Bundle)

Klasa TwojaKlasaActivity (com.hfad.foo)

twojaMetoda()

Na następnej stronie zadbamy o to, by nasza aktywność MainActivity dziedziczyła po AppCompatActivity.

jesteś tutaj  297

Kod klasy MainActivity

Nasza klasa MainActivity musi dziedziczyć po AppCompatActivity

¨  Prosty pasek aplikacji

Chcemy skorzystać z jednego z motywów dostępnych w bibliotece wsparcia AppCompat, musimy się zatem upewnić, że klasa naszej aktywności będzie dziedziczyć po AppCompatActivity, a nie po Activity. Na szczęście, jeśli podczas tworzenia projektu zaznaczyliśmy pole wyboru Backwards Compatibility (AppCompat), to odpowiednia klasa bazowa już powinna być użyta w kodzie. Otwórz plik MainActivity.java i upewnij się, że jego zawartość jest taka sama jak zawartość poniższego fragmentu kodu: package com.hfad.wloskieconieco;

¨  Pasek narzędzi ¨  Akcja ¨  Przycisk W górę ¨  Akcja udostępniania

Klasa AppCompatActivity jest zdefiniowana w bibliotece wsparcia AppCompat v7.

import android.support.v7.app.AppCompatActivity; WloskieCoNieco

import android.os.Bundle;

app/src/main

public class MainActivity extends AppCompatActivity { @Override

Upewnij się, że Twoja aktywnoś ć dziedziczy po klasie AppCompatActivity.

java

protected void onCreate(Bundle savedInstanceState) {

com.hfad.wloskieconieco

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

MainActivity.java

} }

Skoro już zadbaliśmy o to, by nasza aktywność dziedziczyła po klasie AppCompatActivity, możemy do niej dodać pasek aplikacji — w tym celu wystarczy zastosować odpowiedni motyw z biblioteki wsparcia AppCompat. Temat określa się w pliku manifestu aplikacji, AndroidManifest.xml, w sposób przedstawiony na następnej stronie. Nie istnieją

P: Z którymi wersjami Androida

można używać bibliotek wsparcia?

O: To zależy od wersji biblioteki wsparcia. Przed wersją 24.2.0 biblioteki z prefiksem v4 mogły być używane z systemami zgodnymi z API poziomu 4. i nowszymi, a te z prefiksem v7 z systemami zgodnymi z API poziomu 7. i nowszymi. Po udostępnieniu bibliotek wsparcia w wersji 24.2.0 wszystkie one wymagają systemu zgodnego z API poziomu 9. W przyszłości ten minimalny poziom API zapewne ponownie wzrośnie.

298

Rozdział 8.

głupie pytania

P: W poprzednich rozdziałach

Android Studio także generowało aktywności dziedziczące po AppCompatActivity. Dlaczego?

O: Podczas tworzenia aktywności

w Android Studio kreator udostępnia pole wyboru Backwards Compatible (AppCompat). Jeśli zostawiłeś je zaznaczone, to Android Studio generowało aktywności dziedziczące po klasie AppCompatActivity.

P: Widziałem też aktywności

dziedziczące po ActionBarActivity. Co to za typ aktywności?

O: We wcześniejszych wersjach

biblioteki wsparcia AppCompat w celu dodawania pasków aplikacji stosowana była klasa ActionBarActivity. Jednak w wersji 22.1 została ona uznana za przestarzałą i zastąpiono ją klasą AppCompatActivity.

Biblioteki wsparcia i paski aplikacji

Plik AndroidManifest.xml może zmieniać postać paska aplikacji Jak już się przekonałeś w poprzednich rozdziałach, plik manifestu, AndroidManifest.xml, zawiera kluczowe informacje dotyczące aplikacji, takie jak tworzące ją aktywności. Poza tym zawiera on także szereg atrybutów mających bezpośredni wpływ na paski aplikacji.

WloskieCoNieco

Poniżej przedstawiliśmy kod pliku AndroidManifest.xml, który został wygenerowany przez Android Studio (jego kluczowe fragmenty zostały wyróżnione pogrubioną czcionką):

app/src/main



AndroidManifest.xml



Motyw.

...



Atrybut android:icon określa ikonę aplikacji. Ikona ta jest używana w systemie do uruchamiania aplikacji, jak również jest prezentowana na pasku aplikacji, o ile przewiduje to wybrany motyw. Na urządzeniach z systemem Android 7.1 lub nowszym zamiast android:icon można użyć atrybutu android:roundIcon. Ikona jest zasobem typu mipmap. Mipmap to zasoby, których można używać do tworzenia ikon aplikacji i które są przechowywane w podkatalogach mipmap* katalogu app/src/main/res. Podobnie jak w przypadku innych zasobów graficznych, także w przypadku ikon można stosować różne obrazy zależnie od gęstości ekranu — wystarczy je umieścić w odpowiednio nazwanym podkatalogu mipmap. Na przykład plik umieszczony w katalogu mipmap-hdpi będzie używany na urządzeniach z ekranami o dużej gęstości. Do zasobów typu mipmap odwołujemy się, używając prefiksu @mipmap. Atrybut android:label określa przyjazną dla użytkownika etykietę, która będzie wyświetlana na pasku aplikacji. W powyższym przykładzie atrybut ten został umieszczony w znaczniku , co oznacza, że etykieta będzie stosowana w całej aplikacji. Atrybut ten można także umieszczać w znacznikach — a w takim przypadku etykieta będzie stosowana w konkretnej aktywności. Atrybut android:theme określa używany motyw. Podanie tego atrybutu w znaczniku powoduje określenie motywu dla całej aplikacji. Jeśli zostanie on użyty w elemencie , to określi motyw stosowany w konkretnej aktywności. Sposób stosowania motywu zostanie przedstawiony na następnej stronie.

ło Android Studio automatycznie doda zas ikony do katalogów mipmap* podc tworzenia projektu.

jesteś tutaj  299

Stosowanie motywów

Jak zastosować motyw? Motyw w aplikacji możemy zastosować na dwa sposoby:

 Określając motyw na stałe w pliku AndroidManifest.xml.  Korzystając ze stylów. Przyjrzymy się kolejno obu tym rozwiązaniom.

1. Określanie motywu na stałe Aby na stałe określić używany motyw w pliku AndroidManifest.xml, należy zmodyfikować atrybut android:theme, podając w nim nazwę wybranego motywu. Na przykład aby zastosować motyw o jasnym tle i ciemnym pasku aplikacji, należałoby użyć następującego kodu:

To rozwiązanie działa doskonale, jeśli chcemy zastosować prosty motyw bez wprowadzania do niego jakichkolwiek modyfikacji.

2. Użycie stylu w celu zastosowania motywu

To jest łatwy sposób użycia podstawowego motywu, jednak oznacza on, że na przykład nie będzie można zmieniać stosowa nych kolorów.

W większości przypadków będziemy chcieli stosować motywy, wykorzystując do tego style, gdyż takie rozwiązanie zapewnia możliwość modyfikowania wyglądu motywu. Na przykład możemy przesłonić główny kolor motywu, by dostosować wygląd aplikacji do kolorystyki marki. Aby zastosować motyw przy użyciu stylu, w atrybucie android:theme w pliku AnroidManifest.xml należy podać nazwę zasobu stylu (który następnie trzeba będzie utworzyć). W naszym przykładzie zastosujemy zasób stylu o nazwie AppTheme, a zatem atrybut android:theme w pliku AndroidManifest.xml powinien mieć następującą postać: WloskieCoNieco

Prefiks @style informuje, że motyw używany przez aplikację jest stylem zdefiniowanym w pliku zasobów stylów. Zasobami stylów zajmiemy się na następnej stronie.

300

Rozdział 8.

app/src/main

AndroidManifest.xml

Biblioteki wsparcia i paski aplikacji

Zdefiniuj styl w pliku zasobów

¨  Prosty pasek aplikacji ¨  Pasek narzędzi ¨  Akcja ¨  Przycisk W górę ¨  Akcja udostępniania

Plik zasobów stylów zawiera szczegółowe informacje o wszystkich motywach i stylach, których chcemy używać w aplikacji. Podczas tworzenia projektu w Android Studio IDE zazwyczaj tworzy domyślny plik zasobów stylów o nazwie styles.xml, który będzie umieszczony w katalogu app/src/main/res/values. Jeśli Android Studio nie utworzyło tego pliku, będziemy musieli zrobić to sami. A zatem w oknie eksploratora Android Studio przełącz się do widoku Project, zaznacz katalog app/src/main/res/values i z menu głównego wybierz opcję File/New. Następnie wybierz opcję pozwalającą na utworzenie nowego pliku zasobów wartości (Values) i nadaj mu nazwę styles. Po kliknięciu przycisku OK Android Studio utworzy ten plik. Prosty plik zasobów stylów ma następującą postać:

To jest nazwa motywu używanego w aplikacji.



Tu może się znaleźć dodatkowy kod modyfikujący postać motywu. Wrócimy do niego za kilka stron.

W pliku zasobów można określić jeden styl lub większą ich liczbę. Każdy z nich jest definiowany przy użyciu elementu

Te trzy wiersze kodu modyfikują motyw, a konkretnie zmieniają trzy określone w nim kolory.

Powyższy fragment kodu wprowadza trzy modyfikacje opisane przez trzy elementy . Każdy z tych elementów zawiera atrybut name, określający, jaką część motywu chcemy zmienić, oraz wartość określającą nową wartość, której należy użyć, na przykład:

@color/colorPrimary Ten element spowoduje zmianę właściwości motywu o nazwie colorPrimary i nadanie mu nowej wartości, @color/colorPrimary.

colorPrimary to kolor paska aplikacji.

colorPrimaryDark to kolor paska stanu.

name=”colorPrimary” odwołuje się do głównego koloru aplikacji. Jest on używany na pasku aplikacji i służy do nadanie jej „unikalności”. name=”colorPrimaryDark” to ciemna wersja głównego koloru aplikacji. Jest on używany na pasku stanu. name=”colorAccent” określa kolor wszelkich kontrolek interfejsu użytkownika, takich jak pola tekstowe oraz pola wyboru.

colorAccent to kolor wszelkich kontrolek interfejsu użytkownika.

Nowy kolor każdej z tych właściwości motywu określamy poprzez podanie wartości elementu . Może to być zarówno podana na stałe szesnastkowa wartość koloru, jak i odwołanie Istnieje całe mnóstwo innych właściwości motywów, do zasobu koloru. Zasoby kolorów zostaną przedstawione które można zmieniać, nie będziemy ich tu jednak dokładniej przedstawiać. Wszelkie informacje na ich dokładniej na następnej stronie. r.

temat można znaleźć na stronie https://develope android.com/guide/topics/ui/look-and-feel/themes.html.

jesteś tutaj  303

Zasoby kolorów

Zdefiniuj kolory w pliku zasobów kolorów

¨  Prosty pasek aplikacji ¨  Pasek narzędzi ¨  Akcja ¨  Przycisk W górę ¨  Akcja udostępniania

Plik zasobów kolorów jest bardzo podobny do pliku zasobów łańcuchowych, lecz zamiast łańcuchów znaków definiuje kolory. Stosowanie plików zasobów kolorów ułatwia zarządzanie schematem kolorów używanych w aplikacji, gdyż wszystkie kolory, które chcemy wykorzystywać, są umieszczone w jednym miejscu. Plik zasobów kolorów zazwyczaj ma nazwę colors.xml i jest umieszczony w katalogu app/src/main/res/values. Ten plik jest zwykle tworzony przez Android Studio podczas tworzenia nowego projektu. Jeśli jednak Android Studio nie utworzy tego pliku, trzeba to będzie zrobić samemu. A zatem w eksploratorze Android Studio przejdź do widoku Project, zaznacz katalog app/src/main/res/values i wybierz opcję File/New z menu głównego. Następnie wybierz opcję pozwalającą na utworzenie pliku zasobów wartości (Valus), a gdy zostaniesz o to poproszony, podaj nazwę pliku: „colors”. Następnie kliknij przycisk OK, a Android Studio utworzy plik. Później otwórz plik colors.xml i zadbaj o to, by jego zawartość odpowiadała poniższemu kodowi:

WloskieCoNieco



app/src/main

#3F51B5 #303F9F #FF4081

Każdy z tych elementów definiuje zasób koloru.

Powyższy fragment kodu definiuje trzy zasoby kolorów. Każdy z nich określa nazwę i wartość. Wartość podawana jest w formie liczby szesnastkowej: Ten znacznik informuje, że jest to zasób koloru.

#3F51B5 Zasób koloru ma nazwę “colorPrimary” i wartość #3F51B5 (to kolor niebieski).

Plik zasobów stylów odnajduje kolor zdefiniowany w pliku zasobów kolorów, używając zapisu o postaci @color/nazwaKoloru. Na przykład element: @color/colorPrimary

przesłania główny kolor zdefiniowany w motywie przy użyciu wartości koloru o nazwie colorPrimary, zdefiniowanego w pliku zasobów kolorów. Skoro dowiedziałeś się już, jak można dodawać pasek aplikacji przy użyciu motywu, wprowadźmy odpowiednie zmiany w układzie aktywności MainActivity i weźmy aplikację na jazdę próbną.

304

Rozdział 8.

res values

colors.xml

Biblioteki wsparcia i paski aplikacji

¨  Prosty pasek aplikacji

Kod pliku activity_main.xml

¨  Pasek narzędzi ¨  Akcja ¨  Przycisk W górę ¨  Akcja udostępniania

W układzie aktywności MainActivity zamierzamy wyświetlać domyślne teksty i skorzystać z układu liniowego. Poniżej przedstawiony został kod, który w tym celu zastosujemy — wprowadź odpowiednie zmiany w pliku activity_main.xml w swoim projekcie, tak by jego zawartość była identyczna z poniższą:

res layout



W układzie aktywności MainActi vity wyświetlamy jedynie prosty teks t gdyż na razie chcemy się skoncentzastępczy, rować na paskach aplikacji.

Jazda próbna aplikacji Po uruchomieniu aplikacji zostanie wyświetlona jej aktywność główna. Na samej jej górze będzie widoczny pasek aplikacji. To jest pasek aplikacji.

Tło jest jasne, gdyż zastosowaliśmy motyw Theme.AppCompat.Light.DarkActionBar. Ten motyw powoduje także, że tekst wyświetlany w głównym obszarze aktywności będzie ciemny, a tekst prezentowany na pasku aplikacji — biały.

Domyślny kolor paska stanu został przesłonięty; obecnie jest on nieco ciemniejszy niż na pasku aplikacji.

To już wszystko, co trzeba zrobić, by aktywności były wyposażone w prosty pasek aplikacji. Może byś teraz poeksperymentował z modyfikowaniem motywów i kolorów? Kiedy skończysz, odwróć kartkę i przejdź do następnego etapu prac.

jesteś tutaj  305

Paski narzędzi

Pasek aplikacji a pasek narzędzi

¨  Prosty pasek aplikacji ¨  Pasek narzędzi ¨  Akcja ¨  Przycisk W górę ¨  Akcja udostępniania

Jak na razie dowiedziałeś się, jak dodawać do aktywności pasek aplikacji poprzez zastosowanie motywu, w którym taki pasek jest dostępny. Dodawanie paska aplikacji w taki sposób jest proste, lecz ma jedną wadę: niekoniecznie zapewnia dostęp do wszystkich najnowszych możliwości, jakie oferują paski aplikacji. W niewidoczny sposób każda aktywność wyświetlająca pasek aplikacji poprzez zastosowanie motywu używa do jego obsługi klasy ActionBar. Niemniej jednak okazuje się, że najnowsze możliwości pasków aplikacji zostały zaimplementowane w innej klasie biblioteki wsparcia AppCompat — w klasie Toolbar. Oznacza to, że chcąc skorzystać z najnowszych możliwości pasków aplikacji, będziemy musieli użyć klasy Toolbar. Stosowanie klasy Toolbar zapewnia także większą elastyczność. Pasek narzędzi to typ widoku, który można dodawać do układu aplikacji dokładnie tak samo jak wszelkie inne widoki, a to oznacza, że określanie położenia i kontrolowanie wyglądu w przypadku pasków narzędzi jest znacznie łatwiejsze niż w przypadku pasków aplikacji.

o jak Pasek narzędzi wygląda tak sam wcześniej, pasek aplikacji, który dodaliśmy ność tycz elas szą więk wnia zape ak jedn ci, jakimi i udostępnia najnowsze możliwoś acji. aplik i dysponują pask

Jak można dodać pasek narzędzi? Zmodyfikujemy teraz aktywność tworzonej aplikacji w taki sposób, by zamiast paska aplikacji wykorzystywała pasek narzędzi. Aby użyć klasy Toolbar z biblioteki wsparcia, zawsze należy wykonać przedstawioną poniżej sekwencję czynności:

1 2

3 4

5

Dodać bibliotekę wsparcia AppCompat v7 jako zależność projektu.

To konieczne, gdyż klasa Toolbar została umieszczona w tej bibliotece. Upewnić się, że klasa aktywności dziedziczy po klasie AppCompatActivity.

Aktywność musi dziedziczyć po AppCompatActivity (lub jednej z jej klas pochodnych), gdyż w przeciwnym razie nie będzie mogła korzystać z paska narzędzi biblioteki wsparcia AppCompat. Usunąć istniejący pasek aplikacji.

W tym celu trzeba zmienić motyw na taki, który nie używa paska aplikacji. Dodać pasek narzędzi do układu.

Pasek narzędzi jest typem widoku, dzięki czemu można go umieścić w dowolnym miejscu układu i kontrolować jego wygląd. Zaktualizować aktywność, tak by pasek narzędzi był używany jako jej pasek aplikacji.

W ten sposób aktywność będzie mogła reagować na interakcje użytkownika z paskiem narzędzi. Na kolejnych stronach szczegółowo opiszemy wszystkie te czynności.

306

Rozdział 8.

Biblioteki wsparcia i paski aplikacji

1. Dodaj bibliotekę wsparcia AppCompat

¨  Prosty pasek aplikacji ¨  Pasek narzędzi ¨  Akcja ¨  Przycisk W górę ¨  Akcja udostępniania

Zanim będzie można użyć w swojej aktywności klasy Toolbar z biblioteki wsparcia, trzeba się upewnić, że biblioteka wsparcia AppCompat v7 została dodana do projektu jako jedna z jego zależności. W naszym przykładzie biblioteka ta faktycznie została już dodana, gdyż potrzebowaliśmy jej, aby móc korzystać z motywów AppCompat. Jednak aby upewnić się, że biblioteka wsparcia faktycznie jest dodana do zależności projektu, wybierz z menu głównego Android Studio opcję File/Project Structure, następnie kliknij moduł app i przejdź na kartę Dependencies. Biblioteka wsparcia AppCompat v7 powinna zostać wyświetlona, jak pokazano na poniższym rysunku:

pat v7. Oto i biblioteka wsparcia AppCom

2. Zmień klasę bazową aktywności na AppCompatActivity Chcąc używać motywów z biblioteki wsparcia AppCompat, trzeba się upewnić, że aktywność dziedziczy po klasie AppCompatActivity. To samo dotyczy przypadków, gdy chcemy, by aktywność używała paska narzędzi z biblioteki wsparcia zamiast standardowego paska aplikacji. W naszym przykładzie modyfikacja ta została już wykonana, gdyż już wcześniej zmodyfikowaliśmy kod w pliku MainActivity.java, tak by klasa aktywności dziedziczyła po AppCompatActivity: ... import android.support.v7.app.AppCompatActivity; public class MainActivity extends AppCompatActivity { ... }

dziczy po klasie Nasza klasa MainActivity już dzie ty. ctivi patA Com App

Kolejną czynnością, jaką musimy wykonać, jest usunięcie istniejącego paska aplikacji.

WloskieCoNieco app/src/main java com.hfad.wloskieconieco

MainActivity.java

jesteś tutaj  307

Motyw NoActionBar

3. Usuń pasek aplikacji

¨  Prosty pasek aplikacji ¨  Pasek narzędzi

Pasek aplikacji możemy usunąć w dokładnie taki sam sposób, w jaki wcześniej go dodaliśmy — stosując odpowiedni motyw.

¨  Akcja ¨  Przycisk W górę ¨  Akcja udostępniania

Chcąc dodać pasek aplikacji do naszej aplikacji, zastosowaliśmy jeden z motywów, w których pasek aplikacji jest używany. Skorzystaliśmy przy tym z atrybutu theme podawanego w pliku AndroidManifest.xml, aby zastosować styl o nazwie AppTheme:

...

app/src/main

pliku styles.xml. To odwołanie odnajduje motyw w

AndroidManifest.xml

Sam motyw zdefiniowaliśmy w pliku styles.xml w następujący sposób:

To jest motyw używany w aplik acji. Wyświetla on ciemny pasek aplik acji.



WloskieCoNieco

Motyw Theme.AppCompat.Light.DarkActionBar sprawia, że aktywność będzie mieć jasne tło i ciemny pasek aplikacji. Aby usunąć ten pasek aplikacji, zmienimy używany motyw na Theme.AppCompat.Light.NoActionBar. Aplikacja będzie wyglądać tak samo jak wcześniej, z tą różnicą, że nie będzie wyświetlany żaden pasek.

app/src/main res values

Aby zmienić używany motyw, wystarczy zaktualizować plik styles.xml:



styles.xml



Zmodyfikowaliśmy motyw, przesłaniając kilka zdefiniowanych w nim kolorów. Ten kod możesz zostawić w niezmienionej postaci.

onBar Zmień używany motyw z DarkActi ięcie usun e oduj spow To na NoActionBar. paska aplikacji.

Teraz, kiedy już usunęliśmy pasek aplikacji, możemy się zająć dodaniem paska narzędzi.

308

Rozdział 8.

Biblioteki wsparcia i paski aplikacji

4. Dodaj do układu pasek narzędzi

¨  Prosty pasek aplikacji ¨  Pasek narzędzi

Jak już zaznaczyliśmy wcześniej, pasek narzędzi jest widokiem, który można dodawać do układu. Poniższy fragment przedstawia przykładowy kod paska narzędzi:

¨  Akcja ¨  Przycisk W górę ¨  Akcja udostępniania

Zaczynamy od zdefiniowania paska narzędzi:

To pełna ścieżka do klasy Toolbar w bibliotece wsparcia.

gdzie android.support.v7.widget.Toolbar jest pełną nazwą klasy Toolbar w bibliotece wsparcia. Po zdefiniowaniu paska narzędzi można użyć atrybutów widoku do określenia jego identyfikatora oraz wyglądu. Na przykład aby pasek zajął całą szerokość elementu nadrzędnego, w którym go umieścimy, a jego wysokość odpowiadała wysokości domyślnego paska aplikacji używanego motywu, można zastosować dwa poniższe atrybuty: android:layout_width=”match_parent” android:layout_height=”?attr/actionBarSize”

jak Pasek narzędzi będzie tak szerokioki wys tak i y zędn nadr ent jego elem jak domyślny pasek aplikacji.

Prefiks ?attr oznacza, że należy zastosować atrybut z obecnie używanego motywu. W powyższym przykładzie zapis ?attr/actionBarSize odwołuje się do wysokości paska aplikacji określonej w obecnie wybranym motywie. Możemy także zmienić wygląd paska narzędzi, tak by przypominał on zastosowany wcześniej pasek aplikacji. W tym celu należy zmienić kolor tła paska narzędzi i zastosować tak zwaną nakładkę tematu: android:background=”?attr/colorPrimary”

sam kolor tła, Stosujemy na pasku narzędzi ten pasku aplikacji. w ny owa stos był j śnie który wcze

android:theme=”@style/ThemeOverlay.AppCompat.Dark.ActionBar”

Nakładka motywu jest specjalnym typem motywu, który modyfikuje bieżący motyw, przesłaniając jego niektóre atrybuty. Chcemy, by nasz pasek narzędzi wyglądał tak samo jak pasek aplikacji z motywu Theme.AppCompat.Light.DarkActionBar, dlatego też używamy nakładki motywu Theme.AppCompat.Dark.ActionBar.

Ten atrybut sprawia, że pasek narzędzi będzie wyglądał tak samo jak zastosowany wcześniej pasek aplikacji. Musimy do tego użyć nakładki motywu, gdyż w motywie NoActionBar postać pasków narzędzi nie jest określana tak samo jak w stosowanym wcześniej motywie DarkActionBar.

Na następnej stronie zajmiemy się dodaniem paska narzędzi do układu.

jesteś tutaj  309

Tego nie robimy (w naszej aplikacji)

Dodaj pasek narzędzi do układu…

¨  Prosty pasek aplikacji ¨  Pasek narzędzi

¨  Akcja Jeśli tworzona aplikacja zawiera jedną aktywność, to pasek narzędzi można dodać do układu ¨  Przycisk W górę w taki sam sposób, w jaki dodajemy do niego wszelkie inne widoki. Poniżej przedstawiony ¨  Akcja udostępniania został przykładowy kodu układu, który można by zastosować w takiej sytuacji (w naszej aplikacji zastosujemy jednak inne rozwiązanie, więc nie zmieniaj w ten sposób swojego układu):

Ten fragment kodu wyświetla pasek narzędzi na samej górze aktywności.



W dalszej części rozdziału dodamy do naszej aplikacji drugą aktywność i właśnie dlatego nie zastosujemy tego rozwiązania. Z tego względu nie musisz zmieniać kodu swojego układu i dostosowywać go do tego przykładu.

Używamy układu liniowego, więc zostanie umieszczony poniżej paskwidok tekstowy a narzędzi.

Zastosowanie układu o powyższej postaci sprawi, że pasek narzędzi zostanie wyświetlony na samej górze aktywności. Widok tekstowy dodany do układu przez Android Studio umieściliśmy tak, by był wyświetlany poniżej paska narzędzi. Trzeba pamiętać, że pasek narzędzi jest zwyczajnym widokiem — trzeba to brać pod uwagę podczas rozmieszczania w układzie pozostałych widoków. Rozwiązanie polegające na dodawaniu kodu paska narzędzi do układu działa doskonale w przypadku, gdy aplikacja składa się z jednej aktywności — w takiej sytuacji cały kod związany z jej wyglądem jest umieszczony w jednym pliku. Jednak kiedy aplikacja składa się z wielu aktywności, takie rozwiązanie spisuje się już znacznie gorzej. Chcąc zastosować pasek narzędzi w wielu aktywnościach, musielibyśmy bowiem zdefiniować go w kodzie układu każdej z nich. To z kolei oznaczałoby, że gdybyśmy chcieli w jakiś sposób zmienić wygląd paska, musielibyśmy to zrobić w każdym z plików układów. Ale czy jest jakieś alternatywne rozwiązanie?

310

Rozdział 8.

Biblioteki wsparcia i paski aplikacji

…lub zdefiniuj pasek narzędzi jako odrębny układ

¨  Prosty pasek aplikacji ¨  Pasek narzędzi

Alternatywnym rozwiązaniem jest zdefiniowanie paska narzędzi w odrębnym układzie, a następnie dołączenie go do układu każdej z aktywności. Oznacza to, że pasek narzędzi zostanie zdefiniowany tylko w jednym miejscu i w razie konieczności wprowadzenia jakichś zmian w jego wyglądzie niezbędne będzie zmodyfikowanie tylko jednego pliku.

MainActivity Aktywność główna, MainActivity, używa układu activity_main.





activity_main Układ activity_main nie zawiera bezpośrednio kodu definiującego pasek narzędzi. Zamiast tego zawiera odwołanie do układu definiującego ten pasek.

¨  Akcja ¨  Przycisk W górę ¨  Akcja udostępniania

toolbar_main Układ paska narzędzi jest zapisany w odrębnym pliku. Jeśli tworzona aplikacja składa a się z wielu aktywności, to każd z nich może się odwoływać do tego układu.

Właśnie to rozwiązanie zastosujemy w naszej aplikacji. Zacznij od utworzenia nowego pliku układu. W eksploratorze Android Studio przejdź do widoku Project, zaznacz katalog app/src/res/main/layout, a następnie wybierz z menu głównego opcję File/New/Layout resource. Kiedy zostaniesz o to poproszony, nadaj plikowi nazwę „toolbar_main” i kliknij przycisk OK. Spowoduje to utworzenie nowego pliku układu, o nazwie toolbar_main.xml. Następnie otwórz ten układ i zastąp jego pierwotną zawartość wygenerowaną przez Android Studio kodem przedstawionym poniżej: WloskieCoNieco

Ten kod jest niemal taki sam jak kod paska narzędzi przedstawiony wcześniej. Podstawowa zmiana polega na tym, że pominęliśmy w nim atrybut id, który w tej wersji zostanie podany w pliku układu aktywności — activity_main.xml.

app/src/main

layout

toolbar_main.xml Ten kod definiujący pasek ony szcz umie narzędzi jest w odrębnym pliku, dzięki czemu może się do niego odwoływać wiele aktywności.

Na następnej stronie zobaczymy, w jaki sposób można dołączyć ten plik układu paska narzędzi do pliku activity_main.xml.

jesteś tutaj 

311

Dołączenie paska narzędzi

Dołącz pasek narzędzi do układu aktywności

¨  Prosty pasek aplikacji ¨  Pasek narzędzi ¨  Akcja ¨  Przycisk W górę ¨  Akcja udostępniania

Jeden układ można wyświetlać wewnątrz innego, używając znacznika . Znacznik ten musi zawierać atrybut layout, określający nazwę układu, który chcemy dołączyć. Poniżej przedstawiony został przykład pokazujący postać znacznika pozwalającego na dołączenie układu o nazwie toolbar_main.xml:

wi Zapis @layout nakazuje systemo ar_main. odszukanie układu o nazwie toolb

Naszym celem jest dołączenie układu toolbar_main do pliku activity_main.xml. Poniżej przedstawiona została nasza wersja kodu; zmodyfikuj swój plik activity_main.xml, by miał identyczną postać:



Skoro już dodaliśmy pasek narzędzi do układu, musimy wprowadzić jeszcze jedną modyfikację.

Rozdział 8.

app/src/main

Ten atrybut określa identyfikator będzie można odwoływać się do paska narzędzi, dzięki czemu niego w kodzie aktywności.

android:layout_width=”wrap_content”

312

WloskieCoNieco

res layout

activity_main.xml

Biblioteki wsparcia i paski aplikacji

5. Ustaw pasek narzędzi jako pasek aplikacji aktywności

¨  Prosty pasek aplikacji ¨  Pasek narzędzi

W końcu ostatnią czynnością, jaką musimy wykonać, jest nakazanie aktywności MainActivity, by używała paska narzędzi jako swojego paska aplikacji.

¨  Akcja ¨  Przycisk W górę ¨  Akcja udostępniania

Jak na razie jedynie dodaliśmy pasek narzędzi do układu. Choć oznacza to, że pasek zostanie wyświetlony u góry ekranu, to jednak nie sprawi, że zyska możliwości funkcjonalne paska aplikacji. Na przykład gdybyśmy już teraz uruchomili aplikację, okazałoby się, że jej nazwa nie jest wyświetlana na pasku narzędzi, jak wcześniej była na pasku aplikacji.

Jeśli po dodaniu paska narzędzi do układu aktywności nie zmienimy kodu, pasek będzie wyświetlany jej jako pusty, prostokątny obszar pozbawio ny jakiejkolwiek zawartości.

Aby pasek narzędzi zachowywał się jak pasek aplikacji, w metodzie onCreate() aktywności trzeba wywołać metodę setSupportActionBar() klasy AppCompatActivity. Metoda ta pobiera tylko jeden argument: pasek narzędzi, który chcemy zastosować jako pasek aplikacji danej aktywności. Poniżej przedstawiony został kod pliku MainActivity.java; zmodyfikuj ten plik w swoim projekcie, tak by jego zawartość była identyczna z poniższą: package com.hfad.wloskieconieco; import android.support.v7.app.AppCompatActivity; Używamy klasy Toolbar, więc musimy ją zaimportować.

import android.os.Bundle; import android.support.v7.widget.Toolbar;

WloskieCoNieco app/src/main java

public class MainActivity extends AppCompatActivity {

com.hfad.wloskieconieco

@Override MainActivity.java

protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); } }

narzędzi, Pobieramy referencję do paska pasek jako go y wiam usta a następnie aplikacji aktywności.

Musimy zastosować metodę setSupportActionBar(), gdyż używamy paska narzędzi pochodzącego z biblioteki wsparcia (ang. Support Library).

I to już cały kod niezbędny do tego, by zastąpić podstawowy pasek aplikacji aktywności paskiem narzędzi. Zobaczmy zatem, jak wygląda nasz nowy pasek.

jesteś tutaj  313

Jazda próbna

¨  Prosty pasek aplikacji ¨  Pasek narzędzi

Jazda próbna aplikacji Po uruchomieniu aplikacji w miejscu używanego wcześniej paska aplikacji zostaje wyświetlony nowy pasek narzędzi. Jest on bardzo podobny do paska aplikacji, lecz został utworzony przy użyciu klasy Toolbar z biblioteki wsparcia, która implementuje najnowsze możliwości funkcjonalne pasków aplikacji w systemie Android.

¨  Akcja ¨  Przycisk W górę ¨  Akcja udostępniania

. To jest nasz nowy pasek narzędzi Jest on bardzo podobny do używanego wcześniej paska aplikacji, jednak zapewnia większą elastyczność.

A zatem dowiedziałeś się, jak dodać do aktywności pasek aplikacji oraz jak zastąpić ten podstawowy pasek aplikacji paskiem narzędzi. Na kilku następnych stronach wyjaśnimy, jak można wzbogacać pasek aplikacji o nowe możliwości funkcjonalne.

Nie istnieją

głupie pytania

P: Wspominaliście o paskach

aplikacji, paskach akcji oraz paskach narzędzi. Czym się one od siebie różnią?

O: Pasek aplikacji zazwyczaj jest

wyświetlany u góry aktywności. Czasami nazywa się go także paskiem akcji, gdyż we wcześniejszych wersjach systemu Android można go było tworzyć wyłącznie przy użyciu klasy ActionBar. Klasa ActionBar jest używana w niezauważalny sposób w przypadkach, gdy dodajemy pasek aplikacji korzystając z motywu. Jeśli tworzona aplikacja nie korzysta z żadnych nowych możliwości pasków aplikacji, to takie rozwiązanie może być wystarczające.

314

Rozdział 8.

Alternatywnym sposobem dodania paska aplikacji jest zaimplementowanie paska narzędzi przy użyciu klasy Toolbar. Wyniki są podobne jak w przypadku stosowania domyślnego paska aplikacji, tworzonego poprzez zastosowanie motywu, lecz taki pasek zapewnia najnowsze możliwości funkcjonalne.

Ponadto sprawdź, czy w metodzie onCreate() aktywności jest wywoływana metoda setSupportActionBar(), gdyż to ona sprawia, że pasek narzędzi będzie działał jak pasek aplikacji. Bez tego wywołania ani nazwa aplikacji, ani nazwa aktywności nie będą wyświetlane na pasku narzędzi.

P: Dodałem do aktywności pasek

P: Widziałem już znacznik

narzędzi, lecz kiedy uruchamiam aplikację, wygląda on jak pusty prostokąt u góry ekranu. Nawet nie jest na nim wyświetlana nazwa aplikacji.

O: W pierwszej kolejności sprawdź plik

manifestu, AndroidManifest.xml, i upewnij się, że została w nim podana etykieta aplikacji. To właśnie na jej podstawie jest określana nazwa aplikacji.

w niektórych kodach generowanych przez Android Studio. Do czego on służy?

O

: Znacznik służy do dołączania jednego układu do drugiego. W zależności od używanej wersji Android Studio oraz rodzaju tworzonego projektu IDE może dzielić kod układu na kilka odrębnych układów.

Biblioteki wsparcia i paski aplikacji

Dodawanie akcji do paska aplikacji W większości tworzonych aplikacji będziemy chcieli umieszczać na pasku aplikacji jakieś akcje — przyciski lub teksty, które użytkownik może kliknąć, by coś się stało. W naszej aplikacji dodamy do paska przycisk Złóż zamówienie; jego kliknięcie będzie powodować uruchomienie nowej aktywności, OrderActivity, którą zaraz się zajmiemy.

¨  Prosty pasek aplikacji ¨  Pasek narzędzi ¨  Akcja ¨  Przycisk W górę ¨  Akcja udostępniania

Utworzymy nową akcję, CreateOrder, która będzie uruchamiać aktywność OrderActivity.

Utwórz aktywność OrderActivity Zaczniemy od utworzenia aktywności OrderActivity. Zaznacz pakiet com.hfad.wloskieconieco w katalogu app/src/main/java, a następnie wybierz z menu opcję File/New…/Activity/Empty Activity. Utwórz aktywność o nazwie OrderActivity, korzystającą z układu o nazwie activity_order, upewnij się, że zostanie ona utworzona w pakiecie com.hfad.wloskieconieco, i zaznacz pole wyboru Backwards Compatibility (AppCompat).

Jeśli zostaniesz zapytany o język źródłowy aplikacji, wybierz język Java.

Zaostrz ołówek Chcemy, by aktywność OrderActivity wyświetlała ten sam pasek narzędzi co aktywność MainActivity. Przekonajmy się, czy będziesz potrafił uzupełnić kod układu activity_order.xml tak, by był w nim wyświetlany pasek narzędzi.



Tu należy umieścić kod, który doda pasek narzędzi.

jesteś tutaj  315

Rozwiązanie

Zaostrz ołówek Rozwiązanie

Chcemy, by aktywność OrderActivity wyświetlała ten sam pasek narzędzi co aktywność MainActivity. Przekonajmy się, czy będziesz potrafił uzupełnić kod układu activity_order.xml tak, by był w nim wyświetlany pasek narzędzi.





Zaktualizuj plik activity_order.xml Zaczniemy od zaktualizowania zawartości pliku activity_order.xml, tak by był w niej wyświetlany pasek narzędzi. Zastosujemy do tego ten sam układ, który stworzyliśmy przed chwilą. Oto nasza wersja kodu; zmodyfikuj plik w swoim projekcie, by był identyczny z naszym:



316

Rozdział 8.

app/src/main res layout

activity_order.xml

Biblioteki wsparcia i paski aplikacji

Zaktualizuj plik OrderActivity.java

¨  Prosty pasek aplikacji ¨  Pasek narzędzi ¨  Akcja

Teraz zajmiemy się zaktualizowaniem kodu aktywności OrderActivity, tak by używała paska narzędzi dołączonego do układu jako paska aplikacji. W tym celu musimy wywołać metodę setSupportActionBar() i przekazać do niej pasek narzędzi, tak jak zrobiliśmy to już wcześniej.

¨  Przycisk W górę ¨  Akcja udostępniania

Poniżej przedstawiony został pełny kod pliku OrderActivity.java; zaktualizuj ten plik w swoim projekcie, tak by był identyczny z naszym: package com.hfad.wloskieconieco; WloskieCoNieco

import android.support.v7.app.AppCompatActivity; import android.os.Bundle;

app/src/main

import android.support.v7.widget.Toolbar;

java

public class OrderActivity extends AppCompatActivity { @Override

ity. Upewnij się, że aktywność dziedziczy po klasie AppCompatActiv

com.hfad.wloskieconieco

OrderActivity.java

protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_order); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); } }

Ustaw pasek narzędzi jako pasek aplikacji aktywności.

Dodaj zasób łańcuchowy określający tytuł aktywności Zanim zabierzemy się do tworzenia akcji, która uruchomi aktywność OrderActivity, musimy wprowadzić jeszcze jedną zmianę. Chcemy w wyraźny sposób pokazać użytkownikom, że została uruchomiona aktywność OrderActivity. W tym celu zmienimy tekst wyświetlany na pasku aplikacji OrderActivity z nazwy aplikacji na „Złóż zamówienie”. Zaczniemy od utworzenia zasobu łańcuchowego zawierającego tytuł aktywności. Otwórz plik strings.xml umieszczony w katalogu app/src/main/ res/values i dodaj do niego poniższy wiersz kodu: Złóż zamówienie

Na następnej stronie pokażemy, jak zmienić tekst wyświetlany na pasku aplikacji.

WloskieCoNieco app/src/main res

Tego zasobu użyjemy do wyświetlenia tekstu „Złóż zamówienie” na pasku aplikacji.

values

strings.xml

jesteś tutaj  317

Dodanie etykiety

Zmień pasek aplikacji, dodając do niego etykietę Jak już się dowiedziałeś we wcześniejszej części rozdziału, tekst wyświetlany na pasku aplikacji można określić przy użyciu atrybutu label podawanego w pliku AndroidManifest.xml.

¨  Prosty pasek aplikacji ¨  Pasek narzędzi ¨  Akcja ¨  Przycisk W górę ¨  Akcja udostępniania

Poniżej przedstawiliśmy naszą wersję tego pliku. Jak widać, jego kod zawiera atrybut label o wartości @string/app, umieszczony wewnątrz elementu . Oznacza to, że dla całej aplikacji jej nazwa będzie wyświetlana na pasku aplikacji.



Atrybut label informuje system, jaki tekst ma być wyświetlony na pasku aplikacji.



AndroidManifest.xml

To jest element odnoszący się do aktywności MainActivity, który dodaliśmy już wcześniej.

...

To jest element odnoszący się do aktywności OrderActivity. IDE dodało go za nas podczas tworzenia nowej aktywności.



Chcemy przesłonić etykietę w aktywności OrderActivity, by po uruchomieniu i przekazaniu do niej miejsca wprowadzania na pasku aplikacji był wyświetlany tekst „Złóż zamówienie”. W tym celu dodamy do elementu odnoszącego się do aktywności OrderActivity atrybut label określający nowy tekst, który należy wyświetlić:

ci Dodanie atrybutu label do aktywnośpasku na ci wnoś akty tej dla że oznacza, t określony aplikacji będzie wyświetlany teks but atry z prze nie a przez ten atrybut, zdefiniowany dla całej aplikacji.

Na następnej stronie pokażemy ten kod w odpowiednim kontekście.

318

Rozdział 8.

Biblioteki wsparcia i paski aplikacji

Kod pliku AndroidManifest.xml Poniżej przedstawiliśmy zawartość naszej wersji pliku AndroidManifest.xml. Zaktualizuj swoją wersję tego pliku, tak by była identyczna z naszą.

Etykieta zdefiniowana

aktywności MainActivity

...

¨  Prosty pasek aplikacji ¨  Pasek narzędzi ¨  Akcja ¨  Przycisk W górę ¨  Akcja udostępniania

WloskieCoNieco app/src/main

nie musimy zmieniać. Aktywność ta nie definiuje własnej etykiety, dlatego będzie używać tej zdefiniowanej w elemencie .



AndroidManifest.xml



Zdefiniowanie etykiety dla aktywności OrderActivity przesłania domyślną etykietę w tej konkretnej aktywności. Oznacza to, że po uruchomieniu aktywności na pasku aplikacji zostanie wyświetlony inny tekst.

I to już wszystko, co musieliśmy zrobić w celu przygotowania aktywności OrderActivity do użycia. Teraz pokażemy, jak można dodać akcję do paska aplikacji, tak by można jej było użyć do uruchomienia aktywności.

Jak dodać akcję do paska aplikacji? Aby dodać akcję do paska aplikacji, należy wykonać cztery operacje:

1

Zdefiniować zasoby określające ikonę i tekst akcji.

2

Zdefiniować akcję w pliku zasobów menu.

3

Dodać zasób menu do paska aplikacji w kodzie aktywności.

4

Dodać kod określający, co powinno się stać po kliknięciu akcji.

W ten sposób poinformujemy system, jakie akcje mają zostać wyświetlone na pasku aplikacji. W tym celu należy zaimplementować metodę onCreateOptionsMenu(). W tym celu należy zaimplementować metodę onOptionsItemSelected().

Zaczniemy od dodania zasobów określających ikonę i tekst akcji.

jesteś tutaj  319

Dodanie zasobów

¨  Prosty pasek aplikacji ¨  Pasek narzędzi ¨  Akcja

Dodaj zasoby dla akcji Podczas dodawania akcji do paska aplikacji zazwyczaj będziemy chcieli określić jej ikonę oraz krótki tekst stanowiący tytuł akcji. Ikona zwykle jest wyświetlana, gdy akcja jest prezentowana w głównym obszarze paska aplikacji. Jeśli akcja nie zmieści się w tym obszarze, zostanie automatycznie przesunięta do obszaru nadmiarowego i zamiast ikony zostanie wyświetlony jej tytuł.

¨  Przycisk W górę ¨  Akcja udostępniania To jest ikona obszaru nadmiarowego paska aplikacji. Android przenosi do tego obszaru akcje, których nie uda mu się wyświetlić w głównym obszarze paska aplikacji.

Zaczniemy od określenia ikony akcji.

Dodaj ikonę Aby wyświetlić akcję jako ikonę na pasku aplikacji, można bądź to stworzyć taką ikonę w całości samodzielnie, bądź też użyć jednej z ikon dostarczanych przez Google. Ikony przygotowane przez tę firmę można znaleźć na stronie https://material.io/icons/. My skorzystamy z ikony dodawania („add”), ic_add_white_24dp, i dodamy do katalogów drawable* naszego projektu po jednej takiej ikonie dla każdej gęstości ekranu. Podczas działania aplikacji Android zdecyduje, którą wersję ikony należy zastosować, na podstawie gęstości ekranu używanego urządzenia.

To jest ikona nowej akcji.

W pierwszej kolejności przejdź do widoku Project w eksploratorze Android Studio, zaznacz katalog app/src/main/res, a później utwórz następujące katalogi (jeśli jeszcze nie istnieją): drawable-hdpi, drawable-mdpi, drawable-xhdpi, drawable-xxhdpi oraz drawable‑xxxhdpi. Następnie odszukaj te same katalogi w projekcie dostępnym w przykładach dołączonych do książki, znajdującym się w katalogu Rozdzial_08, i z każdego z nich skopiuj plik ic_add_ white_24db.png do analogicznego katalogu swojego projektu.

Dodaj tytuł akcji jako zasób łańcuchowy Oprócz ikony akcji określimy także jej tytuł. Będzie on używany, gdy Android wyświetli akcję w obszarze nadmiarowym paska aplikacji, na przykład kiedy w głównym obszarze tego paska będzie zbyt mało miejsca, by wyświetlić w nim ikonę akcji.

WloskieCoNieco

Tytuł akcji zdefiniujemy jako zasób łańcuchowy. A zatem otwórz plik strings.xml umieszczony w katalogu app/src/main/res/values i dodaj do niego poniższy wiersz kodu:

app/src/main

Złóż zamówienie

Skoro dodaliśmy już zasoby dla ikony akcji oraz jej tytułu, możemy zająć się utworzeniem pliku zasobów menu.

320

Rozdział 8.

Tego zasobu użyjemy jako tytułu akcji.

res values

strings.xml

Biblioteki wsparcia i paski aplikacji

2. Utwórz plik zasobu menu Plik zasobu menu informuje system Android o tym, jakie akcje mają być prezentowane na pasku aplikacji. Aplikacja może zawierać wiele takich plików zasobów menu. Na przykład można przygotować osobne pliki zasobów menu dla każdego zestawu aktywności; takie rozwiązanie jest przydatne, gdy chcemy, by różne aktywności wyświetlały różne akcje na swoich paskach aplikacji. Utworzymy teraz plik zasobu menu o nazwie menu_main.xml, który należy zapisać w katalogu app/src/main/res/menu. W tym katalogu umieszczane są wszystkie pliki zasobów menu. Aby utworzyć nowy plik zasobu menu, wybierz katalog app/src/main/res, a później wybierz z menu opcję File/New. Następnie wybierz opcję pozwalającą utworzyć nowy plik zasobów. Android Studio poprosi o podanie nazwy pliku oraz określenie typu zasobów. Plikowi nadaj nazwę menu_main, jako typ wybierz opcję Menu i dodatkowo upewnij się, że plik zostanie umieszczony w katalogu menu. Po kliknięciu przycisku OK Android Studio utworzy plik zasobu i zapisze go w katalogu app/src/main/res/menu.

¨  Prosty pasek aplikacji ¨  Pasek narzędzi ¨  Akcja ¨  Przycisk W górę ¨  Akcja udostępniania

Być może Android Studio już i utworzyło dla nas ten plik. Jeśl po to o, stał się tak e czni fakty prostu zastąp jego zawartość kodem przedstawionym poniżej.

Poniżej przedstawiony został kod pliku zasobu menu z dodaną akcją. Zastąp zawartość swojego pliku menu_main.xml poniższym kodem:

Element

identyfikuje dany plik

menu

menu_main.xml

Elementem głównym pliku zasobu menu jest . Wewnątrz niego możne być umieszczonych dowolnie wiele elementów , z których każdy opisuje odrębną akcję. W naszym przykładzie zdefiniowana została jedna taka akcja. Do opisu akcji służą atrybuty elementu . W powyższym przykładzie akcja ma identyfikator, atrybut id, action_create_order. Określenie identyfikatora pozwoli nam później odwołać się do danej akcji w kodzie aktywności i zareagować na kliknięcie akcji przez użytkownika. Akcje zawierają także kilka innych atrybutów, które na przykład określają sposób ich wyświetlania na pasku aplikacji, podają używaną ikonę oraz tekst. Przyjrzymy się im wszystkim dokładniej na następnej stronie.

jesteś tutaj  321

Jak wyglądają akcje?

Określ wygląd akcji Podczas prezentowania akcji na pasku aplikacji niemal zawsze będziemy chcieli wyświetlać na nim ikonę. Ikoną może być dowolny zasób graficzny. Do określania ikony akcji używany jest atrybut icon: android:icon=”@drawable/ic_add_white_24dp”

¨  Prosty pasek aplikacji ¨  Pasek narzędzi ¨  Akcja ¨  Przycisk W górę ¨  Akcja udostępniania

To jest nazwa zasobu graficznego, akcji. a który ma zostać użyty jako ikon

Czasami może się zdarzyć, że Android nie będzie w stanie wyświetlić ikony akcji, na przykład dlatego, że akcja nie będzie miała ikony, bądź ze względu na wyświetlenie akcji o obszarze nadmiarowym, a nie w głównej części paska aplikacji. Z tego powodu wskazane jest podanie także tytułu akcji, dzięki czemu zamiast ikony będzie mógł zostać wyświetlony krótki tekst. Tytuł akcji określa się przy użyciu atrybutu title: android:title=”@string/create_order_title”

Tytuł akcji nie zawsze jest wyświetlany, jednak warto go podawać, na wypadek gdyby akcja została wyświetlona w obszarze nadmiarowym paska aplikacji.

Jeśli pasek aplikacji ma zawierać więcej akcji, to warto także określić kolejność, w jakiej mają być wyświetlane. Do tego służy atrybut orderInCategory, którego wartością jest liczba całkowita określająca kolejność danej akcji. Akcje z niższymi wartościami tego atrybutu będą wyświetlane przed akcjami, w których wartość kolejności jest wyższa. android:orderInCategory=”1”

Akcja z atrybutem orderInCategory o wartości 1 zostanie wyświetlona przed akcją, w której atrybut ten ma wartość 10.

I w końcu atrybut showAsAction służy do określania, w jaki sposób akcja ma być wyświetlana na pasku aplikacji. Na przykład używając tego atrybutu, można zażądać, by akcja była umieszczona w obszarze nadmiarowym, a nie w głównym obszarze paska aplikacji. Atrybut showAsAction może przyjmować następujące wartości: "ifRoom"

Element zostanie umieszczony na pasku aplikacji, o ile tylko będzie na nim dosyć miejsca. Jeśli miejsca będzie zbyt mało, element zostanie umieszczony w obszarze nadmiarowym.

"withText"

Zostanie wyświetlony tekst elementu.

"never"

Element będzie umieszczany jedynie w obszarze nadmiarowym, nigdy nie pojawi się w głównym obszarze paska aplikacji.

"always"

Element będzie zawsze umieszczany w głównym obszarze paska. Tej wartości atrybutu należy używać sporadycznie; jeśli zostanie użyta w zbyt wielu elementach, to mogą one się wzajemnie przesłaniać.

My chcemy, by akcja była prezentowana w głównym obszarze paska aplikacji, o ile będzie tam dostatecznie dużo miejsca, dlatego zastosujemy poniższy atrybut: app:showAsAction=”ifRoom”

Teraz nasz plik zasobu menu jest już gotowy. Kolejną rzeczą, którą musimy zrobić, jest implementacja metody onCreateOptionsMenu() w kodzie aktywności.

322

Rozdział 8.

Dostępne są także inne atrybuty służące do kontroli wyglądu akcji, jednak te przedstawione powyżej są najczęściej używane.

Biblioteki wsparcia i paski aplikacji

¨  Prosty pasek aplikacji ¨  Pasek narzędzi ¨  Akcja

3. Dodaj menu do paska aplikacji w metodzie onCreateOptionsMenu() Po utworzeniu pliku zasobu menu zdefiniowane w nim akcje można dodać do paska aplikacji poprzez zaimplementowanie w aktywności metody onCreateOptionsMenu(). Metoda ta jest wykonywana podczas tworzenia menu paska aplikacji. Definiuje ona jeden parametr, obiekt Menu, stanowiący reprezentację pliku zasobu menu w kodzie aplikacji.

¨  Przycisk W górę ¨  Akcja udostępniania

Poniżej przedstawiony został kod metody onCreateOptionsMen(), który należy umieścić w pliku MainActivity.java (zaktualizuj swoją wersję tego pliku, tak by była taka sama, jak nasza): package com.hfad.wloskieconieco; WloskieCoNieco

import android.view.Menu; ...

Metoda onCreateOptionsMenu() używa klasy Menu.

java

public class MainActivity extends AppCompatActivity { spowoduje Zaimplementowanie tej metody ego pliku dodanie wszelkich opcji z określon zasobu menu do paska aplikacji.

...

app/src/main

com.hfad.wloskieconieco MainActivity.java

@Override public boolean onCreateOptionsMenu(Menu menu) { // Przygotowanie menu i dodanie elementów do paska aplikacji getMenuInflater().inflate(R.menu.menu_main, menu); return super.onCreateOptionsMenu(menu); } }

Poniższy wiersz kodu: getMenuInflater().inflate(R.menu.menu_main, menu);

Ogólnie rzecz biorąc, wszystkie metody onCreateOptionsMenu() wyglądają właśnie tak. To jest obiekt Menu, stanowiący w kodzie Javy reprezentację pliku zasobu menu.

To jest plik zasobu menu.

powoduje przygotowanie pliku zasobu menu. Oznacza to, że tworzy on obiekt Menu, stanowiący, w kodzie pisanym w Javie, reprezentację pliku zasobu menu, a wszystkie akcje zdefiniowane w tym pliku zostają przekształcone na obiekty MenuItems, które następnie są dodawane do paska aplikacji. Jest jeszcze jedna rzecz, którą musimy zrobić: zadbanie o to, by kliknięcie akcji powodowało uruchomienie aktywności OrderActivity. Zajmiemy się tym na następnej stronie.

jesteś tutaj  323

Metoda onOptionsItemSelected()

4. Reagowanie na kliknięcia elementów akcji w metodzie onOptionsItemSelected()

¨  Prosty pasek aplikacji ¨  Pasek narzędzi ¨  Akcja ¨  Przycisk W górę ¨  Akcja udostępniania

Aby aktywność mogła reagować na kliknięcia elementów umieszczonych na jej pasku akcji, należy w niej zaimplementować metodę Obiekt MenuItem to umieszczona na onOptionsItemSelected(): pasku aplikacji akcja, która została kliknięta.

@Override

public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { ...

Pobieramy identyfikator akcji.

default: return super.onOptionsItemSelected(item); } }

Metoda onOptionsItemSelected() jest wywoływana za każdym razem, gdy zostanie kliknięta któraś z akcji. Metoda ta ma jeden parametr, obiekt MenuItem, reprezentujący klikniętą akcję paska aplikacji. Używając metody getItemId() przekazanego obiektu, można określić identyfikator klikniętej akcji i na jego podstawie wykonać odpowiednie czynności, na przykład uruchomić nową aktywność. W naszej aplikacji chcemy, aby kliknięcie akcji spowodowało uruchomienie akcji OrderActivity. Zapewni to metoda onOptionsItemSelected() przedstawiona w poniższym przykładzie: @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.action_create_order: // Kod wykonywany po kliknięciu elementu Złóż zamówienie Intent intent = new Intent(this, OrderActivity.class); startActivity(intent); return true; default:

Zwrócenie wartości true informuje system, że kliknięcie elementu zostało obsłużone.

return super.onOptionsItemSelected(item); } }

Na następnej stronie przedstawiony zostanie pełny kod pliku MainActivity.java:

324

Rozdział 8.

Ta intencja pozwala uruchomić aktywność OrderActivity po kliknięciu przycisku Złóż zamówienie.

Biblioteki wsparcia i paski aplikacji

Pełny kod plik MainActivity.java Poniżej przedstawiliśmy pełną zawartość pliku MainActivity.java. Zaktualizuj swoją wersję tego pliku, tak by była identyczna z naszą. Wszystkie wprowadzone zmiany zostały wyróżnione pogrubioną czcionką.

¨  Prosty pasek aplikacji ¨  Pasek narzędzi ¨  Akcja ¨  Przycisk W górę ¨  Akcja udostępniania

package com.hfad.wloskieconieco; import import import import import import

android.support.v7.app.AppCompatActivity; android.os.Bundle; android.support.v7.widget.Toolbar; android.view.Menu; Te klasy są używane przez metodę android.view.MenuItem; onOptionsItemSelected(), więc musimy je zaimportować. android.content.Intent;

WloskieCoNieco app/src/main

public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); }

java com.hfad.wloskieconieco MainActivity.java

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

Ta metoda jest wywoływana po kliknięciu którejś z akcji umieszczonych na pasku aplikacji.

@Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.action_create_order: Intent intent = new Intent(this, OrderActivity.class); startActivity(intent); return true; default: return super.onOptionsItemSelected(item); } } }

Zobaczmy teraz, co się stanie po uruchomieniu aplikacji.

jesteś tutaj  325

Jazda próbna

¨  Prosty pasek aplikacji ¨  Pasek narzędzi ¨  Akcja

Jazda próbna aplikacji Po uruchomieniu aplikacji na pasku aplikacji aktywności MainActivity zostaje wyświetlona nowa akcja, Złóż zamówienie. Kliknięcie tej akcji powoduje uruchomienie aktywności OrderActivity.

Oto przycisk Złóż zamówienie.

Kliknięcie akcji Utwórz zamówienie powoduje uruchomienie aktywności OrderActivity. Na pasku aktywności zostaje wyświetlony tekst „Złóż zamówienie”.

Ale jak wrócić do aktywności MainActivity? Teraz, aby z aktywności OrderActivity powrócić do aktywności MainActivity, trzeba kliknąć przycisk Wstecz na urządzeniu. A co zrobić, gdy będziemy chcieli wrócić do tej akcji z poziomu paska aplikacji? Jednym ze sposobów rozwiązania tego problemu jest dodanie do paska aplikacji aktywności OrderActivity akcji, której kliknięcie spowoduje uruchomienie aktywności MainActivity. Istnieje jednak lepsze rozwiązanie. Możemy wrócić z aktywności OrderActivity do MainActivity, włączając na pasku aplikacji aktywności OrderActivity przycisk W górę.

326

Rozdział 8.

¨  Przycisk W górę ¨  Akcja udostępniania

Biblioteki wsparcia i paski aplikacji

¨  Prosty pasek aplikacji ¨  Pasek narzędzi ¨  Akcja ¨  Przycisk W górę

Włączanie nawigacji w górę

Jeśli tworzona aplikacja zawiera hierarchię aktywności, to możemy włączyć na pasku akcji przycisk W górę, który pozwoli poruszać się po To jest przycisk W górę. ¨  Akcja udostępniania aplikacji według hierarchicznych relacji pomiędzy aktywnościami. Na przykład w naszej aplikacji na pasku akcji aktywności MainActivity umieściliśmy przycisk uruchamiający aktywność OrderActivity. Jeśli włączymy przycisk W górę na pasku akcji aktywności OrderActivity, to klikając go, użytkownik będzie mógł wrócić do aktywności MainActivity.

Kliknij przycisk Złóż zamówienie, by przejść do aktywności OrderActivity.

Kliknij przycisk W górę, by…

vity. …wrócić do aktywności MainActi

Można by sądzić, że ten sposób nawigowania po aplikacji nie różni się od możliwości, jakie daje przycisk Wstecz, ale w rzeczywistości jest inaczej. Przycisk Wstecz pozwala użytkownikom cofać się przez historię uruchomionych wcześniej aktywności. Natomiast działanie przycisku W górę bazuje wyłącznie na hierarchicznej strukturze aplikacji. Aktywność nadrzędna.

Użyj przycisku Wstecz, by przejść do poprzedniej aktywności.

Aktywność podrzędna.

Klikając przycisk W górę w aktywności podrzędnej, przechodzimy w górę hierarchii do aktywności nadrzędnej.

Użyj przycisku W górę, by przejść w górę hierarchii 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  327

Odpowiedzialne zwierzchnictwo

Określanie aktywności nadrzędnej Przycisk W górę pozwala użytkownikowi poruszać się ku górze hierarchii aktywności w danej aplikacji. Ta hierarchia jest deklarowana w pliku manifestu aplikacji, AndroidManifest.xml, poprzez określanie aktywności nadrzędnych. Na przykład w naszej aplikacji chcemy, by klikając przycisk W górę, użytkownik miał możliwość przejścia z aktywności OrderActivity do aktywności MainActivity. Oznacza to, że MainActivity jest aktywnością nadrzędną aktywności OrderActivity.

¨  Prosty pasek aplikacji ¨  Pasek narzędzi ¨  Akcja ¨  Przycisk W górę ¨  Akcja udostępniania

Począwszy od API poziomu 16, aktywność nadrzędną określamy za pomocą atrybutu android:parentActivityName. W starszych wersjach systemu, aby określić nazwę aktywności nadrzędnej, musimy skorzystać z elementu . Poniżej przedstawiliśmy kod naszego pliku AndroidManifest.xml, w którym zostały zastosowane oba te sposoby określania aktywności nadrzędnej:







ivity rAct Orde ci wnoś akty

Elementu trzeba używ

w przypadkach, gdy aplikacja ma ać wyłącznie z API poziomu niższego od 16. korzystać

Dodaliśmy go tu tylko po to, byś mógł poznać ten

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.

328

Rozdział 8.

Biblioteki wsparcia i paski aplikacji

Dodawanie przycisku W górę

¨  Prosty pasek aplikacji ¨  Pasek narzędzi ¨  Akcja ¨  Przycisk W górę

Przycisk W górę jest wyświetlany z poziomu kodu aktywności. W pierwszej kolejności musimy pobrać referencję do paska akcji aktywności. Służy do tego metoda getSupportActionBar(). Następnie musimy wywołać metodę setDisplayHomeAsUpEnabled(), przekazując do niej wartość true.

¨  Akcja udostępniania

ActionBar actionBar = getSupportActionBar(); actionBar.setDisplayHomeAsUpEnabled(true);

W naszej aplikacji chcemy wyświetlać przycisk W górę w aktywności OrderActivity, dlatego powyższy fragment kodu dodamy do metody onCreate() w pliku OrderActivity.java. Oto kompletny kod tej aktywności:

Jeśli chcesz wyświetlić   w aktywności przycisk   W górę, musisz określić jej aktywność nadrzędną.

Obejrzyj to!

package com.hfad.bitsandpizzas; import android.support.v7.app.AppCompatActivity;

W przeciwnym razie wywołanie metody setDisplayHomeAsUpEnabled() spowoduje zgłoszenie wyjątku NullPointerException.

import android.os.Bundle; import android.support.v7.widget.Toolbar; import android.support.v7.app.ActionBar;

Używamy klasy ActionBar, więc musimy ją zaimportować.

public class OrderActivity extends AppCompatActivity { @Override

WloskieCoNieco app/src/main

protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_order); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar);

} }

Musisz użyć metody getSupportActionBar(), ActionBar actionBar = getSupportActionBar(); gdyż używamy paska narzędzi pochodzącego actionBar.setDisplayHomeAsUpEnabled(true); z biblioteki wsparcia.

java com.hfad.wloskieconieco OrderActivity.java

Ten fragment kodu wyświetla przy W górę na pasku aktywności aplikcisk Choć jako paska aplikacji używamyacji. paska narzędzi, to i tak musimy posługiwać się klasą ActionBar.

I to już wszystkie zmiany, które musimy wprowadzić, by wyświetlić przycisk W górę. Zobaczmy teraz, co się stanie po uruchomieniu aplikacji.

jesteś tutaj  329

Jazda próbna

Jazda próbna aplikacji

¨  Prosty pasek aplikacji ¨  Pasek narzędzi ¨  Akcja ¨  Przycisk W górę

Kiedy uruchomisz aplikację i klikniesz przycisk Złóż zamówienie, to podobnie jak wcześniej zostanie wyświetlona aktywność OrderActivity.

¨  Akcja udostępniania

Na pasku akcji aktywności OrderActivity zostanie teraz wyświetlony przycisk W górę. Kliknięcie tego przycisku spowoduje wyświetlenie aktywności nadrzędnej określonej w hierarchii aplikacji, czyli aktywności MainActivity. Kliknij przycisk Złóż zamówienie, by uruchomić aktywność OrderActivity.

To jest ekran aktywności MainActivity.

Aktywność OrderActivity zawiera przycisk W górę. Kiedy go klikniesz… …zostanie wyświetlona aktywność nadrzędna aktywności OrderActivity (czyli MainActivity).

Jak na razie pokazaliśmy, jak można dodawać do aplikacji pasek aplikacji oraz jak dodawać do niego proste akcje. W dalszej części rozdziału dowiesz się, jak dodawać do paska aplikacji bardziej złożone akcje, używając dostawców akcji.

330

Rozdział 8.

Biblioteki wsparcia i paski aplikacji

Dzielenie się treściami z poziomu paska akcji

¨  Prosty pasek aplikacji ¨  Pasek narzędzi ¨  Akcja ¨  Przycisk W górę ¨  Akcja udostępniania

Następnym zagadnieniem, którym się zajmiemy, będzie stosowanie dostawcy akcji z paskiem akcji. Dostawca akcji to akcja definiująca swój własny sposób prezentacji oraz swoje działanie. W tym rozdziale skoncentrujemy się na dostawcy akcji udostępniania. Pozwala on użytkownikom na udostępnianie zawartości naszej aplikacji innym aplikacjom, takim jak Gmail. Na przykład możemy użyć tego dostawcy, by zapewnić użytkownikom możliwość wysyłania szczegółowych informacji o wybranej pizzy do ich znajomych.

Właśnie tak wygląda akcja udostępniania wyświetlona na pasku akcji. Po jej kliknięciu wyświetlana jest lista aplikacji, ci. którym możemy udostępnić treś Akcja udostępniania wyświetla także ikonę najczęściej używanej aplikacji do dzielenia się treściami, a w tym przypadku jest to ikona Wiadomości. Początkowo ikona ta może nie być widoczna.

Dostawca akcji udostępniania definiuje swoją własną ikonę, dzięki czemu nie musimy jej określać własnoręcznie. Po jej kliknięciu dostawca wyświetla listę wszystkich aplikacji, którym dane treści mogą być udostępnione. Dla każdej z najczęściej wybieranych aplikacji do udostępniania treści dostawca wyświetla odrębną ikonę.

Treści są udostępniane za pomocą intencji Aby dostawca akcji udostępniania udostępnił treści, musimy przekazać do niego intencję. Ta intencja definiuje treści, które chcemy udostępnić, i określa ich typ. Na przykład jeśli zdefiniujemy intencję zawierającą tekst, i zastosujemy akcję ACTION_SEND, to akcja udostępniania wyświetli nam listę zainstalowanych na danym urządzeniu aplikacji, które są w stanie udostępniać teksty. A oto w jaki sposób działa akcja udostępniania (na następnych dwóch stronach zobaczysz, jak ona wygląda w działaniu):

1 Aktywność tworzy intencję i przekazuje ją do dostawcy akcji udostępniania. Intencja określa treść, która ma być udostępniona, jej typ i akcję. Intencja

TwojaAktywnosc

2

ACTION_SEND type: “text/plain” messageText:”Cześć!” ShareAction Provider

Kiedy użytkownik kliknie akcję udostępniania, użyje ona przekazanej intencji, by wyświetlić użytkownikowi listę aplikacji, które są w stanie obsłużyć tę intencję.

Użytkownik wybiera aplikację, a dostawca akcji udostępniania przekazuje intencję do odpowiedniej aktywności tej aplikacji, która jest w stanie ją obsłużyć. Intencja

ShareAction Provider

ACTION_SEND type: “text/plain” messageText:”Cześć!”

AktywnoscAplikacji

jesteś tutaj  331

Dodanie dostawcy akcji

Dodanie dostawcy akcji udostępniania do menu_main.xml Akcję udostępniania dodajemy do paska aplikacji poprzez wstawienie jej do pliku zasobów menu. W pierwszej kolejności do pliku strings.xml dodaj nowy zasób łańcuchowy — action_share. Użyjemy go do dodania tytułu, na wypadek gdyby została ona wyświetlona w obszarze nadmiarowym paska akcji:

¨  Prosty pasek aplikacji ¨  Pasek narzędzi ¨  Akcja ¨  Przycisk W górę ¨  Akcja udostępniania

WloskieCoNieco app/src/main

Udostępnij

Akcję udostępniania możemy dodać do pliku zasobów menu dokładnie tak samo jak wcześniej, czyli używając elementu . Tym razem jednak musimy zaznaczyć, że używamy dostawcy akcji udostępniania. Dlatego musimy dodać do elementu atrybut android:actionProviderClass o wartości android.support.v7.widget.ShareActionProvider.

res values

strings.xml

Oto kod, który dodaje do paska akcję udostępniania:

WloskieCoNieco

menu

menu_main.xml



Jeśli dodajemy dostawcę akcji udostępniania do pliku zasobów menu, to nie musimy dołączać do zasobów aplikacji jego ikony — jest ona definiowana przez samego dostawcę. A zatem, skoro już dodaliśmy akcję udostępniania do paska akcji, musimy określić, co chcemy udostępniać.

332

Rozdział 8.

To jest klasa dostawcy akcji udostępniania. Pochodzi ona z bibli oteki wsparcia appcompat.

Biblioteki wsparcia i paski aplikacji

Określanie treści za pomocą intencji Aby akcja udostępniania faktycznie udostępniała jakieś treści po kliknięciu, musimy te treści określić w kodzie aktywności. W tym celu musimy przekazać dostawcy akcji udostępniania intencję, wywołując jego metodę setShareIntent(). Oto, jak użyć akcji udostępniania do udostępnienia domyślnego tekstu: package com.hfad.wloskieconieco;

, Używamy tych dodatkowych klas więc musimy je zaimportować.

... import android.support.v7.widget.ShareActionProvider;

¨  Prosty pasek aplikacji ¨  Pasek narzędzi ¨  Akcja ¨  Przycisk W górę ¨  Akcja udostępniania

WloskieCoNieco app/src/main

import android.support.v4.view.MenuItemCompat;

java

public class MainActivity extends AppCompatActivity {

com.hfad.wloskieconieco

private ShareActionProvider shareActionProvider; MainActivity.java

ShareActionProvider. Dodaliśmy zmienną prywatną typu

... @Override

public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_main, menu); MenuItem menuItem = menu.findItem(R.id.action_share); shareActionProvider = (ShareActionProvider) MenuItemCompat.getActionProvider(menuItem); setShareActionIntent(“Umówimy się na pizzę?”);

Ten fragment pobiera dostawcę akcji udostępniania i zapisuje go w zmiennej prywatnej. Następnie wywołuje metodę setShareActionIntent().

return super.onCreateOptionsMenu(menu); } private void setShareActionIntent(String text) { Intent intent = new Intent(Intent.ACTION_SEND); intent.setType(“text/plain”); intent.putExtra(Intent.EXTRA_TEXT, text); shareActionProvider.setShareIntent(intent);

Utworzyliśmy także metodę setShareActionIntent(). Jej działanie polega na utworzeniu intencji i przekazaniu jej do dostawcy akcji udostępniania poprzez wywołanie jego metody setShareIntent().

} }

Metodę setShareIntent() trzeba wywoływać za każdym razem, gdy zmieni się treść, którą chcemy udostępniać. Na przykład gdybyśmy przechodzili do kolejnych zdjęć w aplikacji fotograficznej, musielibyśmy pamiętać o tym, by udostępniać aktualnie prezentowane zdjęcie. Na następnej stronie przedstawimy kompletny kod aktywności, a potem sprawdzimy, co się stanie po uruchomieniu aplikacji.

jesteś tutaj  333

Kod aktywności MainActivity

Kompletny kod aktywności MainActivity

¨  Prosty pasek aplikacji ¨  Pasek narzędzi ¨  Akcja ¨  Przycisk W górę ¨  Akcja udostępniania

Oto kompletna zawartość pliku MainActivity.java. Zaktualizuj swoją wersję pliku, by była identyczna z naszą. package com.hfad.bitsandpizzas; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.support.v7.widget.Toolbar; import android.view.Menu; import android.view.MenuItem; import android.content.Intent; import android.support.v7.widget.ShareActionProvider; import android.support.v4.view.MenuItemCompat;

Używamy tych dodatkowych klas, więc musimy je zaimportować.

public class MainActivity extends AppCompatActivity { private ShareActionProvider shareActionProvider; @Override WloskieCoNieco

protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);

app/src/main

setContentView(R.layout.activity_main); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);

java

setSupportActionBar(toolbar); }

com.hfad.wloskieconieco

@Override

MainActivity.java

public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_main, menu); MenuItem menuItem = menu.findItem(R.id.action_share); shareActionProvider = (ShareActionProvider) MenuItemCompat.getActionProvider(menuItem); setShareActionIntent("Umówimy się na pizzę?"); return super.onCreateOptionsMenu(menu); }

334

Rozdział 8.

To jest domyślny tekst, który powinna udostępnić akcja udostępniania.

Dalsza część kodu znajduje się na następnej stronie.

Biblioteki wsparcia i paski aplikacji

Ciąg dalszy kodu aktywności MainActivity

WloskieCoNieco

private void setShareActionIntent(String text) { Intent intent = new Intent(Intent.ACTION_SEND);

app/src/main

intent.setType("text/plain");

java

intent.putExtra(Intent.EXTRA_TEXT, text); shareActionProvider.setShareIntent(intent); } To wywołanie określa domyślny tekst w dostawcy akcji udostępniania.

@Override

com.hfad.wloskieconieco MainActivity.java

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); return true; default: return super.onOptionsItemSelected(item); } } }

Na następnej stronie weźmiemy aplikację na jazdę próbną, by sprawdzić, co się z nią stanie.

jesteś tutaj  335

I kolejna jazda próbna

Jazda próbna aplikacji Po uruchomieniu aplikacji na jej pasku zostanie wyświetlony przycisk do udostępniania:

To jest ikona akcji Udostępnij.

Dostawca akcji udostępniania doda ł do paska aplikacji także ikonę Messanger. My często udostępn iamy treści z tej aplikacji w Messang erze, dlatego akcja udostępniania wyświetliła nam skrót do tej aplikacji.

Kliknięcie akcji udostępniania powoduje wyświetlenie listy aplikacji, które mogą obsłużyć intencję udostępniania:

Kliknij ikonę akcji udostępniania .

Intencja przekazana do dostawcy akcji udostępniania stwierdza, że chcemy wysłać tekst używając akcji ACTION_ SEND. W efekcie wyświetlana jest lista wszystkich aplikacji, które mogą to zrobić.

Po wybraniu aplikacji, której chcemy udostępnić treści, zostanie ona uruchomiona, a udostępniona treść zostanie do niej przekazana:

Zdecydowaliśmy się udostępniać treści przy użyciu aplikacji Messanger.

336

Rozdział 8.

Po wybraniu aplikacji zostanie jej udostępniony domyślny tekst. Na naszym urządzeniu wybraliśmy aplikację Messanger, dlatego udostępniony tekst został w niej wyświetlony jako treść wiadomości.

Biblioteki wsparcia i paski aplikacji

Opanowałeś już rozdział 8. i dodałeś do swojego przybornika z narzędziami znajomość bibliotek wsparcia Androida oraz umiejętność tworzenia i stosowania pasków aplikacji.

CELNE SPOSTRZEŻENIA 





Prosty pasek aplikacji dodajemy, wybierając w aplikacji motyw, który zawiera pasek. Biblioteki wsparcia Androida zapewniają zgodność z wcześniejszymi wersjami systemu. Klasa AppCompatActivity to typ aktywności udostępniany przez bibliotekę wsparcia appcompat v7. Ogólnie rzecz biorąc, tworzone aktywności muszą dziedziczyć po tej klasie zawsze, gdy chcemy, by używany pasek aplikacji zapewniał zgodność z wcześniejszymi wersjami Androida.









. Używany motyw można określić w pliku manifestu aplikacji, AndroidManifest.xml, za pomocą atrybutu android:theme. 





Style definiujemy w pliku zasobów stylów, używając elementu



styles.xml

Android Studio mogło samo określić te kolory.

Styl AppTheme używa zasobów kolorów, a te powinny zostać zdefiniowane w pliku colors.xml. W pierwszej kolejności upewnij się, że Android Studio utworzyło ten plik w katalogu app/src/main/res/values (a jeśli go nie utworzyło, to zrób to samodzielnie). Następnie zapisz w pliku colors.xml poniższy fragment kodu: KociCzat



app/src/main

#3F51B5 res

#303F9F #FF4081

Dodaj te kolory, jeśli Android Studio samo tego nie zrobiło.

Skoro już przygotowaliśmy style pozwalające na zastosowanie paska narzędzi, utworzymy dwie aktywności: jedną na potrzeby systemu pomocy aplikacji oraz drugą do podawania opinii. Aktywności te będziemy uruchamiać, kiedy użytkownik wybierze odpowiednie opcje z szuflady nawigacyjnej.

590

Rozdział 14.

values

colors.xml

Szuflady nawigacyjne

¨ Fragmenty i aktywności

Utworzenie aktywności HelpActivity

¨ Nagłówek ¨ Opcje ¨ Szuflada

Zaczniemy od utworzenia aktywności HelpActivity. Zaznacz pakiet com.hfad. kociczat, a następnie wybierz z menu opcję File/New. Potem wybierz opcję utworzenia pustej aktywności, nadaj jej nazwę HelpActivity, a używanemu przez nią plikowi układu nazwę activity_help. Upewnij się, że klasa aktywności będzie należeć do pakietu com.hfad.kociczat oraz że jest zaznaczone pole wyboru Backwards Compatibility (AppCompat). Następnie zaktualizuj swoją wersję pliku activity_help.xml, tak by zawierał poniższy kod:

Aktywność HelpActivity



app/src/main

Do aktywności HelpActivity dodajemy pasek narzędzi oraz widok tekstowy ze słowem „Pomoc”.

res layout



activity_help.xml

Teraz zaktualizuj plik HelpActivity.java, zapisując w nim poniższy kod: package com.hfad.kociczat;

aktywność Używamy motywu AppCompat, więcpatActivity. Com App ie klas po zyć dzic musi dzie

import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.support.v7.widget.Toolbar; public class HelpActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_help); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); } }

KociCzat app/src/main java com.hfad.kociczat HelpActivity.java

jesteś tutaj  591

Kod aktywności FeedbackActivity

Utworzenie aktywności FeedbackActivity

¨ Fragmenty i aktywności

Teraz zaznacz pakiet com.hfad.kociczat i wybierz z menu opcję File/New. Następnie wybierz opcję utworzenia pustej aktywności, nadaj jej nazwę FeedbackActivity, a używanemu przez nią plikowi układu nazwę activity_feedback. Upewnij się, że klasa aktywności będzie należeć do pakietu com.hfad.kociczat oraz że jest zaznaczone pole wyboru Backwards Compatibility (AppCompat). Następnie zaktualizuj swoją wersję pliku activity_feedback.xml, tak by zawierał poniższy kod:

¨ Nagłówek ¨ Opcje ¨ Szuflada

Aktywność FeedbackActivity



res

layout

activity_feedback.xml



Teraz zaktualizuj plik FeedbackActivity.java, zapisując w nim poniższy kod: package com.hfad.kociczat; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.support.v7.widget.Toolbar;

Także ta aktywność musi dziedziczyć po klasie AppCompatActivity.

public class FeedbackActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_feedback); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); } }

592

KociCzat app/src/main java com.hfad.kociczat FeedbackActivity.java

Rozdział 14.

Szuflady nawigacyjne

Musimy przygotować szufladę nawigacyjną Dodaliśmy już do projektu wszystkie fragmenty i aktywności; będą one powiązane z opcjami, które znajdą się w szufladzie nawigacyjnej. Teraz zajmiemy się utworzeniem samej szuflady.

¨ Fragmenty i aktywności ¨ Nagłówek ¨ Opcje ¨ Szuflada

Szuflada nawigacyjna składa się z dwóch odrębnych komponentów:



Nagłówka szuflady.

To układ wyświetlany na samej górze szuflady nawigacyjnej. Zazwyczaj zawiera on obrazek i jakiś tekst, na przykład zdjęcie użytkownika i jego adres poczty elektronicznej.

To jest nagłówek szuflady, który utworzymy w naszej aplikacji. Składa się on z obrazka i dwóch fragmentów tekstu.



Zestawu opcji.

Zestaw opcji jest wyświetlany w szufladzie nawigacyjnej poniżej jej nagłówka. Kiedy użytkownik kliknie jedną z tych opcji, ekran skojarzony z daną opcją zostanie wyświetlony, bądź to jako fragment w aktywności zawierającej szufladę, bądź jako nowa aktywność.

Szuflada nawigacyjna będzie zawierać te opcje.

Zajmiemy się teraz przygotowaniem obu tych komponentów, a następnie użyjemy ich w aktywności MainActivity, aby zaimplementować szufladę nawigacyjną. Zaczniemy od nagłówka szuflady.

jesteś tutaj  593

Utworzenie nagłówka

Utworzenie nagłówka szuflady nawigacyjnej

¨ Fragmenty i aktywności ¨ Nagłówek ¨ Opcje ¨ Szuflada

Nagłówek szuflady nawigacyjnej składa się z prostego układu, który dodamy do nowego pliku układu. Utworzymy w tym celu nowy plik, o nazwie nav_header.xml. A zatem, aby to zrobić, zaznacz w Android Studio katalog app/src/main/res/layout, po czym wybierz opcję File/New/ Layout resource file. Kiedy zostaniesz poproszony o podanie nazwy pliku, wpisz nav_header. Nasz układ nagłówka składa się z trzech widoków: ImageView oraz dwóch TextView. Oznacza to, że do projektu musimy dodać plik obrazka jako zasób graficzny oraz dwa zasoby łańcuchowe. Zaczniemy od pliku obrazka.

Dodanie pliku obrazka Aby dodać obrazek, w pierwszej kolejności w eksploratorze Android Studio musisz przełączyć się do widoku Project, o ile nie zrobiłeś tego już wcześniej. Następnie sprawdź, czy w projekcie istnieje katalog o nazwie app/src/main/res/drawable. Jeśli go nie ma, to zaznacz katalog app/src/ main/res, wybierz z menu opcję File/New..., a następnie opcję pozwalającą utworzyć nowy katalog zasobów aplikacji na Androida. Gdy zostaniesz poproszony o podanie nazwy katalogu, wpisz drawable i kliknij OK.

Nagłówek zawiera widok ImageView…

…oraz dwa widoki TextView.

Po utworzeniu katalogu drawable w przykładach dołączonych do książki odszukaj plik kitten_small.jpg — będzie on umieszczony w analogicznym podrozdziale drawable, w katalogu rozdzial_14 — i skopiuj go do katalogu drawable swojego projektu.

Dodanie zasobów łańcuchowych Kolejnym krokiem będzie dodanie zasobów łańcuchowych, których będziemy używać do określenia tekstów wyświetlanych w nagłówku. Otwórz plik app/src/main/res/values/strings.xml i dodaj do niego dwa poniższe zasoby:

... KociCzat

KociCzat Android Studio mogło dodać ten zasób app/src/main automatycznie podczas tworzenia projektu.

[email protected]

Skoro dodaliśmy już zasoby, możemy się zająć pisaniem kodu układu. Już doskonale potrafisz napisać cały niezbędny kod, dlatego na następnej stronie przedstawimy go w całości.

594

Rozdział 14.

res values

strings.xml

Szuflady nawigacyjne

Kompletny kod pliku nav_header.xml

¨ Fragmenty i aktywności ¨ Nagłówek ¨ Opcje ¨ Szuflada

Poniżej przedstawiliśmy kompletny kod pliku nav_header.xml; zaktualizuj plik w swoim projekcie, tak by był identyczny z naszym:

Tło obrazka jest dość ciemne, więc używamy tego wiersza, który spra wi, że tekst będzie jasny.

KociCzat app/src/main

layout

Ten układ LinearLayout zostanie wyświetlony na tle obrazka. Używamy go do wyświetlenia tekstów u dołu obrazka.



nav_header.xml



To jest wbudowany styl, który powoduje, że wyświetlany w nim tekst jest nieco bardziej wytłuszczony. Jest on dostępny w bibliotece wsparcia AppCompat.



Skoro już utworzyliśmy nagłówek szuflady nawigacyjnej, możemy przejść do przygotowania listy opcji.

jesteś tutaj  595

Dodanie łańcuchów znaków

Szuflada pobiera opcje z menu

¨ Fragmenty i aktywności ¨ Nagłówek ¨ Opcje

Szuflada nawigacyjna pobiera listę swoich opcji z pliku zasobu menu. Kod realizujący tę operację jest bardzo podobny do tego, którego używaliśmy do dodawania opcji do paska aplikacji.

¨ Szuflada

Zanim przyjrzymy się kodowi służącemu do dodawania opcji do szuflady nawigacyjnej, musimy dodać do projektu plik zasobu menu. W tym celu zaznacz w Android Studio katalog app/src/main/res i z menu głównego wybierz opcję File/New. Następnie wybierz opcję pozwalającą na utworzenie nowego pliku zasobu. W efekcie zostaniesz poproszony o podanie nazwy pliku oraz typu zasobu. Jako nazwę wpisz menu_nav, a na liście typów zasobów wybierz opcję Menu. Upewnij się także, że w polu Directory jest wybrany katalog menu. Po kliknięciu przycisku OK Android Studio utworzy plik zasobu. W kolejnym kroku dodamy do projektu zasoby łańcuchowe określające tytuły poszczególnych opcji, dzięki czemu będziemy mogli ich użyć w dalszej części rozdziału. Otwórz plik strings.xml i dodaj do niego poniższe zasoby:

... Skrzynka odbiorcza Szkice Wysłane

KociCzat app/src/main

Kosz

res

Wsparcie values

Pomoc



Opinie

strings.xml

Teraz możemy już przystąpić do pisania kodu menu.

Tworzone menu ma mieć dwie sekcje Jak już zaznaczyliśmy wcześniej, chcemy podzielić opcje w szufladzie nawigacyjnej na dwie sekcje. Pierwsza z nich będzie zawierać opcje pozwalające użytkownikom na przejście w kilka głównych miejsc aplikacji, a konkretnie do: skrzynki odbiorczej, szkiców, wiadomości wysłanych oraz kosza. Poniżej będzie umieszczona odrębna sekcja zawierająca opcje związane z systemem pomocy oraz opiniami. Zacznijmy od dodania opcji głównych.

596

Rozdział 14.

To są główne opcje szuflady nawigacyjnej.

To jest sekcja wsparcia.

Szuflady nawigacyjne

Dodawaj opcje w kolejności, w jakiej mają być wyświetlane

¨ Fragmenty i aktywności ¨ Nagłówek ¨ Opcje ¨ Szuflada

Podczas projektowania zestawu opcji mających się znaleźć w szufladzie nawigacyjnej zazwyczaj te z nich, które najprawdopodobniej będą najczęściej klikane przez użytkownika, powinny się znaleźć na samej górze listy. W naszym przykładzie będą to opcje: skrzynki odbiorczej, szkiców, wiadomości wysłanych i kosza. Poszczególne elementy należy dodawać do pliku zasobu w takiej kolejności, w jakiej później mają być wyświetlane w szufladzie nawigacyjnej. Dla każdego z elementów należy podać identyfikator, dzięki któremu będzie można odwoływać się do niego w kodzie Javy, oraz tytuł określający tekst, jaki chcemy wyświetlić w szufladzie. Dodatkowo można także określić ikonę, która zostanie wyświetlona obok tytułu opcji. W ramach przykładu poniżej pokazaliśmy kod dodający do szuflady nawigacyjnej opcję „Skrzynka odbiorcza”:

KociCzat



app/src/main

Musisz określić identyfikator elementu, tak by kod aktywności mógł odpowiedzieć na kliknięcie danego elementu przez użytkownika.

...

To jest tekst, który zostanie wyświetlony w szufladzie nawigacyjnej.

res menu

To jest wbudowany zasób graficzny, którego można użyć do wyświetlenia ikony e-maila.

menu_nav.xml

W powyższym kodzie zastosowaliśmy jedną z wbudowanych ikon dostępnych w systemie Android: ”@android:drawable/sym_action_email”. System Android zawiera wbudowany zestaw ikon, których można używać w tworzonych aplikacjach. O tym, że chcemy użyć jednej z tych ikon informuje początkowy fragment identyfikatora: ”@android:drawable”. Pełną listę dostępnych ikon można wyświetlić, rozpoczynając wpisywanie identyfikatora w Android Studio:

To niektóre z wbudowanych zasobów graficznych Androida.

jesteś tutaj  597

Grupowanie elementów

Jak można grupować elementy? Oprócz dodawania do szuflady nawigacyjnej pojedynczych elementów można je także dodawać w grupach. Grupy definiuje się przy użyciu znacznika , jak pokazano poniżej:

Tutaj umieszczamy wszystkie elementy, które chcemy dodać do grupy.

...

Rozwiązanie to jest przydatne, gdy chcemy zastosować jakiś atrybut dla całej grupy elementów. Na przykład aby wyróżnić w szufladzie element wybrany przez użytkownika, można dodać do znacznika atrybut android:checkableBehavior o wartości ”single”. Ten sposób działania szuflady jest przydatny, jeśli planujemy wyświetlanie ekranów poszczególnych opcji jako fragmentów podmienianych aktywności, do której została dodana szuflada nawigacyjna (w naszym przykładzie jest to aktywność MainActivity), gdyż ułatwia określenie obecnie wybranej opcji:

...

Użycie tej wartości atrybutu oznacza, że w grupie będzie wyróżniona pojedyncza opcja (ta, którą wybrał użytkownik).



Wybrany element szuflady nawigacyjnej można domyślnie wyróżnić, dodając do niego atrybut android:checked o wartości ”true”. Poniższy przykład pokazuje, w jaki sposób można wyróżnić element „Skrzynka odbiorcza”:

...

Zastosowanie tego atrybutu powoduje domyślne wyróżnienie danego elementu w szufladzie nawigacyjnej.

Na następnej stronie pokażemy pełny kod definiujący cztery początkowe elementy szuflady nawigacyjnej.

598

Rozdział 14.

¨ Fragmenty i aktywności ¨ Nagłówek ¨ Opcje ¨ Szuflada

Szuflady nawigacyjne

Opcje pierwszej sekcji zostaną umieszczone w grupie Opcje dla skrzynki odbiorczej, szkiców, wiadomości wysłanych oraz kosza dodamy do naszego pliku zasobu menu w formie jednej grupy i domyślnie wyróżnimy pierwszą z nich. Opcje te umieścimy w grupie, ponieważ ekrany dla każdej z nich są fragmentami wyświetlanymi Kod zamieszczony w ramach aktywności MainActivity. na tej stronie Poniżej przedstawiliśmy kodu menu; zaktualizuj swoją wersję pliku menu_nav.xml, by była identyczna z naszą:

¨ Fragmenty i aktywności ¨ Nagłówek ¨ Opcje ¨ Szuflada

dodaje do szuflady nawigacyjnej te cztery opcje.







W ten sposób uporaliśmy się z pierwszą grupą opcji. Kolejnymi zajmiemy się w dalszej kolejności.

jesteś tutaj  599

Podmenu

Sekcję wsparcia dodamy jako podmenu

¨ Fragmenty i aktywności ¨ Nagłówek ¨ Opcje

Drugi zestaw elementów tworzonej szuflady nawigacyjnej ma postać odrębnej sekcji. Znajdują się w niej nagłówek „Wsparcie” oraz dwie opcje, „Pomoc” i „Opinie”, które użytkownik będzie mógł klikać.

¨ Szuflada

Tworzenie tej sekcji rozpoczniemy od dodania nagłówka „Wsparcie”, który zdefiniujemy jako osobny element. Ponieważ ma to być nagłówek, zdefiniujemy w nim jedynie tytuł — pominiemy ikonę oraz identyfikator, gdyż ten element szuflady nie będzie musiał reagować na kliknięcia: ...

Ten element dodaje do szuflady nawigacyjnej nagłówek „Wsparcie”.

...

Chcemy, by opcje zapewniające dostęp do systemu pomocy i opinii były wyświetlone w sekcji wsparcia, dlatego dodamy je wewnątrz tej sekcji jako niezależne opcje umieszczone w podmenu: ...

KociCzat



...

Warto zwrócić uwagę na to, że tych opcji nie dodawaliśmy w formie jednej grupy; a zatem kiedy użytkownik kliknie jedną z nich, nie zostanie ona wyróżniona w szufladzie nawigacyjnej. Zastosowaliśmy takie rozwiązanie, ponieważ obie te opcje będą wyświetlane jako aktywności, a nie fragmenty prezentowane wewnątrz aktywności zawierającej szufladę nawigacyjną. Pełny kod menu przedstawimy na następnej stronie.

600

Rozdział 14.



Ten kod powoduje dodanie do szuflady tych dwóch elementów.

Szuflady nawigacyjne

Kompletny kod pliku menu_nav.xml

¨ Fragmenty i aktywności ¨ Nagłówek ¨ Opcje

Oto kompletny kod pliku menu_nav.xml; zaktualizuj ten plik w swoim projekcie, tak by był identyczny z naszym:

¨ Szuflada

Kod zamieszczony na tej stronie tworzy pełne menu.





KociCzat





Skoro dodaliśmy już do układu menu oraz nagłówek szuflady nawigacyjnej, możemy przejść do utworzenia jej samej.

jesteś tutaj  601

Utworzenie szuflady

Jak utworzyć szufladę nawigacyjną? Szufladę nawigacyjną tworzymy poprzez zastosowanie układu DrawerLayout jako głównego elementu układu aktywności. Układ DrawerLayout musi zawierać dwa elementy: pierwszym z nich ma być widok bądź też grupa widoków określające zawartość aktywności, a drugim widok NavigationView, definiujący szufladę:

¨ Fragmenty i aktywności ¨ Nagłówek ¨ Opcje ¨ Szuflada

ladę. Układ DrawerLayout definiuje szuf

Pierwszym widokiem umieszczonym w DrawerLayout jest układ określający główną zawartość aktywności. Jest ona widoczna, gdy szuflada nawigacyjna jest ukryta.

Ten atrybut umieszcza szufladę przy początkowej krawędzi aktywności (w przypadku języków, w których piszemy od lewej do prawej, będzie to lewa krawędź ekranu).

Określamy identyfikator układu, by móc odwoływać się do niego w kodzie aktywności.

...

Widok NavigationView definiuje zawartość szuflady.



A to plik zasobu menu definiującego opcje szuflady.

Wygląd szuflady nawigacyjnej określają dwa kluczowe atrybuty elementu : headerLayout oraz menu. Atrybut app:headerLayout określa układ, który powinien zostać zastosowany jako nagłówek szuflady (w naszym przykładzie jest to plik nav_header.xml). Jest to atrybut opcjonalny. Z kolei atrybut app:menu służy do określania pliku zasobu menu zawierającego opcje szuflady (w naszej aplikacji jest to plik menu_nav.xml). Jeśli pominiemy ten atrybut, szuflada nawigacyjna nie będzie zawierać żadnych elementów.

602

Rozdział 14.

Szuflady nawigacyjne

Kompletny kod układu aktywności activity_main.xml Dodamy teraz do układu aktywności MainActivity szufladę nawigacyjną, korzystającą z przygotowanego wcześniej układu nagłówka oraz pliku zasobu menu. Główna zawartość układu będzie się składać z paska narzędzi oraz układu FrameLayout. Tego układu użyjemy w dalszej części rozdziału do wyświetlania fragmentów.

¨ Fragmenty i aktywności ¨ Nagłówek ¨ Opcje ¨ Szuflada

Poniżej przedstawiliśmy kod pliku activity_main.xml; zaktualizuj zawartość tego pliku w swoim projekcie, tak by była identyczna z tą ukazaną przez nas:

DrawerLayout. Głównym elementem układu jest

Ten fragment definiuje główną zawartość aktywności.

Na główną zawartość aktywności składają się komponent Toolbar oraz układ FrameLayout, w którym będziemy wyświetlali fragmenty.

KociCzat app/src/main



res layout

activity_main.xml

wygląd Widok NavigationView definiuje ważającą szuflady nawigacyjnej oraz prze część jej zachowania. Określamy będziemy identyfikator tego widoku, gdyż w kodzie o nieg do się ć ływa odwo ieli mus ci. aktywnoś

postaci nagłówka oraz menu definiującego zawartość listy opcji.

Zanim uruchomimy aplikacje, by przekonać się, jak wygląda nasza szuflada nawigacyjna, zmienimy jeszcze kod aktywności MainActivity, aby bezpośrednio po utworzeniu aktywności wyświetlany był w niej fragment InboxFragment.

jesteś tutaj  603

Dodanie fragmentu InboxFragment

Dodanie fragmentu InboxFragment do układu aktywności MainActivity

¨ Fragmenty i aktywności ¨ Nagłówek ¨ Opcje ¨ Szuflada

Tworząc plik zasobu menu, zdefiniowaliśmy opcję skrzynki odbiorczej jako domyślnie wyróżnioną. Dlatego powinniśmy teraz wyświetlić fragment InboxFragment podczas tworzenia aktywności, tak by jej zawartość odpowiadała zaznaczonej opcji szuflady nawigacyjnej. Dodatkowo użyjemy paska narzędzi jako paska aplikacji aktywności, tak by został na nim wyświetlony tytuł aplikacji. Poniżej przedstawiliśmy naszą wersję pliku MainActivity.java; zmodyfikuj ten sam plik w swoim projekcie, by był identyczny z naszym:

KociCzat app/src/main

package com.hfad.kociczat;

java

import android.support.v7.app.AppCompatActivity; import android.os.Bundle;

com.hfad.kociczat

import android.support.v7.widget.Toolbar; import android.support.v4.app.Fragment;

MainActivity.java

import android.support.v4.app.FragmentTransaction; public class MainActivity extends AppCompatActivity { @Override

Upewnij się, że aktywność dzie dzic po klasie AppCompatActivity, gdyż zy używamy motywu AppCompat oraz fragmentów z biblioteki wsparcia .

protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); Stosujemy transakcję fragmentu, by wyświetlić instancję InboxFragment.

Fragment fragment = new InboxFragment(); FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); ft.add(R.id.content_frame, fragment); ft.commit();

} }

A teraz sprawdźmy, co się stanie po uruchomieniu aplikacji.

604

Ustawiamy komponent Toolbar jako pasek aplikacji aktywności.

Rozdział 14.

Szuflady nawigacyjne

Jazda próbna aplikacji Po uruchomieniu aplikacji w aktywności MainActivity zostaje wyświetlony fragment InboxFragment. Kiedy wykonamy gest przeciągnięcia od lewej krawędzi ekranu do jego środka (przynajmniej w przypadku języków takich jak polski, w których piszemy od strony lewej do prawej), zostanie wyświetlona szuflada nawigacyjna. Szuflada ta zawiera układ nagłówka oraz listę opcji zdefiniowaną w pliku zasobu menu. Pierwsza z tych opcji jest automatycznie wyróżniona:

¨ Fragmenty i aktywności ¨ Nagłówek ¨ Opcje ¨ Szuflada W językach, w których pisze się od strony prawej do lewej, szuflada jest wyświetlana przy prawej krawędzi ekranu.

To jest gówna zawartość aktywności MainActivity. Zawiera pasek narzędzi oraz układ FrameLayout, w którym domyślnie jest wyświetlany fragment InboxFragment.

Szuflada nawigacyjna zawiera utworzony wcześniej układ nagłówka oraz zestaw opcji. Pierwsza z tych opcji jest domyślnie zaznaczona, a u dołu znajduje się odrębna sekcja Wsparcie.

Kliknięcie którejkolwiek z opcji w szufladzie nawigacyjnej nie daje jeszcze żadnego efektu, gdyż w aktywności MainActivity nie napisaliśmy jeszcze żadnego kodu, który by kontrolował działanie szuflady. Zajmiemy się tym w następnej kolejności.

jesteś tutaj  605

Etapy

Co musi robić kod aktywności? Kod aktywności musi wykonać trzy operacje:

1

¨ Fragmenty i aktywności ¨ Nagłówek ¨ Opcje ¨ Szuflada

Dodać przełącznik szuflady.

Będzie on stanowić wizualny sygnał dla użytkownika informujący o tym, że aktywność zawiera szufladę nawigacyjną. Przełącznik ten ma postać ikony „hamburgera” umieszczonej na pasku narzędzi, którą można kliknąć, aby otworzyć szufladę. To jest ikona „hamburgera”. Jej kliknięcie powoduje wyświetlenie szuflady nawigacyjnej.

2

Zapewnić, że szuflada będzie reagować na kliknięcia.

Kiedy użytkownik kliknie jedną z opcji w szufladzie nawigacyjnej, wyświetlimy odpowiedni fragment lub odpowiednią aktywność i zamkniemy szufladę.

Kiedy użytkownik kliknie jedną z głównych opcji szuflady, wyświetlimy odpowiadający jej fragment i zamkniemy szufladę. Kiedy użytkownik ponownie otworzy szufladę, wybrana wcześniej opcja będzie wyróżnio na.

Kiedy użytkownik kliknie jedną z tych opcji, uruchomimy odpowiednią aktywność.

3

Zamykać szufladę, kiedy użytkownik naciśnie klawisz Wstecz.

Jeśli szuflada będzie widoczna, to po kliknięciu przycisku Wstecz zamkniemy ją. Jeśli szuflada będzie zamknięta, to kliknięcie przycisku Wstecz da standardowe efekty.

Zaczniemy od dodania przełącznika szuflady.

606

Rozdział 14.

Szuflady nawigacyjne

¨ Fragmenty i aktywności ¨ Nagłówek ¨ Opcje ¨ Szuflada

Dodanie przełącznika szuflady Pierwszą rzeczą, jaką musimy zrobić, jest dodanie przełącznika szuflady, dzięki któremu będziemy mogli ją wyświetlić, klikając ikonę na pasku narzędzi. Zaczniemy od dodania dwóch zasobów łańcuchowych, których użyjemy do opisania akcji „otwórz szufladę” oraz „zamknij szufladę”, wymaganych w celu ułatwiania dostępu. Dodaj poniższe dwa zasoby do pliku strings.xml:

KociCzat app/src/main

Otwórz szufladę nawigacyjną Zamknij szufladę nawigacyjną

res

Przełącznik szuflady nawigacyjnej konstruujemy w metodzie onCreate() aktywności, tworząc nową instancję klasy ActionBarDrawerToggle i dodając ją do układu DrawerLayout. Najpierw pokażemy Ci kod realizujący tę operację, a później, w dalszej części rozdziału, dodamy go do kodu aktywności MainActivity.

values

strings.xml

Konstruktor ActionBarDrawerToggle ma aż pięć parametrów: bieżącą aktywność, układ DrawerLayout, obiekt paska narzędzi oraz dwa zasoby łańcuchowe opisujące czynności otwierania i zamykania szuflady (zasoby te dodaliśmy powyżej): Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); ... DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, Ta instrukcja dodaje ikonę „hamburgera” do paska narzędzi.

Układ DrawerLayout używany w aktywności.

Te łańcuchy znaków są niezbędn e na wymogi dotyczące ułatwiania ze względu dostępu.

Po utworzeniu przełącznika szuflady musimy go dodać do układu — w tym celu należy wywołać metodę addDrawerListener() klasy DrawerLayout i przekazać w jej wywołaniu utworzoną instancję przełącznika: drawer.addDrawerListener(toggle);

Następnie należy jeszcze wywołać metodę syncState(), aby zsynchronizować stan ikony na pasku narzędzi ze stanem szuflady nawigacyjnej. To konieczne, gdyż stan ikony zmienia się, gdy klikniemy ją, by otworzyć szufladę: toggle.syncState();

Bieżąca aktywność.

drawer, toolbar,

Pasek narzędzi aktywności.

R.string.nav_open_drawer, R.string.nav_close_drawer);

KociCzat app/src/main java com.hfad.kociczat MainActivity.java

Już niebawem dodamy cały ten kod do metody onCreate() aktywności MainActivity.

jesteś tutaj  607

Reagowanie na kliknięcia

¨ Fragmenty i aktywności ¨ Nagłówek ¨ Opcje ¨ Szuflada

Reagowanie na klikanie elementów szuflady Teraz zajmiemy się zapewnieniem odpowiedniej reakcji aktywności MainActivity na klikanie opcji umieszczonych w szufladzie. W tym celu zaimplementujemy w niej interfejs NavigationView. OnNavigationItemSelectedListener. W ten sposób sprawimy, że każde kliknięcie jednej z opcji dostępnych w szufladzie nawigacyjnej spowoduje wywołanie nowej metody, onNavigationItemSelected(), którą zaimplementujemy w aktywności MainActivity. Użyjemy tej metody, by wyświetlać ekran odpowiedni dla wybranej opcji. W pierwszej kolejności, używając poniższego kodu, zaimplementujemy interfejs w kodzie aktywności MainActivity. W ten sposób aktywność stanie się obiektem nasłuchującym zdarzeń generowanych przez widok NavigationView:

KociCzat app/src/main java com.hfad.kociczat

... import android.support.design.widget.NavigationView;

MainActivity.java

public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener { ... Implementacja tego interfejsu oznacza, że aktywność może odpowiadać na kliknięcia opcji w szufladzie nawigacyjnej.

}

Kolejną czynnością będzie zarejestrowanie obiektu nasłuchującego, czyli aktywności MainActivity, w widoku NavigationView. Dzięki temu aktywność będzie informowana o tym, kiedy użytkownik kliknie jedną z opcji dostępnych w szufladzie nawigacyjnej. W tym celu w metodzie onCreate() aktywności musimy pobrać referencję do obiektu NavigationView i wywołać jego metodę setNavigationItemSelectedListener(): @Override protected void onCreate(Bundle savedInstanceState) { ... NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view); navigationView.setNavigationItemSelectedListener(this); }

I w końcu pozostaje nam zaimplementowanie metody onNavigationItemSelected().

608

Rozdział 14.

To wywołanie rejestruje aktywność jako obiekt nasłuchujący zdarzeń generowanych przez widok NavigationView, dzięki czemu zostanie ona poinformowana, kiedy użytkownik kliknie jedną z opcji dostępnych w szufladzie nawigacyjnej.

Szuflady nawigacyjne

¨ Fragmenty i aktywności ¨ Nagłówek ¨ Opcje ¨ Szuflada

Implementacja metody onNavigationItemSelected() Metoda onNavigationItemSelected() jest wywoływana, kiedy użytkownik kliknie jeden z elementów wyświetlonych w szufladzie nawigacyjnej. Ma ona jeden parametr — obiekt MenuItem, reprezentujący kliknięty element menu — i zwraca wartość logiczną określającą, czy dany element szuflady należy wyróżnić:

@Override

każdym kliknięciu Ta metoda zostaje wywołana po Jej parametrem j. yjne igac naw elementu szuflady jest kliknięty element.

public boolean onNavigationItemSelected(MenuItem item) { // kod obsługujący kliknięcie elementu szuflady }

W naszej aplikacji kod w tej metodzie musi wyświetlać odpowiedni ekran, odpowiadający klikniętemu elementowi szuflady. Jeśli element szuflady będzie powiązany z aktywnością, to musimy ją uruchomić przy użyciu intencji. Jeśli natomiast kliknięty element jest powiązany z fragmentem, to wystarczy, że wyświetlimy go w układzie FrameLayout aktywności MainActivity, używając do tego transakcji fragmentu.

KociCzat app/src/main

Ogólnie rzecz biorąc, w przypadku wyświetlania fragmentów w reakcji na klikanie opcji w szufladzie nawigacyjnej zazwyczaj nie umieszcza się przy tym transakcji na stosie cofnięć, jak robiliśmy to wcześniej. Wynika to z tego, że klikając przycisk Wstecz, użytkownik nie będzie oczekiwał wyświetlania kolejno fragmentów odpowiadających wszystkim wybieranym wcześniej opcjom szuflady nawigacyjnej. Dlatego przeważnie używany jest kod taki jak ten przedstawiony poniżej:

java com.hfad.kociczat MainActivity.java

FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); ft.replace(R.id.content_frame, fragment); ft.commit();

To jest ten sam kod transakcji fragmentu, którego używaliśmy wcześniej, z tą różnicą, że nie dodajemy transakcji do stosu cofnięć aktywności.

I w końcu musimy zamknąć szufladę. W tym celu musimy pobrać referencję do układu DrawerLayout i wywołać metodę closeDrawer() uzyskanego obiektu: DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); drawer.closeDrawer(GravityCompat.START);

Wykonanie powyższego fragmentu kodu spowoduje wysunięcie szuflady poza początkową krawędź aktywności.

Używamy stałej GravityCompat.S umieściliśmy szufladę przy pocz TART, gdyż ątkowej krawędzi aktywności. Gdybyśmy umieścili szufladę przy końcowej krawędzi , musielibyśmy użyć stałej GravityC ompat.END.

Teraz już wiesz wszystko, co trzeba wiedzieć, żeby napisać kod metody onNavigationItemSelected(). Napiszesz tę metodę w ramach ćwiczenia zamieszczonego na kilku kolejnych stronach.

jesteś tutaj  609

Magnesiki

Magnesiki z kodem Po kliknięciu przez użytkownika któregoś z elementów w szufladzie nawigacyjnej chcemy wyświetlić ekran skojarzony z tym elementem. Jeśli element będzie skojarzony z fragmentem, wyświetlimy go w układzie content_frame. Jeśli element będzie skojarzony z aktywnością, to będziemy chcieli ją uruchomić. Na końcu musimy zamknąć szufladę nawigacyjną. Ciekawe, czy będziesz potrafił uzupełnić kod zamieszczony na tej oraz następnej stronie. Nie będziesz musiał użyć wszystkich dostępnych magnesików. @Override public boolean onNavigationItemSelected(MenuItem item) {

int id = item. ........................ ; Fragment fragment = null; Intent intent = null;

switch( ........... ){ case R.id.nav_drafts: fragment = ..................................... ; .......... ; case R.id.nav_sent: fragment = ........................................ ; ...........; case R.id.nav_trash: fragment = ........................................ ; ........... ; case R.id.nav_help: intent = new Intent( ........ , .................. ); ........... ;

610

Rozdział 14.

Szuflady nawigacyjne

case R.id.nav_feedback: intent = new Intent( ......... , .................... ); ..........; default: fragment = ........................ ; } if (................. != null) { FragmentTransaction ft = getSupportFragmentManager(). ................. ; ft.replace(R.id.content_frame, ................. ); ft. ................. ; } else { startActivity( ................ ); }

DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);

drawer. ............. ( .................................. ); return true; }

beginTransaction()

HelpActivity .class id

HelpActivity

commit()

new

break

break

SentItemsFragment() FeedbackActivity

new

DraftsFragment()

intent

break

TrashFragment()

.class FeedbackActivity

fragment

GravityCompat.START

closeDrawer

InboxFragment() new

break

this break

getItemId()

START new

this

fragment

jesteś tutaj 

611

Rozwiązanie magnesików

Magnesiki z kodem. Rozwiązanie Po kliknięciu przez użytkownika któregoś z elementów w szufladzie nawigacyjnej chcemy wyświetlić ekran skojarzony z tym elementem. Jeśli element będzie skojarzony z fragmentem, wyświetlimy go w układzie content_frame. Jeśli element będzie skojarzony z aktywnością, to będziemy chcieli ją uruchomić. Na końcu musimy zamknąć szufladę nawigacyjną. Ciekawe, czy będziesz potrafił uzupełnić kod zamieszczony na tej oraz następnej stronie. Nie będziesz musiał użyć wszystkich dostępnych magnesików. @Override public boolean onNavigationItemSelected(MenuItem item) {

getItemId() int id = item. .................... ;

go elementu. Pobieramy identyfikator wybrane

Fragment fragment = null; Intent intent = null;

id switch( ........... ){

case R.id.nav_drafts:

new DraftsFragment() fragment = ..................................... ; .......... ; break case R.id.nav_sent: new SentItemsFragment() fragment = ........................................ ;

...........; break

Zapisujemy w zmiennej fragment instancję fragmentu, który chcemy wyświetlić.

case R.id.nav_trash: TrashFragment() new fragment = ................................... ;

break ........... ;

case R.id.nav_help:

HelpActivity this .class intent = new Intent( ........ , ......................... ); ........... ; break

612

Rozdział 14.

Tworzymy intencję do uruchomienia aktywności HelpActivity, o ile została wybrana opcja Pomoc.

Szuflady nawigacyjne

case R.id.nav_feedback:

.class ); this intent = new Intent( ......... , ......................... FeedbackActivity break ..........;

Jeśli kliknięto opcję Opinia, musimy uruchomić intencję FeedbackActivity.

default: new InboxFragment() fragment = ................................... ;

} fragment if ( ................. != null) {

Domyślnie wyświetlamy fragment InboxFragment, gdyż jest on powiązany z pierwszą opcją w szufladzie nawigacyjnej.

t, Jeśli musimy wyświetlić fragmen ji to robimy to, używając transakc fragmentu.

beginTransaction() FragmentTransaction ft = getSupportFragmentManager(). .......................... ; fragment ft.replace(R.id.content_frame, ................. ); commit() ft. ................. ;

} else { intent startActivity( .............. );

Jeśli musimy wyświetlić aktywność, uruchamiamy ją używając utworzonej wcześniej intencji

}

DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);

GravityCompat.START closeDrawer( ...(..........................); drawer. .............

return true; W końcu zamykamy szufladę.

}

Już niebawem dodamy ten kod do pliku MainActivity.java.

Tych magnesików nie trzeba było używać.

HelpActivity

FeedbackActivity

START

jesteś tutaj  613

Zamykamy szufladę

Zamknięcie szuflady po naciśnięciu przycisku Wstecz I w końcu przesłonimy czynności wykonywane po naciśnięciu przycisku Wstecz. Jeśli użytkownik naciśnie ten przycisk, kiedy szuflada nawigacyjna będzie otworzona, zamkniemy ją. Jeśli natomiast szuflada już będzie zamknięta, to naciśnięcie przycisku Wstecz da standardowe rezultaty. W celu obsługi przycisku Wstecz zaimplementujemy w aktywności metodę onBackPressed(), która jest wywoływana za każdym razem, kiedy użytkownik naciśnie ten przycisk. Poniżej przedstawiliśmy kod tej metody:

@Override

¨ Fragmenty i aktywności ¨ Nagłówek ¨ Opcje ¨ Szuflada

KociCzat app/src/main java com.hfad.kociczat

Ta metoda jest wywoływana po naciśnięciu przycisku Wstecz.

MainActivity.java

public void onBackPressed() { DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); if (drawer.isDrawerOpen(GravityCompat.START)) { drawer.closeDrawer(GravityCompat.START); } else { super.onBackPressed(); } }

Jeśli szuflada nawigacyjna jest obecnie otworzona, to ją zamykamy.

W przeciwnym razie, jeśli szuflada jest zamknięta, wywołujemy metodę onBackPressed() klasy bazowej.

I to już wszystkie zmiany, które należy wprowadzić w kodzie aktywności MainActivity. Na kilku następnych stronach przedstawimy jej pełny kod.

Nie istnieją

głupie pytania

P: Czy zawartość szuflady musi być określana przy

P: Czy aktywność może zawierać więcej niż jedną

O

O: Aktywność może zawierać po jednej szufladzie nawigacyjnej

użyciu widoku NavigationView?

: Nie, jednak takie rozwiązanie jest znacznie prostsze. Przed udostępnieniem biblioteki wsparcia wzornictwa powszechnie stosowaną praktyką było wykorzystanie do tego widoku listy. Wciąż można stosować takie rozwiązanie, jednak wymaga ono napisania znacznie bardziej rozbudowanego kodu.

614

Rozdział 14.

szufladę nawigacyjną?

dla każdej pionowej krawędzi układu. W celu dodania drugiej szuflady do układu DrawerLayout trzeba dodać drugi widok NavigationView poniżej pierwszego.

Szuflady nawigacyjne

¨ Fragmenty i aktywności ¨ Nagłówek ¨ Opcje ¨ Szuflada

Kompletny kod aktywności MainActivity Poniżej przedstawiliśmy pełny kod aktywności MainActivity.java; zaktualizuj swoją wersję tego pliku, by była identyczna z naszą: package com.hfad.kociczat; KociCzat

import android.support.v7.app.AppCompatActivity; import android.os.Bundle;

app/src/main

import android.support.v7.widget.Toolbar; java

import android.support.v4.app.Fragment; import android.support.v4.app.FragmentTransaction;

com.hfad.kociczat

import android.support.v4.widget.DrawerLayout; import android.support.v7.app.ActionBarDrawerToggle;

MainActivity.java

import android.support.design.widget.NavigationView;

Używamy tych dodatkowych klas , więc musimy je zaimportować.

import android.view.MenuItem; import android.content.Intent; import android.support.v4.view.GravityCompat; public class MainActivity extends AppCompatActivity

implements NavigationView.OnNavigationItemSelectedListener { @Override protected void onCreate(Bundle savedInstanceState) {

u Zaimplementowanie tego interfejs oznacza, że aktywność może nasłuchiwać zdarzeń kliknięć.

super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, Ten fragment kodu dodaje przełącz nik szuflady.

drawer, toolbar, R.string.nav_open_drawer, R.string.nav_close_drawer);

drawer.addDrawerListener(toggle); toggle.syncState();

Dalsza część kodu znajduje się na następnej stronie.

jesteś tutaj  615

Szuflady nawigacyjne

MainActivity.java (ciąg dalszy)

¨ Fragmenty i aktywności ¨ Nagłówek ¨ Opcje ¨ Szuflada

NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view); navigationView.setNavigationItemSelectedListener(this); Fragment fragment = new InboxFragment();

To wywołanie rejestruje aktywność w widoku NavigationView jako obiekt nasłuchujący zdarzeń.

FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); ft.add(R.id.content_frame, fragment); ft.commit(); }

y Ta metoda jest wywoływana, kied w użytkownik kliknie jeden z elementó yjnej. igac umieszczonych w szufladzie naw

@Override

public boolean onNavigationItemSelected(MenuItem item) { int id = item.getItemId(); Fragment fragment = null; Intent intent = null; switch(id){ case R.id.nav_drafts: fragment = new DraftsFragment(); break; case R.id.nav_sent:

KociCzat app/src/main java com.hfad.kociczat

fragment = new SentItemsFragment(); break;

MainActivity.java

case R.id.nav_trash: fragment = new TrashFragment(); break; case R.id.nav_help: intent = new Intent(this, HelpActivity.class); break; case R.id.nav_feedback: intent = new Intent(this, FeedbackActivity.class); break; default: fragment = new InboxFragment(); }

616

Rozdział 14.

Dalsza część kodu znajduje się na następnej stronie.

Szuflady nawigacyjne

MainActivity.java (ciąg dalszy)

¨ Fragmenty i aktywności ¨ Nagłówek ¨ Opcje ¨ Szuflada

if (fragment != null) { FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); ft.replace(R.id.content_frame, fragment); ft.commit(); } else { startActivity(intent);

Wyświetlamy odpowiedni fragmen aktywność, zależnie od opcji wyb t lub odpowiednią ranej przez użytkownika w szufladzie nawigacyjnej.

} DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); drawer.closeDrawer(GravityCompat.START); return true; } @Override

To wywołanie zamyka szufladę po wybraniu jednej z opcji. Kiedy użytkownik naciśnie przycisk Wstecz, zamykamy szufladę, jeśli będzie otworzona.

public void onBackPressed() { DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); if (drawer.isDrawerOpen(GravityCompat.START)) { drawer.closeDrawer(GravityCompat.START); } else { super.onBackPressed(); } }

KociCzat app/src/main java

} com.hfad.kociczat

Sprawdźmy teraz, co się stanie po uruchomieniu naszej aplikacji.

MainActivity.java

jesteś tutaj  617

Jazda próbna

Jazda próbna aplikacji Po uruchomieniu aplikacji ikona przełącznika szuflady zostaje wyświetlona na pasku narzędzi. Kliknięcie tej ikony powoduje otworzenie szuflady. Kliknięcie jednej z pierwszych czterech opcji widocznych w szufladzie powoduje wyświetlenie w aktywności MainActivity odpowiedniego fragmentu i zamknięcie szuflady; kiedy użytkownik ponownie wyświetli szufladę, wybrana wcześniej opcja będzie wyróżniona. Kliknięcie którejś z dwóch ostatnich opcji widocznych w szufladzie powoduje uruchomienie odpowiedniej aktywności.

¨ Fragmenty i aktywności ¨ Nagłówek ¨ Opcje ¨ Szuflada

Aktywność MainActivity zawiera przełącznik szuflady. Kliknięcie go powoduje otworzenie szuflady.

Kiedy klikniemy opcję Wysłane, w aktywności zostanie wyświetlony fragment SentItemsFragment, a szuflada zostanie zamknięta. Kiedy następnym razem otworzymy szufladę, wybrana wcześniej opcja będzie zaznaczona.

Kiedy klikniemy opcję Pomoc, zostanie uruchomiona aktywność HelpActivity, a szuflada zostanie zamknięta.

Jak widać, utworzyliśmy w pełni funkcjonalną szufladę nawigacyjną.

618

Rozdział 14.

Szuflady nawigacyjne

Twój przybornik do Androida j Pełny kod przykładowe j ane tow zen pre cji lika ap z żes mo w tym rozdziale P FT ra we pobrać z ser wydawnictwa Helion: klady/ ftp://ftp.helion.pl/przy andrr2.zip

CELNE SPOSTRZEŻENIA 











Używaj szuflady nawigacyjnej, jeśli chcesz udostępnić użytkownikom znaczną liczbę skrótów lub pogrupować je w sekcje. Aby utworzyć szufladę nawigacyjną, dodaj do układu aktywności układ DrawerLayout. Pierwszym elementem tego układu musi być widok definiujący główną zawartość aktywności; zazwyczaj jest to układ z komponentami Toolbar i FrameLayout. Drugi element definiuje zawartość szuflady, przy czym zazwyczaj jest to widok NavigationView.







Widok NavigationView pochodzi z biblioteki wsparcia wzornictwa. Obsługuje on większość możliwości funkcjonalnych szuflady. Nagłówek szuflady określa się, tworząc kolejny układ i podając jego identyfikator w atrybucie headerLayout widoku NavigationView. Elementy dodajemy do menu, tworząc zasób menu i podając jego identyfikator w atrybucie menu widoku NavigationView.



Jeśli chcesz wyróżnić element szuflady kliknięty przez użytkownika, to dodaj elementy menu do grupy i dodaj do niej atrybut checkableBehavior o wartości ”single”. Użyj obiektu ActionBarDrawerToggle, aby wyświetlić na pasku narzędzi aktywności ikonę „hamburgera”. Stanowi ona wizualny sygnał informujący o tym, że aktywność udostępnia szufladę nawigacyjną. Kliknięcie tej ikony otwiera szufladę. Aby odpowiadać na kliknięcia opcji szuflady, zaimplementuj w aktywności interfejs NavigationView. OnNavigationItemSelectedListener. Następnie zarejestruj aktywność w widoku NavigationView jako obiekt nasłuchujący i zaimplementuj w niej metodę onNavigationItemSelected(). Aby zamknąć szufladę, należy wywołać metodę closeDrawer() obiektu DrawerLayout.

Elementy w pliku menu dodajemy w takiej kolejności, w jakiej mają być widoczne w szufladzie.

jesteś tutaj  619

Rozdział 14.

Opanowałeś już rozdział 14. i dodałeś do swojego przybornika z narzędziami szuflady nawigacyjne

620

Rozdział 14.

15. Bazy danych SQLite

Odpal bazę danych Kiedy powiedziałam, że zależy mi na czymś trwałym, miałam na myśli 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.

to jest nowy rozdział  621

Znowu w kafeterii

Znowu w kafeterii Coffeina We wcześniejszej części książki, a konkretnie w rozdziale 7., napisaliśmy aplikację na potrzeby kafeterii Coffeina. Aplikacja ta pozwalała użytkownikom przeglądać sekwencję ekranów prezentujących napoje oferowane przez kafeterię.

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.

Baza danych aplikacji czerpie dane z klasy Drink zawierającej wybrane napoje oferowane przez kafeterię. Choć zastosowanie takiego rozwiązania znacznie ułatwiło nam napisanie pierwszej wersji aplikacji, to jednak istnieje dużo lepszy sposób trwałego przechowywania danych. W tym i w następnym rozdziale zajmiemy się zmianą źródła danych aplikacji, tak by były one przechowywane i pobierane z bazy danych SQLite. W tym rozdziale pokażemy, jak utworzyć bazę danych, a w następnym — jak połączyć z nią aktywności.

622

Rozdział 15.

Bazy danych SQLite

Android trwale przechowuje dane, używając baz danych SQLite Wszystkie aplikacje muszą przechowywać dane, a w Androidowie robi się to, używając baz danych SQLite. Dlaczego SQLite?



Jest nieskomplikowana.

Większość systemów baz danych wymaga do prawidłowego działania specjalnego procesu serwera bazy danych. SQLite go nie potrzebuje — baza danych SQLite jest zwyczajnym plikiem. Kiedy baza nie jest używana, nie zużywa czasu procesora. W przypadku urządzeń mobilnych ma to ogromne znaczenie, gdyż zależy nam, by nie zużywać niepotrzebnie baterii.



Jest zoptymalizowana pod kątem wykorzystania przez jednego użytkownika.

Jeśli planujesz w znacznym stopniu używać baz danych w swoich aplikacjach, to sugerujemy, byś poszukał dodatkowych informacji o SQLite i języku SQL.

Nasza aplikacja jest jedynym programem, który będzie komunikował się z bazą danych, więc przeważnie nie będziemy musieli identyfikować się przy użyciu nazwy użytkownika i hasła.



W tym rozdziale przedstawimy podstawowe informacje związane z bazami danych SQLite.

Jest stabilna i szybka.

Bazy danych SQLite są zadziwiająco stabilne. Potrafią obsługiwać transakcje, co oznacza, że jeśli aktualizujemy kilka różnych danych i coś pójdzie nie tak, to SQLite potrafi wycofać wszystkie zmiany. Co więcej, kod realizujący operacje odczytu i zapisu do bazy został napisany w języku C i zoptymalizowany. Dlatego nie tylko działa szybko, lecz także ogranicza ilość mocy procesora potrzebnej do jego działania.

Gdzie są przechowywane bazy danych? Android automatycznie tworzy odrębny katalog dla każdej aplikacji i w tym katalogu jest przechowywana jej baza danych. W przypadku naszej aplikacji dla kafeterii Coffeina będzie to katalog:

com.hfad.coffeina to unikalny identyfikator aplikacji.

/data/data/com.hfad.coffeina/databases W katalogu aplikacji może być przechowywanych kilka baz danych. Każda składa się z dwóch plików. Pierwszy to plik bazy danych, którego nazwa odpowiada nazwie bazy, na przykład „coffeina”. Jest to główny plik bazy danych SQLite. To właśnie w nim są przechowywane wszystkie dane. Drugim plikiem jest plik magazynu (ang. journal file). Ma on taką samą nazwę jak plik bazy danych z dodatkową końcówką „-journal”, na przykład „coffeinajournal”. Ten plik zawiera wszelkie zmiany wprowadzane w bazie danych. Jeśli w bazie wystąpią jakieś problemy, Android skorzysta z informacji zapisanych w tym pliku, by odtworzyć (lub wycofać) ostatnio wprowadzone zmiany.

data data com.hfad.coffeina databases

To jest plik bazy danych. coffeina

To jest plik magazynu. coffeina-journal

jesteś tutaj  623

Twoi nowi najlepsi przyjaciele

Android udostępnia kilka klas związanych z SQLite Android udostępnia grupę klas pozwalających zarządzać bazami danych SQLite. Większość związanych z tym operacji realizują obiekty trzech podstawowych typów.

Pomocnik SQLite Pomocnik SQL umożliwia tworzenie baz danych i zarządzanie. Pomocnika SQLite tworzymy poprzez rozszerzenie klasy SQLiteOpenHelper.

Baza danych SQLite Obiekt klasy SQLiteDatabase zapewnia dostęp do bazy danych. Można go porównać 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. Użyjemy tych obiektów, by pokazać Ci, w jaki sposób utworzyć bazę danych SQLite, której nasza aplikacja będzie mogła używać do trwałego przechowywania danych zamiast klasy Drink. Nie istnieją

głupie pytania

P: Skoro baza danych nie używa nazwy użytkownika ani hasła, to jak może być bezpieczna?

O: Zawartość katalogu, w którym

jest przechowywana baza danych, może być odczytywana wyłącznie przez daną aplikację. Baza danych jest więc zabezpieczana na poziomie systemowym.

P: Czy mogę napisać aplikację

na Androida, która będzie mogła komunikować się z jakąś zewnętrzną bazą danych, taką jak Oracle?

624

Rozdział 15.

O

: Nie ma żadnych powodów, dla których nie moglibyśmy uzyskać dostępu do zewnętrznych baz danych za pomocą połączeń sieciowych, trzeba jednak mieć na uwadze oszczędność zasobów zużywanych przez system. Na przykład może się okazać, że korzystanie z usługi sieciowej pozwala ograniczyć zużycie energii. Analogicznie, jeśli nie komunikujemy się cały czas z bazą danych, to nie zużywamy żadnych zasobów.

P: Dlaczego w Androidzie dostęp do baz danych SQLite nie jest realizowany za pomocą JDBC?

O: Wiemy, że będziemy używać baz

danych SQLite, dlatego stosowanie JDBC byłoby grubą przesadą. Te wszystkie warstwy sterowników baz danych, które sprawiają, że JDBC jest tak elastycznym rozwiązaniem, przyspieszałyby zużycie prądu w urządzeniach z Androidem.

P: Czy katalog bazy danych

znajduje się w katalogu aplikacji?

O: Nie. Bazy danych nie są

przechowywane w tym samym katalogu, w którym jest umieszczony kod aplikacji. Dzięki temu można zainstalować nową wersję aplikacji, a informacje przechowywane w bazie będą bezpieczne.

Bazy danych SQLite

Obecna struktura aplikacji kafeterii Coffeina Poniżej przypomnieliśmy obecną strukturę aplikacji napisanej dla kafeterii Coffeina:

1

Aktywność TopLevelActivity zawiera listę opcji Napoje, Przekąski oraz Kafeterie.

2

Kiedy użytkownik kliknie opcję Napoje, uruchamiana jest aktywność DrinkCategoryActivity.

Ta aktywność wyświetla listę napojów, których dane są pobierane z klasy Drink.

3

Kiedy użytkownik kliknie konkretny napój, uruchamiana jest aktywność DrinkActivity, która wyświetla informacje na temat tego napoju.

Aktywność DrinkActivity pobiera szczegółowe informacje o napoju z klasy Drink.





activity_top_level.xml

1

Urządzenie

TopLevelActivity.java

W jaki sposób musimy zmienić tę strukturę aplikacji, abyśmy mogli skorzystać z bazy danych SQLite?

Aktualnie aplikacja pobiera dane z klasy Drink.



activity_drink_ category.xml

activity_drink.xml

Drink.java

2

DrinkCategoryActivity.java

3

DrinkActivity.java

Zrób to sam!

W tym rozdziale planujemy zmodyfikować aplikację kafeterii Coffeina, więc otwórz pierwszą wersję tej aplikacji w Android Studio.

jesteś tutaj  625

Modyfikacja aplikacji

Zmienimy aplikację, by korzystała z bazy danych Użyjemy pomocnika SQLite, by utworzyć bazę danych, której będziemy mogli używać w naszej aplikacji. Planujemy zastąpić klasę Drink bazą danych, a zatem musimy użyć pomocnika SQLite, aby wykonać następujące operacje:

1

Utworzenie bazy danych.

2

Utworzenie tabeli Drink i zapisanie w niej informacji o napojach.

Zanim będziemy mogli wykonać jakiekolwiek inne operacje, musimy użyć pomocnika SQLite do utworzenia pierwszej wersji bazy danych.

Po utworzeniu bazy danych będziemy mogli utworzyć w niej tabelę. Struktura tej tabeli będzie musiała odpowiadać atrybutom klasy Drink, a zatem dla każdego napoju będziemy musieli zapisać jego nazwę, opis oraz identyfikator obrazka. Następnie dodamy do tej tabeli dane trzech napojów. Informacje o napojach zapiszemy teraz w bazie danych, a nie w klasie Drink.

Aplikacja ma niemal taką samą strukturę jak wcześniej, z tą różnicą, że klasę Drink (Drink.java) zastąpimy pomocnikiem SQLite i bazą danych SQLite. 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







activity_top_level.xml

Urządzenie

TopLevelActivity.java

Najpierw poznajmy pomocnika SQLite.

626

Rozdział 15.

activity_drink_ Pomocnik SQLite category.xml

DrinkCategoryActivity.java

activity_drink.xml

DrinkActivity.java

W następnym rozdziale zmienimy aktywności korzystające do tej pory z klasy Drink, tak by informacje o napojach pobierały z bazy danych.

Bazy danych SQLite

Pomocnik SQLite zarządza Twoją bazą danych

¨  Utworzenie bazy danych ¨  Utworzenie tabeli

Klasa SQLiteOpenHelper pomaga nam w tworzeniu i utrzymaniu baz danych SQLite. Można ją sobie wyobrazić jako osobistego kamerdynera, którego zadaniem jest doglądanie bazy danych. Przyjrzyjmy się kilku typowym zadaniom, w których realizacji klasa SQLiteOpenHelper może nam pomóc:

Tworzenie bazy danych

Zapewnianie dostępu do bazy danych

Podczas pierwszego instalowania aplikacji plik bazy danych nie będzie istnieć. Pomocnik SQLite może zadbać o prawidłowe utworzenie pliku o odpowiedniej nazwie i zawierającego tabele o określonej strukturze.

Ponieważ nie ma potrzeby, by aplikacja dysponowała wszystkimi informacjami o lokalizacji plików bazy danych za każdym razem, gdy będziemy tego potrzebowali, pomocnik SQLite może nam udostępniać łatwy w użyciu obiekt bazy danych. O każdej porze dnia i nocy.

Pomocnik SQLite Utrzymywanie bazy danych w doskonałym stanie Istnieje spore prawdopodobieństwo, że wraz z upływem czasu struktura bazy danych będzie ulegała zmianie, a pomocnik SQLite może, w sposób całkowicie godny zaufania, zadbać o skonwertowanie starej bazy w śliczną, nowiutką bazę o nowej strukturze.

jesteś tutaj  627

Tworzenie pomocnika

Tworzenie pomocnika SQLite

¨  Utworzenie bazy danych ¨  Utworzenie tabeli

Pomocnika SQLite możemy utworzyć, pisząc klasę dziedziczącą po klasie SQLiteOpenHelper. W tej klasie musimy przesłonić metody onCreate() i onUpgrade(). Obie te metody są obowiązkowe. Pierwsza z tych dwóch metod, onCreate(), jest wywoływana w momencie, gdy baza danych zostaje po raz pierwszy utworzona na urządzeniu. Powinna ona zawierać cały kod niezbędny do utworzenia tabel bazy danych. Z kolei metoda onUpgrade() jest wywoływana, gdy trzeba zaktualizować bazę danych. Na przykład jeśli trzeba wprowadzić jakieś zmiany w tabelach już po utworzeniu bazy danych, to należy je wykonywać właśnie w tej metodzie. W naszej aplikacji napiszemy klasę pomocnika SQLite o nazwie CoffeinaDatabaseHelper. Aby utworzyć tę klasę w swoim projekcie aplikacji dla kafeterii Coffeina, w eksploratorze Android Studio wybierz widok Project, zaznacz pakiet com.hfad.coffeina w katalogu app/src/main, upewnij się, że zostanie ona dodana do pakietu com.hfad.coffeina, a potem wybierz w menu opcję File/New.../Java Class. Tworzonej klasie nadaj nazwę CoffeinaDatabaseHelper, a następnie zastąp jej zawartość wygenerowaną przez Android Studio poniższym kodem: package com.hfad.coffeina;

To pełna ścieżka klasy SQLiteOpenHelper.

import android.database.sqlite.SQLiteOpenHelper; import android.content.Context;

Klasa pomocnika SQLite musi dziedziczyć po klasie SQLiteOpenHelper.

import android.database.sqlite.SQLiteDatabase; class CoffeinaDatabaseHelper extends SQLiteOpenHelper { CoffeinaDatabaseHelper(Context context) { }

Coffeina app/src/main

Na następnej stronie napiszemy kod tego konstruktora.

@Override

java com.hfad.coffeina

public void onCreate(SQLiteDatabase db) { } @Override

Metody onCreate() i onUpgrade() są obowiązkowe. Na razie zostawiliśmy je puste, ale w dalszej części rozdziału zajmiemy się nimi bardziej szczegółowo.

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ć.

628

Rozdział 15.

CoffeinaDatabase Helper.java

Bazy danych SQLite

Określenie bazy danych

¨  Utworzenie bazy danych ¨  Utworzenie tabeli

Aby pomocnik SQLite mógł utworzyć bazę danych, będzie potrzebował dwóch informacji. W pierwszej kolejności musimy określić nazwę tej bazy. Określenie nazwy bazy danych sprawia, że pozostanie ona na urządzeniu po jego zamknięciu. Jeśli nie podamy tej nazwy, to baza danych zostanie utworzona tylko w pamięci, a po zamknięciu zniknie.

Tworzenie takich baz danych przechowywanych w pamięci może być przydatne podczas testowania aplikacji.

Drugą informacją, którą musimy podać, jest numer wersji bazy danych. Musi to być liczba całkowita, a jej pierwszą wartością musi być 1. Pomocnik SQLite będzie używał tego numeru do określania, czy bazę danych należy zaktualizować. Nazwa: „coffeina” Wersja: 1

Nazwę bazy danych i numer jej wersji określamy, przekazując je w wywołaniu konstruktora klasy bazowej SQLiteOpenHelper. 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 te informacje i ich używa (wprowadź te modyfikacje w swojej wersji pliku CoffeinaDatabaseHelper.java):

Pomocnik SQLite

... class CoffeinaDatabaseHelper extends SQLiteOpenHelper { private static final String DB_NAME = “coffeina”; // Nazwa bazy danych private static final int DB_VERSION = 1; // Numer wersji bazy danych CoffinaDatabaseHelper(Context context) { super(context, DB_NAME, null, DB_VERSION);

Wywołujemy konstruktor klasy bazowej SQLiteOpenHelper, przekazując do niego nazwę i numer wersji bazy danych.

} ... }

Ten argument reprezentuje bardziej zaawa nsowane możliwości związane z kursorami. Kursory poznamy w następnym rozdziale.

Konstruktor określa szczegółowe informacje o bazie danych, ale sama baza nie jest tworzona podczas jego wywołania. Pomocnik SQLite czeka z tym aż do momentu, gdy aplikacja będzie chciała uzyskać dostęp do bazy. W ten sposób zrobiliśmy już wszystko, co trzeba, by przekazać pomocnikowi SQLite informacje na temat tworzonej bazy danych. Kolejnym krokiem jest poinformowanie go, jakie tabele trzeba będzie w tej bazie utworzyć.

Coffeina app/src/main java com.hfad.coffeina CoffeinaDatabase Helper.java

jesteś tutaj  629

Tabele

Wnętrze bazy danych SQLite

¨  Utworzenie bazy danych ¨  Utworzenie tabeli

Dane umieszczane w bazie danych SQLite są zapisywane w tabelach. Tabela składa się z grupy wierszy, a każdy wiersz jest podzielony na kolumny. Każda kolumna zwiera pojedynczą informację, taką jak liczba lub łańcuch znaków. Dla każdego z odrębnych rodzajów informacji, które chcemy przechowywać w bazie danych, musimy utworzyć tabelę. Na przykład w przypadku naszej aplikacji dla kafeterii Coffeina musimy utworzyć tabelę do przechowywania informacji o napojach. Można by ją przedstawić jak na poniższym rysunku: _id

NAME

DESCRIPTON

IMAGE_RESOURCE_ID

1

“Latte”

“Czarne espresso z gorącym mlekiem i mleczną pianką.”

54543543

2

“Cappuccino”

“Czarne espresso z dużą ilością spienionego mleka.”

654334453

3

“Espresso”

“Czarna kawa ze świeżo mielonych ziaren najwyższej jakości.”

44324234

Niektóre kolumny można wskazać jako klucz główny tabeli. Klucz główny 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 wierszy, w których wartość tego klucza się powtarza. Sugerujemy, żebyś w swoich tabelach stosował klucz główny składający się z jednej kolumny o nazwie _id typu INTEGER. Wynika to z faktu, że Android oczekuje, by tabele zawierały kolumnę o nazwie _id typu liczbowego; brak takiej kolumny może być przyczyną późniejszych problemów.

Klasy i typy danych Każda kolumna tabeli jest przeznaczona do przechowywania danych określonego typu. 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 danych SQLite wraz z informacjami, co przy ich użyciu można zapisywać w bazie: INTEGER TEXT REAL NUMERIC BLOB

Dowolne liczby całkowite Dowolne typy znakowe Dowolne liczby zmiennoprzecinkowe Wartości logiczne, daty oraz daty i godziny Duże obiekty binarne (ang. Binary Large Objects)

W odróżnieniu od większości systemów baz danych w SQLite nie musimy określać rozmiaru kolumn. Za kulisami SQLite przekształca te typy danych na klasy o znacznie szerszym zakresie. Dzięki temu rodzaj informacji, które mamy zamiar przechowywać w kolumnach, możemy określać w sposób bardzo ogólny i nie musimy podawać rozmiaru tych danych.

630

Kolumny tabeli mają odpowiednio nazwy _id, NAME, DESCRIPTION i IMAGE_RESOURCE_ID. Klasa Drink definiuje atrybuty o podobnych nazwach.

Rozdział 15.

W systemie Android stosowana jest konwencja, by kolumna klucza głównego miała nazwę _id. Kod systemu oczekuje, że kolumna _id będzie dostępna w tabelach. Zignorowanie tej konwencji może utrudnić pobieranie danych z bazy i wyświetlanie ich w interfejsie użytkownika.

Bazy danych SQLite

Tabele tworzymy w języku SQL Każda aplikacja korzystająca z bazy danych SQLite musi się z nią komunikować, używając standardowego języka: strukturalnego języka zapytań (ang. Structured Query Language), określanego skrótowo jako SQL. Jest on używany przez niemal wszystkie rodzaje baz danych. A zatem, chcąc utworzyć tabelę DRINK, będziemy musieli zrobić to w SQL-u. Poniżej przedstawiliśmy polecenie SQL, które służy do utworzenia tabeli: Kolumna _id jest kluczem głównym tabeli.

CREATE TABLE DRINK (_id INTEGER PRIMARY KEY AUTOINCREMENT, Nazwa tabeli.

NAME TEXT,

To są kolumny tabeli.

DESCRIPTION TEXT, IMAGE_RESOURCE_ID INTEGER)

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ść.

Metoda onCreate() jest wywoływana podczas tworzenia bazy danych Za utworzenie bazy danych odpowiada pomocnik SQLite. Pusta baza danych jest tworzona na urządzeniu w momencie, gdy ma być użyta po raz pierwszy, a następnie zostaje wywołana metoda onCreate() pomocnika SQL. Metoda ta ma jeden parametr — obiekt SQLiteDatabase, reprezentujący nowo utworzoną bazę danych. Do wykonania polecenia SQL w bazie danych można użyć metody execSQL() klasy SQLiteDatabase. Metoda ta ma jeden parametr: łańcuch znaków zawierający polecenie SQL, które chcemy wykonać. SQLiteDatabase.execSQL(String sql);

Klasa SQLiteDatabase zapewnia dostęp do bazy danych.

To wywołanie wykonuje w bazie danych polecenie SQL.

Skorzystamy z metody onCreate(), by utworzyć tabelę DRINK. Oto kod metody onCreate(): public void onCreate(SQLiteDatabase db) { db.execSQL(“CREATE TABLE DRINK (_id INTEGER PRIMARY KEY AUTOINCREMENT, “ + “NAME TEXT, “ + “DESCRIPTION TEXT, “ + “IMAGE_RESOURCE_ID INTEGER);”); }

Wykonanie tej metody utworzy pustą tabelę DRINK. Chcemy tę tabelę wypełnić informacjami o napojach, zobaczmy zatem, jak to zrobić.

Coffeina app/src/main java com.hfad.coffeina CoffeinaDatabase Helper.java

jesteś tutaj  631

Metoda insert()

Wstawianie danych za pomocą metody insert()

¨  Utworzenie bazy danych ¨  Utworzenie tabeli

Aby wstawić dane do tabeli w bazie danych SQL, należy zacząć od określenia wartości, które chcemy w niej zapisać. W tym celu należy utworzyć obiekt ContentValues: ContentValues drinkValues = new ContentValues();

Obiekt ContentValues opisuje zbiór danych. Obiekt klasy ContentValues tworzymy zazwyczaj dla każdego wiersza danych, które chcemy utworzyć i zapisać w bazie. Dane dodaje się do obiektu ContentValues przy użyciu metody put(). Metoda ta dodaje do obiektu pary nazwa-wartość, przy czym jej pierwszy argument (w poniższym przykładzie jest to ”NAZWA”) określa nazwę kolumny, w której dana ma zostać zapisana, a drugi argument (”wartość”) określa samą daną: contentValues.put("NAZWA", "wartość");

W ramach przykładu poniżej pokazaliśmy, jak użyć metody put(), by dodać nazwę, opis oraz identyfikator zasobu graficznego latte do obiektu ContentValues o nazwie drinkValues: To jeden wiersz danych.

drinkValues.put(“NAME”, “Latte”);

NAZWA to nazwa kolumny tabeli, w której chcemy zapisać daną. Drugi argument określa wartość, którą chcemy zapisać.

kolumnie NAME. Zapisz "Latte" w

drinkValues.put(“DESCRIPTION”, “Czarne espresso z gorącym mlekiem i mleczną pianką.”); drinkValues.put(”IMAGE_RESOURCE_ID”, R.drawable.latte);

Po dodaniu wiersza danych do obiektu ContentValues można go wstawić do tabeli przy użyciu metody insert() obiektu SQLiteDatabase. Metoda ta wstawia dane do tabeli, a kiedy to zrobi, zwraca identyfikator wstawionego rekordu. Jeśli metodzie nie uda się zapisać danych w bazie, zwróci wartość -1. Poniższy przykład pokazuje, jak można wstawić do tabeli DRINK dane z obiektu drinkValues: db.insert(”DRINK”, null, drinkValues);

Każdą wartość zapisywaną w wierszu musimy określić za pomocą odrębnego wywołania metody put().

Zapisz "Czarne espresso z gorącym mlekiem i mleczną pianką" w kolumnie DESCRIPTION.

To wywołanie wstawia do tabeli jeden wiersz danych.

Środkowym argumentem wywołania metody insert() jest zazwyczaj wartość null, tak jak w powyższym przykładzie. Zastosowaliśmy ją na wypadek, gdyby obiekt ContentValues był pusty, a my chcielibyśmy w takiej sytuacji wstawić do tabeli pusty wiersz. Można także zastąpić wartość null nazwą jednej z kolumn tabeli, choć jest raczej mało prawdopodobne, byś chciał tak postąpić. Wykonanie powyższej instrukcji spowoduje zapisanie w tabeli DRINK wiersza z informacjami o latte:

Do tabeli został wstawiony lśniący nowością rekord.

_id

NAME

DESCRIPTON

IMAGE_RESOURCE_ID

1

“Latte”

“Czarne espresso z gorącym mlekiem i mleczną pianką.”

54543543

Metoda insert() potrafi zapisać w tabeli tylko jeden wiersz danych. A co zrobić, jeśli chcemy zapisać ich kilka?

632

Rozdział 15.

Bazy danych SQLite

Wstawianie wielu rekordów

¨  Utworzenie bazy danych ¨  Utworzenie tabeli

Aby wstawić do tabeli wiele rekordów, trzeba wykonać kilka wywołań metody insert(). Każde z nich wstawi do tabeli jeden wiersz. W sytuacjach, gdy należy wstawiać do tabeli więcej rekordów, zazwyczaj tworzymy nową, pomocniczą metodę zapisującą jeden wiersz danych i wywołujemy ją za każdym razem, gdy trzeba zapisać nowy wiersz. W ramach przykładu poniżej przedstawiliśmy metodę insertDrink(), którą napisaliśmy w celu wstawiania rekordów do tabeli DRINK: private static void insertDrink(SQLiteDatabase db,

To baza danych, do któ chcemy dodawać rekordrej y.

String name, String description, int resourceId) {

Dane przekazujemy do metody w formie parametrów.

ContentValues drinkValues = new ContentValues(); drinkValues.put(”NAME”, name); drinkValues.put(”DESCRIPTION”, description);

W tych wierszach kodu tworzymy obiekt ContentValues i zapisujemy w nim dane.

drinkValues.put(”IMAGE_RESOURCE_ID”, resourceId); db.insert(”DRINK”, null, drinkValues); }

To wywołanie wstawia wiersz danych do tabeli.

Teraz, aby dodać do tabeli DRINK trzy napoje, z których każdy stanie się osobnym wierszem danych, wystarczy trzykrotnie wywołać metodę insertDrink(): insertDrink(db, ”Latte”, ”Czarne espresso z gorącym mlekiem i mleczną pianką.”, 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);

I to już wszystko, co musisz wiedzieć na temat wstawiania danych do tabel. Na następnej stronie przedstawimy zmodyfikowany kod pliku CoffeinaDatabaseHelper.java.

jesteś tutaj  633

Kod pomocnika bazy danych

Kod klasy CoffeinaDatabaseHelper

¨  Utworzenie bazy danych ¨  Utworzenie tabeli

Poniżej przedstawiliśmy kompletną zawartość pliku CoffeinaDatabaseHelper.java (zaktualizuj ten plik w swoim projekcie, tak by odpowiadał zamieszczonemu poniżej): package com.hfad.coffeina; import import import import

Musisz dodać tę instrukcję import.

Coffeina app/src/main

android.content.ContentValues; android.content.Context; android.database.sqlite.SQLiteDatabase; android.database.sqlite.SQLiteOpenHelper;

java com.hfad.coffeina

class CoffeinaDatabaseHelper extends SQLiteOpenHelper { private static final String DB_NAME = “coffeina”; // Nazwa bazy danych private static final int DB_VERSION = 1; // Numer wersji bazy danych CoffeinaDatabaseHelper(Context context) { super(context, DB_NAME, null, DB_VERSION); } Metoda onCreate() zostaje wywołan

CoffeinaDatabase Helper.java

Te stałe określają nazwę bazy danych i numer jej wersji. To pierwsza wersja naszej bazy, więc ma numer 1.

a podczas pierwszego tworzenia bazy danych, dlatego właśnie jej używamy do utworzenia tabeli bazy danych i zapisania w niej początkowych danych.

@Override public void onCreate(SQLiteDatabase db) { db.execSQL(“CREATE TABLE DRINK (_id INTEGER PRIMARY KEY AUTOINCREMENT, “ + “NAME TEXT, “ Tworzymy tabelę DRINK. + “DESCRIPTION TEXT, “ + “IMAGE_RESOURCE_ID INTEGER);”); insertDrink(db, “Latte”, “Czarne espresso z gorącym mlekiem i mleczną pianką.”, Każdy napój R.drawable.latte); zapisujemy insertDrink(db, “Cappuccino”, “Czarne espresso z dużą ilością spienionego mleka.”, jako osobny R.drawable.cappuccino); wiersz tabeli. insertDrink(db, “Espresso”, “Czarna kawa ze świeżo mielonych ziaren najwyższej jakości.”, R.drawable.cappuccino); } Metoda onUpgrade() jest wywoływana, kiedy pojawi się potrzeba aktualizacji bazy danych. Zajmiemy się nią nieco później.

@Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { }

private static void insertDrink(SQLiteDatabase db, String name, String description, int resourceId) { ContentValues drinkValues = new ContentValues(); drinkValues.put(“NAME”, name); W tabeli musimy zapisać kilka napojów, dlatego drinkValues.put(“DESCRIPTION”, description); napiszemy specjalną metodę, drinkValues.put(“IMAGE_RESOURCE_ID”, resourceId); która nam to ułatwi. db.insert(“DRINK”, null, drinkValues); } }

634

Rozdział 15.

Bazy danych SQLite

Co robi kod pomocnika SQLite? 1

¨  Utworzenie bazy danych ¨  Utworzenie tabeli

Użytkownik instaluje aplikację, a następnie ją uruchamia.

Kiedy aplikacja zażąda dostępu do bazy danych, pomocnik SQLite sprawdza, czy baza już istnieje.

Jaśnie panie, czy potrzebuje pan bazy danych? Pozwolę sobie sprawdzić, czy baza dla jaśnie pana została już przygotowana.

Pomocnik SQLite

2

Jeśli baza danych jeszcze nie istnieje, to zostaje utworzona.

Nazwa bazy danych i numer jej wersji są przekazywane do pomocnika SQLite.

Nazwa: "coffeina" Wersja: 1

Baza danych SQLite Pomocnik SQLite

3

Podczas tworzenia bazy danych zostaje wywołana metoda onCreate() pomocnika SQLite.

Pomocnik dodaje do bazy danych tabelę DRINK i zapisuje w niej dane.

Jaśnie panie, oto pańska baza danych. Czy mogę jeszcze czymś służyć?

DRINK Nazwa: "coffeina" Wersja: 1

onCreate() Baza danych SQLite Pomocnik SQLite

jesteś tutaj  635

Aktualizacja bazy danych

Co zrobić, gdy trzeba będzie zmienić bazę? Dotychczas dowiedziałeś się, jak można utworzyć bazę danych, której aplikacja będzie mogła używać do trwałego zapisywania danych. Ale co zrobić, jeśli w przyszłości trzeba będzie wprowadzić jakieś zmiany w tej bazie? Załóżmy na przykład, że nasza aplikacja została już zainstalowana przez wielu użytkowników, a my doszliśmy do wniosku, że dobrze byłoby dodać do tabeli DRINK nową kolumnę — FAVOURITE. Jak wprowadzić tę zmianę u wszystkich użytkowników aplikacji — zarówno nowych, jak i tych, którzy już ją zainstalowali?

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.

Kiedy pojawia się konieczność zmiany bazy danych aplikacji, to musimy uwzględnić dwa podstawowe scenariusze. Pierwszy z nich odpowiada sytuacji, w której użytkownik jeszcze nie zainstalował aplikacji, więc na jego urządzeniu nie ma utworzonej bazy danych. W takim przypadku pomocnik SQLite utworzy bazę danych za pierwszym razem, gdy aplikacja będzie chciała z niej skorzystać, a to oznacza, że zostanie wywołana metoda onCreate() pomocnika. Drugi scenariusz odpowiada sytuacji, gdy użytkownik instaluje na swoim urządzeniu nową wersję aplikacji korzystającą z innej wersji bazy danych. Kiedy pomocnik SQLite wykryje, że baza danych dostępna na urządzeniu jest nieaktualna, to wywoła metodę onUpgrade() bądź onDowngrade(). A jak pomocnik SQLite się zorientuje, że baza danych jest nieaktualna?

636

Rozdział 15.

Bazy danych SQLite

Bazy danych SQLite mają numer wersji

¨  Aktualizacja bazy danych

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. Wcześniej w tym rozdziale określiliśmy numer wersji naszej bazy danych w następujący sposób: ... private static final String DB_NAME = ”coffeina”; private static final int DB_VERSION = 1;

Dla maniaków

StarbuzzDatabaseHelper(Context context) { super(context, DB_NAME, null, DB_VERSION); } ...

W momencie tworzenia bazy danych jej numer wersji jest ustawiany na podstawie numeru podanego w pomocniku SQLite, a następnie jest wywoływana metoda onCreate(). Kiedy uznamy, że konieczna jest aktualizacja bazy danych, musimy zmienić numer podany w pomocniku SQLite. Aby zaktualizować bazę, musimy podać numer wyższy od stosowanego do tej pory, jeśli natomiast chcemy przywrócić starszą wersję bazy, to wystarczy zmienić numer wersji na niższy: ... private static final int DB_VERSION = 2; ...

Bazy danych SQLite mają numer wersji, który jest używany przez pomocnik SQLite, i wewnętrzny numer schematu. Każda zmiana schematu bazy danych, na przykład zmiana struktury tabeli, powoduje inkrementację numeru wersji schematu. Ta wartość jest poza naszą kontrolę — jest ona używana wewnętrznie przez bazę SQLite.

W tym przykładzie zmieniamy num er wersji bazy danych na wyższy, więc zostanie ona zaktualizowana.

W większości przypadków będziemy chcieli aktualizować bazy danych, więc będziemy podawać wyższe numery wersji. Wynika to z faktu, że przywrócenie starszej wersji bazy jest potrzebne wyłącznie w sytuacjach, gdy chcemy wycofać modyfikacje wprowadzone we wcześniejszej aktualizacji. Kiedy użytkownik instaluje na swoim urządzeniu najnowszą wersję aplikacji, to za pierwszym razem, gdy będzie ona chciała użyć bazy, pomocnik SQLite sprawdzi numer wersji podany w jego kodzie, porównując go z numerem wersji bazy zainstalowanej na urządzeniu. Jeśli numer wersji podany w kodzie pomocnika SQLite jest wyższy od numeru wersji bazy, to pomocnik SQLite wywoła metodę onUpgrade(). Jeśli zaś numer wersji podany w kodzie pomocnika jest niższy od numeru wersji bazy, to wywołana zostanie metoda onDowngrade(). Wywołanie każdej z tych metod sprawi, że w bazie danych zostanie zapisany ten sam numer wersji, który jest podany w kodzie pomocnika SQLite.

jesteś tutaj  637

Co się dzieje?

Co się dzieje w przypadku zmiany numeru wersji?

¨  Aktualizacja bazy danych

Zobaczmy teraz, co się dzieje kiedy udostępnimy nową wersję aplikacji, w której zmienimy numer wersji pomocnika SQL z 1 na 2. Rozważymy dwa przypadki: w pierwszym użytkownik instaluje aplikację po raz pierwszy, a w drugim aplikacja już istnieje na urządzeniu użytkownika.

Scenariusz 1. Użytkownik instaluje aplikację po raz pierwszy 1

Kiedy użytkownik instaluje aplikację po raz pierwszy, baza danych nie istnieje, więc pomocnik SQLite musi ją utworzyć.

Pomocnik SQLite nada tworzonej bazie nazwę i przypisze jej numer wersji określony w jego kodzie.

Nazwa: „coffeina” Wersja: 2

Baza danych SQLite

Pomocnik SQLite przypisze bazie danych numer wersji 2, o ile to właśnie ten numer został podany w jego kodzie.

Pomocnik SQLite

2

Po utworzeniu bazy danych zostaje wywołana metoda onCreate() pomocnika SQLite.

Metoda onCreate() zawiera kod, który określa zawartość bazy danych.

DRINK Nazwa: „coffeina” Wersja: 2

onCreate()

Baza danych SQLite Pomocnik SQLite

Powyższe zdarzenia zachodzą, gdy użytkownik instaluje aplikację po raz pierwszy. A co się stanie, kiedy użytkownik ma już tę aplikację i spróbuje zainstalować jej nową wersję?

638

Rozdział 15.

Bazy danych SQLite

¨  Aktualizacja bazy danych

Scenariusz 2. Istniejący użytkownik instaluje nową wersję aplikacji 1

Kiedy użytkownik uruchamia nową wersję aplikacji, pomocnik bazy danych sprawdza, czy baza już istnieje.

Jeśli baza danych już istnieje, to pomocnik SQLite nie tworzy jej ponownie.

Doskonale, jak widzę, jaśnie pan dysponuje już bazą danych o numerze wersji 1. DRINK Nazwa: „coffeina” Wersja: 1

Baza danych SQLite Pomocnik SQLite

2

Pomocnik bazy danych porównuje jej numer z numerem wersji podanym w jego kodzie.

Jeśli numer wersji podany w kodzie pomocnika SQLite jest wyższy od numeru wersji bazy, to pomocnik wywołuje metodę onUpgrade(). Jeśli natomiast numer wersji podany w kodzie pomocnika SQLite jest niższy od numeru wersji bazy, to pomocnik wywołuje metodę onDowngrade(). Następnie pomocnik zmienia numer wersji bazy danych na ten, który został podany w jego kodzie. DRINK Nazwa: „coffeina” Wersja: 1 2

Baza danych SQLite Pomocnik SQLite

Pomocnik SQLite wywołuje metodę onUpgrade() (jeśli numer wersji jest wyższy), a następnie zmienia numer wersji bazy danych.

A teraz, kiedy już wiesz, w jakich okolicznościach są wywoływane metody onUpgrade() oraz onDowngrade(), wyjaśnimy nieco dokładniej, jak należy ich używać.

jesteś tutaj  639

Metoda onUpgrade()

Aktualizacja bazy w metodzie onUpgrade()

¨  Aktualizacja bazy danych

Metoda onUpgrade() ma trzy parametry: bazę danych SQLite, numer wersji samej bazy danych oraz nowy numer wersji bazy danych przekazany do konstruktora klasy bazowej SQLiteOpenHelper: Numer wersji nieaktualnej @Override

bazy danych zainstalowanej na urządzeniu użytkownika.

Nowy numer wersji podany w kodzie pomocnika SQLite.

public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // Tu możemy umieścić swój kod. }

Numery wersji są ważne, gdyż na ich podstawie można określić, jakie zmiany należy wprowadzić w zależności od aktualnie używanej wersji bazy. Na przykład załóżmy, że musimy wykonać jakiś fragment kodu, jeśli jest używana baza o numerze wersji 1. Kod realizujący taką operację mógłby wyglądać tak:

Pamiętaj, że aby baza danych została zaktualizowana, nowy numer wersji musi być wyższy od dotychczasowego.

@Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { if (oldVersion == 1) { // Kod wykonywany, gdy używana jest baza danych o numerze wersji 1 } }

Ten kod zostanie wykonany tylko wówczas, gdy używana jest baza danych o numerze wersji 1, a numer wersji bazy określony w pomocniku SQLite jest wyższy.

Tych numerów wersji możemy także używać do wprowadzania dalszych modyfikacji bazy: @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { if (oldVersion == 1) { // Kod wykonywany, gdy używana jest baza danych o numerze wersji 1 }

Ten kod zostanie wykonany wyłącznie w przypadku, gdy używana jest baza danych o numerze wersji 1.

if (oldVersion < 3) { // Kod wykonywany, gdy używana jest baza danych o numerach wersji 1 lub 2 } }

Dzięki zastosowaniu takiego rozwiązania można mieć pewność, że w bazie danych zostaną wprowadzone wszelkie niezbędne aktualizacje, niezależnie od tego, która wersja bazy będzie zainstalowana. Metoda onDowngrade() działa bardzo podobnie do metody onUpgrade(). Przedstawimy ją na następnej stronie.

640

Rozdział 15.

Ten kod zostanie wykonany, jeśli jest używana baza danych o numerze wersji 1 lub 2.

Bazy danych SQLite

Przywracanie starszej wersji bazy za pomocą metody onDowngrade()

¨  Aktualizacja bazy danych

Metoda onDowngrade() nie jest używana równie często jak metoda onUpgrade(), gdyż służy do przywracania poprzedniej wersji bazy danych. Taka operacja może się przydać, jeśli udostępniliśmy nową wersję aplikacji zawierającą modyfikacje bazy danych, lecz okazało się, że w tych zmianach jest jakiś błąd. W takich sytuacjach metoda onDowngrade() pozwala odtworzyć zmiany i przywrócić wcześniejszą wersję bazy danych. Podobnie jak onUpgrade(), także metoda onDowngrade() ma trzy parametry: modyfikowaną bazę danych, numer wersji samej bazy danych oraz numer wersji przekazany w wywołaniu klasy bazowej SQLiteOpenHelper: @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.

Podobnie jak w przypadku metody onUpgrade(), także w metodzie onDowngrade() możemy używać przekazanych numerów, by odtworzyć zmiany wprowadzone w konkretnej wersji bazy. Na przykład poniższa metoda pokazuje, jak odtworzyć zmiany w bazie danych o numerze wersji 3: @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 } }

A teraz, kiedy już wiesz, jak aktualizować bazę danych oraz jak przywracać jej starszą wersję, przeanalizujemy dokładniej najczęściej występujący scenariusz, czyli aktualizację.

Ten kod zostanie wykonany, jeśli użytkownik korzysta z bazy danych o numerze wersji 3, lecz chcemy przywrócić jej wcześniejszą wersję.

jesteś tutaj  641

Aktualizacja bazy danych

Zaktualizujmy bazę danych

¨  Aktualizacja bazy danych

Załóżmy, że musimy zaktualizować naszą bazę danych, a konkretnie dodać do tabeli DRINK nową kolumnę. Ponieważ chcemy, by zmiana ta trafiła do wszystkich użytkowników aplikacji, i obecnych, i przyszłych, musimy zadbać o to, by uwzględnić ją zarówno w metodzie onUpdate(), jak i w metodzie onCreate(). Metoda onCreate() sprawi, że nowi użytkownicy aplikacji będą dysponowali bazą z dodatkową kolumną, natomiast metoda onUpdate() wprowadzi niezbędną modyfikację u użytkowników, którzy już wcześniej zainstalowali aplikację. Zamiast umieszczać podobny kod w obu metodach, onCreate() i onUpdate(), napiszemy nową metodę, updateMyDatabase(), a następnie wywołamy ją w obu metodach, onCreate() i onUpdate(). Do metody updateMyDatabase() przeniesiemy aktualny kod metody onCreate(), a oprócz tego uzupełnimy go o kod tworzący dodatkową kolumnę. Dzięki takiemu rozwiązaniu będziemy w stanie umieścić cały kod związany z tworzeniem bazy danych w jednym miejscu i łatwiej nam będzie zapanować nad zmianami wprowadzanymi w każdej kolejnej wersji bazy danych. Poniżej przedstawiliśmy kompletny kod pliku CoffeinaDatabaseHelper.java (zaktualizuj kod tego pliku w swoim projekcie, tak by był identyczny z naszym). Coffeina

package com.hfad.starbuzz;

app/src/main

import android.content.ContentValues; java

import android.content.Context; import android.database.sqlite.SQLiteDatabase;

com.hfad.coffeina

import android.database.sqlite.SQLiteOpenHelper;

CoffeinaDatabase Helper.java

class StarbuzzDatabaseHelper extends SQLiteOpenHelper{ private static final String DB_NAME = ”coffeina”; // Nazwa bazy danych private static final int DB_VERSION = 12; // Numer wersji bazy danych StarbuzzDatabaseHelper(Context context){ super(context, DB_NAME, null, DB_VERSION);

Zmiana numeru wersji na większy oznacza, że pomocnik SQLite będzie wiedzieć, że chcemy zaktualizować bazę danych.

} @Override public void onCreate(SQLiteDatabase db){ updateMyDatabase(db, 0, DB_VERSION); }

642

Rozdział 15.

Zastąp istniejący kod metody onCreate() tym wywołaniem metody updateMyDatabase().

Pozostała część kodu znajduje się na następnej stronie.

Bazy danych SQLite

¨  Aktualizacja bazy danych

Kod pomocnika SQLite (ciąg dalszy)

@Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { Wywołujemy metodę updateMyDatabase() updateMyDatabase(db, oldVersion, newVersion); w metodzie onUpgrade(), przekazując do } niej odpowiednie parametry.

private static void insertDrink(SQLiteDatabase db, String name, String description, int resourceId) { ContentValues drinkValues = new ContentValues(); drinkValues.put(”NAME”, name); drinkValues.put(”DESCRIPTION”, description); drinkValues.put(”IMAGE_RESOURCE_ID”, resourceId); db.insert(”DRINK”, null, drinkValues); } private void updateMyDatabase(SQLiteDatabase db, int oldVersion, int newVersion) { if (oldVersion < 1) { db.execSQL(“CREATE TABLE DRINK (_id INTEGER PRIMARY KEY AUTOINCREMENT, “ + “NAME TEXT, “ + “DESCRIPTION TEXT, “ To kod, który + “IMAGE_RESOURCE_ID INTEGER);”); wcześniej był umieszczony insertDrink(db, “Latte”, w metodzie “Czarne espresso z gorącym mlekiem i mleczną pianką.”, 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) { Coffeina // Kod dodający nową kolumnę tabeli } Ten kod zostanie wywołany app/src/main } w przypadku, gdy użytkownik } już dysponuje pierwszą wersją bazy danych. Już niebawem zaimplementujemy ten kod.

Kolejną rzeczą, którą musimy zrobić, jest napisanie kodu odpowiedzialnego za wykonanie aktualizacji bazy danych. Jednak zanim się tym zajmiemy, spróbuj wykonać ćwiczenie zamieszczone na następnej stronie.

java

com.hfad.coffeina CoffeinaDatabase Helper.java

jesteś tutaj  643

Ćwiczenie

Wczuj się w pomocnika SQLite Z prawej strony zamieściliśmy kod pomocnika SQLite. Twoim zadaniem jest wcielić się w rolę tego pomocnika i określić, które fragmenty kodu zostaną wykonane dla poszczególnych, przedstawionych poniżej użytkowników. Kod, na którym powinieneś się skoncentrować, zaznaczyliśmy literami. Aby ułatwić Ci rozwiązanie ćwiczenia, pierwszą odpowiedź podaliśmy za Ciebie.

... class MyHelper extends SQLiteOpenHelper{ StarbuzzDatabaseHelper(Context context){ super(context, “kamerdyner”, null, 4); } @Override public void onCreate(SQLiteDatabase db){ A // Wykonanie fragmentu kodu A ... }

Użytkownik 1. uruchamia aplikację po raz pierwszy.

@Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion){ if (oldVersion < 2) { B // Wykonanie fragmentu kodu B ... } if (oldVersion == 3) { C // Wykonanie fragmentu kodu C ... } D // Wykonanie fragmentu kodu D ... }

Fragment kodu A. Na urządzeniu użytkownika nie ma jeszcze bazy danych, dlatego wykonywana jest metoda onCreate().

Użytkownik 2. dysponuje bazą danych o numerze wersji 1.

Użytkownik 3. dysponuje bazą danych o numerze wersji 2.

Użytkownik 4. dysponuje bazą danych o numerze wersji 3.

@Override public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion){ if (oldVersion == 3) { E // Wykonanie fragmentu kodu E ... } if (oldVersion < 6) { F // Wykonanie fragmentu kodu F ... } }

Użytkownik 5. dysponuje bazą danych o numerze wersji 4.

Użytkownik 6. dysponuje bazą danych o numerze wersji 5.

644

Rozdział 15.

}

Odpowiedzi na s. 654

Bazy danych SQLite

Aktualizacja istniejącej bazy danych

¨  Aktualizacja bazy danych

W ramach aktualizacji bazy danych możemy wykonywać operacje dwóch różnych typów:



Modyfikacje rekordów bazy.

Z wcześniejszej części rozdziału wiesz, jak za pomocą metod insert(), update() oraz delete() klasy SQLiteDatabase można odpowiednio wstawiać, modyfikować i usuwać rekordy z baz danych SQLite. W ramach aktualizacji bazy można dodawać do niej nowe rekordy albo modyfikować lub usuwać rekordy już istniejące.



Modyfikacje struktury bazy danych.

Wiesz już, jak tworzyć tabele bazy danych. Może jednak się zdarzyć, że zechcesz dodać nowe kolumny do istniejącej tabeli, zmieniać nazwy tabel albo nawet całkowicie je usuwać z bazy.

Zaczniemy od pokazania, w jaki sposób można zmieniać rekordy w bazie danych.

Jak aktualizować rekordy? Rekordy tabeli można aktualizować w podobny sposób, w jaki się je dodaje. Zaczynamy od utworzenia nowego obiektu ContentValues, zawierającego nowe wartości, które chcemy zapisać w bazie. W ramach przykładu załóżmy, że chcemy zaktualizować dane latte w tabeli DRINK, a konkretnie zastąpić dotychczasową zawartość kolumny DESCRIPTION nowym opisem o treści: ”Pyszności”.

_id 1

NAME “Latte”

DESCRIPTION

IMAGE_RESOURCE_ID

“Czarne espresso z gorącym mlekiem i mleczną pianką.” “Pyszności”

54543543

W tym celu musimy utworzyć nowy obiekt ContentValues, opisujący dane, które należy zaktualizować: ContentValues drinkValues = new ContentValues(); drinkValues.put(”DESCRIPTION”, ”Pyszności”);

Warto zwrócić uwagę na to, że w przypadku aktualizowania rekordów w obiekcie ContentValues nie trzeba podawać całej zawartości wiersza danych, a jedynie te dane, które mają zostać zmienione.

Chcemy zmienić zawartość kolumny DESCRIPTION na “Pyszności”, dlate dodajemy parę o nazwie “DESCRIP go TION” i wartości “Pyszności”.

Kiedy już utworzymy obiekt ContentValues i zapiszemy w nim dane, które chcemy zmienić, możemy wykonać operację aktualizacji. Sprowadza się to do wywołania metody update() klasy SQLiteDatabase. Sposób stosowania tej metody przedstawimy dokładniej na następnej stronie.

jesteś tutaj  645

Metoda update()

Aktualizacja rekordów za pomocą metody update()

¨  Aktualizacja bazy danych

Metoda update() pozwala na modyfikowanie zawartości rekordów bazy danych i zwraca liczbę rekordów, w których wprowadzono zmiany. W wywołaniu tej metody należy przekazać: nazwę modyfikowanej tabeli, obiekt ContentValues zawierający nowe wartości, które należy zapisać w tabeli, oraz warunki określające, które wiersze należy zmienić. 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”. ContentValues drinkValues = new ContentValues(); drinkValues.put(”DESCRIPTION”, ”Pyszności”); db.update(“DRINK”, drinkValues, Warunki aktualizacji danych; w tym przypadku jest to NAME = "Latte".

“NAME = ?”,

To nazwa tabeli, której kolumny chcemy zaktualizować.

To obiekt ContentValues zawierający nowe wartości.

new String[] {“Latte”});

Znak ? z łańcucha "NA zastąpiony wartością ME = ?" zostanie "Latte".

Pierwszym parametrem metody update() jest nazwa tabeli, której zawartość należy zaktualizować (w naszym przykładzie jest to tabela DRINK). Drugim parametrem metody jest obiekt ContentValues opisujący wartości, które chcemy zmodyfikować. W powyższym przykładzie dodaliśmy do niego parę "DESCRIPTION" oraz "Pyszności", a zatem zaktualizujemy zawartość kolumny DESCRIPTION, w której zapiszemy łańcuch "Pyszności". Trzeci parametr określa nazwę kolumny w warunku. W powyższym przykładzie chcemy zaktualizować rekordy, w których kolumna NAME ma wartość "Latte", dlatego używamy warunku "NAME = ?". Zapis ten oznacza, że kolumna NAME ma być równa pewnej wartości. Znak pytajnika (?) jest symbolem zastępczym reprezentującym tę wartość. Ostatnim parametrem metody update() jest tablica łańcuchów znaków, określająca wartości, jakich należy użyć w warunku. W naszym przykładzie chcemy, by zaktualizowane zostały rekordy, w których kolumna NAME ma wartość "Latte", dlatego też użyliśmy tablicy o postaci: new String[] {"Latte"});

Na następnej stronie przedstawimy kilka bardziej złożonych przykładów.

Jeśli dwa ostatnie parametry metody update() przyjmą wartość null, to zostaną zmodyfikowane WSZYSTKIE rekordy tabeli.

Obejrzyj to!

Na przykład poniższe wywołanie: db.update(”DRINK”, drinkValues, null, null);

zmodyfikuje wszystkie wiersze tabeli DRINK.

646

Rozdział 15.

Bazy danych SQLite

Stosowanie warunków odnoszących się do wielu kolumn

¨  Aktualizacja bazy danych

Można także tworzyć warunki, które odnoszą się do kilku kolumn tabeli. 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”. ” or

ME = “Latte db.update(”DRINK”, To oznacza: Where NA a kawa ze świeżo arn “Cz = DESCRIPTION drinkValues, ższej jakości.”. mielonych ziaren najwy “NAME = ? OR DESCRIPTION = ?”, new String[] {“Latte”, “Czarna kawa ze świeżo mielonych ziaren najwyższej jakości.”});

Aby zastosować warunek odnoszący się do kilku kolumn, ich nazwy trzeba podać w trzecim parametrze metody update(). Podobnie jak poprzednio, także w tym przypadku należy użyć znaku zastępczego ? zamiast każdej z wartości, które mają być zastosowane w warunkach. Wartości te należy następnie zapisać w tablicy stanowiącej czwarty parametr metody update().

Każdy symbol ? jest zastępowany wartością z tej tablicy. Liczba elementów w tablicy musi odpowiadać liczbie symboli.

Wartościami warunków muszą być łańcuchy znaków (String), i to nawet jeśli kolumny, do których te warunki się odnoszą, zawierają dane jakichś innych typó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”, drinkValues, ”_id = ?”,

Konwertujemy liczbę całkowitą na łańcuch znaków.

new String[] {Integer.toString(1)});

Do usuwania rekordów służy metoda delete() Do usuwania rekordów służy metoda delete() klasy SQLiteDatabase. Przypomina ona nieco przedstawioną powyżej metodę update(). W jej wywołaniu należy podać tabelę, z której chcemy usunąć rekordy, oraz warunki określające, które rekordy należy usunąć. Na przykład poniższe wywołanie metody delete() usuwa z tabeli wszystkie rekordy, w których nazwą napoju jest ”Latte”: db.delete(”DRINK”, ”NAME = ?”,

da delete() Zwróciłeś uwagę, jak bardzo meto te()? upda dę meto przypomina

new String[] {”Latte”});

Usuwany jest cały rekord.

_id

NAME

DESCRIPTON

IMAGE_RESOURCE_ID

1

“Latte”

“Czarne espresso z gorącym mlekiem i mleczną pianką.”

54543543

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”).

jesteś tutaj  647

Ćwiczenie

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”); db.execSQL(”CREATE TABLE DRINK (” + ”_id INTEGER PRIMARY KEY AUTOINCREMENT, ” + ”NAME TEXT, ” + ”DESCRIPTION TEXT);”); db.insert(”DRINK”, null, cappuccino); db.insert(”DRINK”, null, americano); db.delete(”DRINK”, null, null); db.insert(”DRINK”, null, latte); db.update(”DRINK”, mochachino, ”NAME = ?”, new String[] {”Cappuccino”}); db.insert(”DRINK”, null, espresso); }

_id

NAME

DESCRIPTION

Wartości kolumny _id nie musisz uzupełniać.

648

Rozdział 15.

Odpowiedzi na s. 655

Bazy danych SQLite

Modyfikacja struktury bazy danych

¨  Aktualizacja bazy danych

Oprócz tworzenia aktualizacji oraz usuwania rekordów bazy danych czasami pojawia się także konieczność wprowadzania zmian w jej strukturze. Na przykład w naszej aplikacji dla kafeterii Coffeina chcemy dodać do tabeli DRINK kolumnę FAVORITE.

Dodawanie nowych kolumn za pomocą kodu SQL Z wcześniejszej części rozdziału wiesz, jak tworzyć nowe tabele, używając polecenia SQL CREATE TABLE, takiego jak poniżej: Kolumna _id jest kluczem głównym tabeli.

CREATE TABLE DRINK (_id INTEGER PRIMARY KEY AUTOINCREMENT, NAME TEXT,

Nazwa tabeli.

DESCRIPTION TEXT,

Kolumny tabeli.

IMAGE_RESOURCE_ID INTEGER)

Język SQL zawiera także polecenie ALTER, które pozwala modyfikować strukturę istniejących tabel. Na przykład poniższe polecenie pozwala dodać do tabeli nową kolumnę: Nazwa tabeli.

ALTER TABLE DRINK

ADD COLUMN FAVORITE NUMERIC

Kolumna, którą chcemy dodać.

W powyższym przykładzie do tabeli DRINK dodajemy nową kolumnę, FAVORITE, zawierającą wartości liczbowe.

Zmiana nazwy tabeli Polecenia ALTER TABLE można także używać do zmiany nazwy tabeli. Poniższy przykład pokazuje, jak zmienić tabelę DRINK na NAPOJE : ANTER TABLE DRINK RENAME TO NAPOJE

Bieżąca nazwa tabeli. Nowa nazwa tabeli.

Na następnej stronie pokażemy, w jaki sposób można usunąć z bazy danych całą tabelę.

jesteś tutaj  649

Modyfikacja tabel

Usuwanie tabeli

¨  Aktualizacja bazy danych

Aby usunąć tabelę, należy użyć polecenia DROP TABLE. W ramach przykładu poniżej pokazaliśmy, w jaki sposób można by usunąć tabelę DRINK: DROP TABLE DRINK

Nazwa tabeli, którą chcemy usunąć.

To polecenie jest przydatne w sytuacjach, kiedy w strukturze bazy danych znajduje się tabela, która nie będzie nam już dłużej potrzeba i chcemy ją usunąć, by zaoszczędzić miejsce. Upewnij się, że polecenie DROP TABLE będziesz wykonywał wyłącznie w przypadku, gdy będziesz mieć absolutną pewność, że chcesz się pozbyć tabeli oraz wszystkich zapisanych w niej danych.

Wykonywanie poleceń SQL za pomocą metody execSQL() Jak już wiesz z wcześniejszej części rozdziału, polecenia SQL można wykonywać za pomocą metody execSQL() klasy SQLiteDatabase: SQLiteDatabase.execSQL(String sql);

Metody execSQL() używamy zawsze, gdy chcemy wykonać na bazie danych jakieś polecenie SQL. Na przykład poniższe wywołanie tej metody dodaje do tabeli DRINK nową kolumnę o nazwie FAVORITE: db.execSQL(”ALTER TABLE DRINK ADD COLUMN FAVORITE NUMERIC;”);

Skoro już znasz różnego rodzaju operacje, które można wykonywać w ramach aktualizowania bazy danych, spróbujmy je zastosować w kodzie klasy CoffeinaDatabaseHelper.

650

Rozdział 15.

Bazy danych SQLite

Pełny kod pomocnika SQLite

¨  Aktualizacja bazy danych

Poniżej przedstawiliśmy kompletny kod klasy CoffeinaDatabaseHelper, który dodaje do tabeli DRINK kolumnę FAVORITE (zmiany wprowadzone w kodzie zostały wyróżnione pogrubioną czcionką): Coffeina

package com.hfad.coffeina; app/src/main

import android.content.ContentValues;

java

import android.content.Context; import android.database.sqlite.SQLiteDatabase;

com.hfad.coffeina

import android.database.sqlite.SQLiteOpenHelper; CoffeinaDatabase Helper.java

class CoffeinaDatabaseHelper extends SQLiteOpenHelper { private static final String DB_NAME = ”coffeina”; // Nazwa bazy danych private static final int DB_VERSION = 2; // Numer wersji bazy danych CoffeinaDatabaseHelper(Context context) { super(context, DB_NAME, null, DB_VERSION);

Zmiana numeru wersji na wyższy pozwala pomocnikowi SQLite zorientować się, że chcemy zaktualizować bazę danych.

} @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().

Dalsza część kodu znajduje się na następnej stronie.

jesteś tutaj  651

Więcej kodu

Kod pomocnika SQLite (ciąg dalszy)

¨  Aktualizacja bazy danych

private static void insertDrink(SQLiteDatabase db, String name, String description, int resourceId) { ContentValues drinkValues = new ContentValues(); drinkValues.put(“NAME”, name); drinkValues.put(“DESCRIPTION”, description); drinkValues.put(“IMAGE_RESOURCE_ID”, resourceId); db.insert(“DRINK”, null, drinkValues); } private void updateMyDatabase(SQLiteDatabase db, int oldVersion, int newVersion) { if (oldVersion < 1) { db.execSQL(”CREATE TABLE DRINK (_id INTEGER PRIMARY KEY AUTOINCREMENT, ” + ”NAME TEXT, ” + ”DESCRIPTION TEXT, ” + ”IMAGE_RESOURCE_ID INTEGER);”); insertDrink(db, “Latte”, “Czarne espresso z gorącym mlekiem i mleczną pianką.”, 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) { db.execSQL(”ALTER TABLE DRINK ADD COLUMN FAVORITE NUMERIC;”); } }

Do tabeli DRINK dodajemy kolumnę FAVORITE typu liczbowego.

}

Ten nowy kod oznacza, że na urządzeniach użytkowników, którzy już wcześniej zainstalowali naszą aplikację, kolumna FAVORITE zostanie dodana do tabeli DRINK podczas następnej próby odwołania się do bazy danych. Oznacza to także, że na urządzeniach użytkowników, którzy po raz pierwszy instalują aplikację, zostanie utworzona baza danych zawierająca nową kolumnę. Na następnej stronie szczegółowo przeanalizujemy, co się dziej podczas wykonywania tego kodu. Z kolei w następnym rozdziale dowiesz się, jak używać bazy danych w tworzonych aktywnościach.

Coffeina app/src/main java com.hfad.coffeina CoffeinaDatabase Helper.java

652

Rozdział 15.

Bazy danych SQLite

Co się dzieje podczas działania kodu? 1

Kiedy po raz pierwszy aplikacja odwołuje się do bazy danych, pomocnik SQLite sprawdza, czy baza w ogóle istnieje.

Czy jaśnie pan życzy sobie bazę danych? Zaraz sprawdzę, czy baza dla jaśnie pana została już przygotowana.

Pomocnik SQLite

2a

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” Wersja: 2

onCreate()

Baza danych SQLite Pomocnik SQLite

2b

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 przykładzie numer wersji podany w pomocniku SQLite jest wyższy od numeru wersji istniejącej 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” Wersja: 1 2

onUpgrade()

Baza danych SQLite Pomocnik SQLite

jesteś tutaj  653

Rozwiązanie

Wczuj się w pomocnika SQLite. Rozwiązanie Z prawej strony zamieściliśmy kod pomocnika SQLite. Twoim zadaniem jest ... wcielić się w rolę tego pomocnika class MyHelper extends SQLiteOpenHelper{ i określić, które fragmenty StarbuzzDatabaseHelper(Context context){ kodu zostaną wykonane super(context, “kamerdyner”, null, 4); dla poszczególnych, } przedstawionych poniżej Nowy numer wersji użytkowników. Kod, na bazy danych to 4. @Override którym powinieneś się skoncentrować, public void onCreate(SQLiteDatabase db){ zaznaczyliśmy literami. Aby ułatwić A // Wykonanie fragmentu kodu A Ci rozwiązanie ćwiczenia, pierwszą ... Metoda onCreate() będzie odpowiedź podaliśmy za Ciebie. }

Użytkownik 1. uruchamia aplikację po raz pierwszy.

@Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion){ if (oldVersion < 2) { B // Wykonanie fragmentu kodu B Ten fragment zostanie wywołany, ... jeśli użytkownik dysponuje bazą } o numerze wersji 1. if (oldVersion == 3) { Ten fragment zostanie C // Wykonanie fragmentu kodu C wywołany, jeśli ... użytkownik } dysponuje bazą o numerze D // Wykonanie fragmentu kodu D wersji 3. ... Ten fragment zostanie wywołany, }

Fragment kodu A. Na urządzeniu użytkownika nie ma jeszcze bazy danych, dlatego wykonywana jest metoda onCreate().

Użytkownik 2. dysponuje bazą danych o numerze wersji 1. Fragment kodu B, a następnie D. Baza danych musi zostać zaktualizowana, parametr oldVersion ma wartość 1.

Użytkownik 3. dysponuje bazą danych o numerze wersji 2. Fragment kodu D. Baza danych musi zostać zaktualizowana, parametr oldVersion ma wartość 2.

jeśli użytkownik dysponuje bazą o numerze wersji 1, 2 lub 3.

Użytkownik 4. dysponuje bazą danych o numerze wersji 3. Fragment kodu C, a następnie D. Baza danych musi zostać zaktualizowana, parametr oldVersion ma wartość 3.

Użytkownik 5. dysponuje bazą danych o numerze wersji 4. Żaden. Użytkownik dysponuje aktualną wersją bazy danych.

Użytkownik 6. dysponuje bazą danych o numerze wersji 5. Fragment kodu F. Baza danych musi zostać przywrócona do wcześniejszej wersji, parametr oldVersion ma wartość 5.

654

Rozdział 15.

wywoływana tylko w przypadku, gdy na urządzeniu użytkownika jeszcze nie ma bazy danych.

}

@Override public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion){ if (oldVersion == 3) { E // Wykonanie fragmentu kodu E Ten fragment kodu nigdy nie zostanie ... wywołany. Jeśli użytkownik dysponuje bazą danych o numerze wersji 3., to jego } baza musi zostać zaktualizowana, a nie if (oldVersion < 6) { przywrócona do wcześniejszej postaci. F // Wykonanie fragmentu kodu F ... } Ten fragment kodu zostanie wykonany, jeśli użytkownik } dysponuje bazą o numerze wersji 5. Aby została wywołana metoda onDowngrade(), użytkownik musi mieć bazę o numerze wersji wyższym niż 4, gdyż to jest bieżący numer wersji bazy.

Bazy danych SQLite

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 NAME oraz DESCRIPTION.

db.execSQL(”CREATE TABLE DRINK (”

+ ”_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); db.insert(”DRINK”, null, latte);

Usuwamy wszystkie wiersze tabeli. Wstawiamy łańcuch Latte do kolumny NAME.

db.update(”DRINK”, mochachino, ”NAME = ?”, new String[] {”Cappuccino”}); db.insert(”DRINK”, null, espresso); }

Wstawiamy łańcuch Espresso do kolumny DESCRIPTION.

_id Wartości kolumny _id nie musisz uzupełniać.

NAME

W wierszu, w którym pole NAME ma wartość Cappuccino, zmieniamy wartość tego pola na Mochachino. Żaden rekord tabeli nie zostaje zmieniony.

DESCRIPTION

Latte

Espresso

jesteś tutaj  655

Przybornik

Rozdział 15.

Twój przybornik do Androida Opanowałeś już rozdział 15. i dodałeś do swojego przybornika z narzędziami umiejętność tworzenia, aktualizacji i modyfikacji baz danych SQLite.

j Pełny kod przykładowe j ane tow zen pre cji lika ap z żes mo w tym rozdziale P FT ra we pobrać z ser wydawnictwa Helion: klady/ ftp://ftp.helion.pl/przy andrr2.zip

CELNE SPOSTRZEŻENIA 











656

W systemie Android do trwałego przechowywania danych używane są bazy danych SQLite. Dostęp do baz danych SQLite zapewnia klasa SQLiteDatabase. W tworzeniu baz danych i zarządzaniu nimi pomaga nam pomocnik SQLite. Aby go utworzyć, należy napisać klasę dziedziczącą po SQLiteOpenHelper. Tworząc pomocnika SQLite, trzeba zaimplementować metody onCreate() i onUpgrade() klasy SQLiteOpenHelper. Baza danych jest tworzona, gdy aplikacja po raz pierwszy zażąda dostępu do niej. 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. Podczas tworzenia bazy danych wywoływana jest metoda onCreate().

Rozdział 15.















W razie konieczności aktualizacji bazy danych wywoływana jest metoda onUpgrade(). Polecenia SQL można wykonywać za pomocą metody execSQL(String) zdefiniowanej w klasie SQLiteDatabase. Do modyfikowania istniejących tabel służy polecenie SQL ALTER TABLE. Składnia RENAME TO pozwala zmienić nazwę tabeli, a składnia ADD COLUMN dodać kolumnę. Do usuwania istniejących tabel służy polecenie SQL DROP TABLE. Nowe rekordy można dodawać do tabel za pomocą metody insert(). Do modyfikacji istniejących rekordów służy metoda update(). Istniejące rekordy można usuwać za pomocą metody delete().

16. Proste kursory

Pobieranie danych Karol wręczył mi kursor, który zwrócił wszystkie rekordy z tablicy EKSKLUZYWNY_PREZENT.

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. W tym rozdziale skoncentrujemy się na sposobach odczytywania danych z bazy danych. Dowiesz się w nim, jak używać kursora do odczytywania danych z bazy. Nauczysz się także poruszać po kursorach i uzyskiwać dostęp do umieszczonych w nich danych. I w końcu dowiesz się, jak stosować adaptery operujące na kursorach, aby używać wspólnie kursorów i widoków list.

to jest nowy rozdział  657

Na czym stanęliśmy?

Co się wydarzyło wcześniej… Z rozdziału 15. dowiedziałeś się, jak napisać pomocnika SQLite na potrzeby aplikacji kafeterii Coffeina. Ten pomocnik SQLite tworzy bazę danych aplikacji, dodaje do niej tabelę DRINK i zapisuje w niej informacje na temat wszystkich napojów. Obecnie aktywności aplikacji dla kafeterii Coffeina pobierają dane z klasy Drink. Zmodyfikujemy tę aplikację w taki sposób, by aktywności pobierały używane dane z bazy danych SQLite. Oto aktualny schemat aplikacji dla kafeterii Coffeina:

1 2

Aktywność TopLevelActivity wyświetla opcje: Napoje, Przekąski i Kafeterie. Kiedy użytkownik kliknie opcję Napoje, uruchamiana jest aktywność DrinkCategoryActivity.

Aktywność ta wyświetla listę wszystkich napojów pobranych z klasy Drink, napisanej w Javie.

3

Przygotowaliśmy pomocnika SQLite 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

Kiedy użytkownik kliknie jeden z napojów, zostaje uruchomiona aktywność DrinkActivity, prezentująca szczegółowe informacje o wybranym napoju.

Klasa DrinkActivity pobiera szczegółowe informacje o napojach z klasy Drink, napisanej w Javie.

Pomocnik SQLite

Aplikacja ciągle używa klasy Drink.



activity_top_level.xml

activity_drink_ category.xml

1

Urządzenie



TopLevelActivity.java

activity_drink.xml

Drink.java

2

3

DrinkCategoryActivity.java

DrinkActivity.java

Klasy DrinkActivity i DrinkCategoryActivity wciąż używają klasy Drink.

658

Rozdział 16.

Proste kursory

Struktura nowej wersji aplikacji kafeterii Coffeina W aplikacji są dwie aktywności korzystające z klasy Drink: DrinkActivity oraz DrinkCategoryActivity. Musimy je zmienić w taki sposób, by odczytywały dane z bazy danych SQLite, korzystając przy tym ze wsparcia pomocnika SQLite. Poniżej pokazaliśmy, jak będzie wyglądać schemat nowej wersji aplikacji:

Urządzenie

Baza danych kafeterii Coffeina





activity_top_level.xml

activity_drink_ category.xml

Pomocnik SQLite

DrinkCategoryActivity.java

TopLevelActivity.java

activity_drink.xml

DrinkActivity.java

Aktywności, które wcześniej korzystały z klasy Drink, zmienimy w taki sposób, by zamiast niej używały bazy danych.

Nie będziemy już dłużej używać klasy Drink.

Drink.java

Zaczniemy od zaktualizowania aktywności DrinkActivity, a aktywnością DrinkCategoryActivity zajmiemy się w dalszej części rozdziału.

Zrób to sam!

W tym rozdziale będziemy modyfikować aplikację kafeterii Coffeina, a zatem otwórz w Android Studio odpowiedni projekt.

jesteś tutaj  659

Etapy

Co zrobimy, by aktywność DrinkActivity zaczęła korzystać z bazy danych? Aby aktywność DrinkActivity zaczęła korzystać z bazy danych kafeterii Coffeina, musimy wprowadzić w niej kilka zmian:

1

Pobrać referencję do bazy danych.

Zrobimy to, używając pomocnika SQLite, przygotowanego w rozdziale 15.

DrinkActivity.java Pomocnik SQLite

2

Baza danych kafeterii Coffeina

Utworzyć kursor, który pozwoli nam odczytać dane z bazy.

Musimy odczytać przechowywane w bazie danych informacje o napoju, który użytkownik wybrał w aktywności DrinkCategoryActivity. Dostęp do tych danych zapewni nam kursor. (Już niebawem wyjaśnimy, czym on jest).

3

4

Przejść do rekordu napoju.

Zanim będziemy mogli skorzystać z danych pobranych przez kursor, musimy jawnie do nich przejść. Wyświetlić szczegółowe informacje o napoju w aktywności DrinkActivity.

Kiedy już przejdziemy do rekordu napoju w kursorze, musimy odczytać zapisane w nim dane i wyświetlić je w aktywności DrinkActivity.

Aktywność DrinkActivity wyświetla szczegółowe informacje o napoju wybranym przez użytkownika.

Zanim zaczniemy, przypomnijmy sobie, jak wygląda kod aktywności DrinkActivity.java, który napisaliśmy w rozdziale 7.

660

Rozdział 16.

Proste kursory

Aktualny kod aktywności DrinkActivity

¨  Referencja do bazy danych

Poniżej przypomnieliśmy, jak obecnie wygląda kod aktywności DrinkActivity. Metoda onCreate() pobiera identyfikator napoju wybranego przez użytkownika i pobiera szczegółowe informacje o tym napoju z klasy Drink, a następnie używa ich do wypełnienia widoków układu. Musimy zmienić kod metody onCreate(), tak by pobierała dane z bazy danych.

¨  Utworzenie kursora ¨  Przejście do rekordu ¨  Wyświetlenie napoju

package com.hfad.coffeina; Nie pokazujemy tu instrukcji importu.

...

public class DrinkActivity extends Activity {

Coffeina

public static final String EXTRA_DRINKID = ”drinkId”;

app/src/main java

@Override protected void onCreate(Bundle savedInstanceState) {

com.hfad.coffeina

super.onCreate(savedInstanceState); setContentView(R.layout.activity_drink);

To jest napój wybrany przez użytkownika.

DrinkActivity.java

// Pobieramy identyfikator napoju z intencji int drinkId = (Integer)getIntent().getExtras().get(EXTRA_DRINKID); Drink drink = Drink.drinks[drinkId]; // Wyświetlamy nazwę napoju

Używamy numeru napoju przekazanego w intencji, aby pobrać dane napoju z klasy Drink. Ten fragment kodu będziemy musieli zmienić, tak by informacje te były pobierane z bazy danych.

TextView name = (TextView)findViewById(R.id.name); name.setText(drink.getName()); W poszczególnych widokach układu będziemy musieli zapisać informacje odczytane z bazy danych, a nie z klasy Drink.

// Wyświetlamy opis napoju TextView description = (TextView)findViewById(R.id.description); description.setText(drink.getDescription()); // Wyświetlamy zdjęcie napoju ImageView photo = (ImageView)findViewById(R.id.photo); photo.setImageResource(drink.getImageResourceId()); photo.setContentDescription(drink.getName()); }

}

jesteś tutaj  661

Pobranie referencji do bazy danych

Pobranie referencji do bazy danych W pierwszej kolejności musimy pobrać referencję do bazy danych kafeterii Coffeina, używając pomocnika SQLite utworzonego w poprzednim rozdziale. Zaczniemy od pobrania referencji do pomocnika SQLite:

¨  Referencja do bazy danych ¨  Utworzenie kursora ¨  Przejście do rekordu ¨  Wyświetlenie napoju

SQLiteOpenHelper starbuzzDatabaseHelper = new StarbuzzDatabaseHelper(this);

Teraz możemy już pobrać referencję do bazy danych, wywołując w tym celu metodę getReadableDatabase() lub getWritableDatabase() pomocnika SQLite. Pierwszej z tych metod, getReadableDatabase(), używamy, kiedy potrzebujemy dostępu tylko do odczytu, a drugiej, getWritableDatabase(), gdy konieczne jest wprowadzanie zmian w zawartości bazy. Oba te wywołania zwracają obiekt SQLiteDatabase, którego możemy używać, by uzyskać dostęp do bazy danych:

To jest obiekt Context; w tym przypadku jest nim bieżąca aktywność.

SQLiteDatabase db = starbuzzDatabaseHelper.getReadableDatabase();

lub SQLiteDatabase db = starbuzzDatabaseHelper.getWritableDatabase();

Jeśli systemowi nie uda się pobrać referencji do bazy danych, zostanie zgłoszony wyjątek SQLiteException. Może się to zdarzyć, na przykład kiedy wywołaliśmy metodę getWritableDatabase(), by uzyskać dostęp do odczytu i zapisu, lecz zapis danych w bazie jest niemożliwy ze względu na brak miejsca na dysku urządzenia. Nam zależy jedynie na odczycie danych z bazy, więc użyjemy metody getReadableDatabase(). Jeśli system nie będzie mógł pobrać referencji do bazy danych i zgłosi wyjątek SQLiteException, to wyświetlimy komunikat Toast (wyskakujące powiadomienie) informujący użytkownika, że baza danych jest niedostępna: SQLiteOpenHelper starbuzzDatabaseHelper = new StarbuzzDatabaseHelper(this); try { SQLiteDatabase db = starbuzzDatabaseHelper.getReadableDatabase(); // kod odczytujący dane z bazy } catch(SQLiteException e) { Toast toast = Toast.makeText(this, ”Baza danych jest niedostępna”, Toast.LENGTH_SHORT); toast.show(); }

Ten wiersz kodu wyświetla komunikat Toast.

Po pobraniu referencji do bazy danych możemy pobrać z niej dane, używając kursora. I to właśnie kursory będą kolejnym zagadnieniem, któremu się przyjrzymy.

662

Rozdział 16.

Te wiersze kodu tworzą komunikat Toast, który na kilka sekund wyświetli wiadomość: „Baza danych jest niedostępna”.

Proste kursory

¨  Referencja do bazy danych ¨  Utworzenie kursora

Pobranie danych z bazy za pomocą kursora Zgodnie z tym, co napisaliśmy w rozdziale 15., kursory pozwalają na odczyt oraz zapis informacji w bazach danych. Określamy, do których danych chcemy uzyskać dostęp, a kursor udostępnia nam odpowiednie rekordy z bazy. Potem możemy się poruszać pomiędzy rekordami dostępnymi w kursorze.

¨  Przejście do rekordu ¨  Wyświetlenie napoju

To jest baza danych.

To są informacje zapisane w bazie danych, które chcemy odczytywać.

Kursor odczytuje interesujące nas informacje zapisane w bazie danych.

Możemy nawigować po rekordach udostępnianych przez kursor i odczytywać ich wartości.

_id

NAME

DESCRIPTION

IMAGE_RESOURCE_ID

1

“Latte”

2

“Cappuccino” “Czarne espresso z dużą ilością spienionego mleka.”

3

“Espresso”

“Czarne espresso z gorącym mlekiem i mleczną pianką.”

54543543 654334453

“Czarna kawa ze świeżo mielonych 44324234 ziaren najwyższej jakości.”

Kursor tworzymy przy użyciu zapytania do bazy danych. Zapytania pozwalają określać rekordy bazy danych, do których chcemy mieć dostęp. Na przykład możemy zaznaczyć, że interesują nas wszystkie rekordy tabeli DRINK bądź też tylko jeden, konkretny rekord. Tak określone rekordy są następnie zwracane w kursorze. Do tworzenia kursora służy metoda query() klasy SQLiteDatabase: Metoda query() zwraca obiekt typu Cursor.

Cursor cursor = db.query(...);

dy query(). Tu są podawane parametry meto kilku na ej adni dokł im się y jrzym Przy następnych stronach.

Istnieje wiele przeciążonych wersji tej metody mających różne parametry, dlatego zamiast przedstawiać tu wszystkie możliwe wariacje, opiszemy tylko najczęściej stosowane sposoby jej użycia.

jesteś tutaj  663

Metoda query()

¨  Referencja do bazy danych ¨  Utworzenie kursora

Zwracanie wszystkich wierszy tabeli

¨  Przejście do rekordu ¨  Wyświetlenie napoju

Najprostszym rodzajem zapytania, jakie można utworzyć, jest zapytanie zwracające wszystkie rekordy konkretnej tabeli bazy danych. Takie rozwiązanie jest przydatne, kiedy chcemy na przykład wyświetlić wszystkie rekordy na liście w aktywności. W ramach przykładu poniżej pokazaliśmy, jak zwrócić wartości kolumn _id, NAME oraz DESCRIPTION wszystkich wierszy tabeli DRINK: To jest nazw a tabeli.

mn. Chcemy zwrócić wartości tych kolu

Cursor cursor = db.query(“DRINK”,

new String[] {“_id”, “NAME”, “DESCRIPTION”},

Aby zwrócić wszystkie rekordy tabeli, ustaw te parametry na null.

null, null, null, null, null);

To zapytanie zwraca wartości kolumn _id, NAME i DESCRIPTION ze wszystkich wierszy tabeli DRINK.

_id

NAME

DESCRIPTION

1

“Latte”

2

“Cappuccino” “Czarne espresso z dużą ilością spienionego mleka.”

3

“Espresso”

“Czarne espresso z gorącym mlekiem i mleczną pianką.”

“Czarna kawa ze świeżo mielonych ziaren najwyższej jakości.”

Aby zwrócić wszystkie rekordy konkretnej tabeli, jej nazwę należy przekazać jako pierwszy parametr metody query(), a drugim parametrem ma być tablica łańcuchów znaków zawierająca nazwy kolumn tej tabeli. Wszystkie pozostałe parametry metody mają mieć wartość null, gdyż w zapytaniach tego typu nie są nam potrzebne.

Cursor cursor = db.query(“NAZWA_TABELI”,

ość chcesz Nazwę każdej kolumny, której wartcy. zwracać, musisz podać w tej tabli

new String[] {“KOLUMNA1”,”KOLUMNA2”}, To pięć dodatkowych parametrów, które mają przyjąć wartość null.

null, null, null, null, null);

Kolejnym zagadnieniem , które omówimy, będzie zwracanie rekordów w określonej kolejności.

664

Rozdział 16.

Za kulisami Android używa metody query() do utworzenia zapytania SQL SELECT.

Proste kursory

¨  Referencja do bazy danych ¨  Utworzenie kursora

Zwracanie wierszy w określonej kolejności

¨  Przejście do rekordu ¨  Wyświetlenie napoju

Jeśli zależy nam, by dane wyświetlane w aplikacji były uporządkowane w jakiś konkretny sposób, to możemy je posortować na podstawie zawartości wybranej kolumny. Takie rozwiązanie będzie przydatne jeśli, na przykład, zechcemy wyświetlać nazwy napojów w kolejności alfabetycznej. Domyślnie, dane w tabeli są zwracane w kolejności rosnących wartości pola _id, gdyż właśnie w takiej kolejności były one zapisywane w tabeli: _id

NAME

DESCRIPTION

1

“Latte”

2

“Cappuccino” “Czarne espresso z dużą ilością spienionego mleka.”

3

“Espresso”

“Czarne espresso z gorącym mlekiem i mleczną pianką.”

IMAGE_RESOURCE_ID

FAVORITE

54543543

1

654334453

0

“Czarna kawa ze świeżo mielonych 44324234 ziaren najwyższej jakości.”

Aby pobrać dane z kolumn _id, NAME i FAVORITE posortowane w kolejności rosnącej według wartości kolumny NAME, musimy użyć następującego wywołania:

0

_id

NAME

FAVORITE

2

“Cappuccino”

0

3

“Espresso”

0

1

“Latte”

1

_id

NAME

FAVORITE

new String[] {”_id”, ”NAME”, ”FAVORITE”},

2

“Latte”

1

null, null, null, null,

3

“Cappuccino”

0

“FAVORITE DESC, NAME”);

1

“Espresso”

0

Cursor cursor = db.query(”DRINK”, new String[] {”_id”, ”NAME”, ”FAVORITE”}, null, null, null, null, “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. Można także sortować zwracane wyniki na podstawie kilku kolumn. Poniższy przykład pokazuje, jak zwrócić dane posortowane na podstawie wartości kolumny FAVORITE w kolejności malejącej i na podstawie wartości kolumny NAME w kolejności rosnącej: Cursor cursor = db.query(”DRINK”,

Sortujemy na podstawie wartości kolumny FAVORITE w kolejności malejącej, a następnie według wartości kolumny NAME w kolejności rosnącej.

Kolejnym zagadnieniem, któremu się przyjrzymy, będzie zwracanie z bazy danych jedynie wybranych rekordów.

jesteś tutaj  665

Określanie warunków

¨  Referencja do bazy danych ¨  Utworzenie kursora

Zwracanie wybranych rekordów

¨  Przejście do rekordu ¨  Wyświetlenie napoju

Aby filtrować dane, trzeba zadeklarować warunki, które te dane mają spełniać, tak jak zrobiliśmy to w rozdziale 15. Na przykład poniższe wywołanie zwróci tylko te rekordy tabeli DRINK, w których w polu z nazwą jest zapisane słowo ”Lattte”: Cursor cursor = db.query(”DRINK”,

. re chcemy zwracać To są kolumny, któ

new String[] {”_id”, ”NAME”, ”DESCRIPTION”}, “NAME = ?”, new String[] {“Latte”},

Chcemy zwrócić wiersze, w których w kolumnie NAME jest zapisane “Latte”.

null, null, null);

Trzeci i czwarty parametr wywołania metody query() opisują warunki, które muszą spełniać dane. Trzeci parametr określa kolumnę uwzględnianą w warunku. W powyższym przykładzie chcemy zwrócić rekordy, w których wartością kolumny NAME jest wartość ”Latte”, dlatego używamy łańcucha ”NAME = ?”. Chcemy, by wartość kolumny NAME była równa jakieś innej wartości, a symbol ? zastępuje tę wartość. Czwartym parametrem jest tablica łańcuchów znaków określających poszczególne wartości używane w warunku. W powyższym przykładzie interesują nas rekordy, których kolumna NAME ma wartość ”Latte”, dlatego parametr ten przyjmie wartość:

_id

NAME

1

“Latte”

DESCRIPTION “Czarne espresso z gorącym mlekiem i mleczną pianką.”

To zapytanie zwraca wartości kolumn _id, NAME i DESCRIPTION ze wszystkich wierszy tabeli DRINK, w których w kolumnie NAME jest zapisane słowo “Latte”.

new String[] {”Latte”},

Wartość używana w warunku musi być łańcuchem znaków, nawet jeśli kolumna, do której odnosi się warunek, zawiera dane jakiegoś innego typu. W ramach przykładu poniżej pokazaliśmy, jak można zwrócić z tabeli DRINK wiersz, w którym kolumna _id ma wartość 1: Cursor cursor = db.query(”DRINK”, new String[] {”_id”, ”NAME”, ”DESCRIPTION”}, ”_id = ?”, new String[] {Integer.toString(1)}, null, null, null);

Tak oto poznałeś najpopularniejsze sposoby użycia metody query() do tworzenia kursorów; spróbuj zatem wykonać ćwiczenie zamieszczone na następnej stronie, by przygotować kursor, którego potrzebujemy dla aktywności DrinkActivity.

666

Rozdział 16.



na łańcuch znaków. Konwertujemy liczbę 1

e Jeśli chcesz poznać inn tody me a sposoby stosowani entacji um dok do j query(), zajrzy e: bas ata eD Lit klasy SQ oid.com/ https://developer.andr ase/ tab reference/android/da ml .ht ase tab Da sqlite/SQLite

Proste kursory

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?

... int drinkId = (Integer)getIntent().getExtras().get(EXTRA_DRINKID); // Utworzenie kursora SQLiteOpenHelper starbuzzDatabaseHelper = new StarbuzzDatabaseHelper(this); try { SQLiteDatabase db = starbuzzDatabaseHelper.getReadableDatabase(); Cursor cursor = db.query( ................ , new String[] { ............ , ............ , ............ }, ”.....................”, new String[] { .................................. }, null, null,null); } catch(SQLiteException e) { Toast toast = Toast.makeText(this, "Baza danych jest niedostępna", Toast.LENGTH_SHORT); toast.show(); } ...

Nie będziesz potrzebował wszystkich tych magnesików.

"NAME"

toString

)

_id

"DESCRIPTION "

drinkId

"IMAGE_RESOURCE_ID"

"DRINK" =

? Integer

.

(

id

jesteś tutaj  667

Rozwiązanie magnesików

Magnesiki z kodem. Rozwiązanie 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?

... int drinkId = (Integer)getIntent().getExtras().get(EXTRA_DRINKID); // Utworzenie kursora SQLiteOpenHelper starbuzzDatabaseHelper = new StarbuzzDatabaseHelper(this); try { SQLiteDatabase db = starbuzzDatabaseHelper.getReadableDatabase();

K. Chcemy pobrać dane z tabeli DRIN

"DRINK" Pobieramy wartości z kolumn NAME, DESCRIPTION oraz IMAGE_RESOURCE_ID. Cursor cursor = db.query( ................ , "NAME" "DESCRIPTION SOURCE_ID" " "IMAGE_RE new String[] { ............ , ............ , ............ },

? _id = ”.....................”,

Wartość kolumny _id odpowiada wartości zmiennej drinkId.

Integer toString (}, . new String[] { .................................. null, null,null); } catch(SQLiteException e) {

drinkId

)

drinkId jest liczbą całkowitą, dlatego musimy skonwertować ją na łańcuch znaków.

Toast toast = Toast.makeText(this, "Baza danych jest niedostępna", Toast.LENGTH_SHORT); toast.show(); }

Nie potrzebowałeś tego magnesika.

...

id

668

Rozdział 16.

Proste kursory

Dotychczasowy kod aktywności DrinkActivity Chcemy zmienić metodę onCreate() w pliku DrinkActivity.java tak, by aktywność DrinkActivity pobierała informacje o napojach z bazy danych aplikacji Coffeina, a nie z klasy Drink. Poniżej przedstawiliśmy dotychczasową postać kodu tej aktywności (sugerujemy, żebyś, zanim zaczniesz aktualizować swoją wersję pliku DrinkActivity.java, poczekał, aż przedstawimy jej pełny kod — czyli jeszcze kilka stron): package com.hfad.starbuzz; ... public class DrinkActivity extends Activity {

¨  Referencja do bazy danych ¨  Utworzenie kursora ¨  Przejście do rekordu ¨  Wyświetlenie napoju

Nasz dotychczasowy kod korzysta z klasy Activity, jednak gdybyśmy chcieli, moglibyśmy go zmienić i zastosować w nim klasę AppCompatActivity. Coffeina

public static final String EXTRA_DRINKID = ”drinkId”; Nowy kod dodamy do metody onCreate().

@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_drink); // Pobieramy identyfikator napoju z intencji. int drinkId = (Integer)getIntent().getExtras().get(EXTRA_DRINKID); Drink drink = Drink.drinks[drinkId]; Informacji o napojach nie

app/src/main java com.hfad.coffeina

DrinkActivity.java

pobieramy już z klasy Drink.

// Tworzymy kursor. SQLiteOpenHelper starbuzzDatabaseHelper = new StarbuzzDatabaseHelper(this); try { Pobieramy referencję SQLiteDatabase db = starbuzzDatabaseHelper.getReadableDatabase(); do bazy danych. Cursor cursor = db.query (“DRINK”, Tworzymy kursor, który new String[] {“NAME”, “DESCRIPTION”, pobierze nazwę, opis “IMAGE_RESOURCE_ID”}, oraz identyfikator zasobu graficznego napoju wybranego “_id = ?”, przez użytkownika. new String[] {Integer.toString(drinkId)}, null, null, null); } catch(SQLiteException e) { Toast toast = Toast.makeText(this, “Baza danych jest niedostępna”, Toast.LENGTH_SHORT); toast.show(); W razie zgłoszenia wyjątku SQLiteException } wyświetlamy komunikat Toast. Jest więcej kodu, którego jeszcze nie ... zmieniliśmy, jednak } na razie nie musisz zaprzątać sobie nim } głowy.

Skoro już utworzyliśmy kursor, kolejną rzeczą którą musimy zrobić jest pobranie z niego nazwy, opisu oraz identyfikatora zasobu graficznego użyjemy ich później do zaktualizowania widoków aktywności DrinkActivity.

jesteś tutaj  669

Poruszanie się po rekordach

Aby odczytać rekord z kursora, najpierw należy do niego przejść

¨  Referencja do bazy danych ¨  Utworzenie kursora ¨  Przejście do rekordu ¨  Wyświetlenie napoju

Już wiesz, jak utworzyć kursor: wystarczy wywołać metodę query() klasy SQLiteDatabase, określając przy tym, jakie dane kursor ma zwrócić. Ale to jeszcze nie koniec historii — musimy jakoś odczytać wartości z kursora. Ilekroć chcemy pobrać wartości z konkretnego rekordu dostępnego w kursorze, musimy najpierw do niego przejść.

Rekordy, które chcemy pobrać, określamy, tworząc odpowiednie zapytanie i wykonując je w bazie danych.

Kursor zawiera rekordy opisane w zapytaniu.

Aby odczytać dane z rekordu zwróconego w kursorze, musimy najpierw do tego rekordu przejść.

_id

NAME

1

“Latte”

DESCRIPTION

2

“Cappuccino” “Czarne espresso z dużą ilością spienionego mleka.”

3

“Espresso”

“Czarne espresso z gorącym mlekiem i mleczną pianką.”

Rozdział 16.

654334453

“Czarna kawa ze świeżo mielonych 44324234 ziaren najwyższej jakości.”

W powyższym przykładzie obecny jest kursor składający się z pojedynczego rekordu zawierającego informacje o napoju wybranym przez użytkownika. Aby odczytać te szczegółowe informacje, musimy przejść do tego rekordu.

670

IMAGE_RESOURCE_ID 54543543

Proste kursory

Poruszanie się po kursorze Istnieją cztery podstawowe metody służące do poruszania się po kursorach: moveToFirst(), moveToLast(), moveToPrevious() oraz moveToNext(). Aby przejść do pierwszego rekordu kursora, należy wywołać metodę moveToFirst(). Metoda ta zwraca wartość true, jeśli uda się jej znaleźć rekord, lub wartość false, jeśli kursor nie zwrócił żadnych rekordów: if (cursor.moveToFirst()) { // Coś robimy };

¨  Referencja do bazy danych ¨  Utworzenie kursora ¨  Przejście do rekordu ¨  Wyświetlenie napoju Przechodzimy do pierwszego wiersza. NAME “Latte”

DESCRIPTION “Czarne espresso z gorącym 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.”

Jeśli chcemy przejść do ostatniego wiersza zwróconego przez kursor, wystarczy wywołać metodę moveToLast(). Podobnie jak metoda moveToFirst(), także moveToLast() zwraca wartość true, jeśli uda się jej znaleźć rekord, albo wartość false w przeciwnym wypadku: if (cursor.moveToLast()) { // Coś robimy };

Do poruszania się po kolejnych wierszach w kursorze służą metody moveToPrevious() i moveToNext(). Pierwsza z nich, moveToPrevious(), przenosi nas do poprzedniego wiersza w kursorze. Jej wywołanie zwraca wartość true, jeśli uda się znaleźć rekord, lub false, jeśli okaże się to niemożliwe — na przykład dlatego, że kursor jest pusty albo znajdujemy się już w pierwszym wierszu kursora: if (cursor.moveToPrevious()) { // Coś robimy };

Metoda moveToNext() działa tak samo jak moveToPrevious(), z tym że zamiast do poprzedniego wiersza w kursorze przenosi nas do następnego. Jej wywołanie zwraca wartość true, jeśli udało się przejść do następnego wiersza, lub wartość false w przeciwnym wypadku (na przykład jeśli już jesteśmy na pierwszym rekordzie): if (cursor.moveToNext()) { // Coś robimy };

My chcemy odczytać dane z pierwszego (a zarazem jedynego) wiersza w kursorze, dlatego przejdziemy do niego, wywołując metodę moveToFirst(). Kiedy już przejdziemy do wiersza w kursorze, możemy uzyskać dostęp do zapisanych w nim wartości. Na następnej stronie pokazaliśmy, jak to zrobić.

NAME “Latte”

DESCRIPTION “Czarne espresso z gorącym 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.”

Przechodzimy do ostatniego wiersza.

NAME “Latte”

DESCRIPTION “Czarne espresso z gorącym 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.”

Przechodzimy do poprzedniego wiersza.

NAME “Latte”

DESCRIPTION “Czarne espresso z gorącym 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.”

Przechodzimy do następnego wiersza.

jesteś tutaj  671

Pobieranie wartości

Pobieranie wartości z kursora

¨  Referencja do bazy danych ¨  Utworzenie kursora ¨  Przejście do rekordu ¨  Wyświetlenie napoju

Do pobierania wartości z kursora służą metody get*(): getString(), getInt(), i tak dalej. To, której metody użyjemy, będzie zależało od typu pobieranej wartości. Na przykład metoda getString() zwraca wartość kolumny w postaci łańcucha znaków, a metoda getInt() — w postaci liczby całkowitej. Każda z tych metod pobiera jeden argument — indeks kolumny, której wartość chcemy pobrać, licząc od 0. Oto zapytanie zastosowane do utworzenia kursora: Cursor cursor = db.query (”Drink”,

new String[] {”NAME”, ”DESCRIPTION”, ”IMAGE_RESOURCE_ID”}, ”_id = ?”, new String[] {Integer.toString(1)}, To są kolumny kursora.

null, null,null);

Kolumna 0

Utworzony kursor składa się z trzech kolumn: NAME, DESCRIPTION oraz IMAGE_RESOURCE_ID. Pierwsze dwie kolumny zawierają dane typu String, trzecia zaś — dane typu int. Załóżmy, że chcemy pobrać wartość kolumny NAME z bieżącego rekordu. NAME jest pierwszą kolumną kursora i zawiera wartości typu String. Oznacza to, że jej wartość możemy pobrać w poniższy sposób, używając metody getString() i przekazując 0 jako indeks kolumny: String name = cursor.getString(0);

NAME

Kolumna 1.

DESCRIPTION

Kolumna 2. IMAGE_ RESOURCE_ ID

“Latte”

“Czarne espresso z gorącym mlekiem i mleczną pianką.”

54543543

NAME jest kolumną o indeksie 0 i zawiera dane typu String.

A teraz załóżmy, że chcemy pobrać zawartość kolumny IMAGE_RESOURCE_ID. Ta kolumna ma indeks 2 i zawiera wartości typu int, a zatem jej zawartość możemy pobrać w następujący sposób: int imageResource = cursor.getInt(2);

IMAGE_RESOURCE_ID to kolumna o indeksie 2, która zawiera dane typu int.

Na końcu należy zamknąć kursor i bazę danych Po zakończeniu pobierania danych z kursora musimy zamknąć zarówno kursor, jak i samą bazę danych, aby zwolnić przydzielone im zasoby. Służą do tego metody close() klas SQLiteDatabase i Cursor: cursor.close(); db.close();

Te dwa wiersze kodu zamykają kursor i bazę danych.

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.

672

Rozdział 16.

acje e inform w ło ó g e Szcz tkich t wszys na tema żących do łu metod s ch ia dany n a r pobie na ż rów mo z  kurso ie n na stro ndroid. znaleźć r e elop .a id/ v e d / / : http ndro rence/a com/refe Cursor.html. e/ databas

Proste kursory

Kod aktywności DrinkActivity

¨  DrinkActivity ¨  DrinkCategoryActivity ¨  Ulubione napoje

Oto kompletna zawartość pliku DrinkActivity.java (wprowadź zmiany zaznaczone pogrubioną czcionką, a następnie zapisz plik): package com.hfad.coffeina; import android.app.Activity;

Coffeina

import android.os.Bundle; import android.widget.ImageView; Tych dodatkowych klas używamy w kodzie aktywności.

import android.widget.TextView; import android.widget.Toast;

app/src/main java

import android.database.Cursor; com.hfad.coffeina

import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException;

DrinkActivity.java

import android.database.sqlite.SQLiteOpenHelper; public class DrinkActivity extends Activity { public static final String EXTRA_DRINKID = ”drinkId”; @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 drinkId = (Integer)getIntent().getExtras().get(EXTRA_DRINKID); Drink drink = Drink.drinks[drinkId]; // Tworzymy kursor. try {

Nie pobieramy już danych z tablicy drinks, więc ten wiersz kodu możemy usunąć.

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 DRINK bazy danych wartości kolumn NAME, DESCRIPTION oraz IMAGE_RESOURCE_ID wiersza, w którym wartość kolumny _id odpowiada wartości zmiennej drinkId.

“IMAGE_RESOURCE_ID”}, “_id = ?”, new String[] {Integer.toString(drinkId)}, null, null,null);

Dalsza część kodu znajduje się na następnej stronie.

jesteś tutaj  673

Ciąg dalszy kodu

Kod aktywności DrinkActivity (ciąg dalszy)

¨  Referencja do bazy danych ¨  Utworzenie kursora ¨  Przejście do rekordu ¨  Wyświetlenie napoju

// Przechodzimy do pierwszego rekordu w kursorze Nazwa napoju jest pierwszym elementem kursora, opis znajduje się w drugiej kolumnie, a identyfikator zasobu graficznego w trzeciej. To uporządkowanie wynika z faktu, że kazaliśmy, by kursor pobrał z bazy kolumny NAME, DESCRIPTION oraz IMAGE_ RESOURCE_ID w podanej kolejności.

if (cursor.moveToFirst()) {

W kursorze znajduje się tylko jede wiersz, ale i tak musimy do nieg n o przejść.

// Pobieramy z kursora szczegółowe informacje o napoju String nameText = cursor.getString(0); Coffeina

String descriptionText = cursor.getString(1); int photoId = cursor.getInt(2);

app/src/main

// Wyświetlamy nazwę napoju java

TextView name = (TextView)findViewById(R.id.name); name.setText(drink.getName()); name.setText(nameText);

Ustawiamy na wartość nazwę napoju z bazy dany odczytaną ch.

com.hfad.coffeina

DrinkActivity.java

// Wyświetlamy opis napoju TextView description = (TextView)findViewById(R.id.description); description.setText(drink.getDescription());

Używamy opisu napoju z bazy danych.

description.setText(descriptionText); // Wyświetlamy zdjęcie napoju ImageView photo = (ImageView)findViewById(R.id.photo); photo.setImageResource(drink.getImageResourceId()); photo.setContentDescription(drink.getName()); photo.setImageResource(photoId); photo.setContentDescription(nameText);

Ustawiamy identyfikator zasobu graficznego oraz opis obrazka na wartości odczytane z bazy danych.

} cursor.close(); db.close();

Zamykamy kursor i bazę danych.

} catch(SQLiteException e) { Toast toast = Toast.makeText(this, “Baza danych jest niedostępna”, Toast.LENGTH_SHORT); } } }

toast.show(); Jeśli zostanie zgłoszony wyjątek SQLiteException, będzie on oznaczał problemy z bazą danych. W takim przypadku używamy klasy Toast, by wyświetlić użytkownikowi stosowny komunikat.

Tak oto zakończyliśmy prace nad kodem aktywności DrinkActivity. Zobaczmy, co udało nam się już zrobić oraz czym musimy się zająć w następnej kolejności.

674

Rozdział 16.

Spokojnie

Połączenie aktywności z bazą danych wymaga czegoś więcej niż użycia klasy.

Jeśli jednak poświęcisz czas na przyswojenie informacji zamieszczonych w tym rozdziale, to dasz sobie z tym radę.

Proste kursory

Co udało się nam zrobić? Teraz, kiedy już zakończyliśmy aktualizowanie kodu aktywności DrinkActivity, przyjrzyjmy się strukturze naszej aplikacji, aby sprawdzić, co już zrobiliśmy, a co jeszcze przed nami.



activity_top_level.xml

Urządzenie

Aktywność DrinkCategoryActivity wciąż pobiera dane z klasy Drink.





activity_drink_ category.xml

Drink.java

activity_drink.xml

DrinkActivity.java

DrinkCategoryActivity.java

TopLevelActivity.java

Obecnie aktywność DrinkActivity pobiera wszystkie używane dane z bazy danych kafeterii Coffeina. Naszym kolejnym zadaniem będzie aktualizacja kodu aktywności DrinkCategoryActivity, tak by ona także pobierała informacje z bazy danych, a nie z klasy Drink. Na następnej stronie przyjrzymy się czynnościom, jakie należy w tym celu wykonać.

Aktywność DrinkActivity już zmodyfikowaliśmy, dzięki czemu pobiera dane z bazy danych kafeterii Coffeina, używając do tego pomocnika SQLite.

Baza danych kafeterii Coffeina

Pomocnik SQLite

Nie istnieją

głupie pytania

P: W jakim stopniu muszę znać język SQL, by tworzyć kursory?

O: Przydałaby się znajomość polecenia SELECT języka SQL, gdyż za kulisami metoda query() tworzy właśnie takie polecenie. Ogólnie rzecz biorąc, zapytania, które będziesz tworzyć, raczej nie będą zbyt skomplikowane, niemniej jednak znajomość języka SQL może być przydatną umiejętnością. Jeśli chcesz się dowiedzieć czegoś więcej na temat języka SQL, radzimy sięgnąć po książkę „Head First SQL. Edycja polska” autorstwa Lynn Beighley.

P: Napisaliście, że jeśli nie uda się uzyskać dostępu do bazy

danych, to zostanie zgłoszony wyjątek SQLiteException. W jaki sposób powinienem go obsługiwać?

O: Przede wszystkim sprawdź szczegółowe informacje zapisane w wyjątku. Przyczyną wyjątku może być także błąd w składni zapytania SQL, a ten możesz samodzielnie poprawić.

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.

jesteś tutaj  675

Kolejne czynności

Co zrobimy, by aktywność DrinkCategoryActivity korzystała z bazy danych? Podczas modyfikowania aktywności DrinkActivity tak, by pobierała informacje o napojach z bazy danych kafeterii Coffeina, utworzyliśmy kursor, który odczytywał dane napoju wybranego przez użytkownika, a następnie użyliśmy wartości pobranych z tego kursora, by zaktualizować widoki aktywności. Czynności, jakie będziemy musieli wykonać w celu zaktualizowania aktywności DrinkCategoryActivity, są nieco inne. Wynika to z tego, że ta aktywność wyświetla widok listy, którego źródłem danych są informacje o napojach. Musimy zatem zmienić to źródło w taki sposób, by informacje były pobierane z bazy. Oto czynności, które musimy wykonać, by aktywność DrinkCategoryActivity zaczęła korzystać z bazy danych kafeterii Coffeina:

1

Utworzenie kursora, który odczyta dane z bazy danych.

Podobnie jak wcześniej, musimy pobrać referencję do bazy danych. Następnie utworzymy kursor, który pobierze nazwy napojów z tabeli DRINK.

Kursor

2

Baza danych

Zastąpienie adaptera korzystającego z tablicy adapterem operującym na kursorze.

Obecnie widok listy używa adaptera pobierającego nazwy napojów z tablicy. Zastosowaliśmy takie rozwiązanie, gdyż dane były przechowywane w klasie Drink. Jednak obecnie odczytujemy dane za pomocą kursora, dlatego też zastosujemy adapter CursorAdapter.

ListView

CursorAdapter

Zanim zajmiemy się wprowadzaniem tych zmian, przypomnijmy sobie, jak wyglądał kod pliku DrinkCategoryActivity.java, który napisaliśmy w rozdziale 7.

676

Rozdział 16.

Kursor

Proste kursory

Aktualny kod aktywności DrinkCategoryActivity

¨  Utworzenie kursora ¨  Utworzenie adaptera

Poniżej przypomnieliśmy, jak obecnie wygląda kod aktywności DrinkCategoryActivity zapisany w pliku DrinkCategoryActivity.java. W metodzie onCreate() określona jest zawartość widoku ListView — używamy przy tym tablicy napojów i adaptera ArrayAdapter. W metodzie onListItemClick() indeks napoju wybranego przez użytkownika dodajemy do intencji, a następnie uruchamiamy aktywność DrinkActivity:

Aktywność DrinkCategoryActivity wyświetla listę napojów.

package com.hfad.coffeina; public class DrinkCategoryActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { Obecnie, aby powiązać tablicę z widokiem ListView, używamy adaptera ArrayAdapter. Musimy zmodyfikować ten kod, aby informacje były pobierane z bazy danych.

super.onCreate(savedInstanceState); setContentView(R.layout.activity_drink_category);

Coffeina

ArrayAdapter listAdapter = new ArrayAdapter( this,

app/src/main

android.R.layout.simple_list_item_1,

java

Drink.drinks); ListView listDrinks = (ListView) findViewById(R.id.list_drinks);

com.hfad.coffeina

listDrinks.setAdapter(listAdapter); DrinkCategory Activity.java

// Tworzymy obiekt nasłuchujący zdarzeń kliknięć elementów listy AdapterView.OnItemClickListener itemClickListener = new AdapterView.OnItemClickListener(){ public void onItemClick(AdapterView listDrinks, View itemView, int position, long id) { // Przekazujemy kliknięty napój do DrinkActivity Intent intent = new Intent(DrinkCategoryActivity.this, DrinkActivity.class); intent.putExtra(DrinkActivity.EXTRA_DRINKID, (int) id); startActivity(intent); } }; // Przypisujemy obiekt nasłuchujący do listy listDrinks.setOnItemClickListener(itemClickListener); } }

jesteś tutaj  677

Pobranie referencji do bazy danych

Pobranie referencji do bazy danych kafeterii…

¨  Utworzenie kursora ¨  Utworzenie adaptera

Chcemy zmienić aktywność DrinkCategoryActivity w taki sposób, by pobierała dane z bazy danych kafeterii Coffeina. Oznacza to, że podobnie jak wcześniej, musimy utworzyć kursor, który zwróci potrzebne dane. Zaczniemy zatem od pobrania referencji do bazy danych. Musimy jedynie odczytać dane napojów, więc podobnie jak wcześniej pobierzemy tę referencję, używając metody getReadableDatabase(): SQLiteOpenHelper starbuzzDatabaseHelper = new StarbuzzDatabaseHelper(this); SQLiteDatabase db = starbuzzDatabaseHelper.getReadableDatabase();

…a następnie utworzenie kursora zwracającego napoje

Referencję do bazy danych pobieramy w dokładnie taki sam sposób w jaki robiliśmy to we wcześniejszej części rozdziału.

Aby utworzyć kursor, musimy określić, jakie dane ma on zawierać. Chcemy użyć tego kursora do wyświetlenia listy napojów, więc musi on zawierać kolumnę NAME. Ponadto dodamy do kursora także kolumnę _id, aby uzyskać identyfikator napoju: identyfikator ten musimy później przekazać do aktywności DrinkActivity, tak by ta mogła wyświetlić szczegółowe informacje o wybranym napoju. Oto kod tworzący kursor: cursor = db.query(”DRINK”,

i NAME Ten kursor zwraca kolumny _id K. ze wszystkich wierszy tabeli DRIN

new String[]{”_id”, ”NAME”}, null, null, null, null, null);

Łącząc ze sobą wszystkie te fragmenty, uzyskamy kod, który pobierze referencję do bazy danych, a następnie utworzy kursor (dodasz ten kod do pliku DrinkCategoryActivity.java w dalszej części rozdziału, kiedy przedstawimy kompletny kod tej aktywności): SQLiteOpenHelper starbuzzDatabaseHelper = new StarbuzzDatabaseHelper(this); try { SQLiteDatabase db = starbuzzDatabaseHelper.getReadableDatabase(); cursor = db.query(”DRINK”, new String[]{”_id”, ”NAME”}, null, null, null, null, null); // Kod korzystający z danych pobranych z bazy } catch(SQLiteException e) { Toast toast = Toast.makeText(this,

Jeśli baza danych jest niedostępna, zostanie zgłoszony wyjątek SQLiteException. W takim przypadku wyświetlamy komunikat Toast z odpowiednią wiadomością.

”Baza danych jest niedostępna”, Toast.LENGTH_SHORT); toast.show(); }

Teraz użyjemy danych z kursora, by określić zawartość widoku listy prezentowanego w aktywności DrinkCategoryActivity.

678

Rozdział 16.

Proste kursory

Jak zastąpić tablicę przekazywaną do komponentu ListView?

¨  Utworzenie kursora ¨  Utworzenie adaptera

Wcześniej, kiedy chcieliśmy, by widok listy używany w aktywności DrinkCategoryActivity wyświetlał dane z tablicy Drink.drinks, zastosowaliśmy adapter ArrayAdapter. Jak mogliśmy się przekonać w rozdziale 7., ArrayAdapter to typ adaptera, który operuje na tablicach. Pełni on funkcję pomostu łączącego dane zapisane w tablicy z widokiem listy. Utworzyliśmy adapter typu ArrayAdapter, by powiązać widok listy z tablicą.

To jest tablica.

Drink. drinks

To jest baza danych.

Dane z bazy danych

ListView

Adapter Array Adapter

Teraz, kiedy dane pobieramy z kursora, do skojarzenia danych z widokiem listy użyjemy adaptera CursorAdapter. Przypomina on nieco używany wcześniej kursor ArrayAdapter, z tym że zamiast pobierać dane z tablicy, odczytuje je z kursora. Kursor odczytuje dane z bazy.

Cursor

To jest widok listy.

Nasze dane są przechowywane w kursorze, dlatego możemy przekazać je do widoku ListView, używając adaptera klasy CursorAdapter.

Adapter Cursor Adapter

Widok ListView

Na następnej stronie przyjrzymy się temu rodzajowi kursora nieco dokładniej.

Kontrolki ListView oraz Spinner mogą pobierać dane z adapterów dowolnego typu. Oznacza to takie klasy jak: ArrayAdapter, CursorAdapter oraz SimpleCursorAdapter (klasę pochodną CursorAdapter). jesteś tutaj  679

Jak działa prosty adapter kursora

SimpleCursorAdapter odwzorowuje dane na widoki

¨  Utworzenie kursora ¨  Utworzenie adaptera

Spróbujemy zatem utworzyć prosty adapter kursora, typ adaptera kursora, który można stosować w większości przypadków, gdy musimy wyświetlać dane z kursora na listach. Adapter ten pobiera kolumny z kursora i odwzorowuje je na komponenty TextView i ImageView, takie jak te używane w widoku listy. Chcemy wyświetlić na liście w aktywności DrinkCategoryActivity nazwy wszystkich napojów. Zastosujemy zatem klasę SimpleCursorAdapter do utworzenia kursora, który odwzoruje nazwę napoju na widok tekstowy w widoku ListView:

1

Lista prosi adapter o dane.

Widok ListView

Adapter SimpleCursorAdapter

2 Adapter prosi kursor o przekazanie danych z bazy.

Cursor

Adapter SimpleCursorAdapter

Baza danych

3 Adapter zwraca dane do widoku listy.

Nazwa każdego napoju jest wyświetlana na liście jako odrębny widok tekstowy.

Nasza lista ListView wyświetla nazwę każdego napoju w odrębnym widoku TextView.

Latte Cappuccino Filter

Widok ListView

Zaimplementujmy zatem prosty adapter kursora.

680

Rozdział 16.

Adapter SimpleCursorAdapter

Proste kursory

Stosowanie adaptera SimpleCursorAdapter

¨  Utworzenie kursora ¨  Utworzenie adaptera

Prostego adaptera kursora używamy w taki sam sposób jak adaptera ArrayAdapter: najpierw należy go zainicjować, a następnie dołączyć do widoku listy. Mamy zamiar utworzyć prosty adapter kursora, który pozwoli nam wyświetlić listę nazw napojów z tabeli DRINK. W tym celu utworzymy nową instancję klasy SimpleCursorAdapter, przekazując przy tym do niej parametry, które pozwolą adapterowi określić, o jakie dane nam chodzi i jak chcemy je prezentować. Na koniec przypiszemy ten adapter do widoku listy. Poniżej przedstawiliśmy kod, który realizuje te czynności (w dalszej części rozdziału dodasz go do pliku DrinkCategoryActivity.java): CursorAdapter listAdapter = new SimpleCursorAdapter(this, android.R.layout.simple_list_item_1, To jest kursor.

cursor, new String[]{“NAME”}, new int[]{android.R.id.text1}, 0);

listDrinks.setAdapter(listAdapter);

rezentuje „this” rep

ktywność. bieżącą a

To jest ten sam układ, którego używaliśmy wcześniej wraz z adapterem tablicy. Wyświetla on w każdym wierszu listy wartość jednej kolumny. W widokach tekstowych umieszczonych na liście ListView wyświetlamy zawartość kolumny NAME.

Do przekazania adaptera do kontrolki ListView należy użyć metody setAdapter().

Oto ogólna postać konstruktora klasy SimpleCursorAdapter:

„this” reprezentuje bieżącą aktywność.

Ten układ określa, jak należy wyświetlać dane.

SimpleCursorAdapter adapter = new SimpleCursorAdapter(Context kontekst, int uklad, To jest utworzony kursor. Cursor kursor, Powinien on zawierać kolumnę _id i wyświetlane String[] zKolumn, dane. int[] doWidokow, int flagi)

Parametry kontekst i uklad są dokładnie tymi samymi, których używaliśmy w poprzedniej wersji aplikacji, korzystającej z kursora typu ArrayAdapter. Pierwszy z nich określa kontekst, a drugi definiuje, jak należy wyświetlać dane. Zamiast określać, z której tablicy mają pochodzić dane, tym razem używamy parametru kursor, aby określić kursor, który będzie je zawierał. Kolejny parametr, zKolumn, określa, których kolumn kursora chcemy używać, a parametr doWidokow określa widoki, w których te dane należy wyświetlać.

Ostatni parametr, flagi, przyjmuje zazwyczaj domyślną wartość 0. Alternatywnym rozwiązaniem jest zastosowanie wartości FLAG_REGISTER_ CONTENT_OBSERVER w celu zarejestrowania obserwatora zawartości, który będzie informowany o wszelkich zmianach zachodzących w danych. Tego rozwiązania nie będziemy jednak przedstawiać w tej książce, gdyż może ono prowadzić do występowania wycieków pamięci (w dalszej części rozdziału dowiesz się, jak sobie radzić ze zmianami danych prezentowanych na liście).

Które kolumny kursora należy powiązać z którymi widokami.

Ten parametr jest używany do określania zachowania kursora.

Dowolny kursor używany z adapterem kursora MUSI zawierać kolumnę _id (styl kod programu), gdyż w przeciwnym razie nie będzie działać jesteś tutaj  681

Zamykaj to, co otworzysz

Zamykanie kursora i bazy danych

¨  Utworzenie kursora ¨  Utworzenie adaptera

Przedstawiając kursory we wcześniejszej części tego rozdziału, wspominaliśmy, że po zakończeniu używania kursora należy zamknąć zarówno kursor, jak i bazę danych, aby zwolnić przydzielone im zasoby. W kodzie naszej aktywności DrinkActivity używaliśmy kursora do pobrania z bazy danych szczegółowych informacji o napoju, a kiedy już zapisaliśmy te informacje w widokach, natychmiast zwalnialiśmy kursor i bazę danych. W przypadku stosowania adaptera kursora (w tym także prostego adaptera kursora) sprawy wyglądają jednak nieco inaczej — adapter wymaga bowiem, by kursor był cały czas otworzony, na wypadek gdyby konieczne było pobranie dodatkowych danych. Przyjrzymy się dokładniej, jak działa adapter kursora, aby zrozumieć, dlaczego może się to zdarzyć.

1

Widok ListView zostaje wyświetlony na ekranie.

Podczas pierwszego wyświetlania listy zostanie ona dopasowana do wielkości ekranu. Załóżmy, że znajduje się na nim wystarczająco dużo miejsca, by wyświetlić pięć elementów. To są elementy, które zmieszczą się w obszarze ekranu zajmowanym przez kontrolkę ListView. Dla uproszczenia założymy, że będzie ich pięć, jednak w rzeczywistości najprawdopodobniej będzie ich więcej.

2

ListView

Kontrolka ListView prosi swój adapter o pięć pierwszych elementów.

Cześć Adapterze, czy możesz mi przekazać pięć pierwszych elementów danych?

ListView

Jasne. Skoczę i zaraz ci przyniosę.

CursorAdapter

3 Adapter kursora prosi swój kursor o odczytanie pięciu wierszy z bazy danych.

Niezależnie od tego, ile wierszy znajduje się w tabeli bazy danych, kursor musi odczytać tylko pięć pierwszych.

Adapter CursorAdapter

682

Rozdział 16.

Kursor

Baza danych

Proste kursory

Ciąg dalszy opowieści 4

¨  Utworzenie kursora ¨  Utworzenie adaptera

Użytkownik zaczyna przewijać listę.

Kiedy użytkownik przewija listę, adapter prosi kursor o odczytanie z bazy danych kolejnych wierszy. Nie będzie z tym żadnych problemów, jeśli kursor będzie cały czas otworzony. Jeśli jednak został on już zamknięty, to nie będzie w stanie pobrać z bazy danych żadnych dodatkowych wierszy.

Kiedy użytkownik zacznie przewijać listę, pojawi się na niej więcej elementów, a zatem będzie potrzebnych więcej danych.

Hej, kursorze, potrzebuję więcej… Kursorze? Stary, jesteś tam?

Hej, adapterze, potrzebuję więcej danych.

Kursor Widok ListView

Adapter CursorAdapter Jeśli zamkniemy kursor zbyt szybko, to adapter nie będzie mógł pobierać z niego dodatkowych danych.

To wszystko oznacza, że nie będziemy mogli zwolnić kursora i bazy danych bezpośrednio po wywołaniu metody setAdapter() w celu połączenia adaptera i widoku ListView. Zamiast tego powinniśmy to zrobić w metodzie onDestroy(), wywoływanej przed usunięciem 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ąć: public void onDestroy() { super.onDestroy(); cursor.close(); db.close(); }

Podczas usuwania aktywności zamykamy kursor i bazę danych.

To już wszystko, co musisz wiedzieć, by zaktualizować kod aktywności DrinkCategoryActivity; spróbuj zatem wykonać ćwiczenie zamieszczone na następnej stronie.

jesteś tutaj  683

Ćwiczenie

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

private SQLiteDatabase db; app/src/main

private Cursor cursor;

java

@Override protected void onCreate(Bundle savedInstanceState) {

com.hfad.coffeina

setContentView(R.layout.activity_drink_category); ListView listDrinks = (ListView) findViewById(R.id.list_drinks); ................ coffeinaDatabaseHelper = new CoffeinaDatabaseHelper(this);

DrinkCategory Activity.java

try { db = coffeinaDatabaseHelper.

....................;

cursor = db.query(”DRINK”, new String[]{ }, null, null, null, null, null);

Dalsza część kodu znajduje się na następnej stronie.

Uwaga: Każdego fragmentu z basenu można użyć tylko jeden raz! getWritableDatabase()

cursor

,

"DESCRIPTION"

684

Rozdział 16.

db

SimpleCursorAdapter "NAME" "_id"

"NAME"

cursor

SQLiteOpenHelper

getReadableDatabase() SQLiteException

,

DatabaseException

Proste kursory

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(); } // Tworzymy obiekt nasłuchujący zdarzeń kliknięć elementów listy AdapterView.OnItemClickListener itemClickListener = new AdapterView.OnItemClickListener(){ public void onItemClick(AdapterView listDrinks, View itemView, int position, long id) { // Przekazujemy kliknięty napój do DrinkActivity Intent intent = new Intent(DrinkCategoryActivity.this, DrinkActivity.class); intent.putExtra(DrinkActivity.EXTRA_DRINKID, (int) id); startActivity(intent); } }; // Przypisujemy obiekt nasłuchujący do listy listDrinks.setOnItemClickListener(itemClickListener); }

Coffeina app/src/main

@Override public void onDestroy(){ super.onDestroy();

java com.hfad.coffeina

.......... .close(); .......... .close(); }

DrinkCategory Activity.java

}

jesteś tutaj  685

Rozwiązanie

Zagadkowy basen. Rozwiązanie 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

private SQLiteDatabase db; app/src/main

private Cursor cursor;

java

@Override protected void onCreate(Bundle savedInstanceState) {

com.hfad.coffeina

setContentView(R.layout.activity_drink_category); ListView listDrinks = (ListView) findViewById(R.id.list_drinks);

DrinkCategory Dzięki użyciu obiektu SQLiteOpenHelper możesz pobrać referencję do bazy danych. Activity.java

SQLiteOpenHelper ..................... coffeinaDatabaseHelper = new CoffeinaDatabaseHelper(this); try { db = coffeinaDatabaseHelper.

getReadableDatabase() ........................;

cursor = db.query(”DRINK”, "_id" "NAME" new String[]{..................... }, null, null, null, null, null); Te fragmenty kodu nie były potrzebne.

Odczytujemy dane z bazy, a więc wystarczy, że otworzymy ją w tryb ie tylko do odczytu.

Kursor musi zawierać kolumnę _id, gdyż w przeciwnym razie adapter nie będzie działał. Musi także zawierać kolumnę NAME, dzięki której będziemy mogli wyświetlić na liście nazwy napojów.

getWritableDatabase() , "DESCRIPTION"

686

Rozdział 16.

DatabaseException

Proste kursory Używamy adaptera typu SimpleCursorAdapter.

SimpleCursorAdapter CursorAdapter listAdapter = new ......................... (this, android.R.layout.simple_list_item_1, , cursor Wyświetlamy zawartość ............. , kolumny NAME.

j kursora. Tu używamy utworzonego wcześnie

"NAME" new String[]{ ............... }, new int[]{android.R.id.text1}, 0);

listDrinks.setAdapter(listAdapter); SQLiteException e) { } catch(..................

Jeśli baza danych nie jest dostępna, to przechwycimy wyjątek SQLiteException.

Toast toast = Toast.makeText(this, ”Baza danych jest niedostępna”, Toast.LENGTH_SHORT); toast.show(); } // Tworzymy obiekt nasłuchujący zdarzeń kliknięć elementów listy AdapterView.OnItemClickListener itemClickListener = new AdapterView.OnItemClickListener(){

Coffeina

public void onItemClick(AdapterView listDrinks, View itemView,

app/src/main

int position,

java

long id) { // Przekazujemy kliknięty napój do DrinkActivity

com.hfad.coffeina

Intent intent = new Intent(DrinkCategoryActivity.this, DrinkActivity.class); intent.putExtra(DrinkActivity.EXTRA_DRINKID, (int) id);

DrinkCategory Activity.java

startActivity(intent); } }; // Przypisujemy obiekt nasłuchujący do listy listDrinks.setOnItemClickListener(itemClickListener); } @Override public void onDestroy(){ super.onDestroy(); cursor .close(); .......... db .......... .close(); }

Przed zamknięciem połączenia z bazą danych zamykamy także kursor.

}

jesteś tutaj  687

Kod aktywności DrinkCategoryActivity

Zmodyfikowany kod aktywności DrinkCategoryActivity

¨  Utworzenie kursora ¨  Utworzenie adaptera

Poniżej przedstawiliśmy kompletny, zmodyfikowany kod aktywności DrinkCategoryActivity (DrinkCategoryActivity.java), w którym adapter ArrayAdapter zastąpiliśmy adapterem SimpleCurosorAdapter (wszystkie zmiany w kodzie zostały wyróżnione pogrubioną czcionką); zmodyfikuj swoją wersję pliku, by była identyczna z naszą: package com.hfad.coffeina; import import import import import import import import import import import import

android.app.Activity; android.os.Bundle; android.widget.ListView; android.view.View; android.content.Intent; android.widget.AdapterView; android.database.Cursor; android.database.sqlite.SQLiteDatabase; android.database.sqlite.SQLiteException; android.database.sqlite.SQLiteOpenHelper; android.widget.SimpleCursorAdapter; android.widget.Toast;

Coffeina app/src/main java com.hfad.coffeina

DrinkCategory Activity.java

Używamy tych wszystkich dodatkowych klas, więc musimy je zaimportować.

public class DrinkCategoryActivity extends Activity { private SQLiteDatabase db; private Cursor cursor; jako zmienne prywatne, Bazę danych i kursor dodajemy i kursor będziemy dzięki czemu zarówno bazę, jak onDestroy(). dzie meto w knąć potem mogli zam

@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_drink_category); ArrayAdapter listAdapter = new ArrayAdapter( this, Nie używamy android.R.layout.simple_list_item_1, już adaptera ArrayAdapter, Drink.drinks); więc możemy ListView listDrinks = (ListView) findViewById(R.id.list_drinks); usunąć te wiersze kodu. SQLiteOpenHelper coffeinaDatabaseHelper = new CoffeinaDatabaseHelper(this); try { db = coffeinaDatabaseHelper.getReadableDatabase(); cursor = db.query(“DRINK”, new String[]{“_id”, “NAME”}, Tworzymy kursor. null, null, null, null, null);

688

Rozdział 16.

Pobieramy referencję do bazy danych. Dalsza część kodu znajduje się na następnej stronie.

Proste kursory

Kod aktywności DrinkCategoryActivity (ciąg dalszy)

¨  Utworzenie kursora ¨  Utworzenie adaptera

Tworzymy adapter SimpleCursorAdapter.

SimpleCursorAdapter listAdapter = new SimpleCursorAdapter(this, android.R.layout.simple_list_item_1, cursor, new String[]{”NAME”}, kolumny ść Odwzorowujemy zawarto NAME na widok tekstowy wyświetlany new int[]{android.R.id.text1}, w komponencie ListView. 0); listDrinks.setAdapter(listAdapter); Ustawiamy adapter w widoku ListView. } catch(SQLiteException e) { Toast toast = Toast.makeText(this, “Baza danych jest niedostępna”, Toast.LENGTH_SHORT); toast.show(); Jeśli został zgłoszony wyjątek SQLiteException, to wyświetlamy } komunikat dla użytkownika.

} // Tworzymy obiekt nasłuchujący zdarzeń kliknięć elementów listy AdapterView.OnItemClickListener itemClickListener = new AdapterView.OnItemClickListener(){ Coffeina public void onItemClick(AdapterView listDrinks, View itemView, app/src/main int position, long id) { Nie musieliśmy java // Przekazujemy kliknięty napój do DrinkActivity zmieniać kodu obiektu Intent intent = new Intent(DrinkCategoryActivity.this, nasłuchującego com.hfad.coffeina DrinkActivity.class); zdarzeń. intent.putExtra(DrinkActivity.EXTRA_DRINKID, (int) id); startActivity(intent); DrinkCategory Activity.java } }; // Przypisujemy obiekt nasłuchujący do listy listDrinks.setOnItemClickListener(itemClickListener); @Override public void onDestroy(){ super.onDestroy(); cursor.close(); db.close(); }

W metodzie onDestroy() aktywności zamykamy kursor i bazę danych. Kursor pozostanie otwarty aż do momentu, gdy adapter nie będzie go już potrzebował.

A teraz spróbujmy uruchomić aplikację.

jesteś tutaj  689

Jazda próbna

Jazda próba aplikacji

¨  Utworzenie kursora ¨  Utworzenie adaptera

Kiedy uruchomisz aplikację, zostanie wyświetlona aktywność TopLevelActivity.

Po kliknięciu opcji Napoje zostaje uruchomiona aktywność DrinkCategoryActivity. Wyświetla ona listę wszystkich napojów z bazy danych aplikacji. Kliknęliśmy opcję Latte…

Po kliknięciu jednego z napojów zostaje uruchomiona aktywność DrinkActivity, która wyświetla szczegółowe informacje o wybranym napoju. …a to są szczegółowe informacje o tym napoju.

Aplikacja wygląda dokładnie tak samo jak wcześniej, jednak tym razem dane są już pobierane z bazy danych. W zasadzie możesz usunąć z projektu plik Drink.java, gdyż nie potrzebujemy już tablicy z napojami — wszystkie informacje potrzebne aplikacji są pobierane z bazy danych.

690

Rozdział 16.

Proste kursory

Opanowałeś już rozdział 16. i dodałeś do swojego przybornika z narzędziami umiejętność podłączania aplikacji do baz danych SQLite.

j Pełny kod przykładowe j ane tow aplikacji prezen z żes mo ale w tym rozdzi P FT ra we ser z pobrać wydawnictwa Helion: ftp://ftp.helion.pl/ przyklady/andrr2.zip

CELNE SPOSTRZEŻENIA 







Kursory — obiekty klasy Cursor — umożliwiają odczytywanie i zapisywanie informacji w bazach danych. Kursory tworzy się, wywołując metodę query klasy SQLiteDatabase. W niewidoczny sposób kursory tworzą polecenie SQL SELECT. Metoda getWritableDatabase() zwraca obiekt SQLiteDatabase pozwalający odczytywać i zapisywać informacje w bazie danych.







Po zawartości kursora można się poruszać, używając metod moveTo*(). Do odczytywania wartości z kursora służą metody get*(). Po zakończeniu korzystania z kursora i połączenia z bazą danych należy je zamknąć. Do korzystania z kursorów służy adapter klasy CursorAdapter. Do wypełnienia widoku ListView wartościami z kursora można używać adaptera SimpleCursorAdapter.

Metoda getReadableDatabase() zwraca obiekt SQLiteDatabase. Obiekt ten zapewnia dostęp do bazy danych w trybie tylko do odczytu. Może on zapewniać także możliwość zapisu danych w bazie, ale nie ma takiej gwarancji.

jesteś tutaj  691

Rozdział 16.

Twój przybornik do Androida

692

Rozdział 16.

17. Kursory i zadania asynchroniczne

Pozostając w tle Moja metoda doInBackground() jest rewelacyjna. Wyobrażasz sobie, jak wolny byłby Pan Zdarzenie Główne, gdybym zostawiła to wszystko na jego głowie?

Przeważająca większość aplikacji musi aktualizować swoje dane. W poprzednim rozdziale dowiedziałeś się, jak pisać aplikacje, które odczytują dane z bazy danych SQLite. A co w przypadku, kiedy chcemy zaktualizować dane aplikacji? W tym rozdziale dowiesz się, jak sprawić, by aplikacja reagowała na poczynania użytkownika i aktualizowała informacje zapisane w bazie danych. Dowiesz się także, jak odświeżać wyświetlone dane po ich aktualizacji. I w końcu przekonasz się też, że pisanie wydajnego, wielowątkowego kodu przy użyciu klasy AsyncTask pozwala zapewnić odpowiednią szybkość działania aplikacji.

to jest nowy rozdział  693

Aktualizacja danych

Chcemy, by nasza aplikacja aktualizowała dane w bazie W rozdziale 16. dowiedziałeś się jak zmodyfikować aplikację, by odczytywała ona informacje z bazy danych. Zobaczyłeś jak odczytywać konkretny rekord (napój z tabeli bazy danych) i wyświetlać zapisane w nim dane w aktywności. Dowiedziałeś się także jak można wyświetlać dane z bazy w widoku listy (w naszym przypadku były to nazwy napojów) używając do tego celu adaptera kursora. Jednak w obu tych przypadkach, musieliśmy jedynie odczytywać dane z bazy. A co, gdybyśmy chcieli zapewnić użytkownikom możliwość modyfikacji informacji zapisanych w bazie danych? W tym rozdziale zmienimy aplikację dla kafeterii Coffeina w taki sposób, by użytkownicy mogli zapisywać, które z napojów są ich ulubionymi. W tym celu dodamy do aktywności DrinkActivity pole wyboru — kiedy użytkownik je zaznaczy, będzie to oznaczać, że dany napój jest jego ulubionym. Użytkownicy mogą wybierać swoje ulubione napoje zaznaczając pole wyboru. Musimy dodać je do aktywności DrinkActivity i zadbać o to, by jego kliknięcie odpowiednio modyfikowało bazę danych.

Oprócz tego, do aktywności TopLevelActivity dodamy nowy widok listy, który będzie przedstawiał ulubione napoje.

W rzeczywistym świecie do wyświetlenia i poruszania się po tych kategoriach zastosowalibyśmy zapewne karty. Celowo jednak staramy się zachować prostotę aplikacji, gdyż chcemy skoncentrować się wyłącznie na bazach danych.

Do aktywności TopLevelActivity dodamy widok ListView prezentujący ulubione napoje użytkownika.

694

Rozdział 17.

Kiedy użytkownik kliknie któryś z ulubionych napojów, przejdzie bezpośrednio do szczegółowych informacji na jego temat, wyświetlanych w aktywności DrinkActivity.

Kursory i zadania asynchroniczne

W pierwszej kolejności zajmiemy się klasą DrinkActivity W rozdziale 15. dodaliśmy do tabeli DRINK bazy danych kafeterii Coffeina kolumnę FAVORITE. Użyjemy tej kolumny, by zapewnić użytkownikom możliwość zaznaczania napojów jako ulubionych. Wartość tej kolumny będziemy wyświetlać w nowym polu wyboru, które dodamy do aktywności DrinkActivity, a kiedy użytkownik kliknie to pole, zaktualizujemy kolumnę FAVORITE, zapisując w niej nową wartość pola. Oto czynności, które wykonamy w ramach modyfikacji klasy DrinkActivity:

1

Zaktualizujemy układ aktywności DrinkActivity, dodając do niego pole wyboru oraz etykietę tekstową.

To pole wyboru oraz etykietę dodamy do układu activity_drink.xml.

2

3

Wyświetlimy w polu wyboru wartość kolumny FAVORITE.

W tym celu będziemy musieli odczytać wartość tej kolumny z bazy danych kafeterii Coffeina. Zaktualizujemy wartość kolumny FAVORITE, kiedy użytkownik kliknie pole wyboru.

Zaktualizujemy kolumnę FAVORITE, zapisując w niej wartość pola wyboru, tak by informacje zapisane w bazie danych były aktualne.

Zrób to sam!

A zatem zaczynajmy.

W tym rozdziale będziemy modyfikować aplikację dla kafeterii Coffeina, a zatem otwórz w Android Studio projekt Coffeina.

jesteś tutaj  695

Dodawanie ulubionych napojów

Dodanie pola wyboru do układu aktywności DrinkActivity

¨  Aktualizacja układu ¨  Wypełnienie widoku listy ¨  Odświeżenie danych

Zaczniemy od dodania do układu aktywności DrinkActivity nowego pola wyboru, które posłuży nam do wizualnego informowania użytkownika, czy dany napój został oznaczony jako ulubiony, czy nie. Używamy do tego pola wyboru, gdyż zapewnia ono możliwość Coffeina prostego pokazywania wartości logicznych — prawda i fałsz. Zacznijmy od dodania do pliku strings.xml nowego zasobu o nazwie favorite (użyjemy go do określenia etykiety pola wyboru):

app/src/main res

Ulubiony

Następnie dodamy samo pole wyboru do układu activity_drink.xml. Nadamy mu identyfikator favorite byśmy mogli odwoływać się do niego w kodzie aktywności i skorzystamy z atrybutu android:text, by wyświetlić jego etykietę. Oprócz tego atrybutowi android:onClick przypiszemy wartość ”onFavoriteClicked”, żeby kliknięcie pola powodowało wywołanie metody onFavoriteClicked() zdefiniowanej w aktywności DrinkActivity. Poniżej przedstawiliśmy kod układu; zaktualizuj kod w swoim projekcie, tak by był taki sam jak nasz (zmiany zostały wyróżnione pogrubioną czcionką):

jące

prezentu zdjęcie, nazwę oraz opis

pierwszą wersję aplikacji.

values

strings.xml

Coffeina app/src/main res layout

activity_drink.xml

rite.

Pole wyboru ma identyfikator favo

Po kliknięciu pola wyboru

zostanie wywołana metoda

Teraz zajmiemy się zmodyfikowaniem kodu aktywności DrinkActivity, tak by stan pola wyboru odpowiadał wartości kolumny FAVORITE bazy danych.

696

Rozdział 17.

onFavoriteClicked() aktywności DrinkActivity. Musimy zatem napisać tę metodę.

Kursory i zadania asynchroniczne

Wyświetlanie wartości kolumny FAVORITE Aby zaktualizować pole wyboru, w pierwszej kolejności musimy pobrać wartość kolumny FAVORITE z bazy danych. W tym celu możemy zmienić kursor tworzony w metodzie onCreate() aktywności DrinkActivity po to by pobrać z bazy danych informacje o napojach.

¨  Aktualizacja układu ¨  Wypełnienie widoku listy ¨  Odświeżenie danych Coffeina app/src/main

Poniżej przedstawiliśmy kursor w postaci, w jakiej używamy go do pobierania z bazy informacji o napoju wybranym przez użytkownika:

java

Cursor cursor = db.query(”DRINK”, new String[]{”NAME”, ”DESCRIPTION”, ”IMAGE_RESOURCE_ID”}, ”_id = ?”,

com.hfad.coffeina

DrinkActivity.java

new String[]{Integer.toString(drinkId)}, null, null, null);

Aby dodać kolumnę FAVORITE do zwracanych danych, wystarczy dodać ją do tablicy nazw kolumn zwracanych przez kursor:

Dodajemy kolumnę FAVORITE do kursora.

Cursor cursor = db.query (”DRINK”, new String[] {”NAME”,”DESCRIPTION”,”IMAGE_RESOURCE_ID”,”FAVORITE”}, ”_id = ?”, new String[] {Integer.toString(drinkId)}, null, null,null);

Dysponując wartością kolumny FAVORITE, możemy jej użyć do aktualizacji pola wyboru favorite. Aby pobrać tę wartość, musimy najpierw przejść do pierwszego (i jedynego) wiersza kursora, używając następującego wywołania:

Nie musisz już teraz zmieniać kodów w swoim projekcie. Już niebawem pokażemy Ci pełny zestaw zmian, które trzeba będzie wprowadzić w kodzie pliku DrinkActivity.java.

cursor.moveToFirst();

Teraz możemy już pobrać wartość kolumny obecnie wybranego napoju. Kolumna FAVORITE zawiera wartości liczbowe, przy czym 0 oznacza logiczny fałsz (false), a 1 — logiczną prawdę (true). Chcemy, by pole wyboru favorite było zaznaczone, jeśli kolumna będzie mieć wartość 1, oraz by nie było zaznaczone, jeśli w kolumnie będzie 0. Dlatego do aktualizacji pola wyboru użyjemy następującego fragmentu kodu: boolean isFavorite = (cursor.getInt(3) == 1); CheckBox favorite = (CheckBox)findViewById(R.id.favorite); favorite.setChecked(isFavorite);

I to już cały kod niezbędny do aktualizowania stanu pola wyboru favorite na podstawie wartości kolumny FAVORITE bazy danych. W następnej kolejności zajmiemy się zapewnieniem tego, by pole wyboru reagowało na kliknięcia, zapisując w bazie danych wartość odpowiadającą swojemu nowemu stanowi.

Pobieramy wartość kolumny FAVORITE. W bazie danych wartości logicznej true odpowiada 1, a wartości false — 0. Ustawiamy wartość pola wyboru favorite.

jesteś tutaj  697

Reagowanie na kliknięcia

Odpowiadanie na kliknięcia w celu aktualizacji bazy danych

¨  Aktualizacja układu ¨  Wypełnienie widoku listy ¨  Odświeżenie danych

Dodając pole wyboru do układu activity_drink.xml, w atrybucie android:onClick elementu podaliśmy wartość ”onFavoriteClicked”. Oznacza ona, że za każdym razem, gdy użytkownik kliknie pole wyboru, zostanie wywołana metoda onFavoriteClicked() aktywności DrinkActivity. Musimy zaimplementować tę metodę i zadbać, by aktualizowała ona zawartość bazy danych, zapisując w niej bieżącą wartość pola wyboru. Każde zaznaczenie lub usunięcie zaznaczenia z pola wyboru będzie zatem powodowało wywołanie metody onFavoriteClicked() i zapisanie w bazie danych zmiany wprowadzonej przez użytkownika. W rozdziale 15. poznałeś metody klasy SQLiteDatabase, których można używać do modyfikacji danych przechowywanych w bazie danych SQLite: metodę insert(), pozwalającą wstawiać dane, metodę delete(), która służy do usuwania rekordów, oraz metodę update(), która umożliwia ich aktualizację. 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; użyjemy do tego update(). W ramach przypomnienia przedstawiamy poniżej ogólną postać wywołania metody update(): database.update(String tabela,

chcemy aktualizować. To jest tabela, której zawartość

ContentValues wartosci,

To są nowe wartości.

String klauzulaWhere, String[] argumentyWhere);

Kryteria aktualizacji tabeli.

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. Teraz już wiesz wszystko, co trzeba, by w kodzie aktywności DrinkActivity zapisać bieżącą wartość pola wyboru w kolumnie FAVORITE tabeli. update()

DrinkActivity

698

Rozdział 17.

Baza danych kafeterii Coffeina

Kursory i zadania asynchroniczne

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? public class DrinkActivity extends Activity { ... // Aktualizujemy bazę danych po kliknięciu pola wyboru public void onFavoriteClicked( .................. ){ int drinkId = (Integer)getIntent().getExtras().get(EXTRA_DRINKID); CheckBox favorite = (CheckBox)findViewById(R.id.favorite); .............. drinkValues = new .....................; drinkValues.put( ............... , favorite.isChecked()); SQLiteOpenHelper coffeinaDatabaseHelper = new CoffeinaDatabaseHelper(DrinkActivity.this); try { SQLiteDatabase db = coffeinaDatabaseHelper. .......................; db.update( ............., .................., .............. , new String[] {Integer.toString(drinkId)}); db.close(); } catch(SQLiteException e) { Toast toast = Toast.makeText(this, ”Baza danych jest niedostępna”,Toast.LENGTH_SHORT); toast.show(); } }

drinkValues

}

Nie będziesz potrzebował wszystkich magnesików.

"FAVORITE" "DRINK"

getWritableDatabase()

View view

"_id = ?"

ContentValues

ContentValues() getReadableDatabase()

favorite

jesteś tutaj  699

Rozwiązanie magnesików

Magnesiki z kodem. Rozwiązanie 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? public class DrinkActivity extends Activity { ... // Aktualizujemy bazę danych po kliknięciu pola wyboru

View view public void onFavoriteClicked( .................. ){ int drinkId = (Integer)getIntent().getExtras().get(EXTRA_DRINKID); CheckBox favorite = (CheckBox)findViewById(R.id.favorite);

ContentValues() ; ContentValues drinkValues = new ..................... .............. drinkValues.put( ............... , favorite.isChecked()); "FAVORITE" SQLiteOpenHelper coffeinaDatabaseHelper = new CoffeinaDatabaseHelper(DrinkActivity.this); try { getWritableDatabase() SQLiteDatabase db = coffeinaDatabaseHelper. ...........................; Aby aktualizować bazę danych, musimy mieć do niej dostęp w trybie do odczytu i zapisu.

"DRINK" drinkValues db.update( ............. , .................. ,

.............. , new String[] {Integer.toString(drinkId)}); "_id = ?" 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()

700

Rozdział 17.

favorite

Kursory i zadania asynchroniczne

Kompletny kod aktywności DrinkActivity

¨  Aktualizacja układu ¨  Wypełnienie widoku listy ¨  Odświeżenie danych

Zrobiliśmy już wszystko co konieczne, by zmienić aktywność DrinkActivity tak, by zawartość kolumny FAVORITE była odzwierciedlana przez pole wyboru favorite. Później, po zaznaczeniu pola przez użytkownika, zmienia ono wartość kolumny w bazie danych. Oto aktualna zawartość pliku DrinkActivity.java (zmiany zostały wyróżnione pogrubioną czcionką):

package com.hfad.coffeina; Coffeina

import android.app.Activity; app/src/main

import android.os.Bundle; import android.widget.ImageView;

java

import android.widget.TextView; import android.widget.Toast;

com.hfad.coffeina

import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException;

DrinkActivity.java

import android.database.sqlite.SQLiteOpenHelper; import android.view.View; import android.widget.CheckBox;

. Używamy tych dodatkowych klas

import android.content.ContentValues; public class DrinkActivity extends Activity { public static final String EXTRA_DRINKID = ”drinkId”; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_drink); // Pobieramy identyfikator napoju z intencji int drinkId = (Integer)getIntent().getExtras().get(EXTRA_DRINKID); Dalsza część kodu znajduje się na następnej stronie.

jesteś tutaj  701

Kod aktywności DrinkActivity

Kod aktywności DrinkActivity (ciąg dalszy)

¨  Aktualizacja układu ¨  Wypełnienie widoku listy ¨  Odświeżenie danych

// Tworzymy kursor try { SQLiteOpenHelper coffeinaDatabaseHelper = new CoffeinaDatabaseHelper(this); SQLiteDatabase db = coffeinaDatabaseHelper.getReadableDatabase(); Cursor cursor = db.query (”DRINK”, new String[] {”NAME”, ”DESCRIPTION”, ”IMAGE_RESOURCE_ID”, “FAVORITE”}, ”_id = ?”, Do kursora dodajemy kolumnę FAVORITE.

new String[] {Integer.toString(drinkId)}, null, null,null); // Przechodzimy do pierwszego rekordu w kursorze if (cursor.moveToFirst()) {

Coffeina

// Pobieramy szczegółowe informacje o napoju z kursora String nameText = cursor.getString(0);

app/src/main

String descriptionText = cursor.getString(1); java

int photoId = cursor.getInt(2); boolean isFavorite = (cursor.getInt(3) == 1); // Wyświetlamy nazwę napoju

Wartość 1 w kolumnie FAVORITE oznacza logiczną wartość true.

TextView name = (TextView)findViewById(R.id.name);

com.hfad.coffeina

DrinkActivity.java

name.setText(nameText); // Wyświetlamy opis napoju TextView description = (TextView)findViewById(R.id.description); description.setText(descriptionText); // Wyświetlamy zdjęcie napoju ImageView photo = (ImageView)findViewById(R.id.photo); photo.setImageResource(photoId); photo.setContentDescription(nameText); // Zaznaczamy pole wyboru ulubionego napoju

to Jeśli napój należy do ulubionych, oru umieszczamy znacznik w polu wyb favorite.

CheckBox favorite = (CheckBox)findViewById(R.id.favorite); favorite.setChecked(isFavorite); };

702

Rozdział 17.

Dalsza część kodu znajduje się na następnej stronie.

Kursory i zadania asynchroniczne

Kod aktywności DrinkActivity (ciąg dalszy)

¨  Aktualizacja układu ¨  Wypełnienie widoku listy ¨  Odświeżenie danych

cursor.close(); db.close(); } catch(SQLiteException e) {

Toast toast = Toast.makeText(this, ”Baza danych jest niedostępna”, Toast.LENGTH_SHORT); toast.show(); }

Coffeina

}

app/src/main

// Aktualizujemy bazę danych po kliknięciu pola wyboru public void onFavoriteClicked(View view){ int drinkId = (Integer)getIntent().getExtras().get(EXTRA_DRINKID);

java com.hfad.coffeina

// Pobieramy wartość pola wyboru CheckBox favorite = (CheckBox)findViewById(R.id.favorite); ContentValues drinkValues = new ContentValues(); drinkValues.put(“FAVORITE”, favorite.isChecked());

DrinkActivity.java

Dodajemy wartość pola wyboru favorite do obiektu drinkValues typu ContentValues.

// Pobieramy referencję do bazy danych i aktualizujemy kolumnę FAVORITE SQLiteOpenHelper coffeinaDatabaseHelper = new CoffeinaDatabaseHelper(DrinkActivity.this); try { SQLiteDatabase db = coffeinaDatabaseHelper.getWritableDatabase(); db.update( “DRINK”,

Aktualizujemy kolumnę FAVORITE, zapisując w niej wartość pola wyboru.

drinkValues, “_id = ?”, new String[] {Integer.toString(drinkId)}); db.close(); } 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.

}

A teraz sprawdźmy, co się stanie po uruchomieniu aplikacji.

jesteś tutaj  703

Jazda próbna

Jazda próbna aplikacji Kiedy uruchomimy aplikację i przejdziemy do napoju, poniżej jego opisu zostanie wyświetlone nowe (początkowo niezaznaczone) pole wyboru, Ulubiony:

Oto nowe pole wyboru, które dodaliśmy, wraz z etykietą.

Po kliknięciu pola wyboru pojawia się w nim znacznik informujący, że napój zostały oznaczony jako ulubiony:

Kiedy klikniemy pole wyboru, pojawi się w nim znacznik, a wartość zostanie zapisana w bazie danych.

Kiedy zamkniemy aplikację, a następnie ponownie ją uruchomimy i przejdziemy do wybranego wcześniej napoju, pole wyboru będzie zaznaczone. Wartość pola została bowiem zapisana w bazie danych. I to już wszystko, co było konieczne do wyświetlania wartości kolumny FAVORITE z bazy danych oraz aktualizacji bazy w przypadku wprowadzenia jakichkolwiek zmian.

704

Rozdział 17.

¨  Aktualizacja układu ¨  Wypełnienie widoku listy ¨  Odświeżenie danych

Kursory i zadania asynchroniczne

Wyświetlanie ulubionych napojów w aktywności TopLevelActivity Ostatnią modyfikacją, którą chcemy wprowadzić, jest wyświetlenie ulubionych napojów w aktywności TopLevelActivity. Oto czynności, które będziemy musieli w tym celu wykonać:

1

Do układu aktywności TopLevelActivity dodać nowy widok listy i widok tekstowy.

2

Wyświetlić dane w widoku listy i zadbać o to, by reagował on na kliknięcia.

Utworzymy nowy kursor, który będzie pobierał z bazy danych ulubione napoje użytkownika, a później dodamy je do widoku listy, używając adaptera kursora. Następnie utworzymy obiekt nasłuchujący onItemClickListener, dzięki któremu, kiedy użytkownik kliknie jeden z napojów, aktywność TopLevelActivity będzie mogła uruchomić aktywność DrinkActivity.

3 Odświeżyć dane widoku listy, kiedy wybierzemy nowy ulubiony napój.

Jeśli w aktywności DrinkActivity wybierzemy nowy ulubiony napój, to chcemy, by po ponownym przejściu do aktywności TopLevelActivity został on wyświetlony na liście ulubionych.

Wprowadzenie tych wszystkich zmian pozwoli wyświetlić w aktywności TopLevelActivity listę ulubionych napojów użytkownika.

Dane do listy ulubionych napojów będą pobierane z bazy danych przy użyciu kursora.

Kursor Baza danych kafeterii Coffeina

Po kliknięciu napoju na liście ulubionych zostanie uruchomiona aktywność DrinkActivity, a w niej zostaną wyświetlone szczegółowe informacje o wybranym napoju.

Wszystkie te kroki szczegółowo opiszemy na kilku następnych stronach.

jesteś tutaj  705

Wyświetlanie ulubionych napojów

Wyświetlenie listy ulubionych napojów w kodzie układu activity_top_level.xml Jak już zaznaczyliśmy na poprzedniej stronie, planujemy dodać do układu activity_top_level.xml widok ListView, którego użyjemy do wyświetlenia listy ulubionych napojów użytkownika. Oprócz tego do układu dodamy także widok tekstowy, który będzie zawierał nagłówek tej listy. Zacznijmy od dodania do pliku strings.xml poniższego zasobu łańcuchowego (użyjemy go do określenia tekstu wyświetlanego w widoku tekstowym):

Coffeina app/src/main res

Twoje ulubione napoje

values

Teraz musimy zmodyfikować kod pliku activity_top_level.xml i dodać do niego widok tekstowy i widok listy. Poniżej przedstawiliśmy kod pliku activity_top_level.xml; zaktualizuj ten plik w swoim projekcie, tak by był identyczny z naszym:

Układ zawiera już logo kafeterii



strings.xml

Coffeina app/src/main

Dodamy do niego widok tekstowy je”. z napisem „Twoje ulubione napo b Ten tekst zdefiniujemy jako zasó rites. łańcuchowy o identyfikatorze favo



Drugi widok ListView, o identyfikatorze list_favorites, będzie wyświetlał listę ulubionych napojów użytkownika.

To już wszystkie zmiany, jakie musimy wprowadzić w kodzie układu activity_top_level.xml. Dalsze zmiany trzeba będzie wprowadzić w kodzie aktywności, zapisanym w pliku TopLevelActivity.java.

706

Rozdział 17.

res layout

activity_ top_level.xml

Kursory i zadania asynchroniczne

Refaktoryzacja pliku TopLevelActivity.java

¨  Aktualizacja układu ¨  Wypełnienie widoku listy

Zanim napiszemy jakikolwiek kod związany z naszym nowym widokiem listy, musimy wprowadzić pewne modyfikacje w kodzie aktywności TopLevelActivity. Dzięki niemu nasz kod stanie się znacznie łatwiejszy do czytania i analizy. Cały kod związany z widokiem listy prezentującym opcje przeniesiemy do nowej metody, setupOptionsListView(). Tę nową metodę będziemy wywoływać w metodzie onCreate().

¨  Odświeżenie danych

Oto nasza nowa wersja kodu pliku TopLevelActivity.java (zmodyfikuj ten plik w swoim projekcie, by był identyczny z naszym): package com.hfad.coffeina; ... public class TopLevelActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_top_level); setupOptionsListView(); Wywołujemy nową metodę, setupOptionsListView(). }

Coffeina app/src/main java com.hfad.coffeina TopLevel Activity.java

private void setupOptionsListView() { // Implementacja obiektu OnItemClickListener AdapterView.OnItemClickListener itemClickListener = new AdapterView.OnItemClickListener(){ Cały ten fragment public void onItemClick(AdapterView listView, kodu View itemView, znajdował się wcześniej int position, w metodzie long id) { onCreate(). Przenosimy if (position == 0) { go do nowej Intent intent = new Intent(TopLevelActivity.this, metody, aby uporządkować DrinkCategoryActivity.class); kod. startActivity(intent); Jeśli w widoku listy } list_options została kliknięta opcja Napoje, to } uruchamiamy aktywność }; DrinkCategoryActivity. // Dodajemy obiekt nasłuchujący do widoku ListView ListView listView = (ListView) findViewById(R.id.list_options); listView.setOnItemClickListener(itemClickListener); } }

jesteś tutaj  707

Modyfikacja aktywności TopLevelActivity

Jakie zmiany trzeba wprowadzić w kodzie aktywności TopLevelActivity?

¨  Aktualizacja układu ¨  Wypełnienie widoku listy ¨  Odświeżenie danych

W widoku listy list_favorites dodanym do układu aktywności musimy wyświetlić ulubione napoje użytkownika i zadbać o to, by lista ta reagowała na kliknięcia. W tym celu musimy wykonać następujące czynności:

1

Wypełnić widok listy list_favorites, używając kursora.

Kursor będzie zawierał wszystkie napoje, w których kolumna FAVORITE będzie miała wartość 1 — czyli wszystkie napoje, które użytkownik oznaczył jako ulubione. Podobnie jak zrobiliśmy to w kodzie aktywności DrinkCategoryActivity, także tu możemy połączyć widok ListView z kursorem, używając adaptera typu CursorAdapter.

Latte Cappuccino Widok ListView

Filter

2

Adapter CursorAdapter

Kursor Baza danych

Zaimplementować obiekt nasłuchujący OnItemClickListener, tak by widok listy list_favorites mógł reagować na kliknięcia.

Kiedy użytkownik kliknie któryś ze swoich ulubionych napojów, utworzymy intencję, która uruchomi aktywność DrinkActivity, i przekażemy do tej intencji identyfikator klikniętego napoju. Dzięki temu aplikacja wyświetli szczegółowe informacje o wybranym napoju. Intencja drinkId TopLevelActivity

DrinkActivity

Już znasz cały kod, który jest potrzebny do zaimplementowania tych możliwości. W rzeczywistości jest on niemal identyczny z kodem, który napisaliśmy w poprzednich rozdziałach do kontroli listy napojów prezentowanej w aktywności DrinkCategoryActivity. Jedyna różnica polega na tym, że tym razem chcemy wyświetlać wyłącznie te napoje, które w kolumnie FAVORITE mają wartość 1. Zdecydowaliśmy się umieścić kod obsługujący ten widok listy w nowej metodzie, o nazwie setupFavoritesListView(). Metodę tę przedstawimy na kolejnej stronie, a następnie dodamy do pliku TopLevelActivity.java.

708

Rozdział 17.

Kursory i zadania asynchroniczne

¨  Aktualizacja układu ¨  Wypełnienie widoku listy

Metoda setupFavritesListView() wypełnia widok listy list_favorites nazwami ulubionych napojów użytkownika. Zanim przejdziesz na następną stronę, upewnij się, że dobrze rozumiesz działanie jej kodu.

Kod gotowy do użycia

private void setupFavoritesListView() { // Wypełniamy widok ListView list_favorites danymi z kursora

¨  Odświeżenie danych

Pobieramy widok listy o identyfikatorze list_favorites.

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 wartości kolumn ”FAVORITE = 1”, _id i NAME z wierszy, null, null, null, null); w których FAVORITE=1.

Coffeina

Pobieramy nazwy ulubionych napojów użytkownika.

app/src/main java

CursorAdapter favoriteAdapter = Tworzymy nowy adapter typu CursorAdapter.

new SimpleCursorAdapter(TopLevelActivity.this,

com.hfad.coffeina

android.R.layout.simple_list_item_1, Używamy kursora w adapterze.

favoritesCursor, new String[]{”NAME”},

Wyświetlamy nazwy napojów na liście.

TopLevel Activity.java

new int[]{android.R.id.text1}, 0); listFavorites.setAdapter(favoriteAdapter); } catch(SQLiteException e) { Toast toast = Toast.makeText(this, ”Baza danych nie jest dostępna”, Toast.LENGTH_SHORT); toast.show(); }

at. W przypadku jakichś problemów z bazą danych wyświetlamy komunik

// Po kliknięciu ulubionego napoju przechodzimy do aktywności DrinkActivity listFavorites.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView listView, View v, int position, long id){ Ta metoda zostanie wywołana po kliknięciu jednego z elementów wyświetlonych na liście.

}); }

Intent intent = new Intent(TopLevelActivity.this, DrinkActivity.class); intent.putExtra(DrinkActivity.EXTRA_DRINKID, (int)id); startActivity(intent); }

elementów Jeśli użytkownik kliknie jeden z ncję listy list_favorites, tworzymy inte ivity, uruchamiającą aktywność DrinkActdodatkową, zapisując w niej, jako informację identyfikator klikniętego napoju.

jesteś tutaj  709

Kod aktywności TopLevelActivity

Nowy kod aktywności TopLevelActivity

¨  Aktualizacja układu ¨  Wypełnienie widoku listy

Zaktualizowaliśmy kod aktywności TopLevelActivity — dodaliśmy do niego wypełnianie widoku listy list_favorites oraz reagowanie na kliknięcia. Zmodyfikuj swoją wersję pliku TopLevelActivity.java, tak by była identyczna z naszą (nowego kodu jest całkiem dużo, więc nie spiesz się i uważnie wprowadzaj zmiany):

¨  Odświeżenie danych

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;

com.hfad.coffeina

import android.view.View; import android.database.Cursor;

TopLevel Activity.java

import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteException; import android.database.sqlite.SQLiteDatabase; import android.widget.SimpleCursorAdapter; import android.widget.CursorAdapter;

Używamy tych wszystkich klas, więc musimy je zaimportować.

import android.widget.Toast; public class TopLevelActivity extends Activity { private SQLiteDatabase db; private Cursor favoritesCursor;

Bazę danych i kursor dodajemy jako zmienne prywatne, dzięki czemu będziemy mogli z nich korzystać w metodach setupFavoritesListView() oraz onDestroy().

@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_top_level); setupOptionsListView(); setupFavoritesListView(); }

Metodę setupFavoritesListView() wywołujemy w metodzie onCreate().

Dalsza część kodu znajduje się na następnej stronie.

710

Rozdział 17.

Kursory i zadania asynchroniczne

Kod aktywności TopLevelActivity (ciąg dalszy)

¨  Aktualizacja układu ¨  Wypełnienie widoku listy

W tej metodzie nie musimy wprowadzać żadnych zmian.

private void setupOptionsListView() {

¨  Odświeżenie danych

Coffeina

// Implementacja obiektu OnItemClickListener AdapterView.OnItemClickListener itemClickListener = new AdapterView.OnItemClickListener(){

app/src/main

public void onItemClick(AdapterView listView,

java

View itemView, com.hfad.coffeina

int position, long id) {

TopLevel Activity.java

if (position == 0) { Intent intent = new Intent(TopLevelActivity.this, DrinkCategoryActivity.class); startActivity(intent); } } }; // Dodajemy obiekt nasłuchujący do widoku ListView ListView listView = (ListView) findViewById(R.id.list_options); listView.setOnItemClickListener(itemClickListener); } private void setupFavoritesListView() {

To jest metoda, którą utworzyliśmy, by wypełniać listę list_favorites i reagować na kliknięcia jej opcji.

// Wypełniamy widok ListView list_favorites danymi z kursora ListView listFavorites = (ListView) findViewById(R.id.list_favorites); try{ SQLiteOpenHelper coffeinaDatabaseHelper = new CoffeinaDatabaseHelper(this); db = coffeinaDatabaseHelper.getReadableDatabase(); favoritesCursor = db.query(“DRINK”, Widok listy list_favorites będzie używał tego kursora do pobierania danych.

Pobieramy referencję do bazy danych.

new String[] { “_id”, “NAME”}, “FAVORITE = 1”, null, null, null, null);

Dalsza część kodu znajduje się na następnej stronie.

jesteś tutaj 

711

Ciąg dalszy kodu

Kod aktywności TopLevelActivity (ciąg dalszy)

¨  Aktualizacja układu ¨  Wypełnienie widoku listy ¨  Odświeżenie danych

CursorAdapter favoriteAdapter = new SimpleCursorAdapter(TopLevelActivity.this,

Używamy kursora do utworzenia adaptera typu SimpleCursorAdapter.

android.R.layout.simple_list_item_1, favoritesCursor, new String[]{”NAME”}, new int[]{android.R.id.text1}, 0);

listFavorites.setAdapter(favoriteAdapter); } catch(SQLiteException e) {

Ustawiamy adapter kursora, który będzie używany w widoku listy.

Toast toast = Toast.makeText(this, ”Baza danych jest niedostępna”, Toast.LENGTH_SHORT); toast.show(); } // Po kliknięciu ulubionego napoju przechodzimy do aktywności DrinkActivity

Implementujemy sposób reakcji listy list_favorites na kliknięcia.

listFavorites.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView listView, View v, int position, long id) { Intent intent = new Intent(TopLevelActivity.this, DrinkActivity.class); intent.putExtra(DrinkActivity.EXTRA_DRINKID, (int) id); startActivity(intent); } }); }

ivity, Uruchamiamy aktywność DrinkAct przekazując do niej identyfikator klikniętego napoju.

// W metodzie onDestroy() zamykamy kursor i bazę danych

Coffeina app/src/main

@Override public void onDestroy(){ super.onDestroy(); favoritesCursor.close();

Metoda onDestroy() jest wywoływana bezpośrednio przed usunięciem aktywności . W tej metodzie zamkniemy kursor i bazę danych, gdyż po usunięciu aktywności nie będą nam one potrzebne.

db.close(); } }

Powyższy kod wyświetla na liście list_favorites 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. Na następnej stronie weźmiemy naszą nową wersję aplikacji na jazdę próbną i sprawdzimy, jak działa.

712

Rozdział 17.

java com.hfad.coffeina TopLevel Activity.java

Kursory i zadania asynchroniczne

Jazda próbna aplikacji Kiedy uruchomimy aplikację, to w aktywności TopLevelActivity zostaną wyświetlone nowy widok tekstowy i nowa lista. Jeśli oznaczyłeś jakiś napój jako ulubiony, to zostanie on wyświetlony na tej nowej liście.

¨  Aktualizacja układu ¨  Wypełnienie widoku listy ¨  Odświeżenie danych

Kiedy klikniemy napój, zostanie uruchomiona aktywność DrinkActivity, która wyświetli szczegółowe informacje o nim.

To nowy widok listy list_favorites, który przed chwilą utworzyliśmy. Jest w nim wyświetlona opcja Latte, gdyż we wcześniejszej części rozdziału oznaczyliśmy ten napój jako ulubiony.

Kiedy klikniesz napój, zostaną wyświetlone szczegółowe informacje o nim.

Jest jednak pewien problem. Jeśli zaznaczymy nowy napój jako ulubiony i wrócimy do aktywności TopLevelActivity, to ten nowy ulubiony napój nie zostanie wyświetlony w widoku listy list_favorites. Pojawi się on na tej liście wyłącznie wtedy, kiedy obrócimy urządzenie.

Oznaczyliśmy cappuccino jako ulubione, ale nie pojawiło się ono na liście.

Jak sądzisz, dlaczego nowy ulubiony napój nie jest wyświetlany na liście aż do momentu, gdy zmienimy orientację urządzenia? Zastanów się trochę nad tym zanim obrócisz kartkę.

Kiedy obrócimy urządzenie, cappuccino się pojawi. Dlaczego tak się dzieje?

jesteś tutaj  713

Nieaktualne dane

Kursory nie są odświeżane automatycznie

¨  Aktualizacja układu

Kiedy użytkownik zaznaczy nowy ulubiony napój — przechodząc w aplikacji do aktywności DrinkActivity i zaznaczając pole wyboru — to nie zostanie on automatycznie wyświetlony na liście ulubionych napojów prezentowanej w aktywności TopLevelActivity. Dzieje się tak, ponieważ kursory pobierają dane w momencie, gdy są tworzone.

¨  Wypełnienie widoku listy ¨  Odświeżenie danych

W naszym przypadku kursor jest tworzony w metodzie onCreate(), a zatem pobiera on swoją zawartość w momencie tworzenia aktywności. Gdy użytkownik przechodzi do innych aktywności w aplikacji, aktywność TopLevelActivity jest zatrzymywana, a nie usuwana i tworzona ponownie.

Kiedy uruchamiasz drugą aktywność, przesłania ona tę, która działała do tej pory. Ta dotychczasowa aktywność nie jest jednak usuwana. Zamiast tego jej działanie jest wstrzymywane, a następnie zatrzymywane, gdyż traci ona miejsce wprowadzania i przestaje być widoczna dla użytkownika.

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 niezmienionej postaci. Oznacza to, że jeśli użytkownik oznaczy nowy napój jako ulubiony po utworzeniu kursora, to dane w tym kursorze staną się nieaktualne. _id

NAME

1

“Latte”

2

“Cappuccino” “Czarne espresso z dużą ilością mleka.” _id NAMEspienionego DESCRIPTION

3

DESCRIPTION “Czarne espresso z gorącym mlekiem i mleczną pianką.”

Jeśli zaktualizujesz dane w tabeli bazy danych…

IMAGE_RESOURCE_ID

FAVORITE

54543543

1

654334453

0

IMAGE_RESOURCE_ID “Espresso” “Czarna kawa ze świeżo mielonych 44324234 0 1 “Latte” “Czarne espresso z gorącym 54543543 ziaren najwyższej jakości.” mlekiem i mleczną pianką.” 2

“Cappuccino” “Czarne espresso z dużą ilością spienionego mleka.”

3

“Espresso”

A jak rozwiązać ten problem?

714

Rozdział 17.

654334453

“Czarna kawa ze świeżo mielonych 44324234 ziaren najwyższej jakości.”

…to kursor, który został utworzony wcześniej, nie zauważy tej zmiany.

FAVORITE 0 0 0

Kursory i zadania asynchroniczne ¨  Aktualizacja układu Kursor można zmieniać za pomocą metody changeCursor() ¨  Wypełnienie widoku listy Rozwiązaniem naszego problemu jest zmiana kursora używanego w widoku listy list_favorites na jego nową, zaktualizowaną wersję. W tym celu musisz zdefiniować nową wersję kursora, pobrać referencję do adaptera kursora używanego przez widok listy, a następnie wywołać metodę changeCurosor() tego adaptera, aby zmienić używany kursor. Poniżej zamieściliśmy bardziej szczegółowy opis tych czynności.

¨  Odświeżenie danych

1. Zdefiniowanie kursora. Kursor musimy zdefiniować dokładnie tak samo, jak robiliśmy to wcześniej. Chcemy, by zapytanie zwróciło ulubione napoje użytkownika, dlatego tworzymy kursor w następujący sposób: Cursor newCursor = db.query(”DRINK”, To jest to samo zapytanie, którego użyliśmy wcześniej.

new String[] { ”_id”, ”NAME”}, ”FAVORITE = 1”, null, null, null, null);

2. Pobranie referencji do adaptera kursora. Referencję do adaptera kursora używanego przez widok listy można pobrać, wywołując metodę getAdapter() kontrolki ListView. Metoda ta zwraca obiekt typu Adapter. Ponieważ nasza lista używa adaptera kursora, zwrócony wynik możemy rzutować do typu CursorAdapter: ListView listFavorites = (ListView) findViewById(R.id.list_favorites); CursorAdapter adapter = (CursorAdapter) listFavorites.getAdapter();

3. Zmiana kursora poprzez wywołanie metody changeCursor().

Adapter używany przez kontrolkę c ListView można pobrać, wywołują metodę getAdapter().

Do zmieniania kursora używanego przez adapter służy metoda changeCursor(). Metoda ta ma tylko jeden parametr — nowy kursor: adapter.changeCursor(newCursor);

To wywołanie zmienia kursor używany przez adapter kursora na nowy.

Metoda changeCursor() zmienia kursor obecnie używany przez adapter kursora na nowy. Stary kursor jest następnie zamykany, dzięki czemu nie musimy tego robić samodzielnie. Kursor używany przez widok listy list_favorites będziemy zmieniać w metodzie onRestart() klasy TopLevelActivity. Oznacza to, że dane wyświetlane przez widok listy będą odświeżane, kiedy użytkownik ponownie wyświetli aktywność TopLevelActivity. Wszelkie nowe ulubione napoje wybrane przez użytkownika zostaną wyświetlone, a wszelkie napoje, które przestały być oznaczone jako ulubione, zostaną usunięte z listy. Na kilku następnych stronach przedstawimy kompletny, zaktualizowany kod pliku TopLevelActivity.java.

jesteś tutaj  715

Ciąg dalszy kodu

Zmodyfikowany kod pliku TopLevelActivity.java

¨  Aktualizacja układu ¨  Wypełnienie widoku listy ¨  Odświeżenie danych

Poniżej przedstawiliśmy kompletny kod pliku TopLevelActivity.java; zmodyfikuj swoją wersję pliku, dodając do niej fragmenty wyróżnione pogrubioną czcionką. package com.hfad.coffeina; Coffeina

import android.app.Activity; import android.os.Bundle;

app/src/main

import android.content.Intent; import android.widget.AdapterView; import android.widget.ListView; import android.view.View; import android.database.Cursor; import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteException;

java com.hfad.coffeina

TopLevel Activity.java

import android.database.sqlite.SQLiteDatabase; import android.widget.SimpleCursorAdapter; import android.widget.CursorAdapter; import android.widget.Toast;

Na tej stronie nie musisz zmieniać żadnych kodów.

public class TopLevelActivity extends Activity { private SQLiteDatabase db; private Cursor favoritesCursor; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_top_level); setupOptionsListView(); setupFavoritesListView(); }

Dalsza część kodu znajduje się na następnej stronie.

716

Rozdział 17.

Kursory i zadania asynchroniczne

Plik TopLevelActivity.java (ciąg dalszy)

¨  Aktualizacja układu ¨  Wypełnienie widoku listy ¨  Odświeżenie danych

private void setupOptionsListView() { // Tworzymy obiekt OnItemClickListener AdapterView.OnItemClickListener itemClickListener =

new AdapterView.OnItemClickListener(){ public void onItemClick(AdapterView listView, Na tej stronie nie musisz zmieniać żadnych kodów.

View itemView, int position, long id) { if (position == 0) { Intent intent = new Intent(TopLevelActivity.this, DrinkCategoryActivity.class); startActivity(intent); } }

Coffeina

}; // Dodajemy obiekt nasłuchujący dla widoku listy

app/src/main

ListView listView = (ListView) findViewById(R.id.list_options);

java

listView.setOnItemClickListener(itemClickListener);

com.hfad.coffeina

} private void setupFavoritesListView() {

TopLevel Activity.java

// Wypełniamy widok ListView list_favorites danymi z kursora 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”}, ”FAVORITE = 1”, null, null, null, null); CursorAdapter favoriteAdapter = new SimpleCursorAdapter(TopLevelActivity.this, android.R.layout.simple_list_item_1, favoritesCursor, new String[]{”NAME”}, new int[]{android.R.id.text1}, 0);

Dalsza część kodu znajduje się na następnej stronie.

listFavorites.setAdapter(favoriteAdapter);

jesteś tutaj  717

Więcej kodu

Plik TopLevelActivity.java (ciąg dalszy)

¨  Aktualizacja układu ¨  Wypełnienie widoku listy ¨  Odświeżenie danych

} catch(SQLiteException e) { Toast toast = Toast.makeText(this, ”Database unavailable”, Toast.LENGTH_SHORT); toast.show(); }

// Po kliknięciu ulubionego napoju przechodzimy do aktywności DrinkActivity listFavorites.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView listView, View v, int position, long id) { Intent intent = new Intent(TopLevelActivity.this, DrinkActivity.class); intent.putExtra(DrinkActivity.EXTRA_DRINKID, (int)id); startActivity(intent); } }); anie ona wywołana, Dodaj metodę onRestart(). Zost rotem do pow z jdzie prze ik kiedy użytkown aktywności TopLevelActivity.

}

Coffeina

app/src/main @Override public void onRestart() { java super.onRestart(); Cursor newCursor = db.query(“DRINK”, com.hfad.coffeina new String[] { “_id”, “NAME”}, Tworzymy nową wersję kursora. “FAVORITE = 1”, null, null, null, null); TopLevel ListView listFavorites = (ListView) findViewById(R.id.list_favorites); Activity.java CursorAdapter adapter = (CursorAdapter) listFavorites.getAdapter(); Zmieniamy kursor używany do tej pory przez listę adapter.changeCursor(newCursor); list_favorites na nowy kursor. favoritesCursor = newCursor; } Zmieniamy wartość favoritesCursor, zapisując w niej nowy

kursor, dzięki czemu będziemy mogli go zamknąć w metodzie onDestroy() aktywności.

// W metodzie onDestroy() zamykamy kursor i bazę danych @Override public void onDestroy(){ super.onDestroy(); favoritesCursor.close(); db.close(); } }

Zobaczmy teraz, co się stanie, kiedy uruchomimy aplikację.

718

Rozdział 17.

Kursory i zadania asynchroniczne

Jazda próbna aplikacji

¨  Aktualizacja układu ¨  Wypełnienie widoku listy ¨  Odświeżenie danych

Kiedy uruchomimy aplikację, w aktywności TopLevelActivity, tak samo jak wcześniej, są wyświetlane ulubione napoje. Kiedy klikniemy jeden z nich, szczegółowe informacje o nim są wyświetlane w aktywności DrinkActivity. Jeśli usuniemy zaznaczenie z pola wyboru favorite danego napoju i wrócimy do aktywności TopLevelActivity, to dane prezentowane na liście list_favorites zostaną odświeżone, a napój nie będzie już na niej widoczny.

Kontrolka ListView list_favorites zawiera początkowo latte i cappucino.

Kiedy klikniemy opcję Latte, szczegóły napoju zostaną wyświetlone w aktywności DrinkActivity.

Teraz usuniemy znacznik z pola wyboru, by określić, że dany napój nie jest już naszym ulubionym.

Tak sobie myślę… Stosowanie baz danych w aplikacji bez wątpienia ma wiele zalet, ale czy takie otwieranie i zamykanie bazy danych nie spowalnia działania aplikacji?

Kiedy ponownie wyświetlimy aktywność TopLevelActivity, na liście list_favorites nie będzie już opcji Latte.

Bazy danych zapewniają ogromne możliwości, ale są powolne. A to oznacza, że choć nasza aplikacja działa dobrze, to musimy zwrócić uwagę na jej wydajność…

jesteś tutaj  719

W międzyczasie…

Bazy danych mogą spooowooolniiić działanie aplikacji Zastanów się, co aplikacja musi zrobić podczas otwierania bazy danych. W pierwszej kolejności musi przeszukać pamięć urządzenia i odnaleźć plik bazy danych. Jeśli nie uda się go znaleźć, to aplikacja musi utworzyć nową, pustą bazę danych. Następnie musi wykonać polecenia SQL w celu utworzenia w tej bazie danych tabeli i zapisania w niej początkowych danych. I w końcu musi wykonać zapytania, by pobrać z bazy informacje. 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.

Życie staje się prostsze, gdy wątki ze sobą współdziałają Poważnym problemem związanym z korzystaniem z wolnej bazy danych jest to, że może ona sprawić, iż aplikacja będzie wolno reagować na poczynania użytkownika. Aby zrozumieć, dlaczego tak się dzieje, musimy pomyśleć o tym, w jaki sposób działają wątki w systemie Android. Od momentu wprowadzenia Androida Lollipop musimy mieć na uwadze trzy rodzaje wątków:



Główny wątek aplikacji.

To prawdziwy koń roboczy Androida. To właśnie ten wątek nasłuchuje i odbiera intencje, odbiera komunikaty generowane przez ekran urządzenia i wywołuje metody naszych aktywności.



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.



Wszystkie inne wątki tworzone przez aplikację.

Jeśli nie zachowamy należytej ostrożności, to praktycznie wszystkie operacje realizowane przez aplikację będą wykonywane w wątku głównym, 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 bądź uległa awarii. 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. Pokażemy Ci, jak to zrobić z kodem obsługującym bazę danych w aktywności DrinkActivity, który napisaliśmy we wcześniejszej części rozdziału. Pamiętasz zapewne, że kod ten aktualizuje kolumnę FAVORITE bazy danych kafeterii Coffeina, kiedy użytkownik kliknie pole wyboru favorite, bądź wyświetla komunikat, jeśli baza danych nie jest dostępna.

720

Rozdział 17.

Kursory i zadania asynchroniczne

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? Aby ułatwić Ci wykonanie ćwiczenia, na pierwsze pytanie już odpowiedzieliśmy.

A

Przygotowanie interfejsu użytkownika. int drinkId = (Integer) getIntent().getExtras().get(EXTRA_DRINKID); CheckBox favorite = (CheckBox) findViewById(R.id.favorite); ContentValues drinkValues = new ContentValues(); drinkValues.put(”FAVORITE”, favorite.isChecked());

Główny wątek obsługi zdarzeń

Wątek działający w tle

Ten kod musi być wykonywany w wątku głównym, gdyż musi mieć dostęp do widoków prezentowanych w aktywności.

B

Komunikacja z bazą danych. SQLiteOpenHelper coffeinaDatabaseHelper = new CoffeinaDatabaseHelper(this); SQLiteDatabase db = coffeinaDatabaseHelper.getWriteableDatabase(); db.update(”DRINK”,...);

Główny wątek obsługi zdarzeń

C

Wątek działający w tle

Wyświetlenie komunikatu na ekranie. Toast toast = Toast.makeText(...); toast.show();

Główny wątek obsługi zdarzeń

Wątek działający w tle

jesteś tutaj  721

Rozwiązanie zadania

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? Aby ułatwić Ci wykonanie ćwiczenia, na pierwsze pytanie już odpowiedzieliśmy.

A

Przygotowanie interfejsu użytkownika. int drinkId = (Integer) getIntent().getExtras().get(EXTRA_DRINKID); CheckBox favorite = (CheckBox) findViewById(R.id.favorite); ContentValues drinkValues = new ContentValues(); drinkValues.put(”FAVORITE”, favorite.isChecked());

Główny wątek obsługi zdarzeń

B

Wątek działający w tle

Komunikacja z bazą danych. SQLiteOpenHelper coffeinaDatabaseHelper = new CoffeinaDatabaseHelper(this); SQLiteDatabase db = coffeinaDatabaseHelper.getWritableDatabase(); db.update(”DRINK”,...);

Główny wątek obsługi zdarzeń

C

Wątek działający w tle

Wyświetlenie komunikatu na ekranie. Toast toast = Toast.makeText(...); toast.show();

Główny wątek obsługi zdarzeń

722

Kod obsługujący bazę danych chcemy wykonywać w wątku działającym w tle.

Rozdział 17.

Kod aktualizujący widoki musimy wykonywać w wątku głównym, bo w przeciwnym razie zostanie zgłoszony wyjątek.

Wątek działający w tle

Kursory i zadania asynchroniczne

Który kod umieścić w którym wątku? W przypadku używania w aplikacji bazy danych dobrym pomysłem jest umieszczanie kodu obsługującego bazę w wątku działającym w tle i aktualizowanie widoków w wątku głównym. Przerobimy teraz kod metody onFavoriteClicked() aktywności DrinkActivity, aby Ci pokazać, jak rozwiązywać problemy tego typu.

Coffeina

Oto kod metody w jej dotychczasowej postaci (podzieliliśmy go przy tym na części, które opisaliśmy u dołu strony):

app/src/main java com.hfad.coffeina

// Aktualizujemy bazę danych po kliknięciu pola wyboru

DrinkActivity.java

public void onFavoriteClicked(View view){

1

int drinkId = (Integer)getIntent().getExtras().get(EXTRA_DRINKID); CheckBox favorite = (CheckBox)findViewById(R.id.favorite); ContentValues drinkValues = new ContentValues(); drinkValues.put(“FAVORITE”, favorite.isChecked());

2

SQLiteOpenHelper coffeinaDatabaseHelper = new CoffeinaDatabaseHelper(this); try { SQLiteDatabase db = coffeinaDatabaseHelper.getWritableDatabase(); db.update( "DRINK" , drinkValues , "_id = ?" , new String[] {Integer.toString(drinkId)}); db.close(); } catch(SQLiteException e) { Toast toast = Toast.makeText(this, “Baza danych jest niedostępna”, Toast.LENGTH_SHORT); toast.show();

3 } }

1

2

Kod, który musi zostać wykonany przed kodem korzystającym z bazy danych.

Pierwszych kilka wierszy kodu pobiera wartość pola wyboru i zapisuje ją w obiekcie drinkValues typu ContentValues. Ten kod musi zostać wykonany przed wykonaniem kodu operującego na bazie danych. Kod korzystający z bazy danych, który ma być wykonany w wątku działającym w tle.

Ten kod aktualizuje tabelę DRINK.

3

Kod, który ma być wykonany po wykonaniu kodu korzystającego z bazy danych.

Jeśli baza danych będzie niedostępna, to chcemy wyświetlić użytkownikowi stosowny komunikat. Ten blok kodu musi zostać wykonany w głównym wątku aplikacji obsługującym zdarzenia.

Zaimplementujemy ten kod, używając obiektu klasy AsyncTask. Ale co to w ogóle jest?

jesteś tutaj  723

AsyncTask

Klasa AsyncTask służy do wykonywania operacji asynchronicznych Klasa AsyncTask pozwala nam wykonywać operacje w tle. Po zakończeniu takiej operacji możemy zaktualizować widoki w głównym wątku obsługi zdarzeń. Jeśli takie czynności są realizowane cyklicznie, to w trakcie ich wykonywania możemy nawet publikować informacje o postępach prac. Zadanie asynchroniczne, czyli obiekt AsyncTask, tworzymy poprzez utworzenie klasy dziedziczącej po AsyncTask i zaimplementowanie jej metody doInBackground(). Kod tej metody zostanie wykonany w wątku działającym w tle, zatem stanowi ona doskonałe miejsce do umieszczenia kodu obsługującego bazę danych. Klasa AsyncTask definiuje także metodę onPreExecute(), wykonywaną przed metodą doInBackground(), i metodę onPostExecute(), wykonywaną po wywołaniu metody doInBackground(). Dostępna jest także metoda onProgressUpdate(), której można używać do publikowania informacji o postępach prac. Klasę AsyncTask dodajemy jako klasę wewnętrzną do aktywności, Oto ogólna postać zadania asynchronicznego: w której chcemy jej używać. private class MyAsyncTask extends AsyncTask

Ta metoda jest opcjonalna. Jest ona wywoływana przed rozpoczęciem realizacji kodu, który ma być wykonywany w tle.

protected void onPreExecute() { // Kod, który ma zostać wykonany przed rozpoczęciem zadania } protected Result doInBackground(Params... params) { // Kod, który ma zostać wykonany w wątku działającym w tle }

Tę metodę musisz zaimplementować. To ona zawiera kod, który ma być wykonywany w tle. Ta metoda jest opcjonalna. Pozwala ona na publikowanie postępów w realizacji kodu wykonywanego w tle.

protected void onProgressUpdate(Progress... values) { // Kod pozwalający na publikowanie informacji o postępach prac } Także ta metoda jest opcjonalna. Jest ona

wywoływana po zakończeniu kodu wykonywanego w tle.

protected void onPostExecute(Result result) { // Kod, który ma zostać wykonany po zakończeniu zadania } }

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 dodamy tę klasę do naszej aktywności DrinkActivity jako klasę wewnętrzną.

724

Rozdział 17.

Kursory i zadania asynchroniczne

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 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.

onPreExecute()

W naszej aplikacji użyjemy metody onPreExecute() do pobrania wartości pola wyboru i zapisania jej w obiekcie drinkValues typu ContentValues. Aby to zrobić, musimy mieć możliwość dostępu do pola wyboru, a co więcej, musimy się do niego odwołać przed wykonaniem kodu operującego na bazie danych. Poza tym zdefiniujemy poza metodą zmienną drinkValues typu ContentValues, z której będą mogły korzystać także inne metody tej klasy (opiszemy je dokładniej na kilku następnych stronach). A oto nasz nowy kod: private class UpdateDrinkTask extends AsyncTask { ContentValues drinkValues; protected void onPreExecute() {

na bazie Zanim wykonamy kod operujący pola wyboru danych, musimy odczytać wartość ch napojów. używanego do oznaczania ulubiony

CheckBox favorite = (CheckBox)findViewById(R.id.favorite); drinkValues = new ContentValues(); drinkValues.put(“FAVORITE”, favorite.isChecked()); } ... }

A teraz przyjrzymy się metodzie doInBackground().

jesteś tutaj  725

Metoda doInBackground()

Metoda doInBackground() Metoda doInBackground() jest wywoływana i wykonywana w tle, bezpośrednio po zakończeniu realizacji metody onPreExecute(). Mamy możliwość zdefiniowania, jakiego typu będą parametry tej metody i jakiego typu będzie zwracany przez nią wynik. My planujemy użyć metody doInBackground() do wykonania kodu operującego na bazie danych. Dzięki temu operacje na bazie zostaną wykonane w wątku działającym w tle. Do tej metody przekażemy identyfikator aktualizowanego napoju, a ponieważ identyfikator jest liczbą całkowitą, musimy zaznaczyć, że doInBackground() jest metodą pobierającą obiekty typu Integer. Poza tym zwrócimy z niej wynik typu Boolean, dzięki czemu będziemy wiedzieć, czy kod został wykonany prawidłowo, czy nie.

onPreExecute()

doInBackground()

private class UpdateDrinkTask extends AsyncTask { na Integer, private ContentValues drinkValues; Zmieniamy ten parametrrowi metody aby odpowiadał paramet doInBackground().

...

Ten kod zostanie wykonany w wątku działającym w tle.

protected Boolean doInBackground(Integer... drinks) { int drinkId = drinks[0]; SQLiteOpenHelper coffeinaDatabaseHelper =

Zmieniamy ten parametr na Boolean, aby odpowiadał typowi zwracanemu przez metodę doInBackground().

choć To jest tablica liczb typu Integer, tylko my będziemy w niej zapisywać napoju. jedną informację — identyfikator

new CoffeinaDatabaseHelper(DrinkActivity.this); try { SQLiteDatabase db = coffeinaDatabaseHelper.getWritableDatabase(); db.update(“DRINK”, drinkValues, “_id = ?”, new String[] {Integer.toString(drinkId)}); db.close(); return true; } catch(SQLiteException e) { return false; } } ... }

Teraz zajmiemy się metodą onProgressUpdate().

726

Rozdział 17.

Metoda update() używa obiektu drinkValues utworzonego w metodzie onPreExecute().

Kursory i zadania asynchroniczne

Metoda onProgressUpdate() onPreExecute()

Metoda onProgressUpdate() jest wywoływana w głównym wątku aplikacji 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():

onProgressUpdate()

protected Boolean doInBackground(Integer... count) { for (int i = 0; i < count; i++) { publishProgress(i); } }

Ta instrukcja wywołuje metodę onProgressUpdate(), przekazując do niej wartość zmiennej i.

protected void onProgressUpdate(Integer... progress) { setProgress(progress[0]); }

W naszej aplikacji nie publikujemy informacji o postępach wykonywanych operacji, dlatego nie musimy implementować tej metody. Aby zasygnalizować, że nie używamy żadnych obiektów informujących o postępach prac, zmienimy sygnaturę klasy UpdateDrinkTask w następujący sposób:

Nie używamy metody onProgressUpdate(), dlatego ten parametr typu określimy jako Void.

private class UpdateDrinkTask extends AsyncTask { ... }

I na koniec przyjrzymy się metodzie onPostExecute().

jesteś tutaj  727

Metoda onPostExecute()

Metoda onPostExecute() 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 metodę doInBackground(), zatem typ jej parametru musi odpowiadać typowi wyniku zwracanego przez metodę doInBackground(). My zastosujemy metodę onPostExecute() do sprawdzenia, czy kod operujący na bazie danych w metodzie doInBackground() został wykonany prawidłowo. Jeśli coś poszło nie tak, to powiadomimy użytkownika o problemach, wyświetlając odpowiedni komunikat. Operacje te wykonujemy w metodzie onPostExecute(), gdyż ma ona dostęp do interfejsu użytkownika aplikacji — metoda doInBackground() jest wykonywana w wątku działającym w tle, więc nie może aktualizować żadnych widoków.

onPreExecute()

doInBackground()

onProgressUpdate()

Oto kod metody onPostExecute():

onPostExecute()

private class UpdateDrinkTask extends AsyncTask { ackground() To jest typ Boolean, gdyż nasza metoda doInB typu. tego ść warto ie zwraca właśn

...

protected void onPostExecute(Boolean success) { if (!success) {

Tworząc komunikat, przekazujemy do niego kontekst aktywności DrinkActivity.

Toast toast = Toast.makeText(DrinkActivity.this, “Baza danych jest niedostępna”, Toast.LENGTH_SHORT); toast.show(); } } }

Skoro już napisaliśmy kod metod klasy AsyncTask, przyjrzymy się raz jeszcze jej parametrom.

728

Rozdział 17.

Kursory i zadania asynchroniczne

Parametry klasy 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 protected void onPreExecute() { // Kod, który ma zostać wykonany przed rozpoczęciem zadania } protected Result doInBackground(Params... params) { // Kod, który ma zostać wykonany w wątku działającym w tle } protected void onProgressUpdate(Progress... values) { // Kod pozwalający na publikowanie informacji o postępach prac } protected void onPostExecute(Result result) { // Kod, który ma zostać wykonany po zakończeniu zadania } }

W naszym przykładzie metoda doInBackground() ma parametry typu Integer, metoda onPostExecute() — parametr typu Boolean, a metody onProgressUpdate() nie używamy. A to oznacza, że w naszym przypadku parametrem typu Params jest Integer, parametrem , typu Progress jest Void, a parametrem typu Result — Boolean: Tym parametrem typu jest Void

gdyż metody onProgressUpdate() nie implementujemy.

private class UpdateDrinkTask extends AsyncTask { ... protected Boolean doInBackground(Integer... drinks) { ... } protected void onPostExecute(Boolean... success) { ... } }

Teraz już wiesz wszystko, co trzeba, by utworzyć zadanie — przekonajmy się zatem, jak takie zadanie można wykonać.

jesteś tutaj  729

Kod klasy wewnętrznej

Kompletny kod klasy UpdateDrinkTask Poniżej przedstawiliśmy kompletny kod klasy UpdateDrinkTask. Należy ją dodać do klasy DrinkActivity jako klasę wewnętrzną, sugerujemy jednak, byś zrobił to nieco później, kiedy wyjaśnimy, jak można ją wykonać, i pokażemy pełny kod pliku DrinkActivity.java. private class UpdateDrinkTask extends AsyncTask { private ContentValues drinkValues;

drinkValues zdefiniowaliśmy jako zmienną prywatną, gdyż jest używana w metodach onExecute() oraz doInBackground().

protected void onPreExecute() { CheckBox favorite = (CheckBox) findViewById(R.id.favorite); drinkValues = new ContentValues(); drinkValues.put(”FAVORITE”, favorite.isChecked()); }

Kod wykonujący operacje na bazie danych jest umieszczony w metodzie doInBackground().

Przed wykonaniem kodu operującego na bazie danych musimy zapisać wartość pola wyboru oznaczającego napój jako ulubiony w obiekcie typu ContentValues, w zmiennej drinkValues.

protected Boolean doInBackground(Integer... drinks) { int drinkId = drinks[0]; SQLiteOpenHelper coffeinaDatabaseHelper =

new CoffeinaDatabaseHelper(DrinkActivity.this); try { SQLiteDatabase db = coffeinaDatabaseHelper.getWritableDatabase(); db.update(”DRINK”, drinkValues, ”_id = ?”, new String[] {Integer.toString(drinkId)}); db.close(); return true; } catch(SQLiteException e) { return false; }

Kiedy kod operujący na bazie danych zostanie już wykonany w tle, sprawdzamy, czy udało się to zrobić poprawnie. W razie problemów wyświetlamy stosowny komunikat.

}

protected void onPostExecute(Boolean success) { if (!success) { Toast toast = Toast.makeText(DrinkActivity.this, ”Baza danych jest niedostępna”, Toast.LENGTH_SHORT); toast.show(); } } }

730

Rozdział 17.

Kod wyświetlający komunikat mus w metodzie onPostExecute() — imy umieścić aktu wymaga bowiem wykonania w głów alizacja ekranu nym wątku obsługi zdarzeń.

Kursory i zadania asynchroniczne

Wykonanie zadania AsyncTask... Zadanie asynchroniczne można wykonać, wywołując metodę execute() klasy AsyncTask; przekazuje się przy tym do niej wszelkie parametry wymagane przez metodę doInBackground(). Na przykład w naszej aplikacji do metody doInBackground() klasy AsyncTask chcemy przekazać identyfikator napoju klikniętego przez użytkownika, dlatego powinniśmy uruchamiać zadanie w następujący sposób: int drinkId = (Integer)getIntent().getExtras().get(EXTRA_DRINKID); new UpdateDrinkTask().execute(drinkId);

To wywołanie wykonuje zadanie AsyncTask i przekazuje do niego identyfikator napoju.

Typ parametru przekazywanego do metody execute() powinien odpowiadać typowi parametrów metody doInBackground(). My przekazujemy wartość całkowitą (identyfikator napoju), co odpowiada typowi parametru oczekiwanego przez naszą metodę doInBackground(). protected Boolean doInBackground(Integer... drinks) { ... }

…w metodzie onFavoritesClicked() aktywności DrinkActivity Nasza klasa UpdateDrinkTask (utworzony przez nas obiekt AsyncTask) musi zaktualizować kolumnę FAVORITE bazy danych kafeterii Coffeina za każdym razem, kiedy zostanie kliknięte pole wyboru favorite. Oznacza to, że musimy wywołać metodę onFavoritesClicked() aktywności DrinkActivity. Poniżej pokazaliśmy, jak będzie wyglądać nowa wersja tej metody: // Aktualizujemy bazę danych po kliknięciu pola wyboru public void onFavoriteClicked(View view){ int drinkId = (Integer)getIntent().getExtras().get(EXTRA_DRINKID); new UpdateDrinkTask().execute(drinkId); }

Na kilku następnych stronach przedstawimy kompletny kod pliku DrinkActivity.java.

Nowa wersja metody onFavoritesCl nie zawiera już kodu aktualizująceicked() kolumnę FAVORITE bazę danych. go Zamiast tego wywołuje ona obiekt AsyncTas k, który aktualizuje bazę w tle.

jesteś tutaj  731

Kod aktywności DrinkActivity

Kompletny kod pliku DrinkActivity.java Poniżej przedstawiliśmy kompletny kod pliku DrinkActivity.java; zaktualizuj swoją wersję tego pliku, tak by była identyczna z naszą: package com.hfad.coffeina; import import import import import import import import import import import import import

Coffeina

android.app.Activity; android.os.Bundle; app/src/main android.widget.ImageView; android.widget.TextView; java android.widget.Toast; android.database.Cursor; com.hfad.coffeina android.database.sqlite.SQLiteDatabase; android.database.sqlite.SQLiteException; DrinkActivity.java android.database.sqlite.SQLiteOpenHelper; android.view.View; android.widget.CheckBox; android.content.ContentValues; Używamy klasy AsyncTask, więc musimy ją zaimportować. android.os.AsyncTask;

public class DrinkActivity extends Activity { public static final String EXTRA_DRINKID = ”drinkId”; Nie trzeba zmieniać kodu metody onCreate(), zamieściliśmy ją jednak, by pokazać cały kod klasy.

@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_drink);

// Pobieramy identyfikator napoju z intencji int drinkId = (Integer) getIntent().getExtras().get(EXTRA_DRINKID); // Tworzymy kursor SQLiteOpenHelper coffeinaDatabaseHelper = new CoffeinaDatabaseHelper(this); try { SQLiteDatabase db = coffeinaDatabaseHelper.getReadableDatabase(); Cursor cursor = db.query(”DRINK”, new String[]{”NAME”, ”DESCRIPTION”, ”IMAGE_RESOURCE_ID”,”FAVORITE”}, ”_id = ?”, Dalsza część kodu new String[]{Integer.toString(drinkId)}, znajduje się na następnej stronie. null, null, null);

732

Rozdział 17.

Kursory i zadania asynchroniczne

Kompletny kod pliku DrinkActivity.java (ciąg dalszy) // Przechodzimy do pierwszego rekordu w kursorze if (cursor.moveToFirst()) { // Pobieramy z kursora szczegółowe informacje o napoju String nameText = cursor.getString(0); String descriptionText = cursor.getString(1);

Coffeina app/src/main

int photoId = cursor.getInt(2);

java

boolean isFavorite = (cursor.getInt(3) == 1); com.hfad.coffeina

// Wyświetlamy nazwę napoju TextView name = (TextView) findViewById(R.id.name); name.setText(nameText);

DrinkActivity.java

// Wyświetlamy opis napoju TextView description = (TextView) findViewById(R.id.description); description.setText(descriptionText); // Wyświetlamy zdjęcie napoju ImageView photo = (ImageView) findViewById(R.id.photo); photo.setImageResource(photoId); photo.setContentDescription(nameText); // Zaznaczamy pole wyboru ulubionego napoju CheckBox favorite = (CheckBox)findViewById(R.id.favorite); favorite.setChecked(isFavorite); } cursor.close(); db.close(); } catch (SQLiteException e) { Toast toast = Toast.makeText(this,

W kodzie przedstawionym na tej stronie nie trzeba nic zmieniać.

”Baza danych jest niedostępna”, Toast.LENGTH_SHORT); toast.show(); } }

Dalsza część kodu znajduje się na następnej stronie.

jesteś tutaj  733

Ciąg dalszy kodu

Kompletny kod pliku DrinkActivity.java (ciąg dalszy)

// Aktualizujemy bazę po zaznaczeniu pola wyboru public void onFavoriteClicked(View view){ int drinkId = (Integer) getIntent().getExtras().get(EXTRA_DRINKID); Coffeina

// Pobieramy wartość pola wyboru app/src/main

CheckBox favorite = (CheckBox) findViewById(R.id.favorite); ContentValues drinkValues = new ContentValues();

java

drinkValues.put(”FAVORITE”, favorite.isChecked());

com.hfad.coffeina

// Pobieramy referencję do bazy danych i aktualizujem kolumnę FAVORITE SQLiteOpenHelper coffeinaDatabaseHelper =

DrinkActivity.java

new CoffeinaDatabaseHelper(this); try { SQLiteDatabase db = coffeinaDatabaseHelper.getWritableDatabase(); db.update(”DRINK”,

Usuń wszystkie te wiersze — te operacje wykonujemy obecnie w obiekcie AsyncTask.

drinkValues, ”_id = ?”, new String[] {Integer.toString(drinkId)}); db.close(); } catch(SQLiteException e) {

Toast toast = Toast.makeText(this, ”Baza danych jest niedostępna”, Toast.LENGTH_SHORT); toast.show(); } new UpdateDrinkTask().execute(drinkId);

Wykonujemy zadanie.

}

Dalsza część kodu znajduje się na następnej stronie.

734

Rozdział 17.

Kursory i zadania asynchroniczne

Kompletny kod pliku DrinkActivity.java (ciąg dalszy) // Klasa wewnętrzna służąca do aktualizacji danych napoju private class UpdateDrinkTask extends AsyncTask { private ContentValues drinkValues;

Dodajemy klasę AsyncTask do akty wności jako klasę wewnętrzną.

protected void onPreExecute() {

CheckBox favorite = (CheckBox) findViewById(R.id.favorite); drinkValues = new ContentValues(); drinkValues.put(“FAVORITE”, favorite.isChecked()); }

Przed wykonaniem kodu operującego na bazie danych zapisujemy wartość pola wyboru w obiekcie ContentValue przechowywanym w zmiennej drinkValues.

protected Boolean doInBackground(Integer... drinks) { int drinkId = drinks[0];

Wykonujemy kod operujący na bazie danych w tle.

SQLiteOpenHelper coffeinaDatabaseHelper = new CoffeinaDatabaseHelper(DrinkActivity.this); try { SQLiteDatabase db = coffeinaDatabaseHelper.getWritableDatabase(); db.update(“DRINK”, drinkValues, “_id = ?”, new String[] {Integer.toString(drinkId)}); db.close(); return true; } catch(SQLiteException e) {

Aktualizujemy kolumnę FAVORITE. Coffeina

return false; app/src/main

} } protected void onPostExecute(Boolean success) { if (!success) { Toast toast = Toast.makeText(DrinkActivity.this,

“Baza danych jest niedostępna”, Toast.LENGTH_SHORT); toast.show(); } } }

java com.hfad.coffeina

DrinkActivity.java

Jeśli operacje na bazie danych nie wykonane prawidłowo, to wyświet zostały lamy stosowny komunikat.

}

To już wszystko, co musisz zrobić, by utworzyć i wykonać obiekt AsyncTask. Sprawdźmy, co się stanie, kiedy uruchomimy aplikację.

jesteś tutaj  735

Jazda próbna

Jazda próbna aplikacji Kiedy uruchomimy aplikację i przejdziemy do napoju, możemy go oznaczyć jako ulubiony, klikając pole wyboru Ulubiony. Kliknięcie tego pola wciąż modyfikuje zawartość kolumny FAVORITE bazy danych, jednak tym razem kod odpowiedzialny za tę operację jest wykonywany w wątku działającym w tle.

W idealnym świecie cały kod związany z operacjami na bazach danych powinien być wykonywany w tle. My nie będziemy zmieniać kodu pozostałych aktywności aplikacji Coffeina w taki sposób, lecz nic nie stoi na przeszkodzie, byś Ty to zrobił.

Nasza aplikacja zapisuje dane w bazie danych, jednak tym razem robi to w wątku wykonywanym w tle.

Nie istnieją

P: Kiedyś już napisałem kod, który

po prostu wykonywał operacje na bazie danych, i wszystko działało świetnie. Czy takie operacje na bazie naprawdę trzeba wykonywać w tle?

O: W przypadku naprawdę małych

baz danych, takich jak nasza, czas dostępu do bazy danych będzie zapewne niezauważalny. Ale będzie to wynikało wyłącznie z małej wielkości bazy. Jeśli baza będzie większa, albo jeśli uruchomimy aplikację na wolniejszym urządzeniu, czas dostępu do bazy zacznie mieć większe znaczenie. Dlatego owszem — kod operujący na bazie danych zawsze powinien być wykonywany w tle.

P: Przypomnijcie mi: dlaczego

aktualizowanie widoków z wątku wykonywanego w tle jest błędem?

736

Rozdział 17.

O

głupie pytania

: Najkrócej rzecz ujmując, taka próba skończyłaby się zgłoszeniem wyjątku. Rozwijając nieco ten temat, należałoby stwierdzić, że wielowątkowe interfejsy użytkownika są niezwykle podatne na występowanie błędów. Android eliminuje ten problem, uniemożliwiając stosowanie takich rozwiązań.

P: Jeśli odczytanie danych z bazy

zajmuje kilka sekund, to co zobaczy użytkownik?

O: Użytkownik najpierw zobaczy puste

widoki, a dopiero po jakimś czasie zostaną one wypełnione danymi.

P: Które fragmenty kodu

P: Dlaczego w zadaniu AsyncTask

O: Nie sposób tego ogólnie określić. Jeśli

O: Chcieliśmy Ci pokazać sposób

wykonującego operacje na bazie danych są najwolniejsze?

używana baza danych ma złożoną strukturę, to jej pierwsze otwarcie może zająć sporo czasu, bo będzie wymagało utworzenia wszystkich tabel. Także wykonanie złożonych zapytań może zajmować bardzo dużo czasu. Ogólnie rzecz biorąc, należy podejść do zagadnienia ostrożnie i wszystkie operacje na bazie danych wykonywać w tle.

umieściliście kod wykonujący operacje na bazie danych z tylko jednej aktywności?

korzystania z klasy AsyncTask na jednej aktywności w ramach przykładu. W praktyce tak samo należy wykonywać wszystkie operacje na bazach danych we wszystkich aktywnościach.

Kursory i zadania asynchroniczne

Opanowałeś już rozdział 17. i dodałeś do swojego przybornika z narzędziami operacje zapisu w bazach danych SQLite.

j Pełny kod przykładowe j ane tow aplikacji prezen z żes mo ale w tym rozdzi P FT ra we ser z pobrać wydawnictwa Helion: ftp://ftp.helion.pl/ przyklady/andrr2.zip

CELNE SPOSTRZEŻENIA 

Metoda changeCursor() klasy CursorAdapter zastępuje kursor obecnie używany przez adapter nowym kursorem. Wcześniejszy kursor jest zamykany.



Kod operacji na bazie danych powinien być wykonywany w tle przy użyciu klasy AsyncTask.

Podsumowanie etapów działania zadań AsyncTask onPreExecute()

1

2

doInBackground()

Metoda doInBackground() wykonuje zadanie w wątku działającym w tle.

Jest ona wywoływana bezpośrednio po metodzie onPreExecute(). Możemy określić, jakiego typu będą jej parametry i zwracany wynik.

3

onProgressUpdate()

Metoda onProgressUpdate() służy do przekazywania informacji o postępach wykonywanych prac.

Jest ona wykonywana w głównym wątku aplikacji, kiedy w metodzie doInBackground() wywołamy metodę publishProgress().

4

onPostExecute()

Metoda onPreExecute() służy do przygotowania zadania.

Jest ona wywoływana przed uruchomieniem zadania wykonywanego w tle i działa w głównym wątku aplikacji.

Metoda onPostExecute() służy do wyświetlania wyników wykonanego zadania po zakończeniu metody doInBackground().

Jest ona wykonywana w głównym wątku aplikacji. Jej parametrem jest wartość zwrócona przez metodę doInBackground().

jesteś tutaj  737

Rozdział 17.

Twój przybornik do Androida

738

Rozdział 17.

18. Usługi uruchomione

Do usług Czy wspominałem, że świadczę usługę WymuszanieHaraczyService?

Są operacje, które będziemy chcieli realizować niezależnie od tego, która aplikacja jest widoczna na ekranie. Na przykład jeśli rozpoczniesz pobieranie pliku, to zapewne nie będziesz chciał, by proces pobierania był przerywany, kiedy przełączysz się do innej aplikacji. W tym rozdziale przedstawimy usługi uruchomione, komponenty, które wykonują operacje w tle. Dowiesz się, jak stworzyć usługę uruchomioną, używając klasy IntentService, oraz w jaki sposób cykl życia takiej usługi jest powiązany z cyklem życia aktywności. W międzyczasie odkryjesz, jak można rejestrować komunikaty i zadbać o to, by użytkownik zawsze był dobrze poinformowany, wykorzystując do tego usługę powiadomień.

to jest nowy rozdział  739

Usługi

Usługi działają w tle Aplikacje na Androida są kolekcjami aktywności i innych zasobów. Znaczna większość ich kodu jest związana z wchodzeniem w interakcje z użytkownikiem, czasami jednak może się pojawić konieczność wykonywania jakichś operacji w tle, na przykład możemy chcieć pobrać duży plik, strumieniować utwór muzyczny lub nasłuchiwać wiadomości przesyłanych z serwera. Aktywności raczej nie nadają się do wykonywania takich operacji. W prostych przypadkach możemy utworzyć wątek i wykonywać w nim takie czynności, ale jeśli nie zachowamy ostrożności, to kod aplikacji szybko może się stać złożony i nieczytelny. I właśnie dlatego opracowano usługi (ang. services). Usługa jest komponentem aplikacji przypominającym nieco aktywność, lecz pozbawionym interfejsu użytkownika. Cykl życia usług jest prostszy od cyklu życia aktywności, a oprócz tego usługi są wyposażone w zestaw możliwości ułatwiających pisanie kodu, który będzie działał w tle, podczas gdy użytkownik będzie mógł zajmować się czymś innym.

Istnieją trzy typy usług Dostępne są trzy różne rodzaje usług:

 Usługi uruchomione. Usługi uruchomione (ang. started services) mogą działać w tle przez dowolnie długi czas, 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ści powiązane z usługą mogłyby cyklicznie prosić usługę o przekazanie informacji o pokonanym dystansie.

 Usługi zaplanowane. Usługa zaplanowana (ang. scheduled service) to usługa, której wykonanie zostało ustalone na określoną godzinę. Na przykład w API 21 i nowszych można zaplanować wykonanie zadania na określoną godzinę. W tym rozdziale przyjrzymy się tworzeniu usług uruchomionych.

740

Rozdział 18.

Oprócz pisania własnych usług, można także korzystać z wbudowanych usług Androida. Do usług wbudowanych należą: usługa powiadomień, usługa lokalizacji, usługa alarmu oraz usługa pobierania.

Usługi uruchomione

Utworzymy usługę URUCHOMIONĄ Utworzymy teraz nowy projekt aplikacji składającej się z jednej aktywności o nazwie MainActivity i usługi o nazwie DelayedMessageService. Za każdym razem, gdy aktywność odwoła się do usługi, ta poczeka 10 sekund, a następnie wyświetli fragment tekstu.

1… 2… 3… 4… 5… 6… 7… 8… 9… 10… A oto i tekst.

Aktywność MainActivity będzie używała tego układu.



activity_main.xml

Usługa wyświetli tekst po upływie 10 sekund.

Aktywność przekaże do usługi fragment tekstu.

MainActivity.java

DelayedMessageService.java

Aplikację napiszemy w dwóch etapach:

1

Wyświetlenie komunikatu w dzienniku Androida.

2

Wyświetlenie komunikatu w obszarze powiadomień.

Zaczniemy od wyświetlenia komunikatu w dzienniku, tylko po to, abyśmy mogli się przekonać, że usługa działa prawidłowo. Ten dziennik możemy przejrzeć w Android Studio. Zmodyfikujemy naszą usługę DelayedMessageService tak, by korzystała z wbudowanej usługi powiadomień Androida i wyświetlała komunikaty w systemowym obszarze powiadomień.

Utworzymy to powiadomienie.

Utworzenie projektu Zaczniemy od utworzenia projektu. Utwórz zatem nowy projekt aplikacji na Androida, o nazwie Wic, używając nazwy domeny hfad.com, tak by jej kody należały do pakietu com.hfad.wic. Minimalną wersją SDK powinno być API poziomu 19., 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. Podczas tworzenia aktywności upewnij się, że pole wyboru Backwards Compatibility (AppCompat) nie będzie zaznaczone. Kolejną rzeczą, którą się zajmiemy, będzie utworzenie usługi.

jesteś tutaj  741

IntentService

Użycie klasy IntentService do utworzenia prostej usługi uruchomionej

¨  Dziennik

¨ Wyświetlenie komunikatu

Najprostszym sposobem utworzenia usługi uruchomionej jest rozszerzenie klasy IntentService, gdyż udostępnia ona większość niezbędnych możliwości funkcjonalnych. Taką usługę uruchamia się przy użyciu intencji, a kod usługi jest wykonywany w osobnym wątku. Dodamy teraz do projektu nową usługę typu IntentService. W tym celu w eksploratorze Android Studio przełącz się do widoku Project, kliknij pakiet com.hfad.wic umieszczony w katalogu app/src/main/java i z menu głównego IDE wybierz opcję File/New…/Service/Service (IntentService). W polu nazwy klasy wpisz DelayedMessageService i usuń zaznaczenie z pola wyboru Include helper start methods?, aby zminimalizować ilość kodu wygenerowanego przez Android Studio. W końcu kliknij przycisk Finish, po czym, po wygenerowaniu pliku DelayedMessageService.java, zmień jego kod na ten zamieszczony poniżej:

Określ nazwę usługi.

package com.hfad.wic; import android.app.IntentService; import android.content.Intent;

Usuń zaznaczenie tego pola wyboru.

Rozszerzamy klasę IntentService .

public class DelayedMessageService extends IntentService { Niektóre wersje Android Studio mogą zapytać, jaki ma być język źródłowy. W razie pojawienia się takiego pytania, wybierz Javę.

public DelayedMessageService() { super(”DelayedMessageService”); } musimy Kod, który ma wykonywać usługa, nt. umieścić w metodzie onHandleInte

@Override protected void onHandleIntent(Intent intent) { // Tu coś robimy } }

Powyższy kod to wszystko, czego potrzebujemy, by utworzyć prostą usługę uruchomioną. Wystarczy rozszerzyć klasę IntentService, dodać do niej publiczny konstruktor oraz zaimplementować metodę onHandleIntent(). Metoda onHandleIntent() powinna zawierać kod, który chcemy wykonać za każdym razem, gdy przekażemy do usługi intencję. Ten kod będzie wykonywany w odrębnym wątku. Jeśli do usługi przekazanych zostanie więcej intencji, to będą one obsługiwane kolejno, jedna po drugiej. My chcemy, by usługa DelayedMessageService wyświetlała komunikat w dzienniku Androida; zobaczmy zatem, jak to zrobić.

742

Rozdział 18.

Wic app/src/main java com.hfad.wic DelayedMessage Service.java

Usługi uruchomione

Jak rejestrować komunikaty?

¨  Dziennik

¨ Wyświetlenie komunikatu

Dodawanie komunikatów do dziennika jest użytecznym sposobem sprawdzania, czy kod działa zgodnie z oczekiwaniami. Komunikaty, które mają być rejestrowane, podajemy w kodzie Javy, a podczas działania aplikacji sprawdzamy wyniki w systemowym dzienniku Androida (określanym także jako logcat). Komunikaty można rejestrować, używając poniższych metod klasy Android.util.Log: Log.v(String znacznik, String komunikat)

Rejestruje tak zwany „przegadany” komunikat.

Log.d(String znacznik, String komunikat)

Rejestruje komunikat używany do debugowania.

Log.i(String znacznik, String komunikat)

Rejestruje komunikat informacyjny.

Log.w(String znacznik, String komunikat)

Rejestruje komunikat ostrzegawczy.

Log.e(String znacznik, String komunikat)

Rejestruje komunikat o błędzie.

Każdy komunikat składa się z łańcucha znaków określanego jako „znacznik”, który identyfikuje źródło komunikatu, oraz z samego komunikatu. Na przykład aby zarejestrować „przegadany” komunikat pochodzący z usługi DelayedMessageService, możemy użyć następującego wywołania metody Log.v(): Log.v(”DelayedMessageService”, ”To jest komunikat.”);

Zawartość dziennika można przeglądać w Android Studio, filtrując ją dodatkowo na podstawie typu komunikatów. Aby wyświetlić dziennik, należy kliknąć opcję Android u dołu okna Android Studio, a następnie przejść na kartę logcat. Komunikaty można filtrować na podstawie ich typu.

Dostępna jest także metoda Log.wtf(), przeznaczona do rejestrowania wyjątków, które nigdy nie powinny wystąpić. Zgodnie z dokumentacją Androida „wtf” oznacza „What a Terrible Failure” (czyli „Cóż za straszliwa awaria”). My jednak wiemy, że w rzeczywistości to skrót od słów „Welcome to Fiskidagurinn” („Witamy w Fiskidagurinn”), które odwołują się do festiwalu Święta Ryby, odbywającego się co roku w miejscowości Dalvik na Islandii. Często można usłyszeć programistów Androida, którzy mówią: „Moje AVD uruchamia się 8 minut! WTF??”, oddając w ten sposób hołd niewielkiej miejscowości, której nazwa została użyta do określenia formatu wykonywalnych kodów bajtowych Androida.

To jest obszar dziennika logcat. Tu będą wyświetlane wszystkie zarejestrowane komunikaty. Wybierz opcję Logcat.

jesteś tutaj  743

Kod usługi DelayedMessageService

Kompletny kod usługi DelayedMessageService

¨  Dziennik

¨ Wyświetlenie komunikatu

Chcemy, by nasza usługa pobrała fragment tekstu przekazany w intencji, odczekała 10 sekund, a następnie zapisała pobrany tekst w dzienniku systemowym. W tym celu napiszemy metodę showText(), która będzie rejestrowała tekst, a potem wywołamy ją z odpowiednim opóźnieniem w kodzie metody onHandleIntent(). Oto pełny kod naszej usługi, zapisany w pliku DelayedMessageService.java (zaktualizuj zawartość tego pliku w swoim projekcie (tak by była identyczna z tą pokazaną poniżej): package com.hfad.wic; import android.app.IntentService; import android.content.Intent; Używamy klasy Log, więc musimy ją zaimportować.

import android.util.Log;

public class DelayedMessageService extends IntentService { public static final String EXTRA_MESSAGE = ”message”; public DelayedMessageService() { super(”DelayedMessageService”); }

Do przekazywania treści komunikatu z aktywności do usługi użyjemy stałej.

wej. Wywołujemy konstruktor klasy bazo

Ta metoda zawiera kod, który chce wywołać, kiedy usługa odbierze my intencję.

@Override

protected void onHandleIntent(Intent intent) { synchronized (this) { try { wait(10000);

Wic app/src/main

Czekamy 10 sekund.

java

} catch (InterruptedException e) { e.printStackTrace(); }

Pobieramy tekst z intencji.

} String text = intent.getStringExtra(EXTRA_MESSAGE); showText(text); Wywołujemy metodę showText().

}

private void showText(final String text) { Log.v(”DelayedMessageService”, ”Treść komunikatu: ” + text); } }

744

Rozdział 18.

To wywołanie rejestruje fragment tekstu, dzięki czemu możemy go zobaczyć na karcie logcat w Android Studio.

com.hfad.wic DelayedMessage Service.java

Usługi uruchomione

Usługi są deklarowane w pliku AndroidManifest.xml

¨  Dziennik

¨ Wyświetlenie komunikatu

Tak samo jak aktywność, usługę należy zadeklarować w pliku manifestu, AndroidManifest.xml, tak by Android mógł ją wywołać — jeśli usługa nie zostanie zadeklarowana, Android nie będzie wiedzieć, że istnieje, i nie będzie mógł jej wywołać. Android Studio powinno automatycznie aktualizować plik AndroidManifest.xml za każdym razem, gdy tworzymy nową usługę, dodając do niego nowy element . Oto jak wygląda nasz plik manifestu:









Nazwa usługi jest poprzedzona znakiem kropki, aby Android mógł umieścić prze d pakietu, uzyskując w ten sposób nią nazwę pełną nazwę klasy.

Element ma dwa atrybuty: name oraz exported. Pierwszy z nich, name, określa nazwę usługi — w naszym przykładzie jest to DelayedMessageService. Drugi atrybut, exported, informuje system, czy dana usługa może być używana przez inne aplikacje. Przypisanie temu atrybutowi wartości false oznacza, że usługa będzie używana wyłącznie wewnątrz bieżącej aplikacji. A teraz, kiedy już mamy usługę, zadbajmy o to, by aktywność MainActivity ją uruchamiała.

jesteś tutaj  745

Dodanie przycisku

Dodajemy przycisk do układu activity_main.xml

¨  Dziennik

¨ Wyświetlenie komunikatu

Chcemy, aby aktywność MainActivity uruchomiła usługę DelayedMessageService po kliknięciu przycisku. Zaczniemy zatem od dodania przycisku do układu używanego przez tę aktywność. W pierwszej kolejności dodaj do pliku strings.xml poniższe zasoby łańcuchowe: Jaki jest sekret komedii? Wyczucie czasu!

Wic app/src/main res

A teraz zmień zawartość swojego pliku activity_main.xml na tę przedstawioną poniżej, tak by w aktywności MainActivity był wyświetlany przycisk:

values



strings.xml

layout



Każde kliknięcie przycisku spowoduje wywołanie metody onClick(), musimy ją zatem dodać do aktywności MainActivity.

746

Rozdział 18.

Ten element tworzy przycisk. Po jego kliknięciu zostanie wywołana metoda onClick() aktywności.

Usługi uruchomione

Usługę uruchamiamy, wywołując metodę startService()

¨  Dziennik

¨ Wyświetlenie komunikatu

Użyjemy metody onClick() aktywności MainActivity, by uruchamiać usługę za każdym razem, gdy użytkownik kliknie przycisk. Usługę uruchamiamy w kodzie aktywności w podobny sposób, w jaki uruchamiamy inne aktywności. Musimy w tym celu utworzyć intencję jawną skierowaną do usługi, którą chcemy uruchomić. Następnie wywołujemy metodę startService(), przekazując do niej utworzoną intencję. Oto kod naszej aktywności: Intent intent = new Intent(this, DelayedMessageService.class); startService(intent);

Oto nasz kod pliku MainActivity.java; zaktualizuj go w swoim projekcie, tak by był identyczny z naszym:

Uruchamianie usługi przypomina uruchamianie aktywności, z tym że używamy w tym celu metody startService() a nie startActivity().

package com.hfad.wic; Wic

import android.app.Activity; import android.content.Intent; import android.os.Bundle;

Używamy tych wszystkich klas, więc musimy je zaimportować.

import android.view.View;

app/src/main java

public class MainActivity extends Activity { W naszym projekcie używamy klasy Activity, lecz równie dobrze można by użyć klasy AppCompatActivity.

@Override

protected void onCreate(Bundle savedInstanceState) {

DelayedMessage Service.java

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ę.

To cały kod niezbędny do uruchamiania usługi z poziomu aktywności. Zanim weźmiemy aplikację na jazdę próbną, przeanalizujmy krok po kroku, co się stanie po jej uruchomieniu.

jesteś tutaj  747

Co się dzieje?

Co się stanie po uruchomieniu aplikacji?

¨  Dziennik

¨ Wyświetlenie komunikatu

Poniżej pokazaliśmy, co się stanie po uruchomieniu aplikacji:

1

Aktywność MainActivity uruchamia usługę DelayedMessageService, wywołując metodę startService() i przekazując do niej intencję.

Intencja zawiera komunikat, który aktywność MainActivity chciałaby wyświetlić przy użyciu usługi DelayedMessageService; w naszym przykładzie komunikat ten ma treść: „Wyczucie czasu!”. Intencja

MainActivity

2

”Wyczucie czasu!!” DelayedMessageService

Kiedy usługa DelayedMessageService odbiera intencję, zostaje wywołana jej metoda onHandleIntent().

Usługa DelayedMessageService czeka 10 sekund.

Oho, dostałam jakiś tekst. Świetnie, wiem, co z nim zrobić… 1… 2… 3… 4…

onHandleIntent() DelayedMessageService

3

Usługa DelayedMessageService rejestruje komunikat. Log.v()

DelayedMessageService

4

Komunikat to: Wyczucie czasu! Log

Kiedy usługa DelayedMessageService zakończy działanie, zostaje usunięta.

DelayedMessageService

Weźmy teraz aplikację na jazdę próbną, aby sprawdzić, jak działa.

748

Rozdział 18.

Usługi uruchomione

Jazda próbna aplikacji

¨  Dziennik

¨ Wyświetlenie komunikatu

Po uruchomieniu aplikacji zostanie wyświetlona aktywność MainActivity:

A oto i przycisk.

A teraz kliknij przycisk i przejdź do Android Studio. W IDE wyświetl zawartość dziennika — najpierw kliknij opcję Android, umieszczoną u dołu okna programu z lewej strony, a następnie przejdź na kartę logcat. Po 10 sekundach w dzienniku zostanie wyświetlony komunikat „Wyczucie czasu!”.

To jest panel z zawartością dziennika.

12-03 23:09:01.412

11913-11977/com.hfad.wic V/DelayedMessageService: Treść komunikatu: Wyczucie czasu!

Skoro wiemy, że usługa działa, dowiedzmy się czegoś więcej na temat sposobu działania usług uruchomionych.

Po 10-sekundowym opóźnieniu w dzienniku zostanie wyświetlony nasz komunikat.

jesteś tutaj  749

Stany usług uruchomionych

Stany usług uruchomionych Kiedy komponent aplikacji (taki jak aktywność) uruchamia usługę, ta przechodzi ze stanu „utworzona” do stanu działająca, a następnie do stanu usunięta. Usługa uruchomiona przez przeważającą większość czasu znajduje się w stanie uruchomiona — odpowiada on sytuacji, gdy usługa została utworzona przez inny komponent aplikacji, a jej kod jest wykonywany w tle. Usługa może działać, nawet jeśli komponent, który ją początkowo utworzył, został usunięty. Kiedy usługa wykona swe zdanie, przechodzi do stanu usunięta.

Usługa utworzona

Usługa działająca

Usługa usunięta

Obiekt usługi został utworzony.

Usługa została uruchomiona i właśnie w tym stanie pozostaje przez większość czasu.

W tym momencie usługa już nie istnieje.

Podobnie jak w przypadku aktywności, kiedy usługa przechodzi od stanu utworzona do usunięta, wywoływane są kluczowe metody jej cyklu życia, które dziedziczy po swojej klasie nadrzędnej. Bezpośrednio po utworzeniu usługi wywoływana jest jej metoda onCreate(). Należy ją przesłonić, aby wykonać jakiekolwiek czynności związane z przygotowaniem usługi do działania. Kiedy usługa będzie już gotowa do uruchomienia, wywoływana jest jej metoda onStardCommand(). W razie stosowania usługi IntentService (a zazwyczaj właśnie tak się dzieje w przypadku korzystania z usług uruchomionych) metoda ta, ogólnie rzecz biorąc, nie jest przesłaniana. Zamiast tego wszelki kod, który usługa ma wykonać, dodajemy do metody onHandleIntent(), wywoływanej po metodzie onStartCommand(). Metoda onDestroy() jest wywoływana, kiedy usługa przestała już działać i ma zostać usunięta. Metodę tę przesłaniamy w celu wykonywania wszelkich czynności porządkowych, takich jak zwolnienie zasobów. Na następnej stronie przyjrzymy się dokładniej, jak te metody odpowiadają różnym stanom, w jakich może się znajdować usługa.

750

Rozdział 18.

Usługa uruchomiona zaczyna działać, kiedy zostanie uruchomiona. Metoda onCreate() jest wywoływana po pierwszym utworzeniu usługi i jest miejscem, w którym należy wykonywać wszelkie czynności inicjujące. Metoda onDestroy() jest wywoływana bezpośrednio przed usunięciem usługi.

Usługi uruchomione

Cykl życia usług uruchomionych: od utworzenia do usunięcia Poniżej przedstawiliśmy przegląd cyklu życia usług uruchomionych od ich narodzin aż do śmierci.

Usługa utworzona

1

Komponent wywołuje metodę startService() i usługa zostaje utworzona.

2

Bezpośrednio po utworzeniu usługi zostaje wywołana jej metoda onCreate().

To właśnie w tej metodzie należy umieszczać wszelki kod inicjujący, gdyż jest ona zawsze wywoływana po utworzeniu usługi, lecz zanim zacznie być ona wykonywana.

onCreate()

onStartCommand()

3

Jeśli nasza usługa dziedziczy po IntentService (jak jest w przeważającej większości przypadków), to metoda onStartCommand() tworzy osobny wątek, a następnie jest wywoływana metoda onHandleIntent(). Cały kod, który usługa ma wykonywać w tle, należy umieszczać właśnie w metodzie onHandleIntent().

onHandleIntent()

Usługa działająca

4

Większość czasu swego istnienia usługa pozostaje w stanie działająca.

5

Metoda onDestroy() jest wywoływana, gdy usługa zakończy działanie i bezpośrednio przed jej usunięciem.

onDestroy()

Usługa usunięta

Metoda onStartCommand() zostaje wykonana przed uruchomieniem usługi.

Metoda onDestroy() pozwala wykonywać wszelkie czynności związane z zakańczaniem działania usługi, takie jak zwalnianie zasobów.

6

Po wykonaniu metody onDestroy() usługa zostanie usunięta.

Trzema głównymi metodami cyklu życia usług są: onCreate(), onStartCommand() oraz onDestroy(). A skąd one pochodzą?

jesteś tutaj  751

Metody cyklu życia

Nasza usługa dziedziczy metody cyklu życia Jak przekonałeś się we wcześniejszej części rozdziału, utworzona przez nas usługa uruchomiona dziedziczy po klasie android.app.IntentService. To właśnie ta klasa zapewnia usłudze dostęp do jej metod cyklu życia. Poniższy diagram przedstawia hierarchię klas naszej usługi:

Context

Klasa abstrakcyjna Context (android.content.Context)

Interfejs do globalnych informacji o środowisku aplikacji. Zapewnia dostęp do zasobów aplikacji, jej klas oraz operacji.

ContextWrapper

Klasa ContextWrapper (android.content.ContextWrapper)

Implementacja pośrednika dla klasy Context.

Service onCreate() onStartCommand()

Klasa Service (android.app.Service)

Klasa Service implementuje domyślne wersje metod cyklu życia. W dalszej części rozdziału znajdziesz więcej informacji na jej temat.

onDestroy()

IntentService onHandleIntent(Intent)

MojaUslugaUruchomiona onHandleIntent(Intent) mojaMetoda()

Klasa IntentService (android.app.IntentService)

Klasa IntentService ułatwia tworzenie usług uruchomionych. Definiuje ona metodę onHandleIntent(), która odbiera przekazywane do niej intencje i wykonuje je w wątku działającym tle.

Klasa MojaUslugaUruchomiona (com.hfad.costam)

Większość zachowań usługi uruchomionej jest obsługiwana przez metody klas bazowych, dziedziczone przez tworzoną usługę. Nam pozostaje jedynie przesłonić te z nich, które będą nam potrzebne, i dodać publiczny konstruktor.

Skoro już wiesz nieco więcej na temat sposobu działania usług uruchomionych, spróbuj wykonać ćwiczenie. Kiedy już to zrobisz, sprawdzimy, w jaki sposób usługa DelayedMessageService może wyświetlić przekazany do niej komunikat w powiadomieniu.

752

Rozdział 18.

Usługi uruchomione

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. public class WombleService extends .................. { public WombleService() { super(”WombleService”); } @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); } public void onClick(View view) { Intent intent = new Intent(this, ....................... ); ......................... (intent);

Nie będziesz potrzebował wszystkich magnesików.

} } IntentService

WombleService.class

onHandleIntent Underground

Overground

startActivity

startService

WombleService

jesteś tutaj  753

Rozwiązanie magnesików

Magnesiki usługowe. Rozwiązanie 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. IntentService public class WombleService extends .................. {

To jest usługa. Rozszerza ona klasę IntentService.

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.

public void onClick(View view) {

WombleService.class Intent intent = new Intent(this, .............................. ); (intent); startService .........................

} }

Tych magnesików nie musiałeś używać.

To wywołanie uruchamia usługę.

startActivity

Underground

754

Rozdział 18.

Overground

WombleService

Usługi uruchomione

Android dysponuje wbudowaną usługą obsługi powiadomień

¨  Dziennik ¨ Wyświetlenie komunikatu

Zmodyfikujemy teraz naszą aplikację Wic w taki sposób, aby komunikat był wyświetlany w formie powiadomienia. Powiadomienia to komunikaty wyświetlane poza interfejsem użytkownika aplikacji. W momencie utworzenia powiadomienia jest ono wyświetlane jako ikona na pasku stanu. Bardziej szczegółowe informacje o powiadomieniu można obejrzeć w szufladzie powiadomień, wyświetlanej po przesunięciu palcem od górnej krawędzi ekranu w dół: wyświetlane na Powiadomienia typu heads-up są umieszczonym nku okie ym ając pływ w czas ien pew u góry ekranu. To są ikony powiadomień.

To jest szuflada powiadomień.

W odróżnieniu od komunikatów Toast oraz Snackbar powiadomienia są dostępne poza aplikacją, która je wygenerowała, dzięki czemu użytkownik ma do nich dostęp niezależnie od tego, jakiej aplikacji w danej chwili używa (jeśli w ogóle jakiejś używa). Poza tym oferują one znacznie większe możliwości konfigurowania niż komunikaty Toast i Snackbar. Aby wyświetlić powiadomienie, trzeba skorzystać z jednej z wbudowanych usług Androida — usługi powiadomień. Na kilku następnych stronach pokażemy, jak to zrobić.

jesteś tutaj  755

Dodanie biblioteki wsparcia

Użyjemy powiadomień z biblioteki wsparcia AppCompat Do tworzenia powiadomień użyjemy klas pochodzących z biblioteki wsparcia AppCompat, dzięki czemu będą one działać spójnie na szerokiej gamie urządzeń z Androidem. Choć można wyświetlać powiadomienia, posługując się klasami z głównego wydania systemu Android, to jednak ostatnie modyfikacje, które zostały w nich wprowadzone, oznaczają, że najnowsze możliwości powiadomień nie byłyby dostępne na starszych urządzeniach. Zanim będziemy mogli użyć klas powiadomień pochodzących z biblioteki wsparcia, musimy dodać ją jako zależność do naszego projektu. W tym celu wybierz z menu głównego Android Studio opcję File/Project Structure, kliknij moduł app, po czym przejdź na kartę Dependencies. Może się zdarzyć, że Android Studio już wcześniej, automatycznie, doda bibliotekę wsparcia AppCompat. W takim przypadku zostanie ona wyświetlona jako appcompat-v7. Jeśli jednak nie została dodana automatycznie, to będziesz musiał ją dodać samemu. W tym celu kliknij przycisk „+” umieszczony z prawej strony lub u dołu okna dialogowego, wybierz opcję Library Dependency, następnie wybierz opcję appcompat-v7 i kliknij przycisk OK. Kliknij przycisk OK jeszcze raz, by zapisać zmiany i zamknąć okno dialogowe Project Structure.

¨  Dziennik ¨ Wyświetlenie komunikatu

Używaj powiadomień z biblioteki wsparcia AppCompat, aby zapewnić urządzeniom działającym pod kontrolą starszych wersji Androida dostęp do najnowszych możliwości.

To jest biblioteka wsparcia AppCompat v7.

Aby nasza usługa DelayedMessageService wyświetliła powiadomienie, musimy wykonać trzy czynności: utworzyć obiekt budowniczego powiadomień, poinstruować powiadomienie, że po kliknięciu ma uruchomić aktywność MainActivity, oraz wysłać powiadomienie. Kod realizujący te trzy operacje napiszemy na kilku kolejnych stronach, a następnie przedstawimy go jeszcze raz w całości.

756

Rozdział 18.

Usługi uruchomione

W pierwszej kolejności tworzymy budowniczego powiadomień

¨  Dziennik ¨ Wyświetlenie komunikatu

Pierwszą rzeczą, jaką musimy zrobić, jest utworzenie budowniczego powiadomień. Pozwala on na przygotowanie powiadomienia o określonych możliwościach i zawartości. Każde tworzone powiadomienie w najprostszym przypadku musi zawierać małą ikonę, tytuł oraz jakiś tekst. Poniżej przedstawiliśmy kod, który tworzy takie najprostsze powiadomienie:

Klasa NotificationCompat pochodzi z biblioteki wsparcia AppCompat.

NotificationCompat.Builder builder = new NotificationCompat.Builder(this) To wywołanie wyświetla niewielką ikonę; w tym przypadku jest to wbudowana ikona Androida.

.setSmallIcon(android.R.drawable.sym_def_app_icon) .setContentTitle(getString(R.string.question)) .setContentText(text);

Ustawiamy tytuł i treść powiadomienia.

Aby rozbudować możliwości powiadomienia, wystarczy dodać do powyższego kodu kolejne wywołanie odpowiedniej metody budowniczego powiadomień. W kolejnym przykładzie pokazaliśmy, jak zaznaczyć, że powiadomienie ma mieć wysoki priorytet, że urządzenie ma zawibrować w momencie jego zgłoszenia oraz że po kliknięciu powiadomienia ma ono zniknąć: NotificationCompat.Builder builder = new NotificationCompat.Builder(this) .setSmallIcon(android.R.drawable.sym_def_app_icon) .setContentTitle(getString(R.string.question)) Te dwa wywołania nadają powiadomieniu wysoki priorytet i sprawiają, że wygenerowanie powiadomienia spowoduje wibracje urządzenia.

Wystarczy powiększyć sekwencję wywołań, by rozszerzyć możliwości powiadomienia.

.setContentText(text) .setPriority(NotificationCompat.PRIORITY_HIGH) .setVibrate(new long[] {0, 1000}) .setAutoCancel(true);

To wywołanie powoduje, że powiadomienie zniknie, kiedy użytkownik je kliknie.

Czekamy 0 milisekund, po czym na 1000 milisekund włączamy wibracje urządzenia.

To tylko niektóre spośród właściwości powiadomień, które można ustawiać. Dostępne są także inne właściwości, na przykład określające widoczność powiadomienia i pozwalające na wyświetlanie go na ekranie blokady urządzenia, liczba wyświetlana w przypadku, gdybyśmy chcieli wysyłać wiele powiadomień z tej samej aplikacji, czy też komunikat mówiący czy należy odtwarzać jakiś dźwięk. Wszelkie informacje na temat tych (oraz wielu innych) właściwości można znaleźć w dokumentacji na stronie: https://developer.android.com/reference/android/support/v4/app/ NotificationCompat.Builder.html Teraz zajmiemy się dodaniem do powiadomienia akcji określającej aktywność, którą należy uruchomić po kliknięciu powiadomienia.

Powiadomienia typu heads-up (czyli te, wyświetlane w pływającym okienku u góry ekranu) tworzymy, ustawiając priorytet powiadomienia na wysoki i włączając wibracje lub odtwarzając jakiś dźwięk. jesteś tutaj  757

Dodanie akcji do powiadomienia

¨  Dziennik ¨ Wyświetlenie komunikatu

Dodajemy akcję, by określić, którą aktywność należy uruchomić W przypadku tworzenia powiadomień dobrym pomysłem jest dodawanie do nich akcji określających, którą aktywność należy wyświetlić, gdy użytkownik kliknie powiadomienie. Na przykład klient poczty elektronicznej może generować powiadomienie po odebraniu nowej wiadomości i wyświetlać treść tej wiadomości po kliknięciu tego powiadomienia. W naszej przykładowej aplikacji ograniczymy się do wyświetlenia aktywności MainActivity. Akcję dodaje się poprzez utworzenie intencji oczekującej, PendingIntent, uruchamiającej określoną aktywność i dodanie tej intencji do powiadomienia. Intencja oczekująca to intencja, którą nasza aplikacja może przekazywać do innych aplikacji. Taka aplikacja może następnie, w dowolnym późniejszym czasie, przesłać intencję w imieniu naszej aplikacji. Aby utworzyć intencję oczekującą, w pierwszej kolejności należy utworzyć intencję jawną odwołującą się do aktywności, którą chcemy uruchomić po kliknięciu powiadomienia. W naszym przykładzie jest to aktywność MainActivity, a zatem intencję możemy utworzyć w następujący sposób: iająca To jest normalna intencja urucham aktywność MainActivity.

Intent actionIintent = new Intent(this, MainActivity.class);

Takiej intencji możemy teraz użyć do utworzenia intencji oczekującej; w tym celu należy zastosować metodę PendingIntent.getActivity(). PendingIntent actionPendingIntent = PendingIntent.getActivity( To jest flaga, której można użyć, jeśli kiedykolwiek będziemy musieli pobrać intencję oczekującą. My nie musimy tego robić, więc przypisujemy jej wartość 0.

this, 0,

Kontekst; w naszym przykładzie to bieżąca usługa.

actionIntent,

śmy powyżej. To jest intencja, którą utworzyli

PendingIntent.FLAG_UPDATE_CURRENT);

Metoda getActivity() ma cztery parametry: kontekst (zazwyczaj jest to referencja this), liczbę całkowitą określającą kod żądania, zdefiniowaną wcześniej intencję jawną oraz flagę określającą zachowanie intencji oczekującej. W powyższym kodzie zastosowaliśmy flagę FLAG_UPDATE_CURRENT. Oznacza ona, że jeśli już istnieje intencja oczekująca odpowiadająca bieżącej, to skojarzone z nią dodatkowe informacje zostaną zaktualizowane na podstawie zawartości bieżącej intencji. Innymi możliwymi wartościami tego parametru są: FLAG_CANCEL_CURRENT (powoduje anulowanie wszelkich intencji oczekujących odpowiadających tej bieżącej przed wygenerowaniem nowej), FLAG_NO_CREATE (sprawia, że jeśli już istnieje intencja oczekująca odpowiadająca tej bieżącej, to nowa nie zostanie utworzona) oraz FLAG_ONE_SHOT (intencji oczekującej można użyć tylko raz).

To oznacza, że jeśli istnieje inna intencja oczekująca, odpowiadająca bieżącej, to zostanie ona zaktualizowana.

Po utworzeniu intencji oczekującej można ją dodać do powiadomienia, używając metody setContentIntent() obiektu budowniczego: builder.setContentIntent(actionPendingIntent);

To wywołanie nakazuje, by po kliknięciu powiadomienia przez użytkownika została uruchomiona aktywność określona w intencji.

758

Rozdział 18.

To wywołanie dodaje intencję do powiadomienia.

Usługi uruchomione

Wysyłanie powiadomień przy użyciu wbudowanej usługi systemowej

¨  Dziennik ¨ Wyświetlenie komunikatu

I w końcu możemy wysłać powiadomienie, używając systemowej usługi powiadomień. W tym celu w pierwszej kolejności musimy pobrać obiekt NotificationManager. Aby to zrobić, należy wywołać metodę getSystemService(), przekazując do niej wartość NOTIFICATION_SERVICE:

Przekazanie tej wartości zapewnia dostęp do systemowej usługi powiadomień Androida.

NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);

Tak pobranego obiektu NotificationManager można użyć do wysłania powiadomień — wystarczy wywołać jego metodę notify(). Metoda ta pobiera dwa parametry: identyfikator powiadomienia oraz obiekt Notification. Pierwszy z tych parametrów służy do identyfikacji powiadomienia. Jeśli w przyszłości prześlemy drugie powiadomienie, używając tego samego identyfikatora, to zastąpi ono powiadomienie wysłane wcześniej. Rozwiązanie to jest przydatne w sytuacjach, kiedy chcemy zaktualizować istniejące powiadomienie i zapisać w nim nowe informacje. Obiekt Notification tworzy się poprzez wywołanie metody build() obiektu budowniczego powiadomień. Utworzone w ten sposób powiadomienie zawiera wszystkie treści i posiada wszelkie cechy określone przy użyciu metod obiektu budowniczego powiadomień. Poniżej przedstawiliśmy kod wysyłający powiadomienie:

ali To liczba, której będziemy używ ia. jako identyfikatora powiadomien

public static final int NOTIFICATION_ID = 5453; ... NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); notificationManager.notify(NOTIFICATION_ID, builder.build());

To już wszystko, czego potrzebujemy, by utworzyć i wysłać powiadomienie. Na następnej stronie przedstawimy kompletny kod usługi DelayedMessageService.

gi W tym wywołaniu używamy usłu e rzon utwo łać wys by wej, systemo wcześniej powiadomienie.

jesteś tutaj  759

Kod usługi DelayedMessageService

Kompletny kod usługi DelayedMessageService

¨  Dziennik ¨ Wyświetlenie komunikatu

Poniżej zamieściliśmy kompletny kod usługi zapisany w pliku DelayedMessageService.java. Teraz zamiast tostu usługa generuje powiadomienie: package com.hfad.wic; Wic

import android.app.IntentService; import android.content.Intent; import android.util.Log;

app/src/main

Usuń ten wiersz.

import android.support.v4.app.NotificationCompat;

Używamy tych dodatkowych klas, więc musimy je zaimportować.

import android.app.PendingIntent; import android.app.NotificationManager;

public class DelayedMessageService extends IntentService { public static final String EXTRA_MESSAGE = ”message”; public static final int NOTIFICATION_ID = 5453; public DelayedMessageService() { super(”DelayedMessageService”); }

Ta wartość jest używana do identyfikacji powiadomienia. Może to być dowolna liczba, . my zdecydowaliśmy się na 5453

@Override protected void onHandleIntent(Intent intent) { synchronized (this) { try { wait(10000); } catch (InterruptedException e) { e.printStackTrace(); } } String text = intent.getStringExtra(EXTRA_MESSAGE); showText(text); }

760

Rozdział 18.

java com.hfad.wic DelayedMessage Service.java

Usługi uruchomione

Kod usługi DelayedMessageService (ciąg dalszy) private void showText(final String text) { Log.v(”DelayedMessageService”, ”Treść komunikatu: ” + text);

¨  Dziennik ¨ Wyświetlenie komunikatu

Wic app/src/main

// Tworzymy obiekt budowniczego powiadomień NotificationCompat.Builder builder =

java

new NotificationCompat.Builder(this) .setSmallIcon(android.R.drawable.sym_def_app_icon) Używamy obiektu budowniczego do określenia zawartości i cech powiadomienia.

com.hfad.wic

.setContentTitle(getString(R.string.question)) .setContentText(text) .setPriority(NotificationCompat.PRIORITY_HIGH)

DelayedMessage Service.java

.setVibrate(new long[]{0, 1000}) .setAutoCancel(true); // Tworzymy akcję

Tworzymy intencję.

Intent actionIntent = new Intent(this, MainActivity.class); PendingIntent actionPendingIntent = PendingIntent.getActivity( this, 0,

Używamy intencji do utworzenia intencji oczekującej.

actionIntent,

PendingIntent.FLAG_UPDATE_CURRENT); builder.setContentIntent(actionPendingIntent); // Wysyłamy powiadomienie

Dodajemy intencję oczekującą do powiadomienia.

NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); notificationManager.notify(NOTIFICATION_ID, builder.build()); } }

Wyświetlamy powiadomienie, używając menedżera powiadomień.

To cały kod naszej usługi uruchomionej. Sprawdźmy teraz, co się stanie, gdy go uruchomimy.

jesteś tutaj  761

Co się dzieje?

Co się stanie po uruchomieniu aplikacji?

¨  Dziennik ¨ Wyświetlenie komunikatu

Zanim sprawdzimy, jak działa aplikacja, spróbujmy przeanalizować krok po kroku, co się stanie po jej uruchomieniu:

1

Aktywność MainActivity uruchamia usługę DelayedMessageService, wywołując metodę startService() i przekazując do niej intencję.

Intencja zawiera komunikat, który ma wyświetlić usługa DelayedMessageService. Intencja

MainActivity

2

”Wyczucie czasu!” DelayedMessageService

Usługa DelayedMessageService czeka 10 sekund. 1...2...3...4...

DelayedMessageService

3

Usługa DelayedMessageService tworzy obiekt budowniczego powiadomień i używa go do określenia szczegółów konfiguracji powiadomienia. icon=sym_def_app_icon title=”Jaki jest sekret komedii?” text=”Wyczucie czasu!” DelayedMessageService

4

NotificationCompat.Builder

Usługa DelayedMessageService tworzy intencję skierowaną do aktywności MainActivity, której następnie używa do utworzenia intencji oczekującej. Intencja oczekująca Do: MainActivity DelayedMessageService

762

Rozdział 18.

Usługi uruchomione

Ciąg dalszy historii 5

¨  Dziennik ¨ Wyświetlenie komunikatu

Usługa DelayedMessageService dodaje intencję oczekującą do budowniczego powiadomień. Intencja oczekująca

icon=sym_def_app_icon title=”Jaki jest sekret komedii?” text=”Wyczucie czasu!”

Do: MainActivity

NotificationCompat.Builder

DelayedMessageService

6

Usługa DelayedMessageService tworzy obiekt NotificationManager i wywołuje jego metodę notify().

Usługa powiadomień wyświetla powiadomienie na ekranie urządzenia. Intencja oczekująca notify() Do: MainActivity DelayedMessageService

7

NotificationManager

icon=sym_def_app_icon title=”Jaki jest sekret komedii?” Notification text=”Wyczucie czasu!”

Kiedy użytkownik kliknie powiadomienie, intencja oczekująca przekazana w obiekcie Notification uruchamia aktywność MainActivity. Intencja

Notification

MainActivity

A teraz, skoro już przeanalizowaliśmy działanie kodu, zabierzmy naszą nową wersję usługi na jazdę próbną.

jesteś tutaj  763

Jazda próbna

¨  Dziennik ¨ Wyświetlenie komunikatu

Jazda próbna aplikacji 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.

Kiedy powiadomienie zniknie, jego ikona pozostanie widoczna na pasku stanu.

Kliknij przycisk, a po upłynięciu określonego czasu pojawi się powiadomienie.

Kiedy otworzymy szufladę powiadomień i klikniemy powiadomienie, zostanie uruchomiona aktywność MainActivity.

Kliknięcie powiadomienia spowoduje uruchomienie aktywności MainActivity — dokładnie tak, jak chcieliśmy.

A zatem wiesz już, jak utworzyć usługę uruchomioną, która wyświetla powiadomienia, używając usługi systemowej. W następnym rozdziale zajmiemy się usługami powiązanymi.

764

Rozdział 18.

Usługi uruchomione

Twój przybornik do Androida

Rozdział 18.

Opanowałeś już rozdział 18. i dodałeś do swojego przybornika z narzędziami umiejętność tworzenia i stosowania usług uruchomionych.

CELNE SPOSTRZEŻENIA 













Usługa jest komponentem, który może wykonywać zadania w tle. Usługi nie mają własnego interfejsu użytkownika. Usługa uruchomiona może działać w tle dowolnie długo, nawet po zakończeniu działania aktywności, która ją uruchomiła. Po zakończeniu wykonywanych operacji usługa jest zatrzymywana. Usługa powiązana jest skojarzona z innym komponentem, takim jak aktywność. Aktywność może prowadzić interakcję z taką usługą i pobierać z niej wyniki. Aby utworzyć prostą usługę uruchomioną, należy napisać klasę dziedziczącą po IntentService i zaimplementować jej metodę onHandleIntent(). Klasa IntentService została zaprojektowana do obsługi intencji. Usługi należy zadeklarować w pliku manifestu AndroidManifest.xml, używając elementu . Do uruchamiania usług służy metoda startService(). Po utworzeniu usługi uruchomionej wywoływana jest jej metoda onCreate(), a następnie metoda onStartCommand(). Jeśli usługa jest typu IntentService, to następnie, w odrębnym wątku, wywoływana jest metoda onHandleIntent(). Po zakończeniu działania usługi zostaje wywołana metoda onDestroy(), po czym usługa jest usuwana.













Klasa IntentService dziedziczy metody cyklu życia usługi po klasie Service. Do rejestrowania komunikatów służy klasa Android.util.Log. Komunikaty rejestrowane przy jej użyciu można przeglądać w panelu Logcat w Android Studio. Powiadomienia tworzy się przy użyciu budowniczego powiadomień. Absolutnym minimum, które musi zawierać każde powiadomienie, jest mała ikona i jakiś tekst. Powiadomienia typu heads-up mają ustawiony wysoki priorytet, a po wysłaniu takiego powiadomienia włączane są wibracje lub odtwarzane dźwięki. Aby określić aktywność, która zostanie uruchomiona po kliknięciu powiadomienia, należy utworzyć intencję oczekującą i dodać ją do powiadomienia jako akcję. Do wysyłania powiadomień służy menedżer powiadomień; tworzy się go przy użyciu systemowej usługi powiadomień. j aplikacji Pełny kod przykładowe dziale możesz roz prezentowanej w tym dawnictwa wy P FT pobrać z serwera rzyklady/ l/p n.p lio .he ftp Helion: ftp:// andrr2.zip

jesteś tutaj  765

766

Rozdział 18.

19. Usługi powiązane i uprawnienia

Powiązane ze sobą Dziś uprawnienie PHONE_CALL. Jutro całkowita dominacja nad światem. Buahahaha…

Usługi uruchomione są doskonałe do wykonywania operacji w tle, ale co zrobić, gdy potrzebujemy usługi, która będzie bardziej interaktywna? W tym rozdziale dowiesz się, jak tworzyć usługi powiązane, czyli usługi kolejnego typu, z którymi aktywności mogą wchodzić w interakcje. Dowiesz się także, jak powiązać usługę, kiedy to będzie potrzebne, oraz jak ją odłączyć w celu oszczędzania zasobów, kiedy nie będzie już potrzebna. Przy okazji nauczysz się korzystać z usług lokalizacyjnych Androida, by pobierać z odbiornika GPS urządzenia aktualne informacje o położeniu. I w końcu dowiesz się, jak używać modelu uprawnień Androida, w tym także jak obsługiwać żądania przydzielenia uprawnień zgłaszane podczas działania aplikacji.

to jest nowy rozdział  767

Usługi powiązane

Usługi powiązane są skojarzone z innymi komponentami Jak miałeś okazję się przekonać w rozdziale 18., usługi uruchomione to usługi, których realizacja rozpoczyna się od przesłania intencji. Usługi tego typu wykonują kod w tle, a ich działanie kończy się, kiedy wszystkie operacje zostaną wykonane. Takie usługi działają nawet wtedy, gdy komponent, który je uruchomił, zostanie usunięty. Z kolei usługa powiązana to taka, która jest skojarzona z innym komponentem aplikacji, na przykład z aktywnością. W odróżnieniu od usług uruchomionych komponenty mogą wchodzić w interakcje z usługami powiązanymi i wywoływać ich metody. Aby przedstawić taką usługę w akcji, napiszemy aplikację drogomierza i wykorzystamy w niej usługę powiązaną. Konkretnie rzecz biorąc, zastosujemy usługi lokalizacyjne systemu Android do mierzenia przebytej drogi.

Poprosimy o regularne przekazywanie aktualnych informacji o położeniu urządzenia, na ich podstawie zmierzymy przebyty dystans, który następnie wyświetlimy na ekranie.

Na następnej stronie przyjrzymy się poszczególnym etapom prac, które wykonamy, tworząc tę aplikację.

768

Rozdział 19.

Usługi powiązane i uprawnienia

Oto co zamierzamy zrobić Poniżej przedstawiliśmy trzy główne etapy prac nad aplikacją:

1

Utworzenie prostej wersji usługi powiązanej o nazwie OdometerService.

Zaimplementujemy w niej metodę getDistance(), która na razie będzie zwracać wartość losową.

OdometerService

2

Zapewnienie możliwości połączenia aktywności MainActivity z usługą OdometerService i wywołanie jej metody getDistance().

Ta metoda będzie wywoływana co sekundę, a jej zadaniem będzie wyświetlanie wyników na ekranie aktywności MainActivity. getDistance()

0.23

MainActivity

3

OdometerService

Aktualizacja usługi OdometerService i zastosowanie w niej usług lokalizacyjnych Androida.

Usługa będzie otrzymywać aktualne dane o lokalizacji użytkownika i używać ich do wyliczenia pokonanego dystansu.

Czy jesteśmy już blisko?

OdometerService

Utworzenie nowego projektu Odometer Zaczniemy od utworzenia projektu. Utwórz zatem nowy projekt aplikacji na Androida, nadaj mu nazwę Drogomierz, jako domenę firmową wpisz hfad.com; w efekcie pakiet aplikacji będzie miał nazwę com.hfad.drogomierz. Jako minimalną wersję SDK wybierz API poziomu 19., tak by aplikacja działała na większości urządzeń. Aby kod Twojego projektu odpowiadał naszemu, będziesz potrzebował pustej aktywności o nazwie MainActivity, używającej układu activity_main. Podczas tworzenia aktywności koniecznie usuń zaznaczenie z pola wyboru Backwards Compatibility (AppCompat).

jesteś tutaj  769

Utworzenie usługi

Utworzenie nowej usługi

¨

Usługę powiązaną tworzy się poprzez rozszerzenie klasy Service. Jest ona bardziej ogólna od klasy IntentService, której użyliśmy w rozdziale 18. do utworzenia usługi uruchomionej. Rozszerzenie klasy Service zapewnia większą elastyczność, lecz wymaga także napisania bardziej rozbudowanego kodu. Ponieważ zamierzamy dodać do projektu nową usługę powiązaną, przejdź w eksploratorze Android Studio do widoku Project, w katalogu app/src/main/java zaznacz pakiet com.hfad. drogomierz, a następnie wybierz z menu głównego opcję File/New... i w końcu opcję Service (nie IntentService). Tworzonej usłudze nadaj nazwę OdometerService. Usuń zaznaczenie z pola wyboru Exported, gdyż trzeba je zaznaczać wyłącznie w przypadku, gdy do tworzonej usługi mają mieć dostęp usługi spoza aplikacji. Upewnij się także, że jest zaznaczone pole wyboru Enabled; w przeciwnym razie aktywność nie będzie w stanie uruchomić aplikacji. Następnie zastąp wygenerowany kod pliku OdometerService.java tym przedstawionym poniżej (wyróżnionym pogrubioną czcionką). package com.hfad.drogomierz; import android.app.Service; import android.content.Intent; import android.os.IBinder;

public IBinder onBind(Intent intent) { // kod tworzący powiązanie usługi }

W powyższym kodzie zaimplementowaliśmy tylko jedną metodę, onBind(). Jest ona wywoływana, kiedy komponent, taki jak aktywność, chce utworzyć powiązanie z usługą. Metoda onBind() ma jeden parametr, obiekt Intent, i zwraca obiekt typu IBinder. Interfejs IBinder służy do tworzenia powiązania usługi z aktywnością, a jego implementację musimy utworzyć w kodzie usługi. To właśnie tym interfejsem zajmiemy się w następnym kroku.

Rozdział 19.

Zaznacz pole wyboru Enabled.

com.hfad.drogomierz Metoda onBind() jest wywoływana, kiedy komponent chce utworzyć powiązanie z usługą. Odometer Service.java

}

770

Usuń zaznaczenie pola wyboru Exported.

Niektóre wersje Android Studio mogą także pytać o język źródłowy usługi. Drogomierz Jeśli będziesz musiał odpowiedzieć na to pytanie, wybierz język Java. app/src/main zy dzic dzie gi usłu ej Klasa nasz po klasie Service. java

public class OdometerService extends Service { @Override

OdometerService

¨ MainActivity ¨ Usługi lokalizacyjne

Usługi powiązane i uprawnienia

Zdefiniowanie obiektu Binder

¨

OdometerService

¨ MainActivity ¨ Usługi lokalizacyjne

Interfejs IBinder implementujemy, dodając do kodu usługi klasę wewnętrzną dziedziczącą po klasie Binder (która implementuje interfejs IBinder). Ta klasa wewnętrzna musi udostępniać metodę, której aktywności będą mogły używać do pobierania referencji do usługi powiązanej. W naszej aplikacji klasa Binder będzie nosić nazwę OdometerBinder. Aktywność MainActivity będzie jej używać do pobierania referencji do usługi OdometerService. Poniżej przedstawiliśmy kod stanowiący definicję tej klasy:

imy Tworząc usługi powiązane, mus Binder. y klas ję ntac eme impl yć arcz dost

public class OdometerBinder extends Binder { OdometerService getOdometer() { return OdometerService.this;

Aktywność użyje tej metody, by pobr referencję do usługi OdometerServ ać ice.

} }

Metoda onBind() klasy OdometerService musi zwracać instancję typu OdometerBinder. W tym celu zdefiniujemy nową zmienną prywatną, ustawimy jej wartość, a następnie będziemy zwracać tę zmienną w metodzie onBind(). Zmodyfikuj swoją wersję pliku OdometerService.java, tak by zawierała zmiany przedstawione poniżej: ... import android.os.Binder;

Drogomierz

Używamy tej dodatkowej klasy, więc musimy ją zaimportować.

Do przechowywania instancji IBinder używamy prywatnej zmiennej sfinalizowanej.

public class OdometerService extends Service {

private final IBinder binder = new OdometerBinder(); public class OdometerBinder extends Binder { OdometerService getOdometer() { return OdometerService.this;

app/src/main

Implementacja klasy Binder.

java com.hfad.drogomierz Odometer Service.java

} } ... @Override public IBinder onBind(Intent intent) { return binder; Zwracamy instancję IBinder. } }

W ten sposób napisaliśmy już cały kod usługi niezbędny do tego, by powiązać aktywność MainActivity z usługą OdometerService. Teraz dodamy do usługi nową metodę, która będzie zwracać wartość losową.

jesteś tutaj  771

getDistance()

Dodanie metody getDistance() do usługi

¨

OdometerService

¨ MainActivity ¨ Usługi lokalizacyjne

Do usługi OdometerService dodamy teraz metodę getDistance(), która będzie wywoływana przez aktywność. Na razie implementacja tej metody będzie zwracać liczbę losową, lecz w dalszej części rozdziału zaktualizujemy ją tak, by korzystała z usług lokalizacyjnych Androida. Poniżej przedstawiony został pełny kod pliku OdometerService.java; zaktualizuj swoją wersję tego pliku, tak by była identyczna z naszą: package com.hfad.drogomierz; Drogomierz

import android.app.Service; import android.content.Intent;

app/src/main

import android.os.IBinder; import android.os.Binder; import java.util.Random;

java

Używamy tej dodatkowej klasy, więc musimy ją zaimportować.

com.hfad.drogomierz

public class OdometerService extends Service { private final IBinder binder = new OdometerBinder(); private final Random random = new Random();

Obiektu Random() użyjemy do generowania liczb losowych.

public class OdometerBinder extends Binder { OdometerService getOdometer() { return OdometerService.this; } } @Override public IBinder onBind(Intent intent) { return binder; Dodajemy metodę getDistance().

} public double getDistance() {

return random.nextDouble(); }

Zwracamy losową liczbę typu double.

}

Teraz zajmiemy się aktualizacją kodu aktywności MainActivity, tak by korzystał on z usługi OdometerService.

772

Rozdział 19.

Odometer Service.java

Usługi powiązane i uprawnienia

Aktualizacja układu aktywności MainActivity

¨ ¨

Kolejnym etapem prac nad aplikacją będzie utworzenie powiązania pomiędzy aktywnością MainActivity i usługą OdometerService oraz wywoływanie metody getDistance() tej usługi. Zaczniemy od dodania do układu aktywności nowego widoku TextView. Będzie on służył do prezentowania wartości zwróconej przez wywołanie metody getDistance().

OdometerService MainActivity ¨ Usługi lokalizacyjne

Zmodyfikuj swoją wersję pliku activity_main.xml, tak by zawierała kod przedstawiony poniżej:

layout



Skoro już dodaliśmy do układu aktywności MainActivity widok TextView, możemy się zająć aktualizacją kodu aktywności. Zacznijmy od przeanalizowania zmian, które będziemy musieli wprowadzić.

jesteś tutaj  773

Etapy

Co musi robić aktywność MainActivity?

¨ ¨

OdometerService MainActivity ¨ Usługi lokalizacyjne

Aby utworzyć połączenie pomiędzy aktywnością i usługą powiązaną oraz aby móc wywoływać metody tej usługi, trzeba wykonać kilka czynności:

1

Utworzyć obiekt ServiceConnection.

Tworzy on połączenie z usługą, korzystając z obiektu IBinder tej usługi.

Aktywność

2

ServiceConnection

Usługa

Utworzyć powiązanie aktywności z usługą.

Kiedy aktywność zostanie powiązana z usługą, będzie można bezpośrednio wywoływać metody tej usługi.

Aktywność

3

Usługa

Wchodzić w interakcje z usługą.

My będziemy używać metody getDistance() usługi, by aktualizować widok TextView prezentowany w aktywności.

Kiedy aktywność zostanie powiązana z usługą, można używać tej usługi do aktualizowania aktywności.

4

Odłączyć usługę, kiedy już nie będzie potrzebna.

Kiedy usługa nie jest już potrzebna, Android ją usuwa, by zwolnić używane przez nią zasoby.

Aktywność

Usługa

Na kolejnych stronach wykonamy wszystkie te czynności, zaczynając od utworzenia obiektu ServiceConnection.

774

Rozdział 19.

Usługi powiązane i uprawnienia

Utworzenie obiektu ServiceConnection

¨ ¨

OdometerService MainActivity ¨ Usługi lokalizacyjne

ServiceConnection to interfejs pozwalający aktywności na utworzenie powiązania z usługą. Określa on dwie metody, które musimy zaimplementować: onServiceConnected() oraz onServiceDisconnected(). Pierwsza z nich, onServiceConnected(), jest wywoływana w momencie utworzenia powiązania z usługą, a druga, onServiceDisconnected(), kiedy usługa zostanie odłączona. My już dodaliśmy obiekt ServiceConnection do MainActivity. Poniżej pokazaliśmy, jak wygląda podstawowa wersja kodu; zaktualizuj swoją wersję pliku MainActivity.java, by była identyczna z naszą: package com.hfad.drogomierz;

Drogomierz

import android.app.Activity; import android.os.Bundle; import android.content.ServiceConnection;

Używamy tych klas, więc musimy je zaimportować.

import android.os.IBinder; import android.content.ComponentName; public class MainActivity extends Activity {

My użyliśmy w tym miejscu klasy Activity, lecz równie dobrze można by użyć klasy AppCompatActivity.

app/src/main java com.hfad.drogomierz

private ServiceConnection connection = new ServiceConnection() {

Main Activity.java

@Override public void onServiceConnected(ComponentName componentName, IBinder binder) {

Utworzenie obiektu Service Connection.

// kod wykonywany podczas tworzenia powiązania z usługą } Musisz zdefiniować te metody.

@Override public void onServiceDisconnected(ComponentName componentName) { // kod wykonywany, kiedy usługa jest odłączana } };

Dodajemy także do aktywności MainActivity metodę onCreate().

@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } }

Na następnej stronie zmodyfikujemy kod metod onServiceConnected() oraz onServiceDisconnected().

jesteś tutaj  775

onServiceConnected()

Metoda onServiceConnected()

¨ ¨

Jak już zaznaczyliśmy na poprzedniej stronie, metoda onServiceConnected() jest wywoływana, gdy zostanie utworzone powiązanie pomiędzy aktywnością i usługą. Metoda ta ma dwa parametry: obiekt ComponentName, opisujący usługę, z którą utworzono powiązanie, oraz obiekt IBinder zdefiniowany przez usługę:

OdometerService MainActivity ¨ Usługi lokalizacyjne

uje Obiekt ComponentName identyfik gi usługę. Zawiera on pakiet usłu oraz nazwę jej klasy.

public void onServiceConnected(ComponentName componentName, IBinder binder) { // kod wykonywany podczas tworzenia powiązania z usługą }

Ten obiekt IBinder jest definiowa przez usługę. Już wcześniej dodany go do naszej usługi OdometerServliśmy ice.

Metoda onServiceConnected() musi wykonać dwie czynności:



Skorzystać z parametru IBinder, by pobrać referencję do powiązanej usługi; w naszym przykładzie OdometerService. Możemy to zrobić, rzutując obiekt IBinder do typu OdometerService.OdometerBinder (gdyż to jest typ IBinder, który zdefiniowaliśmy w usłudze OdometerService). Mając tę referencję, będziemy już mogli wywołać metodę getOdometer().



Zarejestrować fakt utworzenia powiązania pomiędzy aktywnością i usługą.

Poniżej przedstawiliśmy kod, który wykonuje obie te operacje (zaktualizuj swoją wersję pliku MainActivity.java, dodając do niej wyróżnione fragmenty kodu):

Drogomierz app/src/main

public class MainActivity extends Activity { private OdometerService odometer; private boolean bound = false;

a Użyj tych zmiennych do zapisani ji, referencji do usługi oraz informac na. iąza czy aktywność została z nią pow

java com.hfad.drogomierz

private ServiceConnection connection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName componentName, IBinder binder) { OdometerService.OdometerBinder odometerBinder = (OdometerService.OdometerBinder) binder; odometer = odometerBinder.getOdometer(); bound = true; } ... }; ... }

776

Rozdział 19.

Aktywność i usługa są już powiązane, więc w zmiennej bound zapisujemy true.

Użyj obiektu IBinder, by pobrać referencję do usługi.

Main Activity.java

Usługi powiązane i uprawnienia

Metoda onServiceDisconnected()

¨ ¨

OdometerService MainActivity ¨ Usługi lokalizacyjne

Metoda onServiceDisconnected() jest wywoływana, kiedy usługa zostanie odłączona od aktywności. Ma ona jeden parametr: obiekt ComponentName opisujący usługę: @Override public void onServiceDisconnected(ComponentName componentName) { // kod wykonywany, kiedy usługa jest odłączana }

W momencie wywołania metoda onServiceDisconnected() musimy wykonać tylko jedną operację: zarejestrować fakt, że usługa nie jest już podłączona. Poniżej przedstawiliśmy kod, który to robi; zaktualizuj swoją wersję pliku MainActivity.java, tak by była identyczna z naszą:

Drogomierz

public class MainActivity extends Activity { private OdometerService odometer; private boolean bound = false;

app/src/main java com.hfad.drogomierz

private ServiceConnection connection = new ServiceConnection() {

Main Activity.java

@Override public void onServiceConnected(ComponentName componentName, IBinder binder) { OdometerService.OdometerBinder odometerBinder = (OdometerService.OdometerBinder) binder; odometer = odometerBinder.getOdometer(); bound = true; } @Override public void onServiceDisconnected(ComponentName componentName) { bound = false; } }; ... }

ość Zapisujemy w zmiennej bound wart są nie false, gdyż aktywność i usługa już powiązane.

Na następnej stronie dowiesz się, jak można utworzyć powiązanie oraz jak je później usunąć.

jesteś tutaj  777

Metoda bindService()

Użycie metody bindService() do powiązania usługi

¨ ¨

OdometerService MainActivity ¨ Usługi lokalizacyjne

Operację utworzenia powiązania pomiędzy aktywnością i usługą wykonuje się zazwyczaj w jednym z dwóch miejsc:





W metodzie onStart() aktywności, kiedy aktywność staje się widoczna. To rozwiązanie jest właściwe w przypadkach, gdy chcemy wchodzić w interakcje z usługą jedynie wtedy, gdy aktywność jest widoczna. W metodzie onCreate() aktywności, kiedy aktywność zostaje utworzona. Z tego rozwiązania powinniśmy skorzystać, gdy musimy otrzymywać aktualizowane dane od usługi, nawet kiedy aktywność zostanie zatrzymana.

Zazwyczaj nie tworzymy powiązania aktywności i usługi w metodzie onResume() aktywnoś ci, aby ograniczać do absolutnego minimum liczbę wykonywanych w niej operacji.

W naszym przykładzie aktualne informacje pobierane z usługi OdometerService musimy wyświetlać jedynie wtedy, gdy aktywność MainActivity jest widoczna, dlatego też utworzymy powiązanie z usługą w metodzie onStart() aktywności. W celu utworzenia takiego powiązania w pierwszej kolejności należy przygotować jawną intencję skierowaną bezpośrednio do usługi, z którą chcemy powiązać aktywność. Następnie należy wywołać metodę bindService(), przekazując do niej intencję, obiekt ServiceConnection oraz flagę określającą sposób utworzenia powiązania. Aby pokazać, jak należy wykonać te operacje, poniżej zamieściliśmy kod, który tworzy powiązanie pomiędzy aktywnością MainActivity oraz usługą OdometerService (już niebawem dodamy ten kod do pliku MainActivity.java): Override protected void onStart() {

OdometerService

MainActivity

Drogomierz app/src/main

To jest intencja skierowana do usługi OdometerService.

super.onStart(); Intent intent = new Intent(this, OdometerService.class);

java com.hfad.drogomierz

bindService(intent, connection, Context.BIND_AUTO_CREATE); }

To jest obiekt ServiceConnection.

W powyższym kodzie zastosowaliśmy flagę Context.BIND_AUTO_CREATE, aby nakazać systemowi Android utworzenie usługi, o ile ta jeszcze nie istnieje. Są także inne flagi, których można użyć; wszystkie zostały szczegółowo opisane w dokumentacji Androida pod adresem: https://developer.android.com/reference/android/content/Context.html Na następnej stronie pokażemy, jak można odłączyć aktywności od usługi.

778

Rozdział 19.

Main Metoda bindService() Activity.java używa intencji i obiektu ServiceConnection do powiązania aktywności z usługą.

Usługi powiązane i uprawnienia

Użycie metody unbindService() do odłączenia aktywności od usługi W przypadku odłączania aktywności od usługi kod wykonujący tę operację umieszczany jest zazwyczaj w metodzie onStop() lub onDestroy() aktywności. Wybór metody zależy od miejsca, w którym umieściliśmy wywołanie metody bindService():

¨ ¨

OdometerService MainActivity ¨ Usługi lokalizacyjne

OdometerService

MainActivity



Jeśli utworzenie powiązania zostało wykonane w metodzie onStart(), to aktywność należy odłączać w metodzie onStop().



Jeśli utworzenie powiązania zostało wykonane w metodzie onCreate(), to aktywność należy odłączać w metodzie onDestroy().

W naszym przykładzie powiązanie aktywności MainActivity z usługą OdometerService utworzyliśmy w metodzie onStart() aktywności, więc operację odłączenia musimy wykonać w metodzie onStop(). Odłączenie aktywności od usługi sprowadza się do wywołania metody unbindService(). Wymaga ona przekazania jednego parametru: obiektu ServiceConnection. Oto kod, który musimy dodać do aktywności MainActivity (dodamy go do pliku MainActivity.java kilka stron dalej): Drogomierz

@Override protected void onStop() { super.onStop(); if (bound) {

To wywołanie używa obiektu ServiceConnection do odłączenia aktywności od usługi.

unbindService(connection); bound = false; } }

Po odłączeniu aktywności przypisujemy zmiennej bound wartość false.

app/src/main java com.hfad.drogomierz

Main Activity.java

W powyższym kodzie używamy zmiennej bound, by sprawdzić, czy odłączenie usługi jest konieczne, czy nie. Jeśli zmienna ta ma wartość true, oznacza to, że aktywność MainActivity jest powiązana z usługą OdometerService. W takim przypadku musimy odłączyć aktywność i przypisać zmiennej bound wartość false. Jak na razie dysponujemy aktywnością, która podczas uruchamiania tworzy powiązanie z usługą, a następnie, kiedy zostaje zatrzymana, odłącza tę usługę. Ostatnią operacją, którą musimy wykonać, jest wywołanie w kodzie aktywności MainActivity metody getDistance() usługi OdometerService i wyświetlenie zwróconej przez nią wartości.

jesteś tutaj  779

Pobranie przebytego dystansu

Wyświetlanie przebytego dystansu

¨ ¨

OdometerService MainActivity ¨ Usługi lokalizacyjne

Kiedy już utworzymy powiązanie z usługą, będziemy mogli wywoływać jej metody. Planujemy co sekundę wywoływać metodę getDistance() usługi OdometerService, aby pobierać przebyty dystans i wyświetlać tę informację w widoku tekstowym. W tym celu napiszemy nową metodę o nazwie displayDistance(). Będzie ona działać w dokładnie taki sam sposób jak metoda runTimer() z rozdziału 4. 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. Oto kod metody displayDistance():

Aktywność użyje metody getDistance() usługi OdometerService, by określić zawartość komponentu TextView.

Pobieramy widok tekstowy.

private void displayDistance() { final TextView distanceView = (TextView)findViewById(R.id.distance); final Handler handler = new Handler(); handler.post(new Runnable() { @Override

Tworzymy obiekt Handler.

Wywołujemy metodę post(), przekazując do niej obiekt Runnabl e.

public void run() { double distance = 0.0;

Jeśli dysponujemy referencją do usługi OdometerService, to wywołujemy jej metodę getDistance().

if (bound && odometer != null) { distance = odometer.getDistance(); }

Słowo „kilometra” można by zapisać jako zasób łańcuchowy, my jednak, dla uproszczenia kodu, podaliśmy je bezpośrednio.

String distanceStr = String.format(Locale.getDefault(),”%1$,.2f kilometra”, distance); distanceView.setText(distanceStr); handler.postDelayed(this, 1000); } }); }

Przekazujemy kod umieszczony w obiekcie Runnable, tak by został . on ponownie wykonany po upływie 1000 milisekund, czy 1 sekundy Ponieważ to wywołanie jest umieszczone w metodzie run() obiektu Runnable, w efekcie metoda ta będzie wykonywana co sekundę.

Metodę displayDistance() wywołamy następnie w metodzie onCreate() aktywności, dzięki czemu umieszczony w niej kod zacznie być wykonywany w momencie utworzenia aktywności: protected void onCreate(Bundle savedInstanceState) { displayDistance(); }

Wywołujemy metodę displayDistance() w metodzie onCreate() aktywności MainActivity, aby uruchomić pomiar odległości.

Na następnej stronie przedstawimy kompletny kod aktywności MainActivity.

780

Rozdział 19.

app/src/main java com.hfad.drogomierz Main Activity.java

@Override ...

Drogomierz

Usługi powiązane i uprawnienia

Kompletny kod aktywności MainActivity

¨ ¨

OdometerService MainActivity ¨ Usługi lokalizacyjne

Oto kompletny kod aktywności MainActivity: package com.hfad.drogomierz; import android.app.Activity;

Drogomierz

import android.os.Bundle; import android.content.ServiceConnection;

app/src/main

import android.os.IBinder; import android.content.ComponentName;

java

import android.content.Context; import android.content.Intent;

com.hfad.drogomierz

Używamy tych dodatkowych klas, więc musimy je zaimportować.

import android.os.Handler; import android.widget.TextView;

Main Activity.java

import java.util.Locale; public class MainActivity extends Activity { private OdometerService odometer; private boolean bound = false;

nia Ta zmienna służy do przechowywa ice. referencji do usługi OdometerServ Ta zmienna przechowuje informację, czy aktywność została powiązana z usługą, czy nie.

private ServiceConnection connection = new ServiceConnection() { @Override

Musimy zdefiniować obiekt ServiceConnection.

public void onServiceConnected(ComponentName componentName, IBinder binder) { OdometerService.OdometerBinder odometerBinder = (OdometerService.OdometerBinder) binder; odometer = odometerBinder.getOdometer(); bound = true; }

Po powiązaniu usługi OdometerService pobieramy referencję do niej.

@Override public void onServiceDisconnected(ComponentName componentName) { bound = false; } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);

Dalsza część kodu znajduje się na następnej stronie.

setContentView(R.layout.activity_main); displayDistance(); }

Podczas tworzenia aktywności wywołujemy metodę displayDistance( ).

jesteś tutaj  781

Ciąg dalszy kodu

Kod aktywności MainActivity (ciąg dalszy)

¨ ¨

OdometerService MainActivity ¨ Usługi lokalizacyjne

@Override protected void onStart() { super.onStart(); Intent intent = new Intent(this, OdometerService.class); bindService(intent, connection, Context.BIND_AUTO_CREATE); }

Kiedy aktywność zostaje uruchomiona, próbujemy ją powiązać z usługą. Drogomierz

@Override protected void onStop() { super.onStop();

Powiązanie przerywamy w momencie zatrzymywania aktywności.

app/src/main

if (bound) {

java

unbindService(connection); bound = false;

com.hfad.drogomierz

} Ta metoda aktualizuje wyświetlany dystans, jaki pokonało urządzenie.

}

Main Activity.java

private void watchMileage() { final TextView distanceView = (TextView)findViewById(R.id.distance); final Handler handler = new Handler(); handler.post(new Runnable() { @Override public void run() { double distance = 0.0; if (bound && odometer != null) {

Jeśli dysponujemy referencją do usługi OdometerService, to wywołujemy jej metodę getDistance().

distance = odometer.getDistance(); } String distanceStr = String.format(Locale.getDefault()”%1$,.2f kilometra”, distance); distanceView.setText(distanceStr); handler.postDelayed(this, 1000); } });

Informacje o pokonanym dystansie aktualizujemy co sekundę.

} }

To już cały kod niezbędny do wykorzystania usługi OdometerService w aktywności MainActivity. A teraz przekonajmy się, co się stanie po uruchomieniu aplikacji.

782

Rozdział 19.

Usługi powiązane i uprawnienia

Co się dzieje po uruchomieniu kodu?

¨ ¨

OdometerService MainActivity ¨ Usługi lokalizacyjne

Zanim przygotujemy i uruchomimy aplikację, przeanalizujmy jej działanie:

1

W momencie tworzenia aktywności MainActivity tworzony jest obiekt ServiceConnection i wywoływana jest metoda displayDistance().

displayDistance() MainActivity

2

ServiceConnection

W metodzie onStart() aktywności MainActivity zostaje wywołana metoda bindService().

Metoda bindService() zawiera intencję skierowaną do usługi OdometerService oraz referencję do obiektu ServiceConnection. bindService()

MainActivity

ServiceConnection

Intencja Odometer Service

3

Android tworzy instancję usługi OdometerService i przekazuje do niej intencję, wywołując w tym celu metodę onBind() usługi. Intencja

Android

Odometer Service

onBind()

OdometerService

jesteś tutaj  783

Co się dzieje, ciąg dalszy

Opowieści ciąg dalszy 4

¨ ¨

OdometerService MainActivity ¨ Usługi lokalizacyjne

Wywołanie metody onBind() usługi OdometerService zwraca obiekt Binder.

Obiekt Binder zostaje przekazany do obiektu ServiceConnection aktywności MainActivity.

onBind()

Binder MainActivity

5

ServiceConnection

OdometerService

Obiekt ServiceConnection używa obiektu Binder do przekazania aktywności MainActivity referencji do usługi OdometerService.

MainActivity

ServiceConnection

OdometerService

6

Metoda displayDistance() aktywności MainActivity zaczyna wywoływać co sekundę metodę getDistance() usługi OdometerService.

Usługa OdometerService przekazuje do aktywności MainActivity liczbę losową; w tym przypadku jest to liczba 0.56. getDistance()

MainActivity

784

Rozdział 19.

0.56

OdometerService

Usługi powiązane i uprawnienia

Opowieści ciąg dalszy 7

Kiedy aktywność MainActivity zostaje zatrzymana, odłącza także usługę OdometerService, wywołując w tym celu metodę unbindService().

Nie jestem już widoczna, więc nie muszę już korzystać z twoich usług.

Jasne. Wywołaj mnie, jeśli zmienisz zdanie.

unbindService()

MainActivity

8

OdometerService

Gdy aktywność nie będzie już powiązana z usługą, usługa ta zostanie usunięta.

MainActivity

OdometerService

Teraz, kiedy już rozumiesz, co się dzieje podczas działania kodu aplikacji, nadszedł czas, by wziąć ją na jazdę próbną.

jesteś tutaj  785

Jazda próbna

Jazda próbna aplikacji

¨ ¨

OdometerService MainActivity ¨ Usługi lokalizacyjne

Po uruchomieniu aplikacji w aktywności MainActivity jest wyświetlana liczba losowa. Liczba ta zmienia się co sekundę.

To jest liczba losowa wygenerowana przez usługę OdometerService.

Obecnie mamy zatem działającą usługę, którą można dołączyć do aktywności MainActivity. Wciąż oczywiście musimy zmodyfikować tę usługę, tak by metoda getDistance() zamiast liczby losowej zwracała faktyczny przebyty dystans. Zanim to jednak zrobimy, musimy zgłębić tajniki działania usług powiązanych. Nie istnieją

głupie pytania

P: Wytłumaczcie jeszcze raz,

P: Czy usługa może być

P: Jaka jest różnica pomiędzy

O: Metoda uruchomiona jest tworzona,

O: Tak. W takim przypadku usługa jest

: IBinder to interfejs. Binder to klasa implementująca interfejs IBinder.

jaka jest różnica między usługami uruchomionymi i powiązanymi.

gdy aktywność (lub jakiś inny komponent) wywoła metodę startService(). Taka usługa wykonuje kod w tle, a kiedy kończy działanie, jest usuwana. Usługa powiązana jest tworzona, kiedy aktywność wywoła metodę bindService(). Aktywność może wchodzić w interakcję z taką usługą, czyli wywoływać jej metody. Usługa jest usuwana, gdy nie jest już powiązana z żadnym komponentem.

786

Rozdział 19.

jednocześnie uruchomiona i powiązana?

tworzona poprzez wywołanie metody startService() lub bindService(). Taka usługa jest usuwana wyłącznie wtedy, gdy kod, który nakazał jej działać w tle, sam przestał działać i gdy nie jest powiązana z żadnymi komponentami.

Tworzenie takich uruchomionych i powiązanych usług jest nieco bardziej skomplikowane niż tworzenie usług, które będą wyłącznie uruchomione lub powiązane. Informacje o tym, jak to zrobić, można znaleźć w dokumentacji Androida, zamieszczonej na stronie https://developer. android.com/guide/components/services.html.

Binder oraz IBinder?

O

P: Czy inne aplikacje mogą używać moich usług?

O: Tak, jednak wyłącznie w przypadku,

gdy w pliku AndroidManifest.xml przypiszemy atrybutowi exported usługi wartość true.

Usługi powiązane i uprawnienia

Stany usług powiązanych Kiedy komponent aplikacji (taki jak aktywność) tworzy powiązanie z usługą, przechodzi ona kolejno w trzy stany: utworzona, powiązana oraz usunięta. Usługa powiązana przez przeważającą większość czasu znajduje się w stanie powiązanym.

Usługa utworzona

Usługa powiązana

Usługa usunięta

Utworzono obiekt usługi.

Komponent aplikacji, taki jak aktywność, utworzył powiązanie z usługą. To właśnie w tym stanie usługa pozostaje przez przeważającą większość swojego cyklu życia.

W tym momencie usługa już nie istnieje.

Podobnie jak w przypadku usług uruchomionych, w momencie tworzenia usługi powiązanej zostaje wywołana jej metoda onCreate(). I tak jak w przypadku usług powiązanych, także teraz metodę tę przesłaniamy, kiedy musimy wykonać jakieś czynności konieczne do zainicjowania usługi. Metoda onBind() usługi jest wywoływana w momencie utworzenia powiązania z jakimś komponentem aplikacji. Metodę tę przesłaniamy, by zwrócić do komponentu obiekt IBinder, który następnie jest używany do pobrania referencji do usługi. Kiedy wszystkie komponenty zostaną odłączone od usługi i ma ona zostać usunięta, wywoływana jest jej metoda onUnbind(). Podobnie jak w przypadku usług uruchomionych, metodę tę należy przesłonić, gdy trzeba wykonać jakieś czynności porządkowe i zwolnić zasoby.

Usługa powiązana jest usuwana, kiedy nie jest już powiązana z żadnym komponentem.

Na następnej stronie sprawdzimy, jaki jest związek tych metod ze stanami, w jakich może się znajdować usługa.

jesteś tutaj  787

Metody cyklu życia

Metody cyklu życia usług powiązanych: od utworzenia do usunięcia Poniżej przedstawiliśmy nieco bardziej szczegółowy schemat cyklu życia usług powiązanych, od momentu ich utworzenia aż do śmierci.

Usługa utworzona

1

Komponent wywołuje metodę bindService() i usługa zostaje utworzona.

2

Bezpośrednio po utworzeniu usługi wywoływana jest jej metoda onCreate().

W metodzie onCreate() należy umieszczać wszelki kod inicjujący działanie usługi, gdyż jest ona wywoływana po uruchomieniu usługi, lecz przed powiązaniem jej z jakimikolwiek komponentami aplikacji.

onCreate()

onBind()

3

Kiedy usługa zostanie powiązana z komponentem aplikacji, wywoływana jest metoda onBind().

Metodę tę przesłaniamy, by zwrócić obiekt IBinder, który następnie może zostać użyty przez komponent do pobrania referencji do usługi i wywoływania jej metod.

Usługa powiązana

4

Usługa jest powiązana przez większość czasu swego istnienia.

5

Metoda onUnbind() jest wywoływana, gdy usługa przestaje być powiązana z jakimkolwiek komponentem aplikacji.

6

Metoda onDestroy() jest wywoływana, kiedy usługa nie jest powiązana z żadnym komponentem i gdy ma zostać usunięta.

onUnbind()

onDestroy()

Metodę tę przesłaniamy, gdy konieczne jest wykonanie jakichś czynności porządkowych, takich jak zwolnienie używanych zasobów.

Usługa usunięta

7

Po wywołaniu metody onDestroy() usługa zostaje usunięta.

Skoro już lepiej rozumiesz sposób działania usług powiązanych, zmodyfikujmy naszą aplikację Drogomierz tak by pokazywała faktyczny przebyty dystans.

788

Rozdział 19.

Usługi powiązane i uprawnienia

Aby zwracać pokonany dystans, skorzystamy z usług lokalizacyjnych Androida

¨ ¨ ¨

OdometerService MainActivity Usługi lokalizacyjne

Musimy zmodyfikować naszą usługę OdometerService w taki sposób, by jej metoda getDistance() zwracała pokonaną odległość. Wykorzystamy do tego usługi lokalizacyjne Androida (ang. Android Location Services). Pozwalają one na odczyt położenia użytkownika, zażądanie przesyłania informacji o położeniu co określony czas oraz zgłaszanie intencji, kiedy użytkownik znajdzie się w określonej odległości od zadanego miejsca. My zamierzamy użyć usług lokalizacyjnych, by cyklicznie, co określony czas, pobierać aktualne dane o lokalizacji użytkownika. Użyjemy tych informacji do wyznaczania przebytej odległości. W tym celu wykonamy następujące czynności:

1

Zadeklarujemy potrzebę uzyskania uprawnienia do korzystania z usług lokalizacyjnych.

Nasza aplikacja będzie mogła używać usług lokalizacyjnych wyłącznie w przypadku, gdy użytkownik jej na to pozwoli.

2

Po utworzeniu usługi przygotujemy obiekt nasłuchujący zdarzeń związanych z lokalizacją.

Będziemy go używać do nasłuchiwania informacji generowanych przez usługi lokalizacyjne.

3

4

5

Zażądamy przesyłania zaktualizowanych informacji o lokalizacji.

Utworzymy menedżera lokalizacji i użyjemy go, by zażądać przekazywania aktualnych informacji o położeniu użytkownika. Obliczymy przebytą odległość.

Będziemy przechowywać sumę bieżącą przebytej odległości i zwracać ją w momencie wywołania metody getDistance() usługi OdometerService. Zrezygnujemy z pobierania informacji o lokalizacji bezpośrednio przed usunięciem usługi.

W ten sposób zwolnimy zasoby systemowe. Zanim zaczniemy, dodamy do projektu bibliotekę wsparcia AppCompat, gdyż będzie nam ona potrzebna.

jesteś tutaj  789

Dodanie biblioteki wsparcia

Dodanie biblioteki wsparcia AppCompat Do zapewnienia poprawnego działania kodu pobierającego aktualne informacje o lokalizacji potrzebnych nam będzie kilka klas z biblioteki wsparcia AppCompat, dlatego też musimy ją dodać jako zależność do projektu. Należy to zrobić w taki sam sposób jak wcześniej, a zatem: wybierz z menu głównego Android Studio opcję File/Project Structure, następnie zaznacz moduł app i kliknij kartę Dependencies. Na ekranie zostanie wyświetlone poniższe okno dialogowe:

Compat. To jest biblioteka wsparcia App

Może się zdarzyć, że Android Studio automatycznie doda do projektu zależność od tej biblioteki. W takim przypadku zostanie ona wyświetlona, jak na powyższym rysunku, jako biblioteka appcompat-v7. Jeśli jednak biblioteka ta nie zostanie wyświetlona na liście, to będziesz musiał ją dodać. W tym celu kliknij przycisk ze znakiem „+” umieszczony u dołu lub z prawej strony okna, wybierz opcję Library Dependency, później zaznacz opcję appcompat-v7 i kliknij przycisk OK. Następnie ponownie kliknij przycisk OK, aby zapisać zmiany i zamknąć okno dialogowe Project Structure. A teraz zobaczmy, w jaki sposób zadeklarować konieczność uzyskania uprawnień do korzystania z usług lokalizacyjnych Androida.

790

Rozdział 19.

¨ ¨ ¨

OdometerService MainActivity Usługi lokalizacyjne

Usługi powiązane i uprawnienia

Deklarowanie niezbędnych uprawnień Android domyślnie pozwala na wykonywanie wielu operacji, jednak w przypadku niektórych z nich wymagana jest zgoda użytkownika. To konieczne, gdyż korzystają one z prywatnych informacji o użytkowniku, mogą zmieniać przechowywane dane na jego temat bądź też wpływać na działanie innych aplikacji. Jednym z takich komponentów systemu, których wykorzystanie wymaga uzyskania specjalnego zezwolenia od użytkownika, są usługi lokalizacyjne Androida.

¨ ¨ ¨

OdometerService MainActivity Usługi lokalizacyjne

Uprawnienia, jakie aplikacja musi uzyskać, deklarujemy w pliku manifestu, AndroidManifest.xml, przy użyciu elementu , dodawanego bezpośrednio do elementu głównego — . Nam zależy na dostępie do dokładnej lokalizacji użytkownika, dlatego musimy zadeklarować w pliku manifestu uprawnienie ACCESS_FINE_LOCATION. W tym celu należy dodać do pliku AndroidManifest.xml poniższą deklarację (analogicznie zaktualizuj swoją wersję pliku manifestu):

Deklarujemy konieczność uzyskania uprawnienia.

ję użytkownika. Musimy znać dokładną lokalizac

0 Jeśli użytkownik anulował żądanie, to żadne wyniki nie zostaną zwrócone.

Sprawdzamy, czy kod odpowiada kodowi prośby przekazanemu w wywołaniu metody requestPermissions().

&& grantResults[0] == PackageManager.PERMISSION_GRANTED) { Intent intent = new Intent(this, OdometerService.class); bindService(intent, connection, Context.BIND_AUTO_CREATE); } else {

Jeśli uprawnienie zostało przydzielone, to tworzymy powiązanie z usługą.

// kod wykonywany, jeśli uprawnienie nie zostanie przydzielone } }

Ten kod dopiero musimy napisać.

} }

I w końcu, jeśli użytkownik nie zgodzi się na pobieranie informacji o jego bieżącej lokalizacji, musimy go poinformować, że nasza aplikacja Drogomierz nie będzie działać prawidłowo.

Drogomierz app/src/main java com.hfad.drogomierz MainActivity.java

jesteś tutaj  805

Chcemy wyświetlić powiadomienie

Wyświetlenie powiadomienia w razie odmowy przydzielenia uprawnienia Jeśli użytkownik zdecydował się nie przydzielać uprawnienia do odczytu bieżącej lokalizacji urządzenia, usługa OdometerService nie będzie w stanie wyznaczać przebytego dystansu. W takim przypadku wyświetlimy powiadomienie informujące użytkownika o zaistniałej sytuacji. Zdecydowaliśmy się na użycie powiadomienia, gdyż pozostanie ono w systemowym obszarze powiadomień tak długo, aż użytkownik postanowi coś z nim zrobić. Kolejną zaletą zastosowania powiadomienia jest to, że możemy go użyć, by uruchomić aktywność MainActivity, kiedy użytkownik kliknie powiadomienie. To z kolei oznacza wywołanie metody onStart() aktywności, która ponownie wyświetli prośbę o przydzielenie uprawnienia (chyba że użytkownik wcześniej zaznaczył opcję, by nie zadawać tego pytania ponownie).

¨

Prośba

¨ Przydzielone ¨ Prośba odrzucona Jeśli użytkownik nie przydzielił uprawnienia, wyświetlimy takie powiadomienie.

Na następnej stronie jest zamieszczone ćwiczenie — wykonaj je, by sprawdzić, czy potrafisz wygenerować powiadomienie. Nie istnieją

głupie pytania

P

: Wyłączyłem uprawnienia do określania lokalizacji w trakcie działania aplikacji Drogomierz i wyświetlany w niej dystans wrócił do początkowej wartości 0. Dlaczego?

O: Kiedy odbierzesz aplikacji uprawnienie do określania

lokalizacji, Android może usunąć proces, w ramach którego aplikacja jest wykonywana. To przywróci początkowe wartości wszystkich zmiennych.

P: To brzmi strasznie. Czy są też inne sytuacje, w których Android może usunąć proces?

O: Tak — kiedy ma mało pamięci, jednak będzie się przy tym

starał zachować działające te procesy, które są aktywnie używane.

O: Jednym z rozwiązań jest wywołanie metody ActivityCompat. shouldShowRequestPermissionRationale() przed wywołaniem metody requestPermissions(). Metoda ta zwraca true, jeśli użytkownik wcześniej odrzucił prośbę o przydzielenie uprawnień i nie zaznaczył pola wyboru, by w przyszłości już jej nie wyświetlać. W takim przypadku, jeszcze przed wyświetleniem okna dialogowego z prośbą o przydzielenie uprawnień, możemy wyświetlić użytkownikowi dodatkowe wyjaśnienia, ale poza tym oknem.

P: Jakie inne uprawnienia wymagają zadeklarowania i prośby o przydzielenie?

O: Ogólnie rzecz biorąc, uprawnienia są niezbędne w przypadku

O: Gdyż można jej używać wyłącznie w aktywnościach,

operacji korzystających z prywatnych danych użytkownika lub mogących mieć wpływ na działanie innych aplikacji. Dokumentacja Androida dla każdej z klas powinna podawać niezbędne uprawnienia; także Android Studio powinno informować o braku niezbędnych uprawnień. Pełną listę czynności wymagających uprawnień można znaleźć na stronie:

P: Czy mogę zmienić tekst wyświetlany w oknie

https://developer.android.com/guide/topics/permissions/ requesting.html#normal-dangerous

O: Nie. Test i opcje w oknie dialogowym są stałe, więc Android

operacje i nie poproszę w niej o przydzielenie uprawnień?

P: Dlaczego nie wywołujemy metody

requestPermissions() z kodu usługi OdometerService?

a nie w usługach.

requestPermissions()?

nie pozwoli ich zmieniać.

P: Ale chciałbym przekazać użytkownikom dodatkowe informacje o tym, dlaczego proszę u uprawnienia. Jak mogę to zrobić?

806

Rozdział 19.

P: Co się stanie, gdy stworzę aplikację wykonującą takie O: Jeśli docelowa wersja SDK odpowiada API poziomu 23. lub

wyższego i nie poprosisz o uprawnienia, to kod się nie skompiluje.

Usługi powiązane i uprawnienia

Zagadkowy basen

Napisz kod, który wyświetli takie powiadomienie.

Twoim celem jest przygotowanie i wyświetlenie wyskakującego (heads-up) powiadomienia. Kliknięcie tego powiadomienia powinno uruchamiać aktywność MainActivity i ukrywać samo powiadomienie. Wyłów fragmenty z basenu i umieść je w pustych miejscach kodu. Każdego fragmentu z basenu możesz użyć tylko raz, ale nie będziesz musiał wykorzystywać ich wszystkich. NotificationCompat.Builder builder = new NotificationCompat.Builder(this) .setSmallIcon(android.R.drawable.ic_menu_compass) .setContentTitle(”Drogomierz”) .setContentText(”Wymagany dostęp do lokalizacji”)

Wbudowany zasób graficzny prezentujący ikonę z kompasem.

.setPriority(NotificationCompat. .................... ) .setVibrate(new long[] {0, 1000}) . .................. (true); Intent actionIntent = new Intent(this, MainActivity.class); PendingIntent actionPendingIntent = PendingIntent. ............(this, 0, actionIntent, PendingIntent.FLAG_UPDATE_CURRENT); builder.setContentIntent( ..................... ); NotificationManager notificationManager = (NotificationManager) getSystemService( .................. ); notificationManager.notify(43, ........................ ); Uwaga: Każdego fragmentu z basenu możesz użyć tylko raz! NOTIFICATION getService actionPendingIntent HIGH

PRIORITY_HIGH PRIORITY_LOW

setVanishWhenClicked setAutoCancel builder.build() getActivity getAction

actionIntent

NOTIFICATION_SERVICE

builder

LOW

jesteś tutaj  807

Rozwiązanie

Zagadkowy basen. Rozwiązanie Twoim celem jest przygotowanie i wyświetlenie wyskakującego (heads-up) powiadomienia. Kliknięcie tego powiadomienia powinno uruchamiać aktywność MainActivity i ukrywać samo powiadomienie. Wyłów fragmenty z basenu i umieść je w pustych miejscach kodu. Każdego fragmentu z basenu możesz użyć tylko raz, ale nie będziesz musiał wykorzystywać ich wszystkich. NotificationCompat.Builder builder = new NotificationCompat.Builder(this) .setSmallIcon(android.R.drawable.ic_menu_compass) .setContentTitle(”Drogomierz”) To wywołanie sprawia, że powiadomie zniknie, gdy zostanie kliknięte.

a typu .setContentText(”Wymagany dostęp do lokalizacji”) Powiadomieni zą mieć up mus PRIORITY_HIGH sad he .setPriority(NotificationCompat. .................... ) i priorytet. wysok

.setVibrate(new long[] {0, 1000}) setAutoCancel . .................. (true);

Tworzymy obiekt PendingIntent, używając metody getActivity().

Intent actionIntent = new Intent(this, MainActivity.class); getActivity PendingIntent actionPendingIntent = PendingIntent. ............(this, 0,

actionIntent, PendingIntent.FLAG_UPDATE_CURRENT); actionPendingIntent ); Dodajemy obiekt PendingIntent do powiadomienia, builder.setContentIntent( .....................

tak by kliknięcie tego powiadomienia spowodowało uruchomienie aktywności MainActivity.

NotificationManager notificationManager =

NOTIFICATION_SERVICE ); (NotificationManager) getSystemService(....................... builder.build() notificationManager.notify(43, ........................ ); Konstruujemy powiadomienie.

Używamy usługi powiadomień.

Tych fragmentów nie musisz używać.

NOTIFICATION getService

setVanishWhenClicked actionIntent

HIGH PRIORITY_LOW

builder

getAction LOW

808

Rozdział 19.

Usługi powiązane i uprawnienia

Dodanie kodu wyświetlającego powiadomienia do metody onRequestPermissionsResult()

¨

Prośba

¨ Przydzielone ¨ Prośba odrzucona

Zaktualizujemy teraz kod aktywności MainActivity w taki sposób, że jeśli użytkownik odmówi przydzielenia uprawnienia, wyświetlimy wyskakujące (heads-up) powiadomienie. Drogomierz Zaczniemy od dodania do pliku strings.xml dwóch poniższych zasobów łańcuchowych; użyjemy ich później do wyświetlenia tytułu oraz treści powiadomienia: Może się zdarzyć, że

app/src/main

Android Studio samo doda ten łańcuch znaków.

Drogomierz

res

Wymagany dostęp do lokalizacji values

Następnie możemy zaktualizować kod pliku MainActivity.java, dodając do niego fragmenty wyróżnione pogrubioną czcionką:



strings.xml

... import android.support.v4.app.NotificationCompat; Drogomierz import android.app.NotificationManager; import android.app.PendingIntent; Używamy tych dodatkowych klas , app/src/main więc musimy je zaimportować.

public class MainActivity extends Activity { ... private final int NOTIFICATION_ID = 423;

java

Ta stała posłuży nam za identyfikator powiadomienia.

com.hfad.drogomierz

... Main @Override Activity.java public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { switch (requestCode) { case PERMISSION_REQUEST_CODE: { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { ... } else { // tworzymy obiekt NotificationCompat.Builder NotificationCompat.Builder builder = new NotificationCompat.Builder(this) .setSmallIcon(android.R.drawable.ic_menu_compass) Te ustawienia są niezbędne .setContentTitle(getResources().getString(R.string.app_name)) do wyświetlenia każdego powiadomienia. .setContentText(getResources().getString( R.string.permission_denied)) .setPriority(NotificationCompat.PRIORITY_HIGH) Dodaj te dwa wiersze, by Dalsza część kodu .setVibrate(new long[] {1000, 1000}) przygotować powiadomienie znajduje się na p ds-u (hea wyskakujące następnej stronie. .setAutoCancel(true); Ten wiersz powoduje, że notification).

powiadomienie zniknie po kliknięciu.

jesteś tutaj  809

Ciąg dalszy kodu

Kod wyświetlający powiadomienia (ciąg dalszy)

¨

Prośba

¨ Przydzielone ¨ Prośba odrzucona

// tworzymy akcję Intent actionIntent = new Intent(this, MainActivity.class); PendingIntent actionPendingIntent = PendingIntent.getActivity( Dodanie do powiadomienia intencji PendingIntent oznacza, że po kliknięciu powiadomienia zostanie uruchomiona aktywność MainActivity.

this, 0, actionIntent, PendingIntent.FLAG_UPDATE_CURRENT); builder.setContentIntent(actionPendingIntent); // wyświetlamy powiadomienie

Budujemy i wyświetlamy powiadomienie.

NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); notificationManager.notify(NOTIFICATION_ID, builder.build());

} }

Drogomierz

} } ... }

app/src/main java com.hfad.drogomierz

To już cały kod, którego potrzebujemy, by wyświetlać powiadomienie, jeśli użytkownik zdecyduje się nie przydzielić aplikacji uprawnienia ACCESS_ FINE_LOCATION. Na kilku następnych stronach przedstawimy kompletny kod aktywności MainActivity, a następnie wybierzemy się z aplikacją na ostatnią jazdę próbną.

810

Rozdział 19.

Main Activity.java

Usługi powiązane i uprawnienia

Kompletny kod pliku MainActivity.java

¨

Prośba

¨ Przydzielone ¨ Prośba odrzucona

Poniżej przedstawiliśmy kompletny kod pliku MainActivity.java; upewnij się że Twoja wersja tego pliku będzie identyczna z naszą: package com.hfad.drogomierz; import import import import import import import import import import import import import import import import

Drogomierz

android.app.Activity; android.os.Bundle; app/src/main android.content.ServiceConnection; android.os.IBinder; java android.content.ComponentName; android.content.Context; com.hfad.drogomierz android.content.Intent; android.os.Handler; android.widget.TextView; Main Activity.java java.util.Locale; android.content.pm.PackageManager; android.support.v4.app.ActivityCompat; To wszystko są klasy android.support.v4.content.ContextCompat; pochodzące z biblioteki wsparcia AppCompat. android.support.v4.app.NotificationCompat; android.app.NotificationManager; android.app.PendingIntent; Myśmy używali klasy Activity, lecz

public class MainActivity extends Activity { private private private private

jeśli wolisz, to możesz użyć klasy AppCompatActivity.

OdometerService odometer; boolean bound = false; final int PERMISSION_REQUEST_CODE = 698; final int NOTIFICATION_ID = 423;

Potrzebujemy obiektu ServiceConnection, by utworzyć powiązanie aktywności MainActivity i usługi OdometerService.

private ServiceConnection connection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName componentName, IBinder binder) { OdometerService.OdometerBinder odometerBinder = (OdometerService.OdometerBinder) binder; odometer = odometerBinder.getOdometer(); bound = true; } @Override public void onServiceDisconnected(ComponentName componentName) { Dalsza część bound = false; kodu znajduje się na następnej } stronie. };

jesteś tutaj 

811

Aktywność MainActivity, ciąg dalszy

Kod pliku MainActivity.java (ciąg dalszy) @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); displayDistance(); } Jeśli poprosiliśmy użytkownika

cie o przydzielenie uprawnień w trak wyniki. my działania aplikacji, to sprawdza

¨

Prośba

¨ Przydzielone ¨ Prośba odrzucona Drogomierz app/src/main java

@Override com.hfad.drogomierz public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { Main switch (requestCode) { Activity.java case PERMISSION_REQUEST_CODE: { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { Jeśli użytkownik przydzielił Intent intent = new Intent(this, OdometerService.class); niezbędne bindService(intent, connection, Context.BIND_AUTO_CREATE); uprawnienie, to tworzymy } else { powiązanie // tworzymy obiekt NotificationCompat.Builder aktywności z usługą. NotificationCompat.Builder builder = new NotificationCompat.Builder(this) .setSmallIcon(android.R.drawable.ic_menu_compass) .setContentTitle(getResources().getString(R.string.app_name)) .setContentText(getResources().getString( R.string.permission_denied)) .setPriority(NotificationCompat.PRIORITY_HIGH) Jeśli uprawnienie .setVibrate(new long[] { 1000, 1000}) nie zostało .setAutoCancel(true); przydzielone, to wyświetlamy // tworzymy akcję powiadomienie. Intent actionIntent = new Intent(this, MainActivity.class); PendingIntent actionPendingIntent = PendingIntent.getActivity(this, 0, actionIntent, PendingIntent.FLAG_UPDATE_CURRENT); builder.setContentIntent(actionPendingIntent); // wyświetlamy powiadomienie NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); notificationManager.notify(NOTIFICATION_ID, builder.build()); } } Dalsza część kodu } znajduje się na } następnej stronie.

812

Rozdział 19.

Usługi powiązane i uprawnienia

Kod pliku MainActivity.java (ciąg dalszy)

¨

Prośba

¨ Przydzielone ¨ Prośba odrzucona

@Override protected void onStart() { super.onStart(); if (ContextCompat.checkSelfPermission(this, Prosimy OdometerService.PERMISSION_STRING) o przydzielenie != PackageManager.PERMISSION_GRANTED) { uprawnienia ACCESS_FINE_ ActivityCompat.requestPermissions(this, LOCATION, o ile new String[]{OdometerService.PERMISSION_STRING}, go jeszcze nie mamy. PERMISSION_REQUEST_CODE); Powiązanie z usługą } else { OdometerService Intent intent = new Intent(this, OdometerService.class); tworzymy w dwóch różnych miejscach, więc bindService(intent, connection, Context.BIND_AUTO_CREATE); te dwa wiersze kodu } możesz umieścić w odrębnej metodzie. zostało enie uprawni ne wymaga Jeśli } przydzielone, to tworzymy powiązanie z usługą OdometerService.

@Override protected void onStop() { super.onStop(); if (bound) { unbindService(connection); bound = false; } Wyświetlamy przebyty dystans. }

Kiedy aktywność MainActivity zostaje zatrzymana, odłączamy powiązanie aktywności z usługą.

Drogomierz app/src/main java com.hfad.drogomierz Main

private void displayDistance() { Activity.java final TextView distanceView = (TextView)findViewById(R.id.distance); final Handler handler = new Handler(); handler.post(new Runnable() { @Override public void run() { double distance = 0.0; if (bound && odometer != null) { distance = odometer.getDistance(); } String distanceStr = String.format(Locale.getDefault(), ”%1$,.2f kilometra”, distance); distanceView.setText(distanceStr); handler.postDelayed(this, 1000); } }); } }

jesteś tutaj  813

Jazda próbna

Jazda próbna aplikacji

¨

Kiedy uruchomimy aplikację bez przydzielania jej uprawnienia dostępu do danych lokalizacyjnych, na ekranie zostanie wyświetlone okno dialogowe z prośbą o przydzielenie tego uprawnienia. Po kliknięciu przycisku Odmów zostaje wyświetlone powiadomienie.

Prośba

¨ Przydzielone ¨ Prośba odrzucona

Jeśli odmówimy przydzielenia uprawnienia, zostanie wyświetlone powiadomienie.

Jeśli nie przydzieliliśmy uprawnienia, to zostanie wyświetlone okno dialogowe z prośbą o jego przydzielenie.

Kiedy klikniemy powiadomienie, ponownie zostanie wyświetlone okno dialogowe z prośbą o przydzielenie uprawnienia. Jeśli klikniemy przycisk przydzielający uprawnienie, na pasku stanu pojawi się ikona lokalizacji, a gdy pójdziemy z urządzeniem na wycieczkę, liczba przebytych kilometrów zacznie stopniowo rosnąć. Kiedy klikniemy powiadomienie, okno dialogowe zostanie wyświetlone ponownie.

Usługi lokalizacyjne działają

Kiedy pójdziemy na spacer, liczba przebytych kilometrów będzie stopniowo rosnąć.

Wiemy, że masz głowę pełną świetnych pomysłów na poprawienie aplikacji Drogomierz, więc dlaczego nie miałbyś ich wypróbować? W ramach przykładu spróbuj dodać do aplikacji przyciski: Start, Stop i Kasuj, aby, odpowiednio, rozpoczynać pomiar, zatrzymywać i rozpoczynać od nowa. 814

Rozdział 19.

Usługi powiązane i uprawnienia

Twój przybornik do Androida j Pełny kod przykładowe j ane tow zen pre cji aplika z pobrać w tym rozdziale możes twa nic daw wy z serwera FTP l/ n.p lio .he ftp :// Helion: ftp zip r2. ndr /a ady przykl

CELNE SPOSTRZEŻENIA Usługę powiązaną tworzymy, pisząc klasę dziedziczącą po klasie Service. Następnie trzeba zdefiniować własny obiekt Binder i przesłonić metodę onBind().



Aktualne informacje o lokalizacji można pobierać, korzystając z obiektu LocationListener.



LocationListener zapewnia dostęp do usług lokalizacyjnych Androida. Aby pobrać najlepszego dostawcę lokalizacji dostępnego na danym urządzeniu, należy wywołać metodę getBestProvider(). Otrzymywania danych można zażądać, wywołując metodę requestLocationUpdates() dostawcy.



Aby powiązać komponent z usługą, należy wywołać metodę bindService().



Użycie obiektu ServiceConnection pozwala pobrać w aktywności referencję do usługi, kiedy ta zostanie już powiązana.



W celu odłączenia usługi należy wywołać metodę unbindService().

W celu przerwania odbierania informacji o lokalizacji należy wywołać metodę removeUpdates().





Po utworzeniu usługi powiązanej wywoływana jest jej metoda onCreate(). W momencie tworzenia powiązania komponentu z usługą wywoływana jest jej metoda onBind().



Usługa powiązana jest usuwana, kiedy nie jest powiązana z żadnym komponentem. Bezpośrednio przed usunięciem usługi wywoływana jest jej metoda onDestroy().

Jeśli docelowa wersja SDK jest zgodna z API poziomu 23. lub wyższego, to przydzielenie uprawnienia można sprawdzać w trakcie działania aplikacji, wywołując metodę ContextCompat.checkSelfPermission().





Bieżące położenie urządzenia można sprawdzać, korzystając z możliwości usług lokalizacyjnych Androida.

Prośbę o przydzielenie uprawnień można zgłaszać w trakcie działania aplikacji, wywołując metodę ActivityCompat.requestPermissions().





By pobrać aktualną lokalizację urządzenia, w pliku AndroidManifest.xml należy zadeklarować, że aplikacja wymaga uprawnienia ACCESS_FINE_ LOCATION.



Aby sprawdzać odpowiedź użytkownika na prośbę o przydzielenie uprawnień, należy zaimplementować w aktywności metodę onRequestPermissionsResult().



jesteś tutaj  815

Rozdział 19.

Opanowałeś już rozdział 19. i dodałeś do swojego przybornika z narzędziami usługi powiązane.

Wyjeżdżamy z miasta…

Świetnie, że odwiedziliście nas w Androidowie Smutno nam, że nas zostawiacie, jednak nie ma nic lepszego niż wykorzystanie w praktyce tego wszystkiego, czego się nauczyliśmy. Co prawda w końcowej części książki czeka na Ciebie jeszcze kilka ciekawostek oraz użyteczny indeks, lecz później nadejdzie już czas, by całą tę wiedzę i wszystkie pomysły zastosować w praktyce. Miłej podróży!

Dodatek A. Układy względne i układy siatki

Poznaj krewnych A teraz zapamiętaj: położenie to layout_row=”18”, layout_column=”56”, a nie „za tym białym”.

Istnieją jeszcze dwa inne układy często stosowane w Androidowie. W tej książce koncentrowaliśmy się na stosowaniu prostych układów liniowych i układów ramek, jak również przedstawiliśmy nowy układ z ograniczeniami. Jednak istnieją także dwa inne rodzaje układów, które chcielibyśmy Ci zaprezentować: układ względny oraz układ siatki. W większości zostały one zastąpione układem z ograniczeniami, niemniej jednak i tak pozostaje wiele okazji do ich stosowania, dlatego też przypuszczamy, że będą one używane jeszcze przez ładnych parę lat.

to jest nowy dodatek  817

Układ względny

Układ RelativeLayout rozmieszcza widoki w sposób względny 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 , takiego jak ten przedstawiony poniżej:

...

Atrybuty layout_width i layout_height określają, jak szeroki i wysoki ma być definiowany układ.

W elemencie można umieszczać także inne znaczniki. To tu dodajesz widoki.

Rozmieszczanie widoków względem układu nadrzędnego 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 pośrodku układu, tuż poniżej jego górnej krawędzi:

layout_alignParentTop



818

Układ nadrzędny

layout_centerHorizontal

Układy względne i układy siatki

Umieszczanie widoków po lewej i prawej stronie Widoki można także rozmieszczać po lewej lub po prawej stronie układu nadrzędnego. Można to robić na dwa sposoby.

android:layout_alignParentLeft=”true”

Pierwszy sposób polega na jawnym określeniu, że dany widok należy umieścić z lewej lub prawej strony; można to zrobić następująco: android:layout_alignParentLeft=”true”

Widok podrzędny

Widok podrzędny

lub android:layout_alignParentRight=”true”

Te dwa wiersze kodu oznaczają, odpowiednio, że lewa (bądź prawa) krawędź widoku zostanie wyrównana do lewej (lub prawej) krawędzi układu nadrzędnego, i to niezależnie od wielkości ekranu, orientacji oraz języka wybranego na urządzeniu.

android:layout_alignParentRight=”true”

Stosowanie start i end w celu uwzględniania ustawień języka W przypadku aplikacji korzystających z SDK, które ma poziom API co najmniej 17., istnieje możliwość rozmieszczania widoków po lewej lub prawej stronie zgodnie z ustawieniami języka wybranego na urządzeniu. Na przykład możemy zdecydować, że na urządzeniach z wybranym językiem takim jak polski, w którym piszemy w kierunku od lewej do prawej, widoki mają być umieszczone po lewej stronie. W przypadku wybrania języka, w którym tekst jest zapisywany od prawej do lewej, możemy jednak chcieć wyświetlać widoki po prawej stronie. W tym celu należy użyć atrybutu: android:layout_alignParentStart=”true”

lub android:layout_alignParentEnd=”true”

Atrybut android:layout_alignParentStart=”true” wyrównuje początkową krawędź widoku z początkową krawędzią układu nadrzędnego. W przypadku języków, w których tekst jest zapisywany od lewej do prawej, krawędź początkowa to krawędź lewa, a w przypadku języków, w których tekst jest zapisywany od prawej do lewej, to krawędź prawa. Atrybut android:layout_alignParentEnd=”true” wyrównuje końcową krawędź widoku z końcową krawędzią układu nadrzędnego. W przypadku języków, w których tekst jest zapisywany od lewej do prawej, krawędź końcowa to krawędź prawa, a w przypadku języków, w których tekst jest zapisywany od prawej do lewej, to krawędź lewa.

android:layout_alignParentEnd=”tru

rue”

android:layout_alignParentStart=”t

W językach z zapisem od lewej do prawej.

W językach z zapisem od prawej do lewej.

Widok będzie wyświetlany po lewej lub po prawej stronie, zależnie od kierunku zapisu w języku wybranym na danym urządzeniu.

W językach z zapisem od lewej do prawej.

e”

W językach z zapisem od prawej do lewej.

jesteś tutaj  819

Względem elementu nadrzędnego

Atrybuty do umiejscawiania widoków względem układu nadrzędnego Poniżej przedstawiliśmy kilka najczęściej używanych atrybutów, które służą do umiejscawiania widoków względem ich układu nadrzędnego. Aby zastosować wybrany atrybut, należy go dodać do elementu i przypisać mu wartość ”true”: android:atrybut=”true”

Atrybut

Działanie

layout_alignParentBottom

Wyrównuje dolną krawędź widoku do dolnej krawędzi elementu nadrzędnego.

layout_alignParentLeft

Wyrównuje lewą krawędź widoku do lewej krawędzi elementu nadrzędnego.

layout_alignParentRight

Wyrównuje prawą krawędź widoku do prawej krawędzi elementu nadrzędnego.

layout_alignParentTop

Wyrównuje górną krawędź widoku do górnej krawędzi elementu nadrzędnego.

layout_alignParentStart

Umieszcza początkową krawędź widoku przy początkowej krawędzi widoku nadrzędnego.

layout_alignParentEnd

Umieszcza końcową krawędź widoku przy końcowej krawędzi widoku nadrzędnego.

layout_centerInParent

Umieszcza widok pośrodku elementu nadrzędnego, zarówno w poziomie, jak i w pionie.

layout_centerHorizontal

Umieszcza widok pośrodku elementu nadrzędnego w poziomie.

layout_centerVertical

Umieszcza widok pośrodku elementu nadrzędnego w pionie.

820

Dodatek A

Widok jest wyrównany do lewej i dolnej krawędzi elementu nadrzędnego.

Widok jest wyrównany do prawej i górnej krawędzi elementu nadrzędnego.

W przypadku języków, w których tekst jest zapisywany od lewej do prawej, krawędzią początkową jest krawędź lewa, a krawędzią końcową — prawa. W językach, w których tekst jest zapisywany od prawej do lewej, jest odwrotnie.

Układy względne i układy siatki

Rozmieszczanie widoków względem innych widoków Widoki można rozmieszczać nie tylko względem układu nadrzędnego lecz także względem innych widoków. To rozwiązanie doskonale się sprawdza, 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 atrybutu android:id: android:id=”@+id/button_click_me”

Składnia ”@+id” nakazuje, by Android dodał identyfikator do pliku R.java jako zasób. Dodanie znaku + jest konieczne zawsze, gdy definiujemy nowy widok w układzie. Jeśli tego nie zrobimy, to Android nie doda identyfikatora jako zasobu, a w kodzie wystąpią błędy. Znak + można jednak pominąć jeśli identyfikator już został dodany jako zasób. 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:



Poniższe dwa wiersze kodu: android:layout_alignLeft=”@id/button_click_me” android:layout_below=”@id/button_click_me”

W odwołaniach do widoków, które już zostały zdefiniowane w układzie, możemy używać zapisu @id zamiast @+id.

zapewnią, że drugi przycisk będzie umieszczony poniżej pierwszego, a jego lewa krawędź będzie wyrównana do lewej krawędzi pierwszego przycisku.

jesteś tutaj  821

Względem innych widoków

Atrybuty do rozmieszczania widoków względem innych widoków Poniżej przedstawiliśmy kolejne atrybuty, których można używać do rozmieszczania jednych widoków względem innych. Atrybuty te należy dodawać do elementu, którego położenie określamy, a ich wartością ma być identyfikator widoku stanowiącego punkt odniesienia: android:atrybut=”@+id/identyfikator_widoku”

Pamiętaj, możesz pominąć znak +, jeśli już wcześniej zdefiniowałeś w układzie identyfikator widoku.

Atrybut

Działanie

layout_above

Umieszcza widok powyżej widoku stanowiącego punkt odniesienia.

Twój widok jest umieszczony powyżej.

Widok stanowiący punkt odniesienia.

layout_below

layout_alignTop

Umieszcza widok poniżej widoku stanowiącego punkt odniesienia.

Twój widok jest umieszczony poniżej.

Wyrównuje górną krawędź widoku z górną krawędzią widoku stanowiącego punktu odniesienia. Wyrównuje górne krawędzie widoków.

layout_alignBottom

Wyrównuje dolną krawędź widoku do dolnej krawędzi widoku stanowiącego punkt odniesienia.

layout_alignLeft, layout_alignStart

Wyrównuje lewą (lub początkową) krawędź widoku do lewej (lub początkowej) krawędzi widoku stanowiącego punkt odniesienia.

layout_alignRight, layout_alignEnd

Wyrównuje prawą (lub końcową) krawędź widoku do prawej (lub końcowej) krawędzi widoku stanowiącego punkt odniesienia.

layout_toLeftOf, layout_toStartOf

Umieszcza prawą (lub końcową) krawędź widoku z lewej (początkowej) strony widoku stanowiącego punkt odniesienia.

layout_toRightOf, layout_toEndOf

Umieszcza lewą (lub początkową) krawędź widoku z prawej (końcowej) strony widoku stanowiącego punkt odniesienia.

822

Dodatek A

Wyrównuje dolne krawędzie widoków.

Wyrównuje lewe krawędzie widoków. Wyrównuje prawe krawędzie widoków.

Twój widok jest umieszczony z lewej strony.

Twój widok jest umieszczony z prawej strony.

Układy względne i układy siatki

Układ GridLayout wyświetla widoki w siatce 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 z tych obszarów jest komórką.

Jak jest definiowany układ siatki? Układ siatki definiuje się podobnie jak układy innych typów, lecz należy do tego użyć elementu :

będą dwie kolumny). To właśnie tu dodajesz ... wszelkie nowe widoki.

android:layout_width=”match_parent”

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. Podobnie możemy określić liczbę wierszy siatki: android:rowCount=”liczba”

choć w praktyce można ją pominąć i pozwolić, by Android sam ją obliczył na podstawie liczby widoków umieszczonych w układzie. W takim przypadku Android utworzy tyle wierszy, ile będzie potrzebnych do wyświetlenia wszystkich widoków.

jesteś tutaj  823

Układ GridLayout

Dodawanie widoków do układu siatki Widoki dodajemy do układu siatki podobnie jak do układu liniowego:



Podobnie jak w przypadku układu liniowego, także w przypadku układu siatki umieszczane w nim widoki nie muszą mieć identyfikatorów, chyba że zamierzamy odwoływać się do nich w kodzie aktywności. Poszczególne widoki umieszczane w układzie nie muszą odwoływać się do siebie nawzajem, więc stosowanie identyfikatorów nie jest niezbędne. Domyślnie układ siatki rozmieszcza widoki w kolejnych komórkach w takiej samej kolejności, w jakiej są one zapisane w kodzie XML. A zatem jeśli nasz układ będzie się składał z dwóch kolumn, to pierwszy widok zostanie umieszczony w pierwszej komórce siatki, drugi w drugiej i tak dalej. Wadą takiego sposobu postępowania jest to, że usunięcie widoku z układu może doprowadzić do drastycznej zmiany jego wyglądu. Rozwiązaniem tego problemu jest określanie, w którym miejscu układu mają być wyświetlane poszczególne widoki i ile kolumn ma zajmować każdy z nich.

824

Dodatek A

Układy względne i układy siatki

Utwórzmy nowy układ siatki Aby przyjrzeć się temu rozwiązaniu w akcji, utwórzmy układ siatki, który będzie określał, gdzie należy umieścić poszczególne widoki i ile kolumn ma zajmować każdy z nich. Nasz układ będzie się składał z komponentu TextView prezentującego słowo „Do”, pola tekstowego zawierającego tekst podpowiedzi „Wpisz adres e-mail”, drugiego pola tekstowego, zawierającego tekst podpowiedzi „Treść wiadomości” oraz przycisku z etykietą „Wyślij”:

Oto co mamy zamiar zrobić: 1

Naszkicować wygląd interfejsu użytkownika i podzielić go na wiersze i kolumny.

W ten sposób będzie nam łatwiej sobie wyobrazić, jak należy zdefiniować układ.

2

Utworzyć układ wiersz po wierszu.

jesteś tutaj  825

Naszkicuj to

Zaczynamy od naszkicowania układu Pierwszym etapem prac nad naszym nowym układem będzie jego naszkicowanie. Dzięki temu łatwiej nam będzie określić, z ilu wierszy i kolumn ma się on składać, gdzie należy umieścić poszczególne widoki oraz ile kolumn powinien zajmować każdy z tych widoków. 1. kolumna

1. wiersz

Do

2. kolumna

Wpisz adres e-mail

W pierwszym wierszu w pierwszej kolumnie jest umieszczony napis „Do”, a w drugiej kolumnie znajduje się pole tekstowe z tekstem podpowiedzi „Wpisz adres e-mail”.

Treść wiadomości W drugim wierszu jest umieszczone pole tekstowe z tekstem podpowiedzi „Treść wiadomości”. Pole to zaczyna się w pierwszej kolumnie i rozciąga także na drugą. Musi ono zająć cały dostępny obszar wiersza.

2. wiersz

3. wiersz

Wyślij

Trzeci wiersz zawiera przycisk z napisem „Wyślij”. Przycisk jest wyśrodkowany w obszarze obejmującym obie kolumny, co oznacza, że musi zajmować obie kolumny układu.

Nasz układ siatki musi się składać z dwóch kolumn Wszystkie widoki będziemy mogli rozmieścić w planowany sposób, jeśli utworzymy układ siatki składający się z dwóch kolumn:

Skoro zdefiniowaliśmy podstawę układu, możemy zacząć dodawać do niego poszczególne widoki.

826

Dodatek A

Układy względne i układy siatki

Wiersz 0. Dodajemy widoki do określonych wierszy i kolumn Pierwszy wiersz układu siatki składa się z komponentu TextView umieszczonego w pierwszej kolumnie oraz pola tekstowego umieszczonego w drugiej kolumnie. Zacznij od dodania obu tych widoków do układu:

Do

Wpisz adres e-mail

W układach siatki można używać atrybutów android:gravity i android:layout_gravity.



Atrybutu layout_gravity możemy używać także w układach siatki. W tym układzie zastosowaliśmy wartośc fill_horizontal, gdyż chcemy, by pole tekstowe zajmowało całą szerokość dostępnego obszaru.

Teraz użyjemy dwóch kolejnych atrybutów, android:layout_row i android:layout_ column, aby określić, w którym wierszu i której kolumnie układu ma zostać umieszczony konkretny widok. Indeksy wiersza i kolumny zawsze są liczone od 0, a zatem aby widok został umieszczony w pierwszej kolumnie pierwszego wiersza, musimy użyć poniższych atrybutów: android:layout_row=”0” android:layout_column=”0”

Numeracja wierszy i kolumn zaczyna się od 0, a zatem te dwa atrybuty wskazują na pierwszy wiersz i pierwszą kolumnę.

Spróbujmy więc zastosować te atrybuty, by umieścić komponent TextView w kolumnie 0 i pole tekstowe w kolumnie 1.

Indeksy wierszy i kolumn są numerowane od 0. A zatem atrybut layout_column=”n” wskazuje na kolumnę n+1 w układzie.

Wiersz 0

Do

Wpisz adres e-mail



jesteś tutaj  827

Wiersz 1.

Wiersz 1. Tworzymy widok zajmujący komórki kilku kolumn W drugim wierszu układu chcemy umieścić duże pole tekstowe, które będzie zajmowało obszar komórki z pierwszej i z drugiej kolumny. W ten sposób pole zajmie całą szerokość wiersza. Aby widok został wyświetlony w kilku kolumnach, najpierw musimy określić, w której kolumnie ma się on zaczynać. Chcemy, by widok zaczynał się w pierwszej kolumnie drugiego wiersza, więc określimy jego położenie, używając poniższych atrybutów: android:layout_row=”1”

Wiersz 1

Kolumna 0

Kolumna 1

Treść wiadomości

android:layout_column=”0”

Ponadto chcemy, by widok rozciągał się na obie kolumny wiersza — ten efekt możemy uzyskać, stosując atrybut android:layout_ columnSpan, który jest używany w następujący sposób: android:layout_columnSpan=”liczba”

gdzie liczba określa, ile kolumn dany widok ma zajmować. W naszym przykładzie zastosujemy atrybut o następującej postaci:

Liczba zajmowanych kolumn: 2

android:layout_columnSpan=”2”

Łącząc to wszystko w jedną całość, uzyskamy taki oto kod definiujący pole tekstowe do wpisywania treści wiadomości:



To są widoki dodane na poprzedniej stronie do wiersza 0.



Skoro załatwiliśmy sprawę dwóch pierwszych wierszy układu, czas się zająć przyciskiem umieszczonym w trzecim wierszu.

828

Dodatek A

Układy względne i układy siatki

Wiersz 2. Tworzymy widok zajmujący komórki kilku kolumn Chcemy, by przycisk został wyśrodkowany w obszarze obejmującym obie kolumny wiersza, jak na poniższym rysunku: Kolumna 0

Wiersz 2

Kolumna 2

Wyślij

Liczba zajmowanych kolumn: 2

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?



To są widoki, które dodaliśmy już wcześniej.



"1"

"1" "2"

center_horizontal

jesteś tutaj  829

Rozwiązanie

Magnesiki układowe. Rozwiązanie 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?





Kolumna 0

Kolumna 1

fill_horizontal Wiersz 2

Wyślij

Liczba zajmowanych kolumn: 2

"0" Te magnesiki nie są Ci potrzebne.

"1"

830

Dodatek A

"1"

Układy względne i układy siatki

Pełny kod układu siatki Na tej i następnej stronie przedstawiliśmy kompletny kod układu siatki.

Widok tekstowy Do.

Pole tekstowe do podania adresu e-mail.

jesteś tutaj  831

Kod, ciąg dalszy

Kod układu siatki, ciąg dalszy



832

Dodatek A

Przycisk zajmuje dwie kolumny, licząc od kolumny 0 drugiego wiersza układu.

Dodatek B. Gradle

Program do budowy Gradle Wziąć jedno SDK, dodać szczyptę bibliotek, dobrze wymieszać, a następnie piec przez dwie minuty.

Większość aplikacji na Androida jest budowana przy użyciu programu narzędziowego o nazwie Gradle. Program Gradle działa za kulisami: odnajduje i pobiera biblioteki, kompiluje i wdraża kod, wykonuje testy, czyści fugi i tak dalej… W większości przypadków możemy sobie nawet nie zdawać sprawy, że Gradle istnieje, gdyż Android Studio udostępnia do jego obsługi graficzny interfejs użytkownika. Jednak czasami warto zajrzeć mu pod maskę i ręcznie go podregulować. W tym dodatku pokażemy Ci niektóre spośród wielu talentów i umiejętności programu Gradle.

to jest nowy dodatek  833

Prezentacja Gradle

dało Gradle Bo co nam dali Rzymianie?

Kiedy klikniemy przycisk uruchamiania programu w Android Studio, większość faktycznej pracy zostanie zrobiona przez zewnętrzne narzędzie do budowy — program Gradle. Oto niektóre czynności wykonywane przez Gradle:



Odnajdowanie i pobieranie za nas właściwych wersji bibliotek innych twórców.



Wywoływanie we właściwej kolejności odpowiednich narzędzi do budowy, by przekształcić kod źródłowy i zasoby w gotową do wdrożenia aplikację.



Instalowanie i uruchamianie aplikacji na urządzeniu z Androidem.



Cała masa innych czynności, takich jak wykonywanie testów i sprawdzanie jakości kodu.

Bardzo trudno jest podać kompletną listę wszystkich czynności, jakie może wykonywać Gradle, gdyż narzędzie to zostało zaprojektowane pod kątem łatwego rozszerzania. W odróżnieniu od innych narzędzi do budowy bazujących na XML-u, takich jak Maven lub Ant, program Gradle został napisany w języku proceduralnym (Groovy), który jest używany zarówno do konfigurowania procedury budowy, jak i rozszerzania możliwości funkcjonalnych samego programu.

Pliki Gradle projektu Podczas tworzenia każdego nowego projektu Android Studio generuje także dwa pliki o nazwie build.gradle. Pierwszy z nich znajduje się w katalogu projektu i zawiera stosunkowo niewiele informacji dotyczących podstawowych ustawień aplikacji, takich jak używana wersja programu Gradle oraz stosowane repozytorium: buildscript { repositories { jcenter() } dependencies { classpath ’com.android.tools.build:gradle:2.3.0’ } } allprojects { repositories { MojProjekt jcenter() } } build.gradle task clean(type: Delete) { delete rootProject.buildDir }

Zazwyczaj zmiany w kodzie tego pliku trzeba będzie wprowadzać wyłącznie w przypadku, gdy będziemy chcieli zastosować jakąś zewnętrzną wtyczkę, bądź w razie konieczności określenia innego miejsca, z którego należy pobierać biblioteki.

834

Dodatek B

Gradle

Główny plik Gradle aplikacji Drugi plik build.gradle jest umieszczony w katalogu app projektu. To właśnie on określa, w jaki sposób należy budować wszystkie kody głównego modułu aplikacji na Androida. To także w nim są ustawiane wartości większości właściwości aplikacji, takich jak docelowy poziom API, oraz podawane szczegóły dotyczące niezbędnych bibliotek zewnętrznych: apply plugin: ’com.android.application’ android { compileSdkVersion 25 buildToolsVersion ”25.0.1”

MojProjekt

defaultConfig { applicationId ”com.hfad.example”

app

minSdkVersion 19 targetSdkVersion 25 versionCode 1

build.gradle

versionName ”1.0” testInstrumentationRunner ”android.support.test.runner.AndroidJUnitRunner” } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile(’proguard-android.txt’), ’proguard-rules.pro’ } } } dependencies { compile fileTree(dir: ’libs’, include: [’*.jar’]) androidTestCompile(’com.android.support.test.espresso:espresso-core:2.2.2’, { exclude group: ’com.android.support’, module: ’support-annotations’ }) compile ’com.android.support:appcompat-v7:25.1.1’ compile ’com.android.support.constraint:constraint-layout:1.0.2’ testCompile ’junit:junit:4.12’ }

jesteś tutaj  835

Gradle jest wbudowany

Gradle jest wbudowany w każdy projekt Za każdym razem, gdy tworzysz nową aplikację na Androida, Android Studio dodaje do niej kompletną, zainstalowaną wersję programu Gradle. Jeśli zajrzymy do katalogu projektu, znajdziemy w nim dwa pliki: gradlew i gradlew.bat. Zawierają one skrypty pozwalające na zbudowanie i wdrożenie aplikacji z poziomu wiersza poleceń. Aby nieco dokładniej poznać Gradle, otwórz okno wiersza poleceń lub terminal na komputerze używanym do programowania i przejdź do głównego katalogu projektu. Następnie uruchom skrypt gradlew.bat z parametrem tasks. W efekcie Gradle wyświetli listę niektórych zadań, które może wykonać:

Przyjrzyjmy się najbardziej przydatnym zadaniom wykonywanym przez program Gradle.

836

Dodatek B

iki Trochę skróciliśmy faktyczne wyn generowane przez to polecenie, ń gdyż domyślnie dostępnych zada e. wiel zo bard zo, jest bard

Gradle

Zadanie check Zadanie check wykonuje statyczną analizę kodu źródłowego aplikacji. Możesz o nim myśleć jako o pomocniku programisty, który w mgnieniu oka może przeglądać pliki źródłowe i szukać w nich błędów w kodzie. Domyślnie zadanie check poszukuje błędów programistycznych najczęściej występujących w aplikacjach na Androida, używając narzędzia lint. Zadanie to generuje raport zapisywany w pliku app/build/reports/lint-results.html:

MojProjekt app build reports lint-results.html

Zadanie clean installDebug To zadanie wykona kompletną kompilację aplikacji i zainstaluje ją na podłączonym do komputera urządzeniu z Androidem. Oczywiście to samo można zrobić z poziomu IDE, jednak czasami wygodniej jest móc tę operację wykonać z poziomu wiersza poleceń, na przykład kiedy chcemy automatycznie zbudować aplikację na serwerze integracyjnym.

jesteś tutaj  837

Wyświetlanie zależności

Zadanie androidDependencies Wykonując to zadanie Gradle, wyświetli wszystkie biblioteki wymagane przez aplikację, a niektóre z nich będą wymagały innych bibliotek, które także zostaną wyświetlone, te mogą wymagać jeszcze innych bibliotek, te z kolei… Na pewno rozumiesz, o co chodzi. Choć w pliku build.gradle aplikacji może być podanych jedynie kilka bibliotek, to zapewnienie poprawnego działania aplikacji może wymagać zainstalowania bardzo wielu bibliotek zależnych. Dlatego czasami może się przydać sprawdzenie, których bibliotek aplikacja wymaga i dlaczego. I właśnie do tego służy zadanie androidDependencies — wyświetla ono drzewo wszystkich bibliotek wymaganych przez aplikację:

838

Dodatek B

Gradle

gradlew Prawdziwym powodem, dla którego aplikacje na Androida są powszechnie budowane przy użyciu Gradle, są możliwości rozszerzania tego narzędzia. Wszystkie pliki Gradle są pisane w języku Groovy — języku ogólnego przeznaczenia zaprojektowanym pod kątem wykonywania przez Javę. To z kolei oznacza, że bardzo łatwo można tworzyć i stosować własne zadania.

MojProjekt app

W ramach przykładu dodaj na końcu swojego pliku app/build.gradle poniższy fragment kodu:

build.gradle

task javadoc(type: Javadoc) { source = android.sourceSets.main.java.srcDirs classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) destinationDir = file(”$project.buildDir/javadoc/”) failOnError false }

Powyższy kod definiuje nowe zadanie o nazwie javadoc, które generuje dokumentację Javy na podstawie kodów źródłowych aplikacji. To zadanie można wykonać w następujący sposób: gradlew javadoc

Wygenerowane pliki zostaną zapisane w katalogu app/build/javadoc:

MojProjekt app build javadoc index.html

jesteś tutaj  839

Wtyczki

Wtyczki Gradle Poza tym, że można pisać własne zadania, można także instalować wtyczki do Gradle. Wtyczki mogą w znacznym stopniu rozszerzyć środowisko do budowy kodu. Chciałbyś może pisać kod aplikacji na Androida w Clojure? A może chciałbyś, by proces budowania był zintegrowany z systemem kontroli wersji, takim jak Git? A może by tak uruchamiać całe serwery na Dockerze, a następnie na nich testować pisane aplikacje? Te oraz wiele innych rzeczy możesz robić, korzystając z wtyczek Gradle. Więcej informacji na temat dostępnych wtyczek można znaleźć na stronie https://plugins.gradle.org.

840

Dodatek B

Dodatek C. ART

Środowisko uruchomieniowe Androida A więc to się dzieje pod maską…

Czy kiedykolwiek zastanawiałeś się, jak to się dzieje, że aplikacje na Androida mogą działać na tak wielu rodzajach urządzeń? Aplikacje na Androida są wykonywane w wirtualnej maszynie nazywanej środowiskiem uruchomieniowym Androida (ART — Android runtime), a nie w wirtualnej maszynie Javy firmy Oracle. Oznacza to, że będą one szybciej uruchamiane na niewielkich urządzeniach o niewielkiej mocy obliczeniowej i będą na nich działać bardziej efektywnie. W tym dodatku dowiesz się, jak działa ART.

to jest nowy dodatek  841

Czym jest art?

Czym jest środowisko uruchomieniowe Androida? Środowisko uruchomieniowe Androida (ang. Android Runtime, w skrócie ART) jes systemem, który wykonuje skompilowany kod aplikacji na urządzeniu z Androidem. Po raz pierwszy ART pojawił się w systemie Android wraz z jego wersją KitKat, a w wersji Lollipop stał się już standardowym mechanizmem do wykonywania kodu. ART został zaprojektowany do szybkiego i wydajnego wykonywania skompilowanych aplikacji na Androida na małych urządzeniach o niewielkiej mocy przetwarzania. Android Studio używa programu Gradle, by uwolnić programistów od konieczności samodzielnego wykonywania zadań związanych z tworzeniem i instalowaniem aplikacji, lecz może być on także przydatny, kiedy musimy zrozumieć, co się dzieje za kulisami, gdy klikniemy przycisk Uruchom. Sprawdźmy zatem, co wtedy następuje.

Wcześniej, kody pisane w Javie były wykonywane na JVM firmy Oracle Język Java istnieje już od dość długiego czasu, a skompilowane aplikacje napisane w tym języku niemal zawsze były wykonywane na wirtualnej maszynie Javy (JVM — Java Virtual Machine) firmy Oracle. W tym scenariuszu kod źródłowy pisany w Javie jest kompilowany do postaci plików klasowych, .class. Dla każdej klasy Javy, każdego interfejsu oraz typu wyliczeniowego tworzony jest odrębny plik .class:

javac .java

Pliki klasowe, .class, zawierają kody bajtowe, które mogą być odczytywane i wykonywane przez JVM. JVM jest programową emulacją centralnej jednostki obliczeniowej (CPU), takiej jak procesor stanowiący serce komputera roboczego. Jednak ponieważ jest ona emulowana, można ją uruchomić niemal na każdym urządzeniu. To właśnie ta cecha sprawia, że kod Javy jest pisany raz, lecz można go wykonywać wszędzie. Czy to właśnie to się dzieje na urządzeniach z Androidem? Cóż… nie do końca. Środowisko uruchomieniowe Androida realizuje podobne zadania co JVM, jednak robi to w nieco inny sposób.

842

Dodatek C

.class

informacji Nie musisz dokładnie rozumieć zamieszczonych w tym dodatku, Androida. by tworzyć świetne aplikacje na ciekawy, A zatem jeśli nie jesteś bardzo oida Andr ką mas pod co się dzieje na podczas uruchamiania aplikacji pominąć urządzeniu, to spokojnie możesz ten dodatek.

ART

ART kompiluje kody do plików DEX W przypadku tworzenia aplikacji na Androida kody źródłowe pisane w Javie są kompilowane do postaci plików .dex. Pliki .dex mają podobne przeznaczenie co pliki .class, gdyż, podobnie jak one, zawierają wykonywalne kody bajtowe. Jednak nie zawierają one kodów bajtowych JVM, tylko kody zapisane w innym formacie, nazywanym Dalvik. DEX to skrót od angielskich słów: Dalvik EXecutable. Zamiast tworzyć osobne pliki .dex dla każdej klasy, aplikacje na Androida kompiluje się zazwyczaj do postaci jednego dużego pliku o nazwie classes.dex. Ten jeden plik będzie zawierał kody bajtowe odpowiadające wszystkim kodom źródłowym aplikacji oraz kod wszystkich bibliotek, których ta aplikacja używa.

.class

classes.dex

Format DEX pozwala na obsługę jedynie 65535 metod, więc jeśli tworzona aplikacja ma bardzo rozbudowany kod lub jeśli używa dużych bibliotek, to może się zdarzyć, że będzie musiała zostać skonwertowana do postaci kilku plików .dex:

.class

.dex

Więcej informacji na temat tworzenia aplikacji korzystających z większej liczby plików .dex można znaleźć na stronie: https://developer.android.com/studio/build/multidex.html

jesteś tutaj  843

.class na .dex

Jak są tworzone pliki DEX? Kiedy Android buduje aplikację, korzysta z narzędzia o nazwie dx, które zamienia pliki .class na DEX:

.class

dx .dex

Biblioteki

Może się wydawać dziwne, że proces kompilacji składa się z dwóch etapów: kompilacji kodów źródłowych do plików .class oraz plików .class do formatu DEX. Dlaczego firma Google nie stworzyła jednego narzędzia, które kompilowałoby kod źródłowy w Javie bezpośrednio do kodów bajtowych w formacie DEX? Przez jakiś czas firma Google tworzyła kompilator o nazwie JACK i współpracujący z nim program konsolidujący (ang. linker) JILL, które mogły tworzyć kod DEX bezpośrednio z kodów Javy, jednak rozwiązanie to było pod jednym względem problematyczne. Otóż niektóre narzędzia języka Java nie działają na poziomie kodów źródłowych — operują bezpośrednio na plikach klasowych i modyfikują ich zawartość. Na przykład jeśli używamy narzędzia do sprawdzania analizy kodu, pozwalającego zbadać, które fragmenty kodu są w rzeczywistości weryfikowane przez napisane przez nas testy, to narzędzie to będzie zazwyczaj chciało zmodyfikować zawartość wygenerowanych plików .class i dodać do nich własne kody bajtowe pozwalające na śledzenie wykonywanego kodu. Jednak w przypadku stosowania kompilatora JACK żadne pliki .class nie były generowane. Z tego względu w marcu 2017 roku firma Google ogłosiła, że odkłada projekt kompilatora JACK na półkę, a cały wysiłek będzie wkładać w zapewnienie jak najlepszej pracy narzędzia dx z najnowszymi formatami plików klasowych. Rozwiązanie to ma tę dodatkową zaletę, że wszelkie nowe możliwości języka Java, o ile tylko nie będą wymagać wprowadzania nowych kodów bajtowych, będą automatycznie obsługiwane przez Androida.

844

Dodatek C

ART

Pliki DEX są kompresowane i zapisywane w plikach APK Jednak aplikacje na Androida nie są rozpowszechniane w formie plików .dex. Aplikacje składają się bowiem z bardzo wielu innych plików: graficznych, dźwiękowych, metadanych i tak dalej. Wszystkie te zasoby, wraz z kodami bajtowymi DEX, są umieszczane w jednym pliku ZIP, nazywanym pakietem Androida, Android Package, i mającym rozszerzenie .apk. Ten plik .apk jest tworzony przez kolejne narzędzie — program Android Asset Packing Tool — czyli aapt.

classes.dex

aapt .apk

Zasoby

Kiedy instalujemy aplikację ze sklepu Google Play, to na nasze urządzenie pobierany jest właśnie plik APK. W rzeczywistości, kiedy uruchamiamy aplikację za pośrednictwem Android Studio, system budowy w pierwszej kolejności utworzy plik .apk, a następnie zainstaluje go na urządzeniu z Androidem.

Może się pojawić konieczność podpisania pliku .apk Jeśli zamierzamy rozpowszechniać aplikację za pośrednictwem Sklepu Play firmy Google, to będziemy musieli ją podpisać. Podpisanie pakietu aplikacji oznacza umieszczenie w pliku .apk dodatkowego pliku zawierającego sumę kontrolą zawartości pliku .apk oraz osobno wygenerowanego klucza prywatnego. Pliki .apk używają standardowego programu narzędziowego do podpisywania plików JAR, jarsigner, dostarczanego wraz z JDK firmy Oracle. Program jarsigner został stworzony z myślą o podpisywaniu plików .jar, lecz może także podpisywać pliki .apk. W przypadku podpisywania pliku .apk trzeba go także przetworzyć przy użyciu programu narzędziowego o nazwie zipalign, który upewni się, że skompresowane fragmenty pliku będą wyrównane do granicy bajtu. Android wymaga takiego wyrównywania plików APK, by móc je z łatwością odczytywać bez konieczności rozpakowywania.

jarsigner .apk

zipalign signed .apk

Android Studio wykona za nas wszystkie te czynności, kiedy wybierzemy z menu Build opcję Generate Signed APK. To właśnie w taki sposób tworzona aplikacja jest konwertowana z kodów źródłowych napisanych w Javie na plik, który można zainstalować na urządzeniu z Androidem. Ale w jaki sposób ten plik zostanie zainstalowany, a następnie uruchomiony na urządzeniu?

jesteś tutaj  845

Witaj, adb

Przywitaj się z Android Debug Bridge (adb) Cała komunikacja pomiędzy naszym komputerem roboczym oraz urządzeniem z Androidem odbywa się za pośrednictwem Android Debug Bridge. Można by rzec, że ten „most” ma dwa końce: pierwszym z nich jest program adb wykonywany na komputerze roboczym z poziomu wiersza poleceń, a drugim proces (nazywany także demonem) adbd (Android Debug Bridge Deamon) działający na urządzeniu. Polecenie adb na komputerze roboczym uruchamia w tle swoją kopię, nazywaną serwerem ADB. Serwer ten odbiera polecenia na porcie sieciowym o numerze 5037 i przesyła je do procesu adbd na urządzeniu. Jeśli chcemy skopiować lub przeczytać plik, zainstalować aplikację bądź też przejrzeć dziennik logcat aplikacji, to wszystkie te informacje są przekazywane przez adb. A zatem kiedy nasze narzędzia do budowy aplikacji będą chciały zainstalować plik APK, zrobią to, wysyłając do adb polecenie podobne do tego przedstawionego poniżej: adb install wloskieconieco.apk

Wskazany plik zostanie następnie przesłany na urządzenie wirtualne lub kablem USB na rzeczywiste urządzenie i zainstalowany w katalogu /data/app/, gdzie aplikacja będzie oczekiwać na uruchomienie.

Jak ożywają aplikacje: uruchamianie pliku APK Kiedy aplikacja jest uruchamiana, niezależnie od tego, czy nastąpi to w wyniku kliknięcia ikony, czy też zostanie wywołane przez IDE, urządzenie z Androidem musi przekształcić plik .apk w proces działający w pamięci urządzenia. Do tego używany jest proces o nazwie Zygote. Przypomina on nieco proces, który jest uruchomiony tylko częściowo. Dysponuje on pewnym obszarem przydzielonej pamięci oraz głównymi bibliotekami Androida. Właściwie dysponuje on wszystkim, co niezbędne, z wyjątkiem kodu charakterystycznego dla naszej aplikacji. Kiedy Android uruchamia naszą aplikację, w pierwszej kolejności tworzy kopię (tak zwany fork) procesu Zygote, a następnie nakazuje temu skopiowanemu procesowi uruchomienie aplikacji. Ale dlaczego Android korzysta przy tym z tego częściowo uruchomionego procesu? Dlaczego nie uruchamia każdej aplikacji w całości od zera? Chodzi o uzyskanie lepszej wydajności. Utworzenie nowego procesu od samego początku może zająć Androidowi sporo czasu, a utworzenie kopii istniejącego procesu zajmuje mu ułamek sekundy. Proces Zygote to niemal uruchomiona aplikacja na Androida.

846

Dodatek C

Proces Zygote

fork()

Proces aplikacji

Proces nowej aplikacji będzie kompletną kopią procesu Zygote.

ART

Android konwertuje kod .dex do formatu OAT Proces nowej aplikacji musi następnie wczytać kod naszej aplikacji. Pamiętasz zapewne, że jej kod jest zapisany w pliku classes.dex umieszczonym w pliku .apk. A zatem plik classes.dex jest wyciągany z pliku .apk i umieszczany w osobnym katalogu. Jednak zamiast zwyczajnie używać pliku classes.dex, Android skonwertuje zapisane w nim kody Dalvik na rodzimy kod maszynowy. Z technicznego punktu widzenia kody z pliku classes.dex zostaną skonwertowane do postaci współdzielonego obiektu ELF. Android określa ten format biblioteczny jako OAT, a do konwersji plików classes.dex używa narzędzia dex2oat.

dex2oat classes.dex

classes.dex (wersja OAT)

Skonwertowany plik zostaje następnie zapisany w katalogu o nazwie podobnej do tej przedstawionej poniżej: /data/dalvik-cache/data@[email protected]@[email protected] Ten plik może już zostać wczytany przez proces aplikacji jako rodzima biblioteka, co pozwoli na wyświetlenie ekranu aplikacji.

jesteś tutaj  847

848

Dodatek C

Dodatek D. ADB

Android Debug Bridge Czy dziewczynie, która ma już wszystko, można sprawić lepszy prezent niż narzędzie obsługiwane z poziomu wiersza poleceń?

Najdroższy, jakiż to uroczy wyraz troski!

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.

to jest nowy dodatek  849

ADB

adb — Twój kumpel z wiersza poleceń Za każdym razem, gdy komputer, którego używamy do pisania aplikacji, musi się porozumieć z urządzeniem z Androidem, niezależnie od tego, czy jest to realne urządzenie podłączone kablem USB, czy też urządzenie wirtualne działające w emulatorze, robi to za pomocą Android Debug Bridge (ADB). ADB jest procesem kontrolowanym za pomocą programu o tej samej nazwie — adb. Program adb jest przechowywany w podkatalogu platform-tools, w katalogu, w którym został zainstalowany Android SDK. Jeśli dodamy katalog platform-tools do zmiennej systemowej PATH, to będziemy mogli uruchamiać program adb z poziomu wiersza poleceń. W oknie terminala lub wiersza poleceń można tego programu używać w następujący sposób:

Polecenie adb devices oznacza: „Powiedz, z jakimi urządzeniami z Androidem jesteś połączony”. Polecenie adb komunikuje się z procesem serwera adb, który działa w tle. Serwer adb jest czasami nazywany także demonem adb lub adbd. Wpisanie w oknie terminala polecenia adb powoduje przesłanie żądania na komputer lokalny, na port 5037. Proces adbd oczekuje na polecenia przesyłane na ten port. Kiedy Android Studio chce uruchomić aplikację, sprawdzić wpisy w dzienniku bądź też wykonać jakąkolwiek inną operację wymagającą komunikacji z urządzeniem z Androidem, robi to, przesyłając polecenia na port 5037.

adb

adbd

polecenie adb

proces demona adb

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 jest wprowadzanie zmian na urządzeniu lub zwracanie żądanych informacji.

850

Dodatek D

Urządzenie

Urządzenie

ADB Czasami, gdy serwer adb nie będzie działał, wykonanie polecenia adb spowoduje jego uruchomienie:

I analogicznie, jeśli kiedykolwiek zdarzy się, że podłączymy urządzenie z Androidem do komputera, a Android Studio go nie zauważy, to będziemy mogli ręcznie zakończyć działanie serwera adb i ponownie go uruchomić:

Kończąc działanie serwera, a następnie uruchamiając serwer, zmuszamy adb, by ponownie nawiązało połączenie ze wszystkimi urządzeniami z Androidem podłączonymi do komputera.

jesteś tutaj  851

ADB

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 zdecydowaną 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. Jednym z takich przypadków jest korzystanie z powłoki systemowej na urządzeniu:

Polecenie adb shell uruchomi interaktywną powłokę systemową działającą bezpośrednio na urządzeniu z Androidem. Jeśli do komputera podłączono więcej niż jedno urządzenie z Androidem, to to, o które nam chodzi, można określić, używając opcji -s, po niej zaś należy podać nazwę wyświetloną przez polecenie adb devices. Na przykład wykonanie polecenia adb -s emulator-5554 shell spowoduje uruchomienie powłoki na emulatorze. Po uruchomieniu powłoki systemowej można w niej wykonywać wiele standardowych poleceń systemu Linux:

852

Dodatek D

ADB

Przydatne polecenia powłoki Jeśli otworzymy powłokę do urządzenia z Androidem, uzyskamy dostęp do całej grupy programów narzędziowych wykonywanych z poziomu wiersza poleceń. Poniżej przedstawiliśmy tylko kilka z nich:

Polecenie

Opis

Przykład (i wyjaśnienie przeznaczenia)

pm

Program do zarządzania pakietami.

pm list packages (wyświetla wszystkie zainstalowane aplikacje) pm path com.hfad.wloskieconieco (określa, gdzie jest zainstalowana aplikacja) pm -help (wyświetla inne opcje)

ps

Wyświetla status procesu.

ps (wyświetla listę wszystkich procesów i ich identyfikatorów)

dexdump

Wyświetla szczegółowe informacje na temat pliku APK.

dexdump -d /data/app/com.hfad. wloskieconieco-2/base.apk (deasembluje aplikację)

lsof

Wyświetla listę otworzonych plików oraz innych połączeń dla wskazanego procesu.

lsof -p 1234 (pokazuje, co robi proces 1234)

screencap

Wykonuje zrzut ekranu.

screencap -p /sdcard/screenshot.png (wykonuje zrzut bieżącego ekranu i zapisuje go w pliku /sdcard/ screenshot.png; plik ten można pobrać z urządzenia przy użyciu polecenia adb pull /sdcard/screenshot.png)

top

Wyświetla najbardziej zajęty proces.

top -m 5 (wyświetla 5 najbardziej zajętych procesów)

Każde z tych poleceń można wykonać w interaktywnym wierszu powłoki, lecz poza tym można je także przekazać do powłoki bezpośrednio, z poziomu zintegrowanego środowiska programistycznego. Na przykład poniższe polecenie wyświetli wszystkie aplikacje zainstalowane na urządzeniu: Sesja interaktywna

$ adb shell pm list packages

jesteś tutaj  853

Pobieranie wyników

Zamykanie serwera adb Czasami zdarza się, że połączenie pomiędzy komputerem używanym do tworzenia aplikacji oraz urządzeniem zostanie zerwane. W takim przypadku możne je utworzyć od nowa poprzez zamknięcie serwera adb: Sesja interaktywna

$ adb kill-server

Kiedy następnym razem wykonamy polecenie adb, serwer zostanie ponownie uruchomiony i zostanie nawiązane nowe połączenie.

Pobieranie treści z dziennika logcat Wszystkie aplikacje działające w systemie Android wysyłają wyniki do centralnego strumienia o nazwie logcat. Trafiające do niego bieżące komunikaty można oglądać, wykonując polecenie adb logcat:

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 w panelu logcat.

854

Dodatek D

ADB

Kopiowanie plików do i z urządzenia Do przekazywania plików pomiędzy urządzeniem i komputerem służą polecenia adb pull i adb push. Na przykład poniżej pokazaliśmy, jak można skopiować plik właściwości /default.prop i zapisać go na komputerze jako plik 1.txt:

I wiele, wiele więcej… Dostępnych jest bardzo wiele poleceń, które można wykonywać, używając adb: można kopiować i odtwarzać bazy danych (co jest bardzo przydatne w razie konieczności zdiagnozowania problemów występujących w aplikacji korzystającej z bazy danych), uruchomić serwer adb na innym porcie, wymusić ponowne uruchomienie urządzenia czy też uzyskiwać wiele informacji na temat urządzenia. Aby poznać wszystkie dostępne polecenia, wystarczy wpisać w wierszu poleceń adb, jak na poniższym rysunku:

jesteś tutaj  855

856

Dodatek D

Dodatek E. Emulator Androida

Przyspieszanie emulatora Czy to może działać szybciej?!

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 sprawdzić, 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.

to jest nowy dodatek  857

Szybkość

Dlaczego emulator działa tak wolno? Pisząc aplikacje na Androida, bardzo wiele czasu będziemy tracić na oczekiwanie na uruchomienie emulatora i na instalowanie kodu na AVD. A dlaczego? Dlaczego emulator Androida działa tak woooollllnooooooo? Jeśli miałeś okazję pisać kod na iPhone’y, to wiesz, jak szybko działa symulator tego telefonu. Skoro coś jest możliwe w odniesieniu do iPhone’a, to czemu nie jest w przypadku Androida? Wskazówką pozwalającą uzyskać odpowiedź na to pytanie są nazwy: symulator iPhone’a i emulator Androida. Symulator iPhone’a (ang. iPhone Symulator) symuluje urządzenie działające pod kontrolą systemu operacyjnego iOS. Cały kod pisanej aplikacji jest kompilowany tak, by mógł być natywnie wykonywany na komputerze Mac, a symulator iPhone’a działa z pełną szybkością tego komputera. To z kolei oznacza, że możliwe jest zasymulowanie procesu uruchamiania iPhone’a w ciągu zaledwie kilku sekund. Natomiast emulator Androida (ang. Android Emulator) działa w całkowicie inny sposób. Używa on aplikacji open source o nazwie QEMU (lub Quick Emulator), by emulować kompletne, sprzętowe urządzenie z Androidem. Innymi słowy: wykonywany jest kod interpretujący kod maszynowy, który docelowo ma być wykonywany na procesorze urządzenia. Inny kod emuluje system składowania danych, ekran oraz praktycznie wszystkie inne komponenty sprzętowe urządzenia z Androidem.

AVD

AVD

AVD

AVD

Emulator QEMU

AVD Wszystkie wirtualne urządzenia z Androidem (AVD) są wykonywane przez emulator QEMU.

Emulatory, takie jak QEMU, tworzą znacznie bardziej realistyczną reprezentację wirtualnego urządzenia niż programy takie jak iPhone Symulator, choć ich wadą jest to, że nawet w przypadku prostych operacji, takich jak odczyt z dysku lub wyświetlenie czegoś na ekranie, muszą wykonać nieporównywalnie więcej pracy. I to właśnie dlatego uruchomienie AVD w emulatorze zabiera tak dużo czasu. Musi udawać, że jest każdym najmniejszym komponentem sprzętowym wchodzącym w skład urządzenia, i interpretować każdą instrukcję programu.

858

Dodatek E

Emulator Androida

Jak przyspieszyć pisanie aplikacji na Androida? 1. Używając rzeczywistych urządzeń Najprostszym sposobem przyspieszenia procesu tworzenia aplikacji jest korzystanie z rzeczywistych urządzeń. Takie urządzenia uruchamiają się znacznie szybciej niż wirtualne urządzenia działające w emulatorze i najprawdopodobniej pozwolą także na szybsze instalowanie i działanie aplikacji. Chcąc używać rzeczywistego urządzenia, warto wejść do Opcji programistycznych i zaznaczyć w nich przełącznik Pozostaw ekran włączony. Dzięki temu urządzenie nie będzie blokować ekranu, co na pewno będzie wygodne przy wielokrotnym instalowaniu aplikacji.

2. Używając zrzutu stanu emulatora W przypadku stosowania emulatora jego uruchamianie jest bez wątpienia jedną z rzeczy, która zajmuje najwięcej czasu. Jeśli zapiszemy stan urządzenia podczas jego pracy, to emulator będzie w stanie szybko do niego wrócić, bez przechodzenia całego, długotrwałego procesu uruchamiania systemu. Aby skorzystać z tej możliwości, otwórz menedżer AVD, wybierając w menu Android Studio opcję Tools/Android/AVD Manager, przejdź do edycji wybranego AVD poprzez kliknięcie ikony Edit, a następnie zaznacz pole wyboru Store snapshot for faster startup. 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.

3. Korzystając z akceleracji sprzętowej Domyślnie emulator QEMU musi interpretować każdą instrukcję kodu maszynowego wirtualnego urządzenia. Oznacza to, że jest on bardzo elastyczny, gdyż może udawać, iż jest jednym z wielu różnych rodzajów procesorów, lecz jednocześnie jest to jeden z podstawowych powodów jego wolnego działania. Na szczęście jest sposób, by nasz komputer roboczy wykonywał instrukcje kodu maszynowego bezpośrednio. Istnieją dwa główne typy AVD: urządzenia ARM i urządzenia x86. Jeśli stworzysz urządzenie x86 z Androidem i w swoim komputerze używasz jednego z konkretnych typów procesora z rodziny x86 firmy Intel, to będziesz mógł skonfigurować emulator w taki sposób, by instrukcje maszynowe Androida były wykonywane bezpośrednio na procesorze Intela. 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 je było pobrać ze strony: https://software.intel.com/en-us/android/articles/intel-hardware-accelerated-execution-manager-intel-haxm 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.

A jeśli je przeniesiono w inne miejsce, to szybkie wyszukiwanie powinno pozwolić Ci je odnaleźć.

jesteś tutaj  859

Uruchamianie błyskawiczne

4. Stosując uruchamianie błyskawiczne Począwszy od Android Studio 2.3 pojawiła się możliwość znacznie szybszego wdrażania i uruchamiania aplikacji przy użyciu mechanizmu Instant Run — uruchamiania błyskawicznego. Mechanizm ten pozwala Android Studio rekompilować wyłącznie te pliki, które zostały zmienione, a następnie zastosować zmiany w aplikacji obecnie uruchomionej na urządzeniu. Oznacza to, że zamiast czekać około minuty lub dłużej na ponowne skompilowanie i zainstalowanie aplikacji, możemy zobaczyć wprowadzone modyfikacje już po paru sekundach. Aby skorzystać z mechanizmu Instant Run, należy wybrać z menu Run opcję Apply Changes bądź kliknąć umieszczony na pasku narzędzi przycisk z ikoną błyskawicy: Kliknij ten przycisk na pasku narz aby zastosować zmiany wprowad ędzi, zone w aplikacji, używając mechanizmu Instant Run.

Istnieją jednak dwa warunki limitujące możliwość wykorzystania mechanizmu Instant Run. Po pierwsze, aplikacja musi korzystać z API poziomu 15. lub wyższego. Po drugie, aplikacja musi być uruchamiana na urządzeniu z systemem zgodnym z API poziomu 21. lub wyższego. O ile tylko spełnisz te dwa warunki, przekonasz się, że mechanizm uruchamiania błyskawicznego jest zdecydowanie najszybszym sposobem kompilowania i uruchamiania kodu.

860

Dodatek E

Dodatek F. Pozostałości

Dziesięć najważniejszych zagadnień (których nie opisaliśmy) O rany! Spójrz na te apetyczne wątki, które zostały…

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.

to jest nowy dodatek  861

Dystrybucja

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. Takie rozpowszechnianie aplikacji wiąże się z wykonaniem dwóch podstawowych czynności: przygotowania aplikacji do udostępnienia i udostępnienia jej.

Przygotowanie aplikacji do udostępnienia Zanim będziemy mogli udostępnić aplikację, konieczne będzie jej skonfigurowanie, zbudowanie oraz przetestowanie gotowej wersji. Obejmuje to takie czynności jak wybór ikony aplikacji i zmodyfikowanie pliku manifestu AndroidManifest.xml w taki sposób, by aplikację można było pobierać tylko na te urządzenia, na których będzie ona działać. Przed udostępnieniem aplikacji należy się także upewnić, że została ona przetestowana na przynajmniej jednym tablecie i telefonie, aby mieć pewność, iż wygląda ona zgodnie z oczekiwaniami, a jej wydajność jest zadowalająca. Dodatkowe informacje na temat przygotowywania aplikacji do wydania można znaleźć na stronie: http://developer.android.com/tools/publishing/preparing.html

Udostępnianie aplikacji Ten etap obejmuje reklamowanie aplikacji, sprzedawanie jej i rozpowszechnianie. Aby udostępnić swoją aplikację w sklepie Google Play, konieczne jest zarejestrowanie się, utworzenia konta programisty i użycie serwisu Developer Console do opublikowania aplikacji. Szczegółowe informacje na ten temat można znaleźć na stronie: http://developer.android.com/distribute/googleplay/start.html Aby uzyskać więcej informacji o najlepszych sposobach kierowania aplikacji do odpowiednich użytkowników i wzbudzania zainteresowania nią, warto zajrzeć na stronę: http://developer.android.com/distribute/index.html

862

Dodatek F

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.

Jeśli chcesz uzyskać dostęp do bazy danych innej aplikacji, musisz skorzystać z moich usług.

Nasza aktywność

Dostawca treści

Baza danych

Jeśli chcemy, by inne aplikacje mogły używać naszych danych, to możemy w tym celu utworzyć własnego dostawcę treści. Więcej informacji na temat dostawców treści można znaleźć na stronie: http://developer.android.com/guide/topics/providers/content-providers.html Na poniższej stronie można natomiast znaleźć poradnik dotyczący stosowania we własnych aplikacjach dostawcy treści aplikacji Kontakty: http://developer.android.com/guide/topics/providers/contacts-provider.html Podobny poradnik dotyczący stosowania dostawcy treści aplikacji Kalendarz jest dostępny na stronie: http://developer.android.com/guide/topics/providers/calendar-provider.html

jesteś tutaj  863

Obiekty Loader i adaptery synchronizujące

3. Klasy Loader 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 Loader. Obiekty tego typu ułatwiają wczytywanie danych w taki sposób, by można je było wyświetlać w aktywnościach lub we fragmentach. Obiekty typu Loader działają w osobnych wątkach w tle i ułatwiają zarządzanie tymi wątkami, udostępniając metody zwrotne. Obiekty te nie są usuwane podczas zmian konfiguracji i przechowują dane w pamięci podręcznej, dzięki czemu, jeśli użytkownik na przykład odwróci urządzenie, nie będzie trzeba powtórnie tworzyć i wykonywać zapytań. Można także zażądać od nich przekazywania powiadomień w razie zmiany pobranych danych, dzięki czemu będzie można odpowiednio zareagować na te zmiany w interfejsie użytkownika. Interfejs API obiektów tego typu obejmuje ogólną klasę bazową Loader, po której dziedziczy kilka innych klas. Własne obiekty tego typu można tworzyć rozszerzając klasę Loader, choć można także skorzystać z kilku jej wbudowanych klas pochodnych: AsyncTaskLoader lub CursorLoader. Pierwsza z tych klas używa klasy AsyncTask, a druga wczytuje dane z przekazanego dostawcy treści. Więcej informacji na ten temat można znaleźć na stronie: https://developer.android.com/guide/components/loaders.html

4. Adaptery synchronizujące Adaptery synchronizujące pozwalają synchronizować dane przekazywane pomiędzy aplikacją na Androida i serwerem sieciowym. Dzięki nim można wykonywać takie operacje jak tworzenia kopii danych użytkownika na serwerze bądź też przekazywanie danych z serwera do aplikacji, tak by można z nich było korzystać bez połączenia z internetem. Stosowanie adapterów synchronizujących ma kilka zalet w porównaniu z tworzeniem własnego rozwiązania do transferu danych ponieważ adaptery: 

Pozwalają na automatyzowanie transferu danych na podstawie określonych kryteriów — na przykład o określonej godzinie lub gdy dane zostaną zmienione.



Automatycznie sprawdzają, czy jest dostępne połączenie sieciowe, i zaczynają działać jedynie w przypadku, gdy takie połączenie istnieje.



Przesyłają dane w pakietach, co ułatwia poprawianie wydajności działania baterii.



Pozwalają dodawać do przesyłanych danych informacje uwierzytelniające użytkownika lub inne dane niezbędne do zalogowania się na serwerze.

Więcej informacji na temat adapterów synchronizujących można znaleźć na stronie: https://developer.android.com/training/sync-adapters/index.html

864

Dodatek F

Pozostałości

5. 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? Zdarzenia systemowe informują o takich sytuacjach jak osiągnięcie niskiego poziomu naładowania baterii, nadchodzące połączenie telefoniczne czy też ponowne uruchamianie systemu. Android rozgłasza te zdarzenia w momencie ich wystąpienia, a nasza aplikacja może je odbierać i obsługiwać, korzystając z tak zwanych odbiorców komunikatów. Odbiorcy komunikatów pozwalają subskrybować i odbierać konkretne rodzaje komunikatów, a to oznacza, że nasza aplikacja może reagować na zdarzenia systemowe.

Hej, aktywność! Android właśnie powiedział, że bateria już jest prawie rozładowana.

Bateria już jest prawie rozładowana… jeśli to kogoś interesuje.

Android

Odbiorca komunikatów

OK, w porządku. Chwilowo zatrzymam więc wszystkie operacje, które mogą powodować zwiększone zużycie prądu.

Nasza aktywność

Więcej informacji na ten temat można znaleźć na stronie: http://developer.android.com/reference/android/content/BroadcastReceiver.html

jesteś tutaj  865

WebView

6. 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. Klasa WebView pozwala na wyświetlanie zawartości stron WWW w układzie aktywności. Można jej używać, aby udostępniać całą aplikację internetową jako aplikację kliencką albo aby wyświetlać jej konkretne strony. Takie rozwiązanie jest wygodne w przypadkach, gdy nasza aplikacja zawiera treści, które od czasu do czasu mogą być modyfikowane, takie jak licencja lub instrukcja. Aby dodać do układu widok WebView, należy użyć poniższego elementu:

Tak utworzony widok informujemy o tym, którą stronę należy wyświetlić, używając w kodzie aktywności metody loadUrl(): WebView webView = (WebView) findViewById(R.id.webview); webView.loadUrl(”http://helion.pl/”);

Oprócz tego musimy zaznaczyć, że aplikacja wymaga uprawnień do dostępu do internetu. W tym celu musimy dodać do pliku AndroidManifest.xml uprawnienie INTERNET:

....

Więcej informacji na temat stosowania w aplikacjach treści pochodzących z internetu można znaleźć na stronie: http://developer.android.com/guide/webapps/index.html

866

Dodatek F

Pozostałości

7. Ustawienia Wiele aplikacji posiada ekran ustawień, pozwalający użytkownikom na zapisywanie ich preferencji. Na przykład aplikacja do obsługi poczty elektronicznej może pozwalać użytkownikom na określanie, czy przed wysłaniem wiadomości e-mail ma być wyświetlane okienko dialogowe z żądaniem potwierdzenia:

To jest ekran ustawień aplikacji Gmail.

Ekrany ustawień aplikacji tworzy się przy użyciu Preferences API. W ten sposób możemy dodawać poszczególne preferencje i zapisywać wartości każdej z nich. Wartości te są zapisywane we współdzielonym pliku preferencji aplikacji. Więcej informacji na temat ekranów ustawień można znaleźć na stronie: https://developer.android.com/guide/topics/ui/settings.html

jesteś tutaj  867

Animacje

8. 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. W systemie Android można wykonywać kilka różnych rodzajów animacji.

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. Przejścia aktywności (ang. activity transitions) pozwalają na animowanie wybranego widoku z jednej aktywności, który będzie także prezentowany w następnej aktywności. Dzięki nim możemy sprawić, że obrazek z listy będzie płynnie animowany i znajdzie się w odpowiednim miejscu kolejnej aktywności. Taka animacja zapewni wrażenie płynnego działania aplikacji. Więcej informacji na temat animacji w Androidzie można znaleźć na stronie: https://developer.android.com/guide/topics/graphics/index.html Z kolei szczegółowe informacje na temat przejść aktywności można znaleźć na stronie: https://developer.android.com/training/material/animations.html

868

Dodatek F

Pozostałości

9. 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. Oto przykład takiego widżetu: To jest widżet aplikacji. Zapewnia on bezpośredni dostęp do jej podstawowych możliwości funkcjonalnych.

Więcej informacji na temat tworzenia widżetów aplikacji można znaleźć na stronie: http://developer.android.com/guide/topics/appwidgets/index.html

jesteś tutaj  869

Testowanie

10. Testy zautomatyzowane Obecnie podczas tworzenia aplikacji bardzo dużą uwagę zwraca się na zautomatyzowane testy. Jeśli tworzymy aplikację, która ma być używana przez tysiące, a może nawet miliony użytkowników, to szybko ich stracimy, jeśli w aplikacji będą występować błędy lub jeśli często będzie ona ulegać awariom. Istnieje wiele sposobów na przeprowadzanie zautomatyzowanych testów aplikacji, jednak zazwyczaj można je zaliczyć do jednej z dwóch kategorii: testów jednostkowych (ang. unit testing) oraz testowania na urządzeniu (ang. on-device testing, czasami nazywanego także testami instrumentacyjnymi).

Testy jednostkowe Testy jednostkowe są wykonywane na komputerze używanym do pisania aplikacji i sprawdzają poszczególne jednostki — fragmenty — kodu. Najpopularniejszym frameworkiem do tworzenia testów tego typu jest JUnit, a Android Studio najprawdopodobniej automatycznie doda go do tworzonego projektu. Testy jednostkowe są zapisywane w katalogu app/src/test projektu, a typowa metoda takiego testu wygląda jak w poniższym przykładzie: @Test public void returnsTheCorrectAmberBeers() { BeerExpert beerExpert = new BeerExpert(); assertArrayEquals(new String[]{”Miś Paddington”, ”Kłapouchy”}, beerExpert.getBrands(”paddington”).toArray()); }

Więcej informacji na temat frameworka JUnit można znaleźć na stronie: http://junit.org/

870

Dodatek F

Pozostałości

Testy na urządzeniu Testy tego rodzaju są wykonywane w emulatorze Androida lub na fizycznym urządzeniu i sprawdzają kompletną aplikację. Ich działanie opiera się na zainstalowaniu, oprócz samej aplikacji, także dodatkowego pakietu, korzystającego z warstwy oprogramowania nazywanego oprzyrządowaniem (ang. instrumentation), służącej do prowadzenia interakcji z aplikacją, w taki sam sposób w jaki robi to użytkownik. Spośród dostępnych rozwiązań do przeprowadzania testów tego typu coraz większą popularnością cieszy się framework Espresso, a Android Studio najprawdopodobniej doda go do Twojego projektu. Testy wykonywane na urządzeniu są przechowywane w katalogu app/src/androidTest, a typowy test frameworka Espresso wygląda jak na poniższym przykładzie: @Test public void ifYouDoNotChangeTheColorThenYouGetAmber() { onView(withId(R.id.find_beer)).perform(click()); onView(withId(R.id.brands)).check(matches(withText( ”Jail Pale Ale\nGout Stout\n”))); }

Podczas wykonywania takiego testu, aplikacja będzie widoczna na ekranie telefonu lub tabletu i będziesz mógł zobaczyć jak odpowiada ona na gesty i naciśnięcia klawiszy — zupełnie jak gdyby była ona obsługiwana przez człowieka. Więcej informacji o frameworku Espresso możesz znaleźć na stronie

https://developer.android.com/training/testing/ui-testing/espresso-testing.html

jesteś tutaj  871

Skorowidz A

adapter, 247, 269, 288, 375 ArrayAdapter, 269–271, 288, 376, 382 CaptionedImagesAdapter, 546 CursorAdapter, 691 FragmentPagerAdapter, 491–493, 535 RecyclerView, 545, 547 adaptery synchronizujące, 864 tablicowe, 376 wielokrotnego użycia, 572 akceleracja sprzętowa, 859 akcja, 77, 100, 319 ACTION_DIAL, 101 ACTION_SEND, 100–105, 109, 118 ACTION_WEB_SEARCH, 101 aktualizacja aplikacji Trenażer, 398 bazy danych, 636, 639–645, 698 rekordów, 646 układu, 39, 90, 704, 714, 719 aktywności, 2, 31, 97, 140, 156, 250 cykl życia, 119 dodawanie, 13 domyślne, 41 dostosowywanie, 14 działające, 120, 138 kategorii, 249, 250, 267 kategorii z listą, 252 nadrzędne, 328 poziomu głównego, 249, 250, 252 przeniesione do pierwszego planu, 155 restartowanie, 146 stany, 137 szczegółów, 253, 279 szczegółów/edycji, 249 tworzenie, 146 uruchomione, 138, 146, 364 usunięte, 146, 155, 364 utworzone, 364 widoczne, 155 widzialny czas życia, 147 wstrzymane, 364 wznowione, 364 zapisywanie stanu, 428 zatrzymane, 146, 364

872

Skorowidz

aktywność, activity, 12 AppCompatActivity, 307 CreateMessageActivity, 79, 89, 92, 95 DetailActivity, 347, 384, 389, 391 DrinkActivity, 255, 277, 283, 658– 661, 673, 701–703, 732 DrinkCategoryActivity, 255, 267– 278, 658, 675, 688 FeedbackActivity, 592 HelpActivity, 591 MainActivity, 334, 774 OrderActivity, 315, 317, 507, 517, 526 OrderDetail, 507 PizzaDetailActivity, 566–568 ReceiveMessageActivity, 91, 97, 105 StopwatchActivity, 130, 141, 151, 160, 435 TempActivity, 437 TopLevelActivity, 258, 274, 625, 658, 694, 705–712 Android, 3 Debug Bridge, 846, 849 SDK, 5, 23 Studio, 5 instalacja, 6 Virtual Device Manager, 24 animacja widoków, 508, 535, 868 właściwości, 868 API, 3 aplikacja dla kafeterii Coffeina, 248, 255, 622, 625, 658, 695 Doradca Piwny, 38 Drogomierz, 769 KociCzat, 583 Komunikator, 79 Stoper, 122 Trenażer, 341, 398, 435 Układ z ograniczeniami, 223 Wic, 741 Włoskie Co Nieco, 290, 295, 482, 538 aplikacje częściowo widoczne, 154 interaktywne, 37

wersje na tablety, 411 wersje na telefony, 343 ART, Androidruntime, 841 atrybut cardCornerRadius, 578 drawableLeft, 213 exported, 745 gravity, 182–184, 220 hint, 202 icon, 299 id, 44, 91 inputType, 202 label, 299 layout_above, 822 layout_alignBottom, 822 layout_alignLeft, 822 layout_alignParentBottom, 820 layout_alignParentEnd, 819 layout_alignParentLeft, 820 layout_alignParentRight, 820 layout_alignRight, 822 layout_alignTop, 822 layout_behavior, 510 layout_below, 822 layout_centerHorizontal, 820 layout_centerVertical, 820 layout_column, 827 layout_gravity, 184, 187, 193, 220 layout_height, 44, 171, 175, 188 layout_margin, 176 layout_marginEnd, 176 layout_marginLeft, 176 layout_marginTop, 176 layout_row, 827 layout_scrollFlags, 510 layout_toEndOf, 822 layout_toLeftOf, 822 layout_toRightOf, 822 layout_width, 44, 175, 188 name, 745 onClick, 203, 452, 480 orderInCategory, 322 orientation, 172 padding, 173 paddingEnd, 173 roundIcon, 299

Skorowidz scorllbars, 554 text, 44, 50, 203 theme, 300 title, 322 atrybuty padding*, 220 komponentu Toolbar, 519 układu AppBarLayout, 519 CollapsingToolbarLayout, 519 widoku NestedScrollView, 519 automatyczne odświeżanie, 714 AVD, Android Virtual Device, 23 weryfikowanie konfiguracji, 26 wybór obrazu systemu, 25 zablokowane, 29 AVD Manager, 399 wybór komponentów, 24 AVD tabletu, 398, 400 obraz systemu, 400 weryfikowanie konfiguracji, 401 awaria aplikacji, 450

B

baza danych SQLite, 621, 656 biblioteka Androida, 16 AppCompat v7, 294, 296, 306, 307, 345, 584, 756, 790 AWT, 4 cardview v7, 294 Constraint Layout Library, 224 recycleview v7, 294 Swing, 4 układu z ograniczeniami, 294 wsparcia wzornictwa, 294, 495–527, 533, 584 Blueprint, 225 budowniczy powiadomień, 757, 765

C

cofnięcia, 414 cykl życia aktywności, 119, 137, 146–149, 157, 165, 439 metody, 167 fragmentów, 365, 439 metody, 392

D

Dalvik, 843 dane typu MIME, 101, 105 debugowanie USB, 109

definiowanie interfejsu, 385 kolorów, 304 kursora, 715 stylu, 301 układu FrameLayout, 193 układu ramki, 188 deklarowanie aktywności, 85 uprawnień, 791 usługi, 745 demon, 850 dodanie adaptera do kontrolki, 493 akcji, 315, 319 akcji do powiadomienia, 758 aktywności, 12, 13, 85 biblioteki wsparcia, 224, 345, 498, 542, 790 dostawcy akcji, 332 fragmentów, 348 do układu aktywności, 352, 449 dynamicznych, 434 InboxFragment, 604 ikony, 320 przycisku FAB, 527 kart do aktywności, 498, 500 klasy, 360 kolumn, 649 komponentów, 39 komunikatów, 743 kontrolki ViewPager, 490 łańcuchów znaków, 56, 596 menu, 323 obiektu nasłuchującego, 262 obrazków, 189, 211, 541 594 do paska narzędzi, 523 do przycisków, 213 ograniczeń, 230 paska narzędzi, 306, 309 pływających przycisków, 506 pola wyboru, 696 przełącznika szuflady, 607 przycisku, 43, 329, 346 FAB do układu, 526, 527 W górę stopera do fragmentu, 469 tytułu akcji, 320 wagi do widoków, 180, 181 widoków do układu siatki, 824 zasobów, 320 łańcuchowych, 123, 225, 594 zwijanego paska narzędzi, 517

dokumentacja, 5 dostawca akcji udostępniania, 292, 331, 332 lokalizacji, 793 treści, 863 dostęp do bazy danych, 624, 627 do lokalizacji, 804 dostosowywanie aktywności, 14 wyglądu aplikacji, 303 dystrybucja, 862 działanie aktywności, 120 aplikacji Coffeina, 284, 736 Drogomierz, 783, 801 Stoper, 152 Trenażer, 368, 424 Wic, 762 Włoskie Co Nieco, 336 przewijania, 510 dziennik, 741, 743 logcat, 854

E

edytor kodu, 18, 32 projektu, 18, 32, 42 ekran głównego poziomu, 290 kategorii, 290 ustawień, 867 element, 45 , 43–46, 76 , 220, 698 , 33 , 174 , 80, 118, 202 , 353, 392, 449, 463, 480 , 188 , 215 , 220 , 312, 314 , 303, 332 , 41, 45, 90, 171, 187 , 259 , 602 , 220 , 215, 220 , 745, 765 , 76, 210 , 57

jesteś tutaj  873

Skorowidz element , 303 , 403 , 220 , 33, 36, 46, 47 , 204, 220 , 791 emulator QEMU, 23, 858 uruchamianie aplikacji, 27 etykieta, 318

F

FAB, floating action button, 526 filtr intencji, 105, 106, 117 LAUNCHER, 437 format DEX, 30, 843 OAT, 847 fragment, 342, 392 DraftsFragment, 586 InboxFragment, 585, 604 ListFragment, 374 PastaFragment, 487 PizzaFragment, 486, 553, 554, 575 SentItemsFragment, 587 StopwatchFragment, 435, 444–447, 456–460, 469 StoresFragment, 488 TopFragment, 485, 508, 514 TrashFragment, 588 WorkoutDetailFragment, 344, 348, 412, 417 WorkoutListFragment, 344, 372, 378, 383, 424 z listą, 372, 373 fragmenty cykl życia, 365 dla większych interfejsów, 393, 403, 409–425 dodanie zawartości do przewijania, 512 dynamiczne, 433–437, 449, 461, 471, 475 identyfikator, 409 identyfikator treningu, 361 komunikacja z aktywnością, 384 metody zwrotne, 365 miejsce wyświetlania, 471 na stosie cofnięć, 473 programowe dodawanie, 419 programowe zmiany, 416 przewijanie, 489 transakcje, 472

874

Skorowidz

usuwanie, 419 zapis stanu, 429 zmienne lokalne, 428

G

galeria motywów, 302 gest przeciągania, 484, 535 górny wiersz układu, 240 GPS, 793 Gradle, 7, 833 pliki projektu, 834 wtyczki, 840 zadanie androidDependencies, 838 check, 837 clean installDebug, 837 graficzny interfejs użytkownika, GUI, 2 grupowanie elementów, 598 widoków, 169, 198 GUI, graphical user interface, 2

intencje dodawanie informacji, 101 filtry, 105 określające akcje, 101 treści, 333 tworzenie, 92 uruchamianie aktywności, 87, 99 wyznaczanie, 105 interakcja fragmentu i aktywności, 359 interfejs, 384, 572 Binder, 786 IBinder, 771, 786 Listener, 388, 572 obiektu nasłuchującego, 385 OnClickListener, 455 programowania aplikacji, API, 3 użytkownika, 721, 722

J

hierarchia klasy AppCompatActivity, 297 widoków, 200 widoku RecyclerView, 570

Java SE, 4 VM, 30 język Groovy, 839 SQL, 631 JVM, Java Virtual Machine, 842

I

K

H

identyfikator fragment_container, 417 klikniętego elementu, 277 time_view, 135 treningu, 361, 363 widoku, 241 ikona, 320 przycisku FAB, 527 ikony wbudowane, 597 implementacja interfejsu, 388, 575 klasy, 70 logiki, 64 Material Design, 506 informacje o błędzie, 450 o lokalizacji, 815 o napojach, 626, 660 o stronach, 491 instalacja Android Studio, 6 IntelliJ IDEA, 5 intencja, 77, 86, 118, 280 jawna, 118 niejawna, 101

karta, 498, 535 Design, 41 tworzenie widoku, 543 wyświetlenie danych pizzy, 542 katalog app, 17 build, 17 databases, 623 java, 17, 36, 82 layout, 17, 36, 402 layout-large, 402, 408 main, 36 res, 17 src, 17 values, 174, 301 kategoria o wartości DEFAULT, 118 klasa, 631 ActionBarDrawerToggle, 607 Activity, 21, 139, 148 Android.util.Log, 765 AppCompatActivity, 297 AsyncTask, 724 CoffeinaDatabaseHelper, 634 Context, 139, 376, 752

Skorowidz ContextThemeWrapper, 139 ContextWrapper, 139, 752 Cursor, 624 Drink, 255, 256, 622, 626 DrinkActivity, 695, 730 Fragment, 366, 392 FragmentActivity, 354 FragmentPagerAdapter, 491 Handler, 127 IntentService, 742, 752, 765 MainActivity, 298 MojaAktywnosc, 139 MojaUslugaUruchomiona, 752 Object, 366 OdometerService, 771 Pasta, 562 Pizza, 541 R, 63 ReceiveMessageActivity, 96 Service, 752, 770 SQLiteOpenHelper, 627, 648 TempActivity, 438 TopLevelActivity, 255 UpdateDrinkTask, 730 View, 44, 48, 198 ViewGroup, 198 ViewPager, 489 WebView, 866 Workout, 360 WorkoutListFragment, 374, 377 kliknięcie, 566, 570 elementu listy, 386 przycisku, 347 klucz główny, 630 RSA, 109 kod adaptera CaptionedImagesAdapter, 546 FragmentPagerAdapter, 492 aktywności, 61 CreateMessageActivity, 96 DrinkActivity, 283, 661, 669–674, 701–703, 732 DrinkCategoryActivity, 278, 688 FeedbackActivity, 592 MainActivity, 334, 418, 423, 494, 615–617, 781, 782 StopwatchActivity, 130, 142, 151, 160, 162 TempActivity, 438, 467 TopLevelActivity, 262, 263, 711, 712 dodający zwijany pasek narzędzi, 520

dołączający kontrolkę, 489 fragmentu, 349, 351 DraftsFragment, 586 PizzaFragment, 558 StopwatchFragment, 444–460 WorkoutDetailFragment, 430 klasy CoffeinaDatabaseHelper, 634 MainActivity, 298 UpdateDrinkTask, 730 WorkoutListFragment, 377 pliku, Patrz plik pomocnika SQLite, 634, 643, 651, 652 realizujący transakcję fragmentu, 475 układu, 45, 46, 192 activity_main.xml, 381 aktywności głównego poziomu, 260 siatki, 831, 832 umożliwiający przewijanie, 511 usługi DelayedMessageService, 744, 760, 761 OdometerService, 795 wielokrotne stosowanie, 342 wyświetlający powiadomienia, 809, 810 kolumna _id, 681 kompilacja aplikacji, 837 komponent AppBarLayout, 499 Plain Text, 243 Spinner, 38, 48 dodanie do odwołania, 57 dodanie wartości, 56 TabLayout, 499 TextView, 64 Toolbar, 499, 519 komponenty GUI, 48, 198 Material Design, 506 sprzętowe, 24, 399 komunikacja z bazą danych, 721, 722 komunikat, 86, 216, 721, 722, 741, 743 konfiguracja projektu, 9 urządzenia, 136 konsola, 28 konstruktor bezargumentowy, 350 kontrolka NestedScrollView, 514, 535 ViewPager, 489, 490, 501, 535 konwersja stopera, 438, 450 koordynacja fragmentów, 385 kopiowanie plików, 855

kreator Create New Project, 12 kursor, 671, 681–723

L

lista, 251, 537 rozwijana, 38, 48, 210 z widokiem szczegółów, 384 logcat, 743, 854 logika aplikacji, 39

Ł

łańcuch znaków, 52 łączenie widoków, 270

M

marginesy, 176 przycisku, 229 widoku, 228 maszyna wirtualna VirtualBox, 23 VMWare, 23 Maven, 7 mechanizm wnioskowania ograniczeń, 241 menedżer GridLayoutManager, 556, 578 LinearLayoutManager, 556, 578 StaggeredGridLayoutManager, 556, 557 menedżery fragmentów, 362 lokalizacji, 793 transakcji, 480 układów, 556 metoda addToBackStack(), 432 bindService(), 778, 815 bundle.get*(), 168 bundle.put*(), 168 changeCursor(), 715, 737 checkSelfPermission(), 794, 800, 803, 815 close(), 672 commit(), 432 createChooser(), 112, 113, 117, 118 delete(), 647 displayDistance(), 780, 784 doInBackground(), 724–726, 731, 737 execSQL(), 650 findViewById(), 63, 76, 370, 392 getActivity(), 758 getBrands(), 71, 72 getChiIdFragmentManager(), 474 getChiIdFragmentTransaction, 475

jesteś tutaj  875

Skorowidz metoda getChildFragmentManager(), 474, 480 getCount(), 535 getDistance(), 772, 774, 797 getFragmentManager(), 473, 480 getInt(), 672 getIntent(), 92 getItemCount(), 547 getPageTitle(), 501 getReadableDatabase(), 691 getSelectedItem(), 69, 76 getString(), 115, 672 getStríngExtra(), 96 getSupportActionBar(), 329 getSupportFragmentManager(), 362 getSystemService(), 759 getWritableDatabase(), 691 insert(), 632, 656 itemClicked(), 386, 390, 412, 424 moveToFirst(), 671 moveToLast(), 671 moveToNext(), 671 moveToPrevious(), 671 onActivityCreated(), 365, 439 onAttach(), 365, 439 onBind(), 770, 783, 787, 815 onBindViewHolder(), 550, 571 onClick(), 480 onClickDone(), 529 onClickFindBeer(), 60–63, 71, 74 onClickStop(), 455 onCreate(), 21, 61, 121, 133, 137–141, 263, 365, 439, 631, 648–656, 750, 787 onCreateOptionsMenu(), 323, 337 onCreateView(), 350, 365, 439, 392, 559 onCreateViewHolder(), 549 onDestroy(), 140, 167, 365, 439, 750, 788, 797, 815 onDestroyView(), 365, 439 onDetach(), 365, 439 onDowngrade(), 641 onFavoriteClicked(), 696, 723 onHandleIntent(), 742, 751 onItemClick(), 261 onListItemClick(), 277 onListItemClicked(), 386 onLocationChanged(), 792 onNavigationItemSelected(), 609 onOptionsItemSeIected(), 324 onPause(), 155, 167, 365, 439 onPostExecute(), 724, 728, 737 onPreExecute(), 725, 737

876

Skorowidz

onProgressUpdate(), 727, 737 onProviderDisabled(), 792 onProviderEnabled(), 792 onRequestPermissionsResuIt(), 809 onRestart(), 146, 153, 167, 439 onResume(), 155, 159, 167, 365, 439 onSaveInstanceState(), 140, 146, 168, 428, 429, 432 onSendMessage(), 88, 97, 103, 115 onServiceConnected(), 776 onServiceDisconnected(), 777 onShowDetails(), 347 onStart(), 146, 158, 167, 365, 439, 804 onStartCommand(), 750, 751, 765, 792 onStop(), 146, 155, 158, 167, 365, 439 onUnbind(), 787, 788 onUpgrade(), 640 post(), 128 postDelayed(), 128 putExtra(), 92, 118 query(), 664 removeUpdates(), 815 requestLocationUpdates(), 815 requestPermissions(), 803 runTimer(), 125, 127, 129, 133, 440 setAdapter(), 270 setContentDescription(), 212 setContentView(), 61 setDisplayHomeAsUpEnabled(), 329, 337 setImageResource(), 212 setOnClickListener(), 457 setShareIntent(), 333 setSupportActionBar(), 317 setText(), 76 setupFavritesListView(), 709 startActivity(), 86, 121, 132 startService(), 747, 765, 786 syncState(), 607 Toast.makeText(), 216 unbindService(), 779, 815 update(), 645, 646 metody cyklu życia aktywności, 439 fragmentów, 392, 439 usług powiązanych, 788 prywatne, 454 zwrotne fragmentów, 365 miejsce wprowadzania, input focus, 137 modyfikacja struktury bazy danych, 649, 645

tabel, 650, 656 tekstu, 34 motyw, 302, 590 Theme.AppCompat.Light. DarkActionBar, 308 Theme.AppCompat.Light. NoActionBar, 308 motywy AppCompat, 297

N

nagłówek listy, 706, 713 szuflady nawigacyjnej, 594 narzędzia SDK, 5 narzędzie wnioskowania ograniczeń, 240 nasłuchiwanie zdarzeń, 261, 571 nawigacja, 291, 535 po aktywnościach, 250 w górę, 327 nazwa bazy danych, 629 katalogu, 17 klasy aktywności, 85 pakietu, 9, 22, 85 projektu, 17 tabeli, 649 zasobu stylu, 300 nowy projekt, 8, 15 numer wersji bazy danych SQLite, 629, 637

O

obiekt aktywności, 12 Binder, 771 Bundle, 141 Loader, 864 nasłuchujący, 199, 261, 385, 797 danych o lokalizacji, 792 MainActivity, 385 OnItemClickListener, 261 ServiceConnection, 774, 775 View, 199 ViewHolder, 548, 578 Workout, 360 obraz systemu, 25 obrazki, 541 obrót ekranu, 462 zmiana konfiguracji urządzenia, 136 obsługa kliknięcia przycisku, 347 kliknięć, 276

Skorowidz powiadomień, 755 stopera, 125 transakcji fragmentu, 464 zdarzeń, 199, 721 odbiorcy komunikatów, 865 odczytywanie wartości z kursora, 691 właściwości, 199 odłączenie aktywności od usługi, 779 odpowiedź użytkownika, 805 odświeżanie automatyczne, 714 odwołanie do string-array, 57 ograniczenie w poziomie, 227 okno dialogowe, 104 wyboru aktywności, 112, 116 określenie poziomu API, 10 opcja Empty Activity, 82, 279 File/New/Activity, 82 File/Project Structure, 790 FilelNew/Layout resource, 553 Undo Infer Constraints, 241 operacje asynchroniczne, 724 w tle, 724, 739 orientacja układu, 172

P

pakiet Support Libraries, 294 panel konsoli, 28 parametry klasy AsyncTask, 724, 729 pasek akcji, 293 aplikacji, 292, 301, 306 akcje, 315 aktywności, 313 dodanie akcji, 319 etykieta, 318 narzędzi, 292, 306, 309, 310, 312, 508, 589 dodanie obrazka, 522 przewijany, 512, 515 zwijany, 517, 535 snackbar, 526, 530 pierwsza aplikacja, 7 planowanie wykonania kodu, 128 platforma Android, 3 SDK, 5 plik activity_create_message.xml, 79, 80, 83, 88 activity_detail.xml, 355

activity_drink.xml, 696, 698 activity_drink_category.xmI, 268 activity_find_beer.xml, 41, 44, 53 activity_help.xml, 591 activity_main.xml, 14, 17–20, 32, 189, 225, 305, 346, 381, 417, 509, 603, 773 activity_order.xml, 316, 520, 521, 524, 525, 528, 529 activity_stopwatch.xml, 123, 124 activity_top_level.xml, 706, 713 AndroidManifest.xml, 17, 36, 84, 299, 319, 568 build.gradle, 838 CaptionedImagesAdapter.java, 551, 552, 573 card_captioned_image.xml, 544 CoffemaDatabaseHelper.java, 642 colors.xml, 590 CreateMessageActivity.java, 81, 95 DetailActivity.java, 363, 389 dimens.xml, 174 DraftsFragment. java, 586 Drink.java, 274 DrinkActivity.java, 669, 673, 701, 732–735 DrinkCategoryActivity.java, 271, 681 FindBeerActivity.java, 67, 73 fragment_pizza.xml, 554 fragment_sent_items.xml, 587 fragment_top.xml, 515 MainActivity.java, 14, 17–22, 325, 418, 494–496, 502–504, 747, 811, 812 menu_nav.xml, 599, 601 nav_header.xml, 595 OdometerService.java, 796–800 OrderActivity.java, 317, 533 PastaFragment.java, 487 PizzaDetailActivity.java, 569 PizzaFragment.java, 486, 555, 558, 576 R.java, 63, 69, 76 Stopwatch.java,, 438 StopwatchActivity.java, 126, 129, 130 StoreFragment.java, 488 strings.xml, 17, 50, 54, 76 TempActivity.java, 467 TopFragment.java, 485 TopLevelActivity.java, 262, 706–708, 713, 716–718 WorkoutDetailFragment.java, 430, 476 WorkoutListFragment.java, 387 pliki .apk, 27, 845, 847

.class, 5 .class, 842 .dex, 843 .jar, 5 build.gradle, 834 graficzne, 257 Javy, 16 konfiguracyjne, 16 projektu, 17 zasobów, 16 kolorów, 304 łańcuchowych, 54 menu, 321 stylów, 301, 304 źródłowe, 16 płatności Google Play, 5 pływający przycisk akcji, FAB, 506, 526, 535 pobieranie danych, 657 z bazy, 663 z intencji, 280 referencji do adaptera kursora, 715 do bazy, 662 wartości z kursora, 672 wyników, 854 pole Default Margin, 228 tekstowe, 38, 80, 202 wyboru, 206, 207, 695, 696 Backwards Compatibility, 40, 437, 769 Exported, 770 polecenia powłoki, 853 polecenie adb devices, 850 adb logcat, 854 ALTER TABLE, 656 CREATE TABLE, 636 DROP TABLE, 650, 656 SELECT, 675 SQL SELECT, 691 pomocnik SQLite, 624, 626, 634, 644, 651–656 ponowne utworzenie aktywności, 135 powiadomienia, 755, 765, 806, 809 typu heads-up, 757, 765 powiązanie aktywności z usługą, 774, 778 komponentu z usługą, 815 poziom API, 10, 122 SDK, 223

jesteś tutaj  877

Skorowidz prefiks @style, 300 proces, 133, 168 adbd, 850 program ANT, 7 AVD Manager, 24 Blueprint, 225 Gradle, 7, 833 Maven, 7 programowe zmieniane fragmenty, 416 prośba o uprawnienie, 802, 803 przeciąganie, 484 przejścia aktywności, 868 przekazywanie identyfikatora treningu, 361 przełącznik, 204, 205 Pozostaw ekran włączony, 859 szuflady, 607 przesłanianie metod, 148, 549 przesunięcie, bias, 231 przewijanie, 510 fragmentów, 489 paska narzędzi, 507–511, 515 treści, 513 zagnieżdżone, 513 przycisk Align Left Edges, 238 FAB, 526, 527, 535 Finish, 41 Infer Constraints, 240, 242, 245 Odszukaj piwo, 59 Run, 28 Show Blueprint, 226 Show Constraints, 238 Start, 126, 134 Stop, 126, 134 W górę, 326, 328, 329 Wstecz, 326, 413, 614 Wyślij wiadomość, 86–90, 97 przyciski, 44 dodawanie, 43 marginesy, 229 ograniczenie w pionie, 228 ograniczenie w poziomie, 227 opcji, 208, 209 pływające, 506, 535 przełącznika, 204 wyświetlanie obrazu, 213 wyświetlanie tekstu, 213 wywołanie metody aktywności, 60 z obrazami, 214 przywracanie starszej wersji bazy, 641 pusta aktywność, 13, 40, 188

878

Skorowidz

R

reagowanie na kliknięcia, 608, 698 refaktoryzacja, 707 referencja do adaptera kursora, 715 do bazy danych, 662, 670 do listy rozwijanej, 64 do widoku, 63 this, 758 rejestracja komunikatów, 765 obiektu nasłuchującego, 386 rodzaje aktywności, 13 rozmieszczenie widoków, 190, 227, 818, 821 rozpowszechnianie aplikacji, 862 rzutowanie, 64

S

SDK, Software Development Kit, 5 serwer adb, 850 siatka, 823 sieci, 793 słowo kluczowe ASC, 655 DESC, 655 sprawdzanie uprawnień, 794 w trakcie działania, 803 odpowiedzi na prośbę, 805 SQL, Structured Query Language, 631 SQLite, 621 stany aktywności, 137, 364 usług powiązanych, 787 usług uruchomionych, 750 stoper, 435 problem zerowania, 462 stos cofnięć, 414, 415 stosowanie adaptera SimpleCursorAdapter, 681, 687 dostawcy akcji, 331 intencji, 87 motywów, 300 stylu, 300 zasobów łańcuchowych, 50, 52 struktura katalogów, 16, 404, 406 styl, 300 AppTheme, 590 szablon Empty Activity, 13, 40

Fragment (Blank), 374 Fragment (List), 374 szuflady nawigacyjne, 580, 591, 611, 616 grupowanie elementów, 598 nagłówek, 583, 594 opcja Skrzynka odbiorcza, 585 opcje, 583, 597 przełącznik, 606 reagowanie na klikanie, 608 reakcja na kliknięcia, 606 sekcja wsparcia, 600 tworzenie, 583, 602 używanie, 619 zamknięcie, 606, 614

Ś

środowisko programistyczne, 4, 5 uruchomieniowe, 3 ART, 30, 841 Dalvik, 30

T

tabela, 630 Drink, 626, 633, 656 tablet, 397 zmiana orientacji, 427 tablica, 270 łańcuchów znaków, 76 obiektów Drink, 272 telefon, 396 test aplikacji, 75 klasy, 70 stopera, 462 testy jednostkowe, 870 na urządzeniu, 871 zautomatyzowane, 870 tosty, 216 transakcje, 415 dodawanie, 432 fragmentów, 463, 464, 472 określanie zmian, 420 rozpoczęcie, 419 usuwanie, 432 zagnieżdżone, 433, 474 zastępowanie, 432 zatwierdzenie, 421 tworzenie adaptera widoku, 545 aktywności

Skorowidz FeedbackActivity, 592 HelpActivity, 591 PizzaDetailActivity, 567 aplikacji, 8 AVD, 24–26 tabletu, 400 bazy danych, 624–627 drugiej aktywności, 82 fragmentu SentItemsFragment, 587 TrashFragment, 588 intencji, 92, 101 niejawnej, 102 interaktywnych aplikacji, 37 kursora, 683 menedżera lokalizacji, 793 nagłówka szuflady nawigacyjnej, 583, 594 nowego projektu, 8 obiektu ServiceConnection, 775 opcji szuflady nawigacyjnej, 583 pomocnika SQLite, 628 projektu, 40 szkicu układu, 225 szuflady nawigacyjnej, 583, 602 tabeli, 626 transakcji fragmentu, 419 zagnieżdżonych, 474 układu siatki, 825 widoku karty, 543 własnej klasy, 70 zasobu łańcuchowego, 51 zasobu tablicowego, 56 typ danych, 630 BLOB, 630 INTEGER, 630 NUMERIC, 630 REAL, 630 TEXT, 630 typy nawigacji, 291 usług, 740

U

udostępnianie aplikacji, 862 układ, layout, 2, 12, 31, 32 AppBarLayout, 499, 510, 519 CollapsingToolbarLayout, 519 CoordinatorLayout, 508, 509, 535 ConstraintLayout, 228 FrameLayout, 170, 188, 193, 416, 464 GridLayout, 823, 824

layout-large, 409 LinearLayout, 170 RelativeLayout, 818 StopwatchFragment, 447, 448 TabLayout, 535 układy aktywności dodawanie fragmentu, 352 głównego poziomu, 258 kart, 580 liniowe, 170, 178, 186, 220 wyświetlanie widoku, 175 nadrzędne, 820 orientacja, 172 ramki, 169, 220 siatki, 817, 823, 825, 831 względne, 817 z ograniczeniami, 223, 245 zagnieżdżone, 191, 222 uprawnienie, 767, 769, 777, 787, 791–794 ACCESS_FINE_, 815 ACCESS_FINE_LOCATION, 791, 797 uproszczenie układu, 353 uruchamianie aktywności, 87, 89, 99 innych aplikacji, 99 aplikacji, 23, 30, 88, 163 na prawdziwym urządzeniu, 110 w emulatorze, 27 błyskawiczne, 860 powłoki, 852 urządzenia wygląd aplikacji, 340 wirtualne, Patrz AVD usługa, 739, 765 DelayedMessageServ1ce, 748 DelayedMessageService, 741, 744, 746, 756, 759–761 obsługi powiadomień, 755 OdometerService, 770, 783, 792–797, 802 usługi, services, 740 lokalizacyjne, 789, 792 metody cyklu życia, 752 powiązane, 740, 767–769, 777, 786 metody cyklu życia, 788 stany, 787 systemowe, 759 uruchomione, 740, 747–753, 759, 765, 786 stany, 750 usunięte, 751 zaplanowane, 740

usprawnianie aplikacji, 31 ustawianie właściwości, 199 usunięcie atrybutu onClick, 453 działającej aktywności, 135 paska aplikacji, 306, 308 rekordów, 647 tabeli, 650, 656

W

waga, weight, 179 widoku, 179 wątek działający w tle, 721 główny, 168, 720 wyświetlania, 720 wersja aplikacji na tablety, 397 na telefony, 396 wersje Androida, 11 wiązanie układu z aktywnością, 39 widok, 169 CardView, 540–543, 578 ListView, 251, 259, 270 NavigationView, 614 NestedScrollView, 513, 519 RecyclerView, 539–542, 553, 562, 570, 578 widoki dopasowane do ograniczeń, 230, 233 dopasowane do zawartości, 232 identyfikator fragment_container, 418 kart, card views, 537 kolejność wyświetlania, 187, 190 list, 247 obrazów, 211, 212 ograniczenia, 230 położenie, 199 położenie zawartości, 187 proporcje, 233 przesuwanie, 231 przewijanie, 215 ramek, 190 rozmieszczanie, 227, 818, 821 stała wielkość, 232 tekstowe, 201 ukryte, 199 ustawianie właściwości, 199 w górnym wierszu, 240 w siatce, 823 wagi, 187 wielkość, 199, 232 wypełnienie danymi, 281

jesteś tutaj  879

Skorowidz usługi, services wyśrodkowane, 230, 238 zmiana marginesów, 228 zmiana rozmiaru, 179 widżet Button, 226 widżety aplikacji, 869 wielokrotne stosowanie kodu, 342 wirtualna maszyna Javy, JVM, 842 wirtualne urządzenie, Patrz AVD, 23 własna klasa Javy, 70 włączanie nawigacji w górę, 327 wsparcie dla Androida, 5 wstawianie rekordów, 633 wybieranie aktywności, 112, 114 komponentów sprzętowych, 399 obrazu systemu, 25, 400 wygląd akcji, 322 aplikacji, 303 wyjątek ActivityNotFoundException, 103 CalledFromWrongThreadException, 127 wykrywanie urządzenia, 109 wyliczenie przebytego dystansu, 796 wypełnienie, 173 widoku listy, 719 wysyłanie powiadomień, 759 wiadomości, 98, 99 wyświetlenie danych kategorii, 267 danych pizzy, 542 dystansu, 780 fragmentu, 378, 471

880

Skorowidz

komunikatu, 721, 743, 755–759 w dzienniku, 741 w obszarze powiadomień, 741 listy opcji, 259 MainActivity, 470 ograniczeń, 238 okna dialogowego, 115 powiadomienia, 806, 809 widoku, 175 zależności, 838 wywoływanie metod aktywności, 60 fragmentu, 452 wyznaczanie intencji, intent resolution, 105

Z

zadania asynchroniczne, 693, 697, 705, 709, 715, 721, 723 zagnieżdżanie fragmentów, 433 transakcji, 474 układów, 191, 222 zamknięcie szuflady, 614 bazy danych, 672, 682 kursora, 672, 682 serwera adb, 854 zapis stanu, 140 aktywności, 168, 428 fragmentu, 429 zarządzanie bazą danych, 627 fragmentami, 362 zasoby, 2 graficzne, 257 kolorów, 304

łańcuchowe, 50, 76 menu, 321 stylów, 304 tablicowe, 56 wymiaru, 174 zastosowanie akcji, 102 zdarzenie, 199, 261 przewijania, 510 zintegrowane środowisko programistyczne, IDE, 5 zmiana marginesów widoku, 228 nazwy pakietu, 40 nazwy tabeli, 649 orientacji urządzenia, 427, 462 paska aplikacji, 299 położenia widoku, 231 rozmiaru widoku, 179 stanu aktywności, 154 sygnatury metody, 454 tytułu aktywności, 317 wielkości widoku, 199, 232 zmienne lokalne, 428 znacznik, Patrz element znak kropki, 85 pionowej kreski, 183 zrzut stanu emulatora, 859 zwijany pasek narzędzi, 517, 518, 535 zwracanie wierszy tabeli, 664 wierszy w kolejności, 655 wybranych rekordów, 656

Ż

żądanie aktualizacji, 794