V tokratnem prispevku se bomo naučili, kako podatke delimo med posameznimi zavihki znotraj naše aplikacije.

Če želi uporabnik dobiti dodatne informacije, ima na voljo naslednje metode:

1. klik na informacijo – če je mogoče z informacijo napredovati le v eno smer (primer so na primer povezave na spletnih straneh),
2. globalni meni – če je nadaljevanje enolično določeno za vse informacije skupaj ali pa če uporabniku ponudimo možnost izbire ene ali več informacij (na primer potrditvena polja – checkbox),
3. pojavni meni – ko uporabnik z dolgim pritiskom izbere določeno informacijo, mu prikažemo meni z možnostmi za to informacijo.

Tokrat si bomo pogledali prvo in tretjo točko, saj smo drugo že implementirali. V naši aplikaciji bomo omogočili, da uporabnik z dolgim klikom dobi možnosti, da prikaže izbrano sporočilo na zemljevidu ali pa odgovori na to sporočilo.

Kontekstni meni se prikaže nad vsebino, ki se malo zatemni. Prekličemo ga s pritiskom na gumb Nazaj.

Kontekstni meni

Meni, ki se prikaže nad vsebino, če uporabnik uporabi dolgi klik, se imenuje kontekstni meni. To je ustreznik desnemu kliku z miško pri računalnikih in predstavlja meni možnosti nad izbranim elementom. Vsebino menija, podobno kot pri globalnem meniju, definiramo z novo .xml datoteko v mapi res/menu. Napravimo torej datoteko res/menu/list_context_menu.xml in vanjo napišimo naslednjo vsebino:

< ?xml version="1.0" encoding="utf-8"?>
< menu xmlns:android="http://schemas.android.com/apk/res/android">
< item android:id="@+id/show_on_map" android:title="@string/show_on_map"/>
< item android:id="@+id/reply" android:title="@string/reply"/>
< /menu>

Meni bo imel dva elementa: prvi bo namenjen prikazu sporočila na zemljevidu, drugi odgovoru na sporočilo. V strings.xml moramo seveda dodati še prevode:

< string name="reply"> Odgovori< /string>
< string name="show_on_map"> Prikaži na zemljevidu< /string>

S tem smo definirali elemente menija, zdaj ga moramo še dodati v aplikacijo. Kontekstni meni je vezan na posamezni pogled (View), tako da ga bomo mi dodali na pogled s seznamom. Odprimo torej razred MessagesListActivity.java in na konec metode onCreate dodajmo naslednjo kodo, ki zgolj vklopi prikaz kontekstnega menija:

registerForContextMenu(getListView());

Da aplikaciji povemo, kateri kontekstni meni naj se prikaže ob dolgem kliku v tem pogledu, moramo povoziti metodo onCreateContextMenu (v Idei pritisnemo Ctrl+o in poiščemo ali napišemo to metodo) z naslednjo vsebino:

@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.list_context_menu, menu);
}

pri čemer R.menu.list_context_menu kaže na datoteko list_context_menu.xml, ki smo jo prej ustvarili. To že pomeni, da se bo meni prikazal, če dolgo pritiskamo na element v seznamu. Vendar pa se ob kliku na meni ne bo zgodilo nič drugega, kot da bo meni izginil. Dodati moramo seveda še akcije ob kliku na posamezni element. Povozimo še eno metodo, in sicer:

@Override
public boolean onContextItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.show_on_map:
Toast.makeText(this, "Prikaži na zemljevidu", Toast.LENGTH_SHORT).show();
return true;
case R.id.reply:
Toast.makeText(this, "Odgovori", Toast.LENGTH_SHORT).show();
return true;
}
return super.onContextItemSelected(item);
}

Enakovredno kot pri globalnem meniju bo ta metoda poklicana, ko uporabnik pritisne na element menija. Item.getItemId() nam pove id elementa, ki smo ga definirali v datoteki list_context_menu.xml, mi pa moramo samo preveriti, ali je enak R.id.show_on_map ali R.id.reply. Na tem mestu bomo izvedli ustrezno akcijo, vendar v tem trenutku samo prikažimo Toast sporočilo in preizkusimo, ali novi kontekstni meni deluje.

Preklapljanje med zavihki

Ob kliku na meni želimo prikazati ustrezni zavihek. Za to moramo v razred MojMikroAndroidActivity.java najprej dodati metodo:

public void switchTab(String tag) {
TabHost tabHost = getTabHost();
tabHost.setCurrentTabByTag(tag);
}

Ta metoda kot parameter sprejme tag (ime) zavihka, kot smo ga definirali v metodi onCreate, ko smo klicali metodo tabHost.newTabSpec("..."). Mi smo jih poimenovali "my_tab", "messages_tab", "map_tab" in "send_message_tab". Zdaj odprimo razred MessagesListActivity.java in iz metode onContextItemSelected odstranimo prikaz Toast sporočil ter dodajmo kodo za zamenjavo zavihka:

@Override
public boolean onContextItemSelected(MenuItem item) {
MojMikroAndroidActivity mmaActivity = (MojMikroAndroidActivity) this.getParent();
switch (item.getItemId()) {
case R.id.show_on_map:
mmaActivity.switchTab("map_tab");
return true;
case R.id.reply:
mmaActivity.switchTab("send_message_tab");
return true;
}
return super.onContextItemSelected(item);
}

Izbrano sporočilo se na zemljevidu označi in postavi na sredino zaslona.

Označevanje izbranega sporočila

Ko odpremo drugi zavihek, mu moramo nekako sporočiti, da mora eno sporočilo prikazati drugače. To najlažje dosežemo tako, da napravimo nov vmesnik (interface), ki bo vseboval metodo za prikaz izbranega sporočila:

public interface MessageSelectable {
void showSelectedMessage(Message selectedMessage);
}

Ta vmesnik bomo dodali razredom, ki predstavljajo aktivnosti, ki lahko prikažejo izbrano sporočilo. Dodajmo ga najprej razredu MessagesMapActivity in implementirajmo metodo showSelectedMessage:

public class MessagesMapActivity extends MapActivity implements MessagesLoadable, MessageSelectable {
...
private DialogOverlay selectedMessageDialogOverlay;
public void onCreate(Bundle savedInstanceState) {
...
selectedMessageDialogOverlay = new DialogOverlay(getResources().getDrawable(R.drawable.ic_map_marker_selected), this);
}

@Override
public void showSelectedMessage(Message selectedMessage) {
MapView mapView = (MapView) findViewById(R.id.messages_map);
if (!mapView.getOverlays().contains(selectedMessageDialogOverlay)) {
mapView.getOverlays().add(selectedMessageDialogOverlay);
}
selectedMessageDialogOverlay.clear();
selectedMessageDialogOverlay.addItem(selectedMessage.getLatitude(), selectedMessage.getLongitude(),
MessageFormat.format("{0} - {1}", selectedMessage.getAuthor(), DATE_FORMAT.format(selectedMessage.getDate())),
selectedMessage.getMessage());
GeoPoint center = new GeoPoint(selectedMessage.getLatitude(), selectedMessage.getLongitude());
mapView.getController().animateTo(center);
}

V metodi onCreate najprej v polje DialogOverlay selectedMessageDialogOverlay shranimo novo plast. Definirajmo jo tako, da ji podamo posebno ikono za prikaz sporočila na zemljevidu, ki je podobna ikoni za neizbrana sporočila, le da je pobarvana. Ikono shranimo v datoteko z imenom ic_map_marker_selected.png. Nato implementiramo metodo showSelectedMessage, ki prvič plast doda prikazu, vsakič pa na plasti prikažemo eno samo sporočilo, ki ga dobimo kot parameter metode.

Če bo uporabnik izbral točko, ki je zunaj prikaza na zemljevidu, ne bo videl, ali je izbrano sporočilo dejansko označeno. Zato je dobro, da prestavimo zemljevid tako, da bo bila izbrana točka na sredini zaslona. Android ima zelo dobro podporo za to – najprej določimo zemljepisno širino in dolžino mesta na zemljevidu, ki ga želimo v centru, nato pa pokličimo metodo animateTo, ki bo zemljevid lepo počasi prestavila od trenutnega mesta do izbrane točke. Rezultat vsega skupaj je, da je izbrano sporočilo obarvano in postavljeno na sredino zaslona.

Prikaz in premik izbrane točke smo definirali, zdaj moramo to metodo še sprožiti. Za to odpremo razred MojMikroAndroidActivity.java in mu dodamo še eno metodo:

public void switchTab(String tag, Message selectedMessage) {
switchTab(tag);
Activity tabActivity = getLocalActivityManager().getActivity(tag);
if (tabActivity instanceof MessageSelectable) {
MessageSelectable activity = (MessageSelectable) tabActivity;
activity.showSelectedMessage(selectedMessage);
}
}

Ta metoda ima nalogo, da odpre ustrezni zavihek (za kar pokliče istoimensko metodo, ki smo jo definirali že prej) in pogleda, ali ima aktivnost tega zavihka vmesnik MessageSelectable, ter mu poda izbrano sporočilo.

Nato moramo v razredu MessagesListActivity.java izvedeti, katero sporočilo je uporabnik izbral, in ga nato posredovati v zgornjo metodo. Ko uporabnik klikne na kontekstni meni seznama, lahko ugotovimo indeks izbranega elementa. Če si shranimo še vrstni red sporočil, lahko iz kombinacije teh dveh informacij ugotovimo izbrano sporočilo.

Seznam sporočil shranimo v polje List< Message> messages, ko ga prejmemo v metodi loadMessages. Priredimo jo takole:

private List< Message> messages;
@Override
public void loadMessages(List< Message> messages) {
this.messages = messages;
listAdapter.clear();
for (String message : flattenMessages(messages)) {
listAdapter.add(message);
}
Toast.makeText(this, "Sporočila osvežena.", Toast.LENGTH_SHORT).show();
}

Naslednji korak je prirejanje metode onContextItemSelected, ki naj pri klicu metode switchTab zraven poda še izbrano sporočilo. Za to ustvarimo še pomožno metodo getSelectedMessage, ki iz MenuItema ugotovi indeks izbranega sporočila in ga nato poišče v pravkar shranjenem seznamu.

@Override
public boolean onContextItemSelected(MenuItem item) {
MojMikroAndroidActivity mmaActivity = (MojMikroAndroidActivity) this.getParent();
switch (item.getItemId()) {
case R.id.show_on_map:
mmaActivity.switchTab("map_tab", getSelectedMessage(item));
return true;
case R.id.reply:
mmaActivity.switchTab("send_message_tab", getSelectedMessage(item));
return true;
}
return super.onContextItemSelected(item);
}

private Message getSelectedMessage(MenuItem item) {
AdapterView.AdapterContextMenuInfo menuInfo = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
return messages.get(menuInfo.position);
}

Gumb »Prikaži na zemljevidu« kontekstnega menija zdaj že deluje in ga lahko preizkusimo v praksi.

Če izberemo Odgovori, se odpre zavihek Pošlji in v okence za besedilo samodejno vpiše hashtag.

Odgovori na sporočilo

Pripravimo še preprosto možnost odgovarjanja na sporočila v slogu priljubljenega Twitterja. Odgovor naj torej vsebuje tako imenovani hashtag, kar je lojtra, ki ji sledi uporabniško ime brez presledkov. Če bi kdo hotel odgovoriti meni, bi torej v sporočilo dodal #RokKončina. Uporabnik lahko to besedilo v sporočilo doda ročno, mi pa mu dajmo še to možnost, da se to besedilo doda samodejno, če izbere opcijo Odgovori. Pri prikazu sporočila na zemljevidu smo večino dela že opravili, tako da moramo le dodati vmesnik MessageSelectable razredu SendMessageActivity.java in implementirati metodo showSelectedMessage tako, da bo iz izbranega sporočila prebrala uporabnika in na koncu besedila novega sporočila dodala hashtag:

public class SendMessageActivity extends Activity implements LocationListener, MessageSelectable {
...
@Override
public void showSelectedMessage(Message selectedMessage) {
TextView messageTextView = (TextView) findViewById(R.id.message_input);
String author = selectedMessage.getAuthor();
messageTextView.append(getHashtag(author));
}

private String getHashtag(String userName) {
String condensedUserName = userName.replaceAll(" ", "");
return MessageFormat.format("#{0}", condensedUserName);
}

Metoda najprej iz sporočila prebere avtorja, pomožna metoda odstrani presledke in doda lojtro, na koncu pa besedilo dodamo obstoječemu besedilu v pogledu z id-jem message_input. Ker smo prej omogočili procesiranje vseh akcij, ki implementirajo vmesnik MessageSelectable, ta koda že deluje.

Kratek klik na seznam

Zapisali smo, da bomo omogočili še kratek pritisk na seznam, ki bo izbrano sporočilo prikazal na zemljevidu, enako kot to stori možnost »Prikaži na zemljevidu« kontekstnega menija. Za to odpremo razred MessagesListActivity.java in v metodo onCreate dopišemo vrstico:

public void onCreate(Bundle savedInstanceState) {
...
getListView().setOnItemClickListener(this);
}

ki pove, kateri razred naj se sproži ob kliku na element seznama.

Idea nam ob tem javi napako, saj razred MessagesListActivity ne implementira razreda AdapterView.OnItemClickListener. Zna pa Idea to sama rešiti – na napaki pritisnemo Alt+Enter in izberemo Make 'MessagesListActivity' implement 'android.widget.AdapterView.OnItemClickListener'. Samodejno nam implementira omenjeni razred in tudi njegovo metodo, tako da moramo mi napisati samo vsebino:

public class MessagesListActivity extends ListActivity implements MessagesLoadable, AdapterView.OnItemClickListener {

@Override
public void onItemClick(AdapterView< ?> adapterView, View view, int i, long l) {
MojMikroAndroidActivity mmaActivity = (MojMikroAndroidActivity) this.getParent();
mmaActivity.switchTab("map_tab", messages.get(i));
}

Koda je zelo podobna tisti pri kontekstnem meniju, le da nam tu že vrednost i pove, katero sporočilo je uporabnik pritisnil.

Takole je bil videti seznam med drsenjem, ko smo pustili privzeto barvo ozadja.

Popravek drsenja seznama

Ko smo ravno pri seznamu, ne moremo mimo tega, da ne bi opazili napačnega prikaza. Kadar koli drsimo po seznamu, se nam namreč ozadje obarva temno sivo. To je privzeta barva ozadja, ki ga aplikacija uporabi pri predpomnjenju in se sklada s privzeto barvno shemo androidnih aplikacij. Pri nas pa ta privzeta barva ozadja ne deluje, ker smo spremenili ozadje v sliko. Rešitev tega problema je zelo preprosta – vse, kar moramo napraviti, je, da ustrezno spremenimo to barvo. Če bi naša aplikacija imela enobarvno ozadje, bi bilo najbolje nastaviti to isto barvo, ker pa imamo mi sliko, moramo to ozadje nastaviti za prozorno. Ta opcija ni najbolj optimalna, saj je prozornost procesorsko zahtevna lastnost, a aplikacije ne bo toliko upočasnilo, kot je nesprejemljivo trenutno spreminjanje ozadja.

Za spremembo barve moramo odpreti razred MessageListActivity.java in v metodo onCreate dodati zgolj:

getListView().setCacheColorHint(0);

Številka 0 predstavlja popolnoma prozorno črno barvo. Če zdaj preizkusimo seznam, vidimo, da se ne obarva več sivo.

Prva stran projekta na GitHub.com: 1) projekt v .zip datoteki, 2) povezava za kloniranje projekta na disk, 3) gumb za forkanje projekta.

GitHub

Koda članka je po novem na priljubljenem javnem repozitoriju GitHub. Ta omogoča hranjenje in verzioniranje kode s pomočjo priljubljenega orodja Git, ki ga je razvil Linus Torvalds, avtor operacijskega sistema Linux. Repozitorij omogoča brezplačno hranjenje neomejenega števila projektov, če je le njihova koda objavljena javno. Plačljiva različica pa omogoča tudi hranjenje zasebnih projektov. Koda našega projekta je seveda javna in je na naslovu: https://github.com/koncinar/moj-mikro-android.

To kodo si lahko na računalnik prenesemo na tri načine:

1. tako, da si s spletne strani potegnemo datoteko .zip in si jo razpakiramo na disk,
2. tako, da si na računalnik namestimo Git z naslova http://git-scm.com/ in kodo naložimo z ukazom git clone https://github.com/koncinar/moj-mikro-android.git,
3. obstoječi (ali novi) uporabniki GitHuba pa lahko projekt klonirajo v svoj uporabniški račun z opcijo Fork in potem urejajo svojo različico.

Ko imamo kodo pri sebi, jo moramo še uvoziti v Ideo. Navodila za to smo že napisali v prejšnjih člankih, tako da jih bomo le na kratko ponovili: v Idei kliknemo File > New project... > Create project from existing sources, nato poiščemo mapo, kamor smo razpakirali naš projekt, nato pa klikamo Next in na koncu še Finish. Uvoziti moramo še knjižnico za zemljevide, za kar odpremo File > Project structure... in v Libraries dodamo javansko knjižnico: < android-sdk> /add-ons/addon-google_apis-google-10/libs/maps.jar. Nato pa tej knjižnici v Modules > Moj-mikro-android > Dependencies nastavimo Scope na provided.

Zdaj imamo projekt uvožen v Ideo in lahko uporabljamo tudi kar Idejin Git vtičnik, če želimo naložiti spremembe, in lasten račun za GitHub.

Zaključek

Iz sporočila, ki je bilo prikazano na seznamu, prej nismo mogli ugotoviti, od kod je bilo poslano. Zdaj pa lahko z eno potezo dobimo tudi ta podatek. Ali pa uporabniku, ki je to sporočilo poslal, odgovorimo. S tem smo še dodatno povezali zavihke in napravili aplikacijo bolj celovito.
Kot vedno lahko kodo najdete na spletni strani http://logica.pro/mojmikro, po novem pa tudi na https://github.com/koncinar/moj-mikro-android. In spet ste vabljeni, da komentirate, vprašate ali predlagate.

Moj mikro, Januar Februar 2013 | Rok Končina |