Kurs Android (17)
Autor: Damian Chodorek • Opublikowany: 31 stycznia 2016 • Ostatnia aktualizacja: 20 maja 2016 • Kategoria: android, kursy
Wykorzystanie komponentu Toolbar
oraz CoordinatorLayout
. Jak animować toolbar podczas przewijania listy?
Dzisiejsza lekcja w pełni będzie opierać się na projekcie i kodzie z lekcji poprzedniej (link).
Toolbar czy ActionBar?
Jakiś czas temu na moim blogu pojawiły się dwie lekcje dotyczące ActionBara, czyli paska znajdującego się w górnej części aplikacji (link 1, link 2). Jaka jest więc różnica pomiędzy ActionBarem a Toolbarem, który dziś omówię?
Otóż Toolbar jest nowym komponentem, który stanowi generalizację ActionBara. Toolbar jest dużo bardziej elastyczny i łatwiej nim manipulować. Stanowi zwykły element hierarchii widoków i może znajdować się w dowolnym miejscu. ActionBar jest pod tym względem znacznie ograniczony.
Jeśli więc potrzebujesz podstawowego zestawu funkcjonalności, który opisałem w podanych lekcjach, skorzystaj z ActionBara. Jeśli jednak chcesz mieć nowoczesny, animowany pasek, to w tym artykule zapoznasz się z Toolbarem.
Zaczynamy
Zakładam, że posiadasz lub przynajmniej umiesz stworzyć kod z poprzedniej lekcji. W pliku res/values/styles.xml
zdefiniowany jest główny styl aplikacji. Domyślnie wygenerowany plik, wygląda tak jak poniżej:
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
</style>
</resources>
Styl nazywa się AppTheme
i dziedziczy po Theme.AppCompat.Light.DarkActionBar
. Zmieńmy rodzica tego stylu na Theme.AppCompat.Light.NoActionBar
, co sprawi, że domyślny pasek aplikacji zniknie.
Od Androida 5.0 (API Level 21) możliwe jest ustawienie koloru status bara, czyli tego paska, na którym znajduje się godzina, poziom naładowania baterii, połączenie z siecią, itp.
- 1 – kolor tekstu (
textColorPrimary
), - 2 – kolor status bara (
colorPrimaryDark
), - 3 – kolor Toolbara (
colorPrimary
).
Zdefiniujmy więc colorPrimaryDark
oraz colorPrimary
w pliku res/values/color.xml
zgodnie ze wskazówkami specyfikacji Material Design.
<resources>
<color name="ColorPrimary">#03A9F4</color>
<color name="ColorPrimaryDark">#0288D1</color>
</resources>
Teraz w wykorzystajmy te kolory w pliku styles.xml
.
<resources>
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="colorPrimary">@color/ColorPrimary</item>
<item name="colorPrimaryDark">@color/ColorPrimaryDark</item>
</style>
</resources>
Toolbar
Toolbarem posługujemy się jak innymi komponentami aplikacji. Ponieważ z założenia Toolbar powinien być w każdej aktywności, zdefiniujmy go w oddzielnym pliku res/layout/tool_bar.xml
<android.support.v7.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android"
android:theme="@style/ThemeOverlay.AppCompat.Dark"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/ColorPrimary"
android:elevation="4dp">
</android.support.v7.widget.Toolbar>
Następnie wykorzystajmy layout Toolbara w głównej aktywności activity_main.xml
.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FAFAFA">
<include
android:id="@+id/tool_bar"
layout="@layout/tool_bar"></include>
<android.support.v7.widget.RecyclerView
android:id="@+id/articles"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/tool_bar" />
</RelativeLayout>
Skoro już podążamy za specyfikacją Material Design, udoskonalmy nasz layout pojedynczego artykułu w pliku article_layout.xml
.
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:layout_marginBottom="4dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
card_view:cardBackgroundColor="#FFF"
card_view:cardCornerRadius="2dp"
card_view:cardElevation="2dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/article_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Title"
android:textColor="#DD000000"
android:paddingTop="24dp"
android:paddingBottom="16dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:textSize="24sp" />
<TextView
android:id="@+id/article_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Content"
android:textColor="#8A000000"
android:paddingBottom="24dp"
android:paddingLeft="16dp"
android:lineSpacingExtra="8dp"
android:paddingRight="16dp"
android:textSize="14sp" />
</LinearLayout>
</android.support.v7.widget.CardView>
Teraz nasza aplikacja powinna wyglądać jak na obrazku poniżej.
Toolbar jest pusty i nie zawiera żadnych elementów. Warto upodobnić go do ActionBara, czyli nadać mu odpowiedni tytuł i zestaw opcji.
public class MainActivity extends AppCompatActivity {
private Toolbar mToolbar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// ustawiamy Toolbar jako ActionBar
mToolbar = (Toolbar) findViewById(R.id.tool_bar);
setSupportActionBar(mToolbar);
// to z poprzedniej lekcji:
setRecyclerView();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// tworzymy menu, dodajemy elementy do ActionBara
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// wykonujemy akcje po kliknięciu na element menu
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
private void setRecyclerView() {
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.articles);
recyclerView.setHasFixedSize(true);
// ustawiamy LayoutManagera
recyclerView.setLayoutManager(new LinearLayoutManager(this));
// ustawiamy animatora, który odpowiada za animację dodania/usunięcia elementów listy
recyclerView.setItemAnimator(new DefaultItemAnimator());
// tworzymy źródło danych - tablicę z artykułami
ArrayList<Article> articles = new ArrayList<>();
for (int i = 0; i < 20; ++i)
articles.add(new Article());
// tworzymy adapter oraz łączymy go z RecyclerView
recyclerView.setAdapter(new MyAdapter(articles, recyclerView));
}
}
Oto jak wygląda aplikacja po zmianach.
CoordinatorLayout
CoordinatorLayout
to zaawansowany FrameLayout
. Stanowi element nadrzędny (jest rodzicem) i umożliwia koordynowanie zachować swoich potomków. Zmiana stanu jednego dziecka może wywołać zmianę stanu innego dziecka. Oczywiście akcje i reakcje możemy zdefiniować sami, ale część zachowań została zdefiniowana domyślnie. Przykładowo dla Toolbara, który chowa się w przypadku przewijania RecyclerView
. Najpierw przedstawię kod activity_main.xml
, później wyjaśnię o co w nim chodzi.
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FAFAFA">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<include
android:id="@+id/tool_bar"
layout="@layout/tool_bar"></include>
</android.support.design.widget.AppBarLayout>
<android.support.v7.widget.RecyclerView
android:id="@+id/articles"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
</android.support.design.widget.CoordinatorLayout>
Przede wszystkim zmieniliśmy RelativeLayout
na android.support.design.widget.CoordinatorLayout
. Dodaliśmy także android.support.design.widget.AppBarLayout
jako rodzica naszego Toolbara. Jest to rozszerzony LinearLayout
zaprojektowany do współpracy z CoordinatorLayotu
w taki sposób, aby umożliwić reagowanie na przewijanie.
Elementowi RecyclerView
ustawiliśmy app:layout_behavior="@string/appbar_scrolling_view_behavior"
. Bez tego nasza lista wjeżdżałaby pod Toolbara oraz nie reagowałaby na jego obecność. Wartość wskazuje na android.support.design.widget.AppBarLayout$ScrollingViewBehavior
. Jest to statyczna klasa zagnieżdżona w AppBarLayout
, która rozszerza android.support.design.widget.CoordinatorLayout.Behavior
Klasy typu Behavior
umożliwiają CoordinatorLayout
wywoływanie odpowiednich interakcji pomiędzy jego potomkami. Nic nie stoi na przeszkodzie, aby tworzyć własne zachowania.
Dodanie więc ScrollingViewBehavior
(przypominam: app:layout_behavior="@string/appbar_scrolling_view_behavior"
) umieści RecyclerView
pod AppBarLayout
oraz spowoduje aktualizację pozycji, podczas gdy toolbar zostanie schowany.
Musimy dodać jeszcze jeden atrybut do naszego Toolbara.
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/ColorPrimary"
android:elevation="4dp"
android:theme="@style/ThemeOverlay.AppCompat.Dark"
app:layout_scrollFlags="scroll|enterAlways">
</android.support.v7.widget.Toolbar>
Dodaliśmy więc app:layout_scrollFlags="scroll|enterAlways"
. Flaga scroll
spowoduje, że Toolbar będzie się chował podczas przewijania listy, jednak pojawi się dopiero, gdy wrócimy na jej początek. Jeśli dodamy flagę enterAlways
, Toolbar powróci zawsze podczas przewijania listy do góry.
Oto aplikacja ze schowanym Toolbarem (przewijamy listę w dół). Zauważ, że RecyclerView
został podniesiony wyżej i przylega do góry ekranu.
Oto aplikacja z pokazującym się Toolbarem (przewijamy w górę).