Baza Wiedzy

ActionBar - przykłady użycia

Tym razem na nasz celownik trafia ActionBar. Pełni on aktualnie sporo funkcji:

     

    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: 2013-09-14 13.45.55

    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)

       

      flow

       

       

      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):

        2013-09-14 16.24.13

         

        Na telefonie poziomo (jak widać napis Pomoc Pomoc Pomoc) się już mieści:

        2013-09-14 16.24.20

        I na tablecie. Tutaj dodany został overflow button (te 3 kropki) który pełni rolę wcześniejszego przycisku opcji.

        2013-09-14 16.22.00

        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)

        2013-09-14 16.03.40

        Ł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:

        ładowanie2

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

        Najlepszy newsletter o Javie w Polsce

        Czy chcesz otrzymywać nowości ze świata Javy oraz przykładowe pytania rekrutacyjne? Zapisz się na newsletter i bądź na bieżąco! Otrzymasz także ekskluzywne materiały oraz informacje o nowych kursach i promocjach.

        Nikomu nie udostępniamy Twojego maila, a jeśli zechcesz to w każdej chwili możesz się wypisać.

        Komentarze do artykułu

        Wyłączyliśmy możliwość dodawania komentarzy. Poniżej znajdziesz archiwalne wpisy z czasów gdy strona była jeszcze hobbystycznym blogiem. Zapraszamy natomiast do zadawnia pytań i dyskusji na naszej grupe na facebooku.