ActionBar - przykłady użycia
Spis treści
Funkcje
Tym razem na nasz celownik trafia ActionBar. Pełni on aktualnie sporo funkcji:
- informuje użytkownika gdzie aktualnie znajduje się w aplikacji
- umożliwia przejście w górę (nie mylić z przejściem wstecz)
- udostępnia akcje dostępne dla danego widoku, dla nowszych urządzeń bez przycisku "opcje" dostarcza overflow button, który pełni tę rolę
- akcje dla wybranego elementu
- informuje o ładowaniu, odświeżaniu danego widoku
- udostępnia akcje nawigacyjne
Historia
ActionBar został wprowadzony razem z Androidem 3.0 (API 11), ale dzięki Support Library dostępne jest już od API 7. Jeśli planujemy wypuścić aplikację na urządzenia z API 11+, to powinniśmy korzystać z dostępnego w SDK ActionBara, a w przeciwnym wypadku tego z dodatkowej biblioteki.
Na cele tego artykułu będziemy zajmować się tylko API 11+. Ponieważ API 10 to podczas pisania tego artykułu 30% rynku (źródło) w przyszłości na pewno zajmiemy się kompatybilnością z poprzednimi wersjami.
Z góry informuję, że kod źródłowy jak i APK aplikacji będzie dostępne na końcu artykułu więc w razie czego istnieje możliwość ich pobrania.
Aktualne położenie w aplikacji
Tutaj szału nie ma. Po prostu ustawiamy tytuł.
getActionBar().setTitle("Ekran wyboru");
Efekt:
Przejście w górę
Przejście w górę często pokrywa się z przejściem wstecz, ale jest między nimi zdecydowana różnica. Załóżmy sytuację, że mamy listę artykułów. Po wybraniu pierwszego uruchomiony zostaje widok pierwszego artykułu, który zawiera przycisk "następny". Naciskamy przycisk i otworzony zostaje artykuł drugi. Kolejne akcje zaznaczyłem i ponumerowałem kolorem zielonym.
Jaka akcja powinna zostać podjęta po naciśnięciu poszczególnych przycisków?
- wstecz powinno przenieść nas do artykułu 1 (kolor zielony)
- w górę powinno przenieść nas do listy artykułów (kolor czerwony)
Dodatkowe założenia przycisku "w górę":
- strzałka w lewo powinna być widoczna tylko gdy naciśnięcie przycisku spowoduje akcję
- nie powinien doprowadzać do wyłączenia się aplikacji
- nie powinien prowadzić do innej aplikacji
- powinien zawsze prowadzić do jednego widoku, bez znaczenia jaką ścieżkę nawigacji wybrał użytkownik
Dobra, koniec teorii. Do dzieła!
Dla celów przykładu wszystkie dane umieszczę bezpośrednio w kodzie. Oczywiście w realnej aplikacji warto skorzystać z bazy danych.
Zacznijmy od listy wpisów:
public class EntryListActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_entry_list);
getActionBar().setTitle("Lista artykułów");
ListView listView = (ListView) findViewById(R.id.entry_list_view);
List<String> articles = new ArrayList<String>();
articles.add("Pierwszy artykuł");
articles.add("Drugi artykuł");
listView.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, articles));
listView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Intent intent = new Intent(getApplicationContext(), EntryActivity.class);
intent.putExtra(EntryActivity.EXTRA_ARTICLE_ID, position);
startActivity(intent);
}
});
}
}
Do listy dodajemy dwa elementy i korzystając z ArrayAdaptera podpinamy całość pod widok listy.
Pojedynczy wpis:
public class EntryActivity extends Activity {
protected static final String EXTRA_ARTICLE_ID = "extra_article_id";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_entry);
getActionBar().setDisplayHomeAsUpEnabled(true);
int id = getIntent().getExtras().getInt(EXTRA_ARTICLE_ID);
Button btnPrevious = (Button) findViewById(R.id.btn_previous);
Button btnNext = (Button) findViewById(R.id.btn_next);
Button btnEnabled;
Button btnDisabled;
String title = "Artykuł ";
final int extra;
if (id == 0) {
btnDisabled = btnPrevious;
btnEnabled = btnNext;
title += " pierwszy";
extra = 1;
} else {
btnDisabled = btnNext;
btnEnabled = btnPrevious;
title += " drugi";
extra = 0;
}
setTitle(title);
btnDisabled.setEnabled(false);
btnEnabled.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(getApplicationContext(), EntryActivity.class);
intent.putExtra(EntryActivity.EXTRA_ARTICLE_ID, extra);
startActivity(intent);
}
});
}
/**
* Możliwość wykrycia kliknięcia i obsługi przycisku w górę w własny sposób
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
Toast.makeText(getApplicationContext(), "Naciśnięto przycisk w górę", Toast.LENGTH_SHORT).show();
// Użycie finish() jest niezalecane
// finish();
return false;
}
return super.onOptionsItemSelected(item);
}
/**
* Obsługa przycusku wstecz
*/
@Override
public void onBackPressed() {
Toast.makeText(getApplicationContext(), "Naciśnięto przycisk wstecz", Toast.LENGTH_SHORT).show();
super.onBackPressed();
}
}
Tutaj nowością jest linia:
getActionBar().setDisplayHomeAsUpEnabled(true);
Dodaje ona strzałkę przy logo. Poprawne działanie aplikacji wymaga jeszcze odpowiednich wpisów w AndroidManifest.xml
<activity
android:name="pl.javastart.actionbartest.upbutton.EntryActivity"
android:parentActivityName="pl.javastart.actionbartest.upbutton.EntryListActivity" >
<!-- Wsparcie dla 4.0 i poprzednie -->
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="pl.javastart.actionbartest.upbutton.EntryListActivity" />
</activity>
Jako bonus zamieszczam manualną obsługę przycisków w górę oraz wstecz:
/**
* Możliwość wykrycia kliknięcia i obsługi przycisku w górę w własny sposób
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
Toast.makeText(getApplicationContext(), "Naciśnięto przycisk w górę", Toast.LENGTH_SHORT).show();
// Użycie finish() jest niezalecane
// finish();
return false;
}
return super.onOptionsItemSelected(item);
}
/**
* Obsługa przycusku wstecz
*/
@Override
public void onBackPressed() {
Toast.makeText(getApplicationContext(), "Naciśnięto przycisk wstecz", Toast.LENGTH_SHORT).show();
super.onBackPressed();
}
Dokładny opis i wskazówki użycia przycisków wstecz i w górę znajdziecie tutaj.
Akcje dla danego widoku
Akcje dla danego widoku definiujemy niemalże identycznie jak menu. Co ciekawe Android za nas obsługuje problem ograniczonego miejsca na pasku akcji, a także fakt, że niektóre urządzenia(nowsze) nie posiadają przycisku opcji.
public class ActionsActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_actions);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_actions, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
Toast.makeText(getApplicationContext(), "Naciśnięto: " + item.getTitle().toString(), Toast.LENGTH_SHORT).show();
return super.onOptionsItemSelected(item);
}
}
Menu:
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:id="@+id/menu_delete"
android:icon="@android:drawable/ic_menu_delete"
android:orderInCategory="100"
android:showAsAction="always"
android:title="Usuń"/>
<item
android:id="@+id/menu_settings"
android:orderInCategory="100"
android:showAsAction="never"
android:title="Ustawienia"/>
<item
android:id="@+id/menu_help"
android:icon="@android:drawable/ic_menu_help"
android:orderInCategory="100"
android:showAsAction="ifRoom|withText"
android:title="Pomoc Pomoc Pomoc"/>
</menu>
Tak to wygląda na telefonie pionowo (po naciśnięciu przycisk opcji):
Na telefonie poziomo (jak widać napis Pomoc Pomoc Pomoc) się już mieści:
I na tablecie. Tutaj dodany został overflow button (te 3 kropki) który pełni rolę wcześniejszego przycisku opcji.
Tryb akcji: Action mode
public class ActionModeActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_action_mode);
findViewById(R.id.btn_action_mode).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActionMode(new ActionMode.Callback() {
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
MenuInflater inflater = mode.getMenuInflater();
inflater.inflate(R.menu.menu_action_mode, menu);
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
switch (item.getItemId()) {
default:
return false;
}
}
@Override
public void onDestroyActionMode(ActionMode mode) {
}
});
}
});
}
}
Menu:
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:id="@+id/menu_settings"
android:icon="@android:drawable/ic_menu_delete"
android:orderInCategory="100"
android:showAsAction="always"
android:title="delete"/>
</menu>
Tutaj ciekawostka. Możliwość przejścia w tryb akcji, możemy go użyć np przy zaznaczaniu elementów. Po zastosowaniu paru powyższych linijek powstaje coś takiego (po lewej przed naciśnięciem przycisku, a po prawej po)
Ładowanie / odświeżanie widoku
Przy okazji tworzenia pewnego projektu napotkałem potrzebę udostępnienia użytkownikowi możliwości odświeżenia widoku pobierając dane z internetu. Udało mi się znaleźć rozwiązanie korzystające z ActionBar. Tak to wygląda:
Po zakończeniu akcji wraca do pierwszego widoku. Samo rozwiązanie wydaje mi się trochę "dookoła", ale działa bez zastrzeżeń. Jeśli znasz sposób jak zrobić to ładniej, to daj proszę znać :)
public class LoadingActivity extends Activity {
private boolean mRefreshButtonEnabled = true;
private Task mTask;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
setContentView(R.layout.activity_blank);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
if (mRefreshButtonEnabled) {
getMenuInflater().inflate(R.menu.menu_loading, menu);
((MenuItem) menu.findItem(R.id.refreshButton)).setOnMenuItemClickListener(new OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
mTask = new Task();
mTask.execute();
return true;
}
});
} else {
getMenuInflater().inflate(R.menu.menu_no_loading, menu);
}
return true;
}
@Override
public void onPause() {
super.onPause();
if (mTask != null) {
mTask.cancel(true);
mTask = null;
}
setRefreshButtonVisible(false);
}
private void setRefreshButtonVisible(boolean state) {
setProgressBarIndeterminateVisibility(!state);
mRefreshButtonEnabled = state;
invalidateOptionsMenu();
}
private class Task extends AsyncTask<Void, Void, Void> {
@Override
protected void onPreExecute() {
setRefreshButtonVisible(false);
}
@Override
protected void onPostExecute(Void param) {
setRefreshButtonVisible(true);
}
@Override
protected Void doInBackground(Void... params) {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
}
}
Kluczowe elementy:
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
Linia musi zostać wywołana przed setContentView()
@Override
public boolean onCreateOptionsMenu(Menu menu) {
if (mRefreshButtonEnabled) {
getMenuInflater().inflate(R.menu.menu_loading, menu);
((MenuItem) menu.findItem(R.id.refreshButton)).setOnMenuItemClickListener(new OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
mTask = new Task();
mTask.execute();
return true;
}
});
} else {
getMenuInflater().inflate(R.menu.menu_no_loading, menu);
}
return true;
}
Tutaj zależnie od zmiennej mRefreshButtonEnabled dodajemy odpowiednie menu. Jest tutaj pokazana kolejna możliwość obsługi naciśnięcia przycisku dodając do niego odpowiednio Listenera.
private void setRefreshButtonVisible(boolean state) {
setProgressBarIndeterminateVisibility(!state);
mRefreshButtonEnabled = state;
invalidateOptionsMenu();
}
setProgressBarIndeterminateVisibility() pokazuje lub ukrywa widok ładowania
invalidateOptionsMenu - wymusza odświeżenie menu, czyli m.in. wywołanie metody onCreateOptionsMenu()
private class Task extends AsyncTask<Void, Void, Void>
Tym zadaniem symulujemy dłuższe akcje wywoływane w tle. O AsyncTasku dostępny jest osobny artykuł.
Akcje nawigacyjne
Artykuł zostanie dokończony wkrótce...
Dyskusja i komentarze
Masz pytania do tego wpisu? Może chcesz się podzielić spostrzeżeniami? Zapraszamy dyskusji na naszej grupie na Facebooku.