Vse vsebine, ki jih androidna aplikacija vsebuje, razen tistih, ki jih ustvari uporabnik sam, so shranjene v mapi res (okrajšava za resources). Tu govorimo o besedilih, slikah, barvah, slogih, postavitvah, menijih in podobno. Postavitve in besedila smo v prejšnjih delih Male šole že obravnavali – postavitve so shranjene v mapi layout, besedila pa v mapi values. V slednjo zapišemo tudi barve in sloge, v mapo drawable uvozimo slike (dovoljeni formati so .png, .jpg in .gif), menije pa definiramo v mapi menu.

Do tu je še vse dokaj preprosto, zdaj pa postanejo stvari zapletene, a hkrati zelo uporabne. Android namreč omogoča prilagajanje vsebine glede na uporabnikovo napravo, in sicer tako, da imenu mape dodamo oznake. To pomeni, da lahko na primer ustvarimo dve mapi za besedila: values ter values-sl in v prvo shranimo vsa angleška besedila, v drugo pa vsa slovenska. Posledica tega bo, da bo aplikacija vsem uporabnikom, ki imajo svojo napravo nastavljeno na slovenski jezik, prikazala besedila iz mape values-sl, vsem ostalim pa iz mape values. Če Android ne najde mape z oznako za trenutno nastavitev, vzame mapo s privzetim imenom, zato je dobro vedno imeti takšno mapo v aplikaciji. Izjema so le resolucije, kjer Android poišče mapo z najbolj ustreznim kvalifikatorjem.

Tipov kvalifikatorjev je kar precej, poleg jezika pa sta najbolj uporabna naslednja:

• resolucijo zaslona določamo z oznakami -ldpi, -mdpi, -hdpi in -xhdpi (nizka, srednja, visoka in zelo visoka resolucija), s čimer lahko določamo resolucijo uporabljenih slik,
• usmerjenost zaslona določamo z oznakama -port in -land (pokončno in ležeče), kar je priročno za določanje različnih postavitev glede na orientacijo.

Seveda lahko oznake med seboj sestavljamo in določimo na primer posebne slike za slovenske uporabnike, ki uporabljajo aplikacijo v ležečem položaju na napravi z majhno ločljivostjo: drawable-sl-land-ldpi.

Zagonska ikona

Zagonska ikona aplikacije je zelo pomembna, saj se z njo naša aplikacija loči od ostalih, še preden jo zaženemo. Za pravilen prikaz na vseh napravah jo moramo imeti v štirih ločljivostih: 36 × 36 px, 48 × 48 px, 72 × 72 px in 96 × 96 px. Vse štiri ikone z enakim imenom shranimo v mape drawable-ldpi, drawable-mdpi, drawable-hdpi in drawable-xhdpi, v enakem vrstnem redu, kot so ločljivosti.

Ikona, ki se prikaže v zagonskem meniju naprave z operacijskim sistemom Android.

Pa začnimo z delom in dodajmo ikono naši aplikaciji. Najprej v Idei odprimo aplikacijo, ki smo jo ustvarili v drugem delu Male šole. Z desnim gumbom kliknemo na mapo res in izberemo New > Android resource directory. Odpre se nam priročno okno za dodajanje mape z vsebino. Pri Resource type izberemo drawable, pri Available qualifiers izberemo Density in kliknemo >>, da dodamo to oznako, nato pa le še izberemo gostoto zaslona pri Density: Low Density in kliknemo OK. To je ustvarilo mapo res/drawable-ldpi, kamor bomo shranili slike za nizkoločljivostne naprave. Postopek ponovimo še za Medium, High in X-High Density. Zdaj imamo pripravljene mape za slike različnih ločljivosti. Pripravimo našo ikono v čim večji ločljivosti, vsaj 512 x 512 px (tako kakovostno sliko potrebujemo, če želimo aplikacijo naložiti na Android Play, nekdanji Android Market), po možnosti v vektorski obliki, nato pa jo zmanjšamo in shranimo v ustrezne mape. Priporočljiva je uporaba formata .png, da lahko na ikoni uporabimo tudi prozornost. Vse štiri ikone shranimo z istim imenom: launcher_icon.png.

Ko smo ikone ustvarili, le še povemo naši aplikaciji, naj jo uporabi. V Idei odpremo datoteko AndroidManifest.xml in v njej znački <activity android:name=".MojMikroAndroidActivity"…> dodamo lastnost: android:icon="@drawable/launcher_icon". V isti datoteki moramo dodati tudi podporo za visoke ločljivosti, ki so zaradi združljivosti s starejšimi različicami Androida privzeto izklopljene. Korenski znački <manifest …> dodamo značko: <supports-screens android:anyDensity="true"/>.

Če zaženemo našo aplikacijo v emulatorju in v njem odpremo zagonski meni, že vidimo našo ikono.

Format 9-patch

V razvojnem okolju Android obstaja zelo uporabna oblika .png formata, imenovana 9-patch, ki vsebuje raztegljiva in fiksna območja slike. Takšna slika tako zapolni celoten prostor, ne glede na dimenzije prostora, raztegne pa le območja, ki jih določimo sami. Uporabo tega formata bomo najlažje videli na primeru, zato kar pripravimo ozadje naše aplikacije.

Ker želimo spet podpreti vse ločljivosti zaslonov, bomo najprej pripravili sliko za največjo ločljivost, potem pa jo zmanjšali še za ostale. Razmerja ločljivosti so ldpi:mdpi:hdpi:xhdpi = 3:4:6:8, in ta razmerja upoštevamo tudi pri ustvarjanju slik za aplikacijo. Pri ustvarjanju ozadja moramo imeti v mislih, da se bo vsaj en stolpec raztegnil v vodoravni smeri in vsaj ena vrstica v navpični, tako da bo lahko slika zavzela celotno površino.

Raztegnil se bo lahko stolpec ob levem robu slike (kjer ni napisa) in vrstica v sredini slike.

Sliko shranimo nekje izven naše aplikacije pod imenom main_bg.png. Nato odpremo priročen program, ki je del razvojnih orodij: <Android SDK>/tools/draw9patch.bat, in vanj potegnemo našo sliko. Program nam omogoča risanje črnih točk po skrajnem robu slike. Črne točke na zgornjem in levem robu slike povedo, katera območja se bodo raztegovala, črne točke na spodnjem in desnem robu pa to, kje bo vsebina (podobno kot padding v css). Na desni polovici programa lahko vidimo predogled raztegovanja, če pa vklopimo še možnost Show content, lahko vidimo tudi, kje bo vsebina. Ko smo z raztegovanjem zadovoljni, kliknemo File > Save 9-patch in sliko z imenom main_bg shranimo v mapo res/drawable-xhdpi v naši aplikaciji. Zdaj ponovimo postopek še za preostale ločljivosti in shranimo slike z enakim imenom v ustrezne mape. Če pogledamo v te mape, vidimo v njih slike s končnicami .9.png, kar označuje 9-patch sliko.

Ozadje aplikacije

Ko imamo izdelano ozadje, ga lahko dodamo v našo aplikacijo. Najprej bomo dodali datoteko s slogi. V Idei z desnim gumbom kliknemo na mapo values in izberemo New > Values resource file. Za ime datoteke vnesemo style in pritisnemo OK. Ustvari in odpre se nam datoteka style.xml s korensko značko <resource>. Znotraj te značke zdaj določimo slog tako, da dodamo kodo:

<style name="main_window">
<item name="android:background">@drawable/main_bg</item>
</style>

S tem smo ustvarili slog z imenom main_window in vsi elementi, ki ga bodo uporabili, bodo imeli za ozadje sliko main_bg. Nato moramo le še določiti te elemente. Ozadje želimo dati na vsako stran zavihkov, zato odpremo datoteko main.xml in korenski znački <TableLayout …> dodamo lastnost style="@style/MainWindow". Malce drugače je pri zavihku z regijami, saj smo tam določili zgolj posamezne elemente seznama, zato bomo vsakemu posebej določili tudi ozadje. Temu zavihku določimo ozadje v datoteki RegionsListActivity.java, kjer na konec metode onCreate dodamo naslednjo kodo:

getListView().setBackgroundResource(R.drawable.main_bg);

Zdaj lahko zaženemo aplikacijo in vidimo, da se ozadje izriše pri obeh zavihkih.

Gumbi

Privzeti gumbi v Androidu niso preveč privlačni, zato ima večina aplikacij svojo obliko gumbov. Sprememba gumba je zelo preprosta – spremenimo mu le ozadje, kot smo se naučili že v prejšnjem poglavju. Ker pa gumbi niso tako statični kot ozadje, bomo določili še, da se gumb spremeni, ko uporabnik pritisne nanj.

Gumb je v osnovi zelo ozek, a bo Android zeleno označen del raztegnil po potrebi.

Ustvarimo torej dve 9-patch sliki, ki predstavljata ozadje gumba. Poimenujmo ju button_bg.9.png (gumb v normalnem stanju) in button_bg_down.9.png (pritisnjen gumb). Ustrezne velikosti slik shranimo v mape drawable-ldpi in druge. Ustvarimo še peto mapo za slike, ki je brez kvalifikatorjev: drawable. Ko je ustvarjena, v Idei nanjo kliknemo z desnim gumbom in izberemo New > Drawable resource file ter napišemo ime button. Ustvari in odpre se nam datoteka button.xml s korensko značko <selector>. Znotraj te značke dodamo naslednji znački:

<item android:state_pressed="true" android:drawable="@drawable/button_bg_down"/>
<item android:drawable="@drawable/button_bg"/>

Normalen in pritisnjen osnutek gumba

Prva vrstica pove, naj ima gumb, ki je pritisnjen, za ozadje sliko z imenom button_bg_down, v vseh ostalih primerih pa button_bg. Značka item, ki nima določenega stanja android:state_ …, mora biti zadnja, saj Android ignorira vse značke, ki so pod njo.

Nato dodajmo to ozadje v sloge. Odprimo datoteko style.xml in dodajmo novo značko style:

<style name="nice_button">
<item name="android:background">@drawable/button</item>
<item name="android:textColor">#ffffff</item>
</style>

S tem slogom povemo, naj gumbi uporabijo za ozadje sliko button, ki kaže na datoteko button.xml, ki smo jo pravkar uporabili, ta pa bo potem prikazala ustrezne slike glede na »pritisnjenost« in ločljivost. Ker pa so moji gumbi temni, sem določil še za belo barvo besedila na gumbu. Naslednji korak je, da ta slog dodamo gumbu – odprimo datoteko main.xml, poiščimo značko <Button …> ter ji dodajmo lastnost style="@style/nice_button". Poženimo aplikacijo in pritisnimo na naš čudoviti gumb.

Ikone zavihkov

Trenutni zavihki zasedajo ogromno prostora in so videti zelo okorno, saj je Android postavil standard, da morajo zavihki vsebovati tudi ikone, zato je že pripravljen prostor zanje. Standardi so se spreminjali z različnimi vrstami Androidov, zato je dobro poiskati podatke, značilne za posamezno različico. V tej Mali šoli smo se osredotočili na verzijo 2.3, torej bomo delali po standardu za različice 2.0 – 2.3.

Ikone zavihkov moramo, tako kot slike gumbov, pripraviti v dveh variantah, le da smo tu omejeni pri izbiri barv: siva (#808080) za izbrani zavihek in bela (#ffffff) za neizbrane zavihke.

Dve različici ikone za profil

Ikone morajo imeti tudi točno določeno velikost: ldpi: 24 × 24 px; mdpi: 32 × 32 px; hdpi: 48 × 48 px; xhdpi: 64 × 64 px. Na internetu lahko poiščemo brezplačne ikone ali pa jih narišemo. Najprej potrebujemo ikono velikosti 64 × 64 px v beli in sivi izvedbi. Poimenujmo ju ic_tab_profile_unselected.png (bela) in ic_tab_profile_selected.png (siva) ter ju shranimo v mapo drawable-xhdpi. Ustvarimo še ikone za ostale ločljivosti in jih shranimo v ustrezne mape.

Enako, kot smo to storili pri ozadju gumba, tudi tu z desnim gumbom kliknemo na mapo drawable, izberemo New > Drawable resource file ter napišemo ime ic_tab_profile. Ustvarjeni datoteki znotraj značke <selector> dodamo naslednjo kodo:

<item android:drawable="@drawable/ic_tab_profile_selected" android:state_selected="true" android:state_pressed="false"/>
<item android:drawable="@drawable/ic_tab_profile_unselected"/>

Ta koda pove, naj ima izbrani zavihek ikono ic_tab_profile_selected, vsi ostali zavihki pa ic_tab_profile_unselected. Zdaj moramo le še povedati Androidu, naj to ikono uporabi za naš prvi zavihek. To naredimo tako, da odpremo datoteko MojMikroAndroidActivity.java in v kodi, kjer določimo prvi zavihek, dodamo parameter z našo novo ikono:

tabHost.addTab(tabHost.newTabSpec("my_tab")
.setIndicator(getString(R.string.my_info),
getResources().getDrawable(R.drawable.ic_tab_profile))
.setContent(new Intent(this, MyActivity.class)));

Postopek ponovimo še za drugi zavihek in že lahko občudujemo boljšo preglednost naše aplikacije.

Meni

Android ima standardni meni za uporabo dodatnih možnosti aplikacije, večina androidnih naprav pa poseben gumb na ohišju za dostop do tega menija. Čeprav naša aplikacija za zdaj še nima potrebe po tem meniju, ga bomo na tem mestu dodali, ker tudi zanj potrebujemo ikone. Dodali bomo možnost izhoda iz aplikacije. Spet najprej pripravimo ikone, ki naj bodo enakih velikosti kot ikone zavihkov, tu potrebujemo le sivo različico. Ikono z imenom ic_quit.png shranimo za vse ločljivosti.

Pritisk na to ikono bo zaprl aplikacijo.

Ko imamo pripravljene ikone, določimo meni – za to potrebujemo .xml datoteko, podobno kot za sloge, besedila ali postavitve. V Idei z desnim gumbom kliknemo na mapo res in izberemo New > Android resource directory. Pod Resource type izberemo menu in kliknemo OK. Nato z desnim gumbom kliknemo še na novoustvarjeno mapo in izberemo New > Menu resource file in vpišemo ime datoteke: main_menu. Ustvari in odpre se nam datoteka main_menu.xml z že ustvarjeno korensko značko <menu>. Vanjo vnesemo naslednjo kodo:

<item android:id="@+id/quit" android:icon="@drawable/ic_quit" android:title="@string/quit"/>

Ne pozabimo v datoteko strings.xml dodati besedila 'Izhod' za povezavo @string/quit. Odpremo razred MojMikroAndroidActivity.java in dodamo metodo, ki v aplikacijo vključi meni:

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

Zdaj že lahko vidimo meni v naši aplikaciji, ki se aktivira, če dodamo še eno metodo v razredu MojMikroAndroidActivity.java:

public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.quit:
finish();
return true;
default:
return super.onOptionsItemSelected(item);
}
}

Ta koda preveri, ali gre za gumb z id-jem, enakim R.id.quit, in če je pravi, zapre aplikacijo z metodo finish(). Ker imamo le en gumb, je to preverjanje nepotrebno, vendar je koda pripravljena za takrat, ko jih bomo imeli več.

Shranjevanje podatkov

Android omogoča različne načine shranjevanja podatkov. Za osnovne podatke (besedila, števila, logične vrednosti) je najbolj uporabna možnost shranjevanje v skupne lastnosti (Shared Preferences). Android sam poskrbi za to, kako in kam bo shranil lastnosti, mi le zapišemo ključ in podatek. Pa shranimo naše podatke. Odprimo razred MyActivity.java in ustvarimo novo metodo:

private void saveData() {
SharedPreferences.Editor editor = getPreferences(0).edit();
String name = ((TextView) findViewById(R.id.name_input)).getText().toString();
editor.putString("name", name);
int gender = ((RadioGroup) findViewById(R.id.gender_radio_group)).getCheckedRadioButtonId();
editor.putInt("gender", gender);
int region = ((Spinner) findViewById(R.id.regions_spinner)).getSelectedItemPosition();
editor.putInt("region", region);
boolean aliens = ((CheckBox) findViewById(R.id.aliens_checkbox)).isChecked();
editor.putBoolean("aliens", aliens);
int experiences = ((SeekBar) findViewById(R.id.android_experiences_seek_bar)).getProgress();
editor.putInt("experiences", experiences);
editor.commit();
}

V prvi vrstici te metode smo pripravili razred, v katerega bomo shranjevali naše podatke, nato pa smo vsak podatek shranili v dveh vrsticah – najprej smo ga pridobili iz pogleda, nato pa ga s ključem označili za shranitev. V zadnji vrstici smo vse te podatke dejansko shranili.

Metoda je pripravljena, zdaj jo moramo le še poklicati. Želimo, da se podatki shranijo ob kliku na gumb, zato uporabimo kar tega, ki ga že imamo. Spremenimo besedilo gumba v 'Shrani', akcijo ob kliku pa spremenimo, da bo klicala našo novo metodo:

findViewById(R.id.ok_button).setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
saveData();
}
});

Obveščanje

Android ima dobro metodo za obveščanje uporabnika o dogodkih aplikacije. Kot primer lahko omenimo kar shranjevanje podatkov: zelo prijazno do uporabnika je, če mu potrdimo, da je shranjevanje uspelo. To naredimo tako, da na koncu naše nove metode saveData dodamo kodo:

Toast.makeText(this, "Podatki so shranjeni", Toast.LENGTH_SHORT).show();

Ta koda za kratek čas prikaže sporočilo 'Podatki so shranjeni' v spodnjem delu zaslona.

Takšno sporočilo se prikaže za nekaj trenutkov

Branje podatkov

Branje podatkov je podobno shranjevanju, saj beremo z istega mesta. V razredu MyActivity.java spet ustvarimo novo metodo:

private void loadData() {
SharedPreferences pref = getPreferences(0);
String name = pref.getString("name", "");
((TextView) findViewById(R.id.name_input)).setText(name);
int gender = pref.getInt("gender", -1);
if (gender != -1) {
((RadioButton) findViewById(gender)).setChecked(true);
}
int region = pref.getInt("region", 0);
((Spinner) findViewById(R.id.regions_spinner)).setSelection(region);
boolean aliens = pref.getBoolean("aliens", false);
((CheckBox) findViewById(R.id.aliens_checkbox)).setChecked(aliens);
int experiences = pref.getInt("experiences", 0);
((SeekBar) findViewById(R.id.android_experiences_seek_bar)).setProgress(experiences);
}

Ta metoda v prvi vrstici pripravi lastnosti, nato pa s pomočjo ključa poišče vsako lastnost in jo nastavi ustrezni sestavini. Pri tem moramo biti pazljivi, da uporabimo enake ključe, kot smo jih uporabili pri shranjevanju podatkov.

Ko je metoda pripravljena, jo moramo le še poklicati. Podatke želimo prebrati in nastaviti takoj, ko se aplikacija naloži in postavi vse sestavine. Dodamo torej klic metode na konec metode onCreate:

public void onCreate(Bundle savedInstanceState) {

loadData();
}

Shranjeni podatki se bodo naložili, ko se bo aplikacija zagnala. Zdaj le še ustvarimo gumb 'Razveljavi', ki bo ob kliku naložil shranjene podatke in s tem preklical morebitne neželene spremembe podatkov. Koda gumba bo skoraj povsem enaka kodi gumba 'Shrani', le da bo namesto metode saveData klicala metodo loadData. Gumb lahko umestimo v main.xml namesto sestavine <TextView android:id="@+id/message"… />, ki je ne potrebujemo več.

Z zelo malo truda se aplikacija iz dolgočasne spremeni v zanimivo.

Zaključek

V tokratni Mali šoli smo temeljito polepšali našo aplikacijo, ki je zdaj bolj prijazna očem, pa tudi prstom, saj zna shraniti podatke, ki jih ni več treba vnašati vsakič znova. Ker je možnosti za lepotne popravke veliko, lahko nekaj dodatnih zvijač najdete na spletni strani www.logica.pro/mojmikro, kjer boste našli tudi izvorno kodo prispevka. Za tiste, ki vam je risanje tuje, bo na spletni strani tudi paket vseh grafik, uporabljenih zgoraj, ostali pa ste vabljeni, da posnetke svojih umetnin delite še z ostalimi.

Moj mikro, Maj 2012 | Rok Končina