Zaawansowane programowanie UI na platformie Android

Komentarze

Transkrypt

Zaawansowane programowanie UI na platformie Android
Zaawansowane programowanie UI
na platformie Android
Zasoby, kontrolki, dialogi i cały ten jazz
Karol Kuczmarski
Kim jestem?
Programista w firmie Polidea



głównie Android i Google App Engine
projekt Apphance
Programista Androida


Taphoo
Autor bloga xion.log (http://xion.org.pl)
Moderator forum serwisu warsztat.gd
Autor popularnego kursu C++



2
Plan na dziś
Wykorzystanie zasobów do modyfikacji wyglądu UI




Nine patch drawables
Listy stanów (state list drawables / color state lists)
Animacje typu tween (przejścia)
Praca z układami kontrolek (layouts)



Wybór właściwego układu (podklasy ViewGroup)
Ponowne używanie układów (include, merge, style/tematy)
Korzystanie z powiadomień




3
Dialogi
Powiadomienia na pasku stanu
Toasty
Wykorzystanie zasobów
Drawables i animacje
Co dają nam zasoby?
Określenie układu, wyglądu, a nawet zachowania
interfejsu użytkownika
Automatyczne uwzględnianie istotnych parametrów
urządzenia i aktualnego systemu



Rozmiar ekranu, orientacja, język, …
Łatwość tworzenia i używania zasobów
Zaawansowane mechanizmy
gotowe do wykorzystania



Zasoby typu drawable, animacje, plurals, …
Niekiedy skorzystanie z zasobów jest koniecznością


5
Najprostszy przykład: ikona aplikacji
Nine patch drawables
Obrazy ze specjalnym rodzajem skalowania
Służą do określania wyglądu obramowań i wnętrz



Przyciski, pola tekstowe, okienka i inne
Używane jak wartość dla android:background

6
9p drawables: jak działają?
Obramowanie wyznaczane jest przez 9 „łatek”


Cztery narożniki + cztery boki + środek
Łatki odpowiednio się skalują




7
Narożniki nie zmieniają rozmiarów
Boki są powielane w jednym wymiarze
Środek skaluje się w obu wymiarach
9p drawables: tworzenie
Pojedynczy obrazek z
jednopikselową ramką
Białe i czarne rejony
wyznaczają obszary
skalowania
Można też określić
obszar zawartości
elementu
Wystarczy dowolny
program graficzny
Edytor (Draw 9 patch)
dołączony do SDK





8
9p drawables: niekoniecznie 9
Wbrew nazwie, łatek nie musi być dokładnie 9
Bardziej skomplikowane obramowanie
może mieć ich więcej




Poszczególne obszary skalują się wtedy proporcjonalnie
Przykład: obszar stały pośrodku boku obramowania
Ramka nie musi też skalować się w obu wymiarach

9
Zasoby z listą stanów (state list)

Domyślnie kontrolki zmieniają się, aby pokazać
swoją interaktywność


Jeśli modyfikujemy wygląd kontrolek, to powinniśmy
wziąć ten fakt pod uwagę


Osobne grafiki dla poszczególnych stanów kontrolki
Jak zapewnić ich odpowiednie przełączanie?...


Przykład: wciskany przycisk rzeczywiście „się wciska”
Implementując OnClickListener!... Niezupełnie :-)
Rozwiązanie: zasoby z listą stanów
10
State lists: definiowanie


Zasobami z listami stanów definiujemy jako XML
Każda pozycja to mapowanie:


Zbiór stanów => odpowiedni zasób
Kolejność jest istotna

Ostatnia pozycja to zwykle zasób domyślny
<?xml version="1.0" encoding="utf-8"?>
<selector ...>
<item android:state_pressed="true"
android:drawable="@drawable/button_pressed" />
<item android:state_focused="true"
android:drawable="@drawable/button_focused" />
<item android:drawable="@drawable/button_normal" />
</selector>
11
State lists: możliwe stany

android:state_enabled – czy element jest aktywny
 Zwykle określa się zasób dla stanu nieaktywnego (false)

android:state_focused – czy element ma fokus
 Rzadko dotyczy trybu dotykowego (touch mode)

android:state_pressed – czy element jest wciśnięty

android:state_checked – czy element jest wybrany
 Pola wyboru (checkboxes) i przyciski radiowe

Pozostałe:



12
android:selected
android:checkable
android:window_focused
State list drawables


Definiujemy jako pliki XML w res/drawable
Elementy listy odwołują się do innych, już
istniejących zasobów


Gotowe zasoby pasują do wszystkich atrybutów
typu drawable


Mogą być nimi prawie dowolne drawable
android:background, android:drawableLeft, itd.
Przykład podany wcześniej
13
Color state lists


Definiujemy jako pliki XML w res/color
Elementy listy bezpośrednio przechowują
wartości kolorów


Standardowe formaty #RGB, #RRGGBB, itd.
Gotowe zasoby pasują do wszystkich atrybutów
typu color
android:backgroundColor, android:textColor, itd.
<?xml version="1.0" encoding="utf-8"?>
<selector ...>
<item android:state_pressed="true"
android:color="#ffff0000"/>
<item android:state_focused="true"
android:color="#ff0000ff"/>
<item android:color="#ff000000"/>
</selector>

14
Animacje

Android obsługuje dwa odmienne typy animacji dla widoków:



Klatkowe (frame animations)
Typu tween
Animacje klatkowe działają jak zasoby typu drawable
<?xml version="1.0" encoding="utf-8"?>
<animation-list ... android:oneshot="false">
<item android:drawable="@drawable/spinner1"
android:duration="200" />
<item android:drawable="@drawable/spinner2"
android:duration="200" />
<item android:drawable="@drawable/spinner3"
android:duration="200" />
</animation-list>
spinner.setBackgroundResource(R.drawable.spinner_anim);
((AnimationDrawable)spinner.getBackground()).start();
15
Animacje typu tween

Animacje tween pozwalają na geometryczne
przekształcenia kontrolek:






Zmianę położenia (translację - <translate>)
Zmianę rozmiarów (skalowanie - <scale>)
Obrót 2D wokół punktu - <rotate>
Zmianę przezroczystości (alfy - <alpha>)
Możliwe jest kontrolowanie sposobu interpolacji
animowanych wartości
Transformacje można składać w zbiory (<set>), aby
zgrupowane animacje uruchamiały się jednocześnie
16
Przykład animacji typu tween
<?xml version="1.0" encoding="utf-8"?>
<set ...>
<alpha
android:fromAlpha="1.0" android:toAlpha="0.0"
android:duration="300" />
<scale
android:interpolator="@android:anim/accelerate_interpolator"
android:fromXScale="1.0" android:toXScale="0.0"
android:fromYScale="1.0" android:toYScale="0.0"
android:pivotX="0%"
android:pivotY="100%"
android:duration="300" />
res/anim/example.xml
</set>
17
Stosowanie animacji typu tween

Ręczne wczytywanie i kontrola animacji:
Animation anim = AnimationUtils.loadAnimation(this,
R.anim.example);
view.startAnimation(anim);

Ustawianie jako przejść, np.:
activity.overridePendingTransition(R.anim.in,
R.anim.out); // API 5+
viewAnimator.setInAnimation(R.anim.in);
viewAnimator.setOutAnimation(R.anim.out);
18
Przykład animowanego przejścia
<?xml version="1.0" encoding="utf-8"?>
<!-- Odjazd w lewą stronę -->
<translate ...
android:fromXDelta="0%p" android:toXDelta="-100%p"
android:interpolator="@android:anim/accelerate_interpolator"
android:duration="300">
</translate>
res/anim/out.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- Wjazd z prawej strony -->
<translate ...
android:fromXDelta="100%p" android:toXDelta="0%p"
android:interpolator="@android:anim/accelerate_interpolator"
android:duration="300">
</translate>
res/anim/in.xml
19
Praca z układami kontrolek
Efektywne tworzenie layoutów
Tworzenie układów nie jest proste

…zwłaszcza dla programistów tradycyjnych GUI


Układy wymuszają elastyczność, o którą w
klasycznych PC-towych frameworkach trzeba dbać


Kontrolki nie mają właściwości X i Y?!
Nie można po prostu ustawić stałych pozycji/wymiarów
Ceną jest większy stopień skomplikowania przy
mniejszej swobodzie manewru
private void MainForm_Resize(EventArgs e) {
ctl.X = (Width – ctl.Width) / 2;
ctl.Y = (Height – ctl.Height) / 2;
}

Należy znać typy układów (podklasy ViewGroup na
Androidzie) i ich przypadki użycia
21
Który *Layout wybrać?


Prawie zawsze odpowiedzią jest RelativeLayout
Pewne wyjątki:



Tylko jedna kontrolka - FrameLayout
Układ przypomina tabelę - TableLayout
Układ jest liniowy i:
Wszystkie elementy mają stały rozmiar w jednostkach
bezwzględnych (dip)
lub
 Wszystkie elementy mają rozmiar proporcjonalny do rozmiaru
całego układu (android:layout_weight)
- LinearLayout

30dip
22
40dip
30dip
45%
55%
Przykład: holy grail
http://www.alistapart.com/articles/holygrail
23
Rozwiązanie: tylko jeden RelativeLayout …
<RelativeLayout ...>
<View android:id="@+id/header" android:layout_alignParentTop="true"
android:layout_width="match_parent" android:layout_height="50dip"/>
<View android:id="@+id/footer" android:layout_alignParentBottom="true"
android:layout_width="match_parent" android:layout_height="50dip"/>
<View android:id="@+id/left" android:layout_alignParentLeft="true"
android:layout_below="@id/header" android:layout_above="@id/footer"
android:layout_width="70dip" android:layout_height="match_parent" />
<View android:id="@+id/right" android:layout_alignParentRight="true"
android:layout_width="70dip" android:layout_height="match_parent"
android:layout_below="@id/header" android:layout_above="@id/footer" />
<View android:id="@+id/content"
android:layout_width="match_parent" android:layout_height="match_parent"
android:layout_below="@id/header"
android:layout_above="@id/footer"
android:layout_toRightOf="@+id/left"
android:layout_toLeftOf="@+id/right" />
</RelativeLayout>
24
…z odpowiednio wyrównanymi…
<RelativeLayout ...>
<View android:id="@+id/header" android:layout_alignParentTop="true"
android:layout_width="match_parent" android:layout_height="50dip"/>
<View android:id="@+id/footer" android:layout_alignParentBottom="true"
android:layout_width="match_parent" android:layout_height="50dip"/>
<View android:id="@+id/left" android:layout_alignParentLeft="true"
android:layout_below="@id/header" android:layout_above="@id/footer"
android:layout_width="70dip" android:layout_height="match_parent" />
<View android:id="@+id/right" android:layout_alignParentRight="true"
android:layout_width="70dip" android:layout_height="match_parent"
android:layout_below="@id/header" android:layout_above="@id/footer" />
<View android:id="@+id/content"
android:layout_width="match_parent" android:layout_height="match_parent"
android:layout_below="@id/header"
android:layout_above="@id/footer"
android:layout_toRightOf="@+id/left"
android:layout_toLeftOf="@+id/right" />
</RelativeLayout>
25
…i wymierzonymi kontrolkami
<RelativeLayout ...>
<View android:id="@+id/header" android:layout_alignParentTop="true"
android:layout_width="match_parent" android:layout_height="50dip"/>
<View android:id="@+id/footer" android:layout_alignParentBottom="true"
android:layout_width="match_parent" android:layout_height="50dip"/>
<View android:id="@+id/left" android:layout_alignParentLeft="true"
android:layout_below="@id/header" android:layout_above="@id/footer"
android:layout_width="70dip" android:layout_height="match_parent" />
<View android:id="@+id/right" android:layout_alignParentRight="true"
android:layout_width="70dip" android:layout_height="match_parent"
android:layout_below="@id/header" android:layout_above="@id/footer" />
<View android:id="@+id/content"
android:layout_width="match_parent" android:layout_height="match_parent"
android:layout_below="@id/header"
android:layout_above="@id/footer"
android:layout_toRightOf="@+id/left"
android:layout_toLeftOf="@+id/right" />
</RelativeLayout>
26
Ponowne wykorzystanie elementów UI


Zasada DRY (Don’t Repeat Yourself) działa też przy
konstruowaniu interfejsu
W Androidzie jest to możliwe poprzez
wyodrębnianie:



Powtarzalnych wartości do postaci zasobów (res/values)
Właściwości kontrolek do postaci styli/tematów
Grup kontrolek do postaci dołączanych układów
<?xml version="1.0" encoding="utf-8">
<resources>
<color name="background_color">#c0ffee</color>
<color name="text_color">#face</color>
</resources>
27
Ponowne wykorzystanie: style widoków

Style pozwalają na zdefiniowanie zestawu wartości
dla atrybutów widoków
<?xml version="1.0" encoding="utf-8">
<resources>
<style name="Button">
<item name="android:textColor">@color/textColor</item>
<item name="android:gravity">right</item>
<item name="android:paddingLeft">15dip</item>
<item name="android:layout_height">wrap_content</item>
</style>
</resources>
<Button android:id="@+id/my_button" style="@style/Button"/>
28
Style widoków: dziedziczenie


Dla stylu możemy ustawić styl nadrzędny
Właściwości są dziedziczone, ale możemy je
nadpisywać
<style name="RedButton" parent="@style/Button">
<item name="android:background">#f00</item>
</style>

Jeżeli nadrzędnym jest nasz własny styl, możemy
używać notacji kropkowej:
<style name="Button.Red">
<item name="android:background">#f00</item>
</style>
<style name="Button.Red.Big">
<item name="android:textSize">30dip</item>
</style>
29
Ponowne wykorzystanie układów



Układy kontrolek nie muszą być wykorzystywane
bezpośrednio (np. w Activity.setContentView)
W razie potrzeby układ można załadować z zasobu
przy pomocy klasy LayoutInflater
W ten sposób można tworzyć dynamiczne układy
kontrolek
LayoutInflater inflater = getLayoutInflater();
for (...) {
View element = inflater.inflate(R.layout.item, null,
false);
// ...
container.addView(element);
}
30
Ponowne wykorzystanie: <include> i <merge>

Możliwe jest też włączanie układu do XML-a innego
układu za pomocą elementu <include>
<RelativeLayout ...>
<include layout="@layout/top_bar"
android:layout_alignParenTop="true" />
<LinearLayout android:id="@+id/main_content" ... />
</RelativeLayout>

Jeśli włączany układ ma <merge> jako główny
element, wtedy wstawiana jest tylko jego zawartość
<merge ...>
<Button android:id="@+id/button1" ... />
<Button android:id="@+id/button2" ... />
</merge>
31
Korzystanie z powiadomień
…i ich upiększanie
Android notyfikacjami stoi


System oferuje kilka opcji powiadomień
Zajmiemy się trzema najczęściej używanymi:




Toasty
Powiadomienia na pasku stanu
Dialogi
Różnią się one m.in. inwazyjnością i stopniem
możliwej interakcji użytkownika
Interaktywność
Inwazyjność
Toasty
żadna
mała
Pasek stanu
mała
żadna
Dialogi
pełna
duża
33
Toasty


Krótko widoczne informacje wyświetlane domyślnie
pośrodku dolnej części ekranu
Najprostsza wersja: sam tekst
Toast.makeText(getContext(), R.string.toast_text,
Toast.LENGTH_SHORT).show();

Bardziej skomplikowana: własny układ kontrolek
Toast toast = new Toast(getContext());
toast.setDuration(Toast.LENGTH_LONG);
toast.setView(toastView);
toast.show();
34
Notyfikacje na pasku stanu


Reprezentują zdarzenia oczekujące akcji
użytkownika, np. przychodzące wiadomości
Składają się z:




Powiadomieniom mogą towarzyszyć inne sygnały


Ikony na pasku stanu
Tekstu powiadomienia (ticker text) na pasku stanu
Widoku pokazywanego po rozwinięciu paska
Dźwięki, wibracje, miganie diody LED
Interakcja użytkownika z powiadomieniem powoduje
wysłanie ustalonego wcześniej intenta
35
Przykład notyfikacji tekstowej
Notification notification = new
Notification(R.drawable.icon, "Hello world!",
System.currentTimeMillis());
Intent tapIntent = new Intent(this, MyActivity.class);
PendingIntent contentIntent =
PendingIntent.getActivity(this, tapIntent, 0);
notification.setLatestEventInfo(this, "Hello", "Nice to meet
you.", contentIntent);
NotificationManager nm = (NotificationManager)
getSystemService(NOTIFICATION_SERVICE);
nm.notify(1, notification);
// ...
nm.cancel(1);
36
Własne widoki w notyfikacjach

Notyfikacje mogą zawierać bardziej skomplikowany
UI niż tylko sam tekst


W tym celu należy użyć zdalnych widoków
(RemoteViews), podając:



Paski postępu ściągania plików, kontrolki odtwarzania
muzyki, itp.
Identyfikator układu kontrolek (R.layout)
Opcjonalne modyfikacje właściwości kontrolek,
aplikowane po załadowaniu układu
Nie można użyć View bezpośrednio, bo notyfikacje
istnieją w procesie systemowym, a nie aplikacji
37
Przykład: pasek postępu
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="5dip">
<ProgressBar android:id="@+id/progress_bar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</FrameLayout>
res/layout/notification_progress.xml
38
Pasek postępu: użycie RemoteViews…
RemoteViews contentView = new RemoteView(getPackageName(),
R.layout.notification_progress);
contentView.setProgressBar(R.id.progress_bar, 100, 0,
false);
Notification notification = new Notification(...);
notification.flags = Notification.FLAG_ONGOING_EVENT;
notification.contentIntent = PendingIntent.getActivity(...);
notification.contentView = contentView;
notificationManager.notify(NOTIFICATION_ID, notification);
// update
notification.contentView.setProgressBar(R.id.progress_bar,
100, percentage, false);
notificationManager.notify(NOTIFICATION_ID, notification);
39
…stworzenie notyfikacji…
RemoteViews contentView = new RemoteView(getPackageName(),
R.layout.notification_progress);
contentView.setProgressBar(R.id.progress_bar, 100, 0,
false);
Notification notification = new Notification(...);
notification.flags = Notification.FLAG_ONGOING_EVENT;
notification.contentIntent = PendingIntent.getActivity(...);
notification.contentView = contentView;
notificationManager.notify(NOTIFICATION_ID, notification);
// update
notification.contentView.setProgressBar(R.id.progress_bar,
100, percentage, false);
notificationManager.notify(NOTIFICATION_ID, notification);
40
…i jej aktualizacja
RemoteViews contentView = new RemoteView(getPackageName(),
R.layout.notification_progress);
contentView.setProgressBar(R.id.progress_bar, 100, 0,
false);
Notification notification = new Notification(...);
notification.flags = Notification.FLAG_ONGOING_EVENT;
notification.contentIntent = PendingIntent.getActivity(...);
notification.contentView = contentView;
notificationManager.notify(NOTIFICATION_ID, notification);
// update
notification.contentView.setProgressBar(R.id.progress_bar,
100, percentage, false);
notificationManager.notify(NOTIFICATION_ID, notification);
41
Dialogi

Android oferuje kilka wbudowanych klas dialogów na
typowe (i mniej typowe) okazje



AlertDialog i jego podklasy, np. ProgressDialog
Możemy też używać klasy Dialog bezpośrednio
Za pomocą własnych układów kontrolek i styli,
możemy dostosować dialogi do wyglądu naszej
aplikacji
42
Przypadki użycia dialogów

AlertDialog ze standardowym układem


AlertDialog z własnym układem kontrolek



Proste komunikaty o błędach, potwierdzenia operacji,
dialogi typu Loading…, itp.
Tworzony poprzez AlertDialog.Builder metodą
setView()
Odpowiedni do bardziej skomplikowanych komunikatów
Dialog lub własna podklasa Dialog



43
Gdy chcemy mieć pełną kontrolę nad wyglądem dialogu
Gdy chcemy mieć interaktywne kontrolki w dialogu
Przykład: własne menu opcji
Przykład własnego dialogu
public class CustomDialog extends Dialog {
public CustomDialog(Context context) {
super(context, android.R.style.Theme_Translucent_NoTitleBar_Fullscreen);
getWindow().getAttributes().windowAnimations = R.style.DialogTransition;
setContentView(R.layout.dialog);
((Button)findViewById(R.id.dialog_button))
.setOnClickListener(buttonListener);
}
private OnClickListener buttonListener = new OnClickListener(){
public void onClick(View view) {
dismiss();
Toast.makeText(getContext(), "Dialog closed",
Toast.LENGTH_SHORT).show();
}
};
}
44
Style dla dialogów

Domyślnie dialogi używają stylu android.R.style.Theme_Dialog




Zajmują tylko część ekranu
Mają tytuł i systemową ramkę z paddingiem dookoła
Używając innych styli, możemy eliminować niechciane
elementy (np. android.R.style.Theme_NoTitleBar)
Największe możliwości daje
android.R.style.Theme_Translucent_NoTitleBar_Fullscreen,
ale wymaga ręcznego pozycjonowania dialogu
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout ...
android:layout_width="250dip" android:layout_height="300dip"
android:layout_gravity="center">
...
</RelativeLayout>
45
Przykład stylizowanego dialogu
<RelativeLayout width=dialogWidth height=dialogHeight
layout_gravity=center>
c
<RelativeLayout width=match_parent height=match_parent
layout_marginBottom=buttonsHeight/2>
<LinearLayout width=match_parent height=buttonsHeight
layout_alignParentBotom=true>
46
Animacje przejścia dla dialogów

Modyfikując atrybuty okna (Window) dialogu,
możemy ustawić dla niego animacje przejścia

..chociaż teoretycznie to nie powinno działać :-)
<style name="DialogTransition">
<item name="android:windowEnterAnimation">
@anim/in</item>
<item name="android:windowExitAnimation">
@anim/out</item>
</style>
getWindow().getAttributes().windowAnimations =
R.style.DialogTransition;
47
Na zakończenie
Co jeszcze?...

Pozostałe rodzaje zasobów

Samych zasobów typu drawable jest kilkanaście!

Lokalizacja
Media: audio i wideo
…

Nowości z wersji 3.0






49
Fragmenty
Action Bar
Drag & drop
Schowek
Przydatne źródła

Specyfikacje systemowych styli i tematów



Wskazówki odnośnie konwencji UI w Androidzie




http://developer.android.com/reference/android/R.style.html
http://developer.android.com/guide/topics/ui/themes.html#PlatformStyles
http://www.androidpatterns.com/
http://www.androiduipatterns.com/
http://developer.android.com/guide/practices/ui_guidelines/index.html
Optymalizowanie aplikacji pod 3.0

50
http://developer.android.com/guide/practices/optimizing-for-3.0.html
Dziękuję za uwagę :-)

Podobne dokumenty