ViewPager
: prstom posúvame obsah¶Nejedna aplikácia spočíva z dvoch aktívit: v jednej zobrazujeme zoznam mailov, úloh, súborov, a po kliknutí na položku prejdeme do druhej aktivity, kde zobrazíme všetky podrobnosti.
Lenže ak nás naraz zaujíma viacero položiek (napr. chceme čítať viacero nových mailov), budeme sa preklikávať zo zoznamu do detailu, potom naspäť do zoznamu, kde vyberieme novú položku, jedným slovom hore-dole ako na pružine.
Vďaka view pageru môžeme zobraziť podrobnosti o jednej položke a ťahaním prsta ju vieme odsunúť mimo displej tak, aby uvoľnila miesto nasledovnej položke. Máme teda niečo ako filmový pás položiek, ktorý vieme prstom posúvať v oboch smerom.
Ako vidno, pager je skvelý pre prípady, kde by ste sa tradične posúvali medzi položkami pomocou tlačidiel Ďalší a Predošlý
Ukážme si príklad použitia!
V ukážke o kartách sme vytvorili aplikáciu zobrazujúcu informácie o krajoch.
Predveďme si to ešte raz, s použitím view pagera. Založme si teda nový projekt
s jednou prázdnou aktivitou RegionBrowserActivity
.
Ak chceme zaviesť tento widget do aplikácie, budeme potrebovať:
FragmentStatePagerAdapter
, ktorý zobrazí položku reprezentovanú fragmentomNeskôr dodáme ešte jeden krok:
Widget stránkovača ViewPager
preberá dáta do stránok skrz adaptér PageAdapter
,
kde máme na výber z dvoch implementácií (povieme o nich viac neskôr), ktoré
napĺňajú obsahy stránok z fragmentov.
Voliteľnou súčasťou sú navigačné prudy (pager strips), kde máme opäť na výber dva typy: statický a interaktívny, pričom si ukážeme použitie oboch.
Vyrobme layout aktivity, ktorá obsiahne widget pre view pager. Do súboru
activity_region_browser.xml
dodajme obsah:
<android.support.v4.view.ViewPager xmlns:android="https://schemas.android.com/apk/res/android"
android:id="@+id/regionViewPager"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<android.support.v4.view.PagerTabStrip
android:id="@+id/pager_title_strip"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:background="#33b5e5"
android:textColor="#fff"
android:paddingTop="4dp"
android:paddingBottom="4dp" />
</android.support.v4.view.ViewPager>
Jadro tvorí widget android.support.v4.view.ViewPager
, ktorý vyhlásime za
koreňový element layoutu. Žiadny špeciálny layout (napr. RelativeLayout
či LinearLayout
) nám netreba, pretože pager sa sám postará o správu
komponentov.
V kóde aktivity potrebujeme vyhľadať v layoute inštanciu widgetu typu ViewPager
,
a priradiť mu adaptér, ktorý bude reprezentovať dáta pre jednotlivé položky,
medzi ktorými sa bude používateľ hýbať.
Odkiaľ získame dáta? Jednoducho: zoznam krajov Slovenska definujeme v reťazcovom
resource podobným spôsobom, ako sme to spravili pri kartách. V súbore
strings.xml
uvedieme pole reťazcov:
<string-array name="regionNames">
<item>Bratislavský</item>
...
<item>Košický</item>
</string-array>
A ako nastavíme adaptér? Ako je známe, každý widget má obvykle svoj vlastný
typ adaptéra. V prípade ViewPager
a sú dáta reprezentované adaptérom
typu PagerAdapter
. Táto trieda sa však nevytvára priamo, ale zvyčajne
sa využije jeden z dvoch vzorov pripravených v platforme. Android tak ponúka:
FragmentPagerAdapter
je jednoduchý adaptér, ktorý fragmenty a ich stav udržuje v pamäti. Je jednoduchý pre prípady, keď je fragmentov málo (cca 4) a ich počet sa nemení. Fragment, ktorý zodpovedá práve zobrazovanej položke, sa napojí na aktuálnu aktivitu (cez metódu attach()
na transakcii), a ostatné, nezobrazované fragmenty, sú odpájané cez detach()
.FragmentStatePagerAdapter
. Fragmenty vytvára a ničí dynamicky, podľa potreby. Mení sa počet fragmentov? Máte ich veľa? S týmto adaptérom ušetríte.FragmentStatePagerAdapter
¶V aplikácii implementujeme adaptér cez pamäťovo efektívnejšiu predlohu
FragmentStatePagerAdapter
. Potrebujeme implementovať dve metódy:
getCount()
vráti počet fragmentov, teda počet položiek v zobrazovanej kolekciigetItem()
vráti konkrétnu inštanciu fragmentu na danej pozícii.Kostra bude vyzerať nasledovne:
public class RegionBrowserActivity extends ActionBarActivity {
private ViewPager viewPager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_region_browser);
final String[] regionNames = getResources().getStringArray(R.array.regionNames);
this.viewPager = (ViewPager) findViewById(R.id.regionViewPager);
this.viewPager.setAdapter(new FragmentStatePagerAdapter(getSupportFragmentManager()) {
@Override
public Fragment getItem(int i) {
return ________________________;
}
@Override
public int getCount() {
return regionNames.length;
}
});
}
}
Každá zobrazovaná položka v kolekcii, ktorú zobrazujeme s využitím adaptéra na základe fragmentov, potrebuje, áno, fragment. Vytvorme teda fragment i jeho layout.
Layout fragmentu bude jednoduchý:
<?xml version="1.0" encoding="utf-8"?>
<TextView
xmlns:android="https://schemas.android.com/apk/res/android"
android:id="@+id/regionDetailTextView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:textSize="50sp"
/>
Kód fragmentu nebude ničím mimoriadnym: azda len „konštruktorom“, ktorý
vezme reťazec zobrazený v obrovskom textovom políčku. Na prenos reťazca
využijeme argumenty v podobe objektu typu Bundle
.
Widget ViewPager
pochádza z knižnice kompatibility a preto i fragmenty
musia dediť od triedy z tejto knižnice, čiže od triedy typu
android.support.v4.app.Fragment
.
Výsledný zdrojový kód teda vyzerá:
public class RegionDetailFragment extends android.support.v4.app.Fragment {
public static final String ARG_REGION_NAME = "RegionName";
private TextView regionDetailTextView;
public static RegionDetailFragment newInstance(String regionName) {
Bundle args = new Bundle();
args.putString(ARG_REGION_NAME, regionName);
RegionDetailFragment regionDetailFragment = new RegionDetailFragment();
regionDetailFragment.setArguments(args);
return regionDetailFragment;
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_region_detail, container, false);
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
this.regionDetailTextView = (TextView) view.findViewById(R.id.regionDetailTextView);
Bundle args = getArguments();
if(args != null && args.containsKey(ARG_REGION_NAME)) {
this.regionDetailTextView.setText(args.getString(ARG_REGION_NAME));
}
}
}
Ak je fragment hotový, môžeme ho využiť v adaptéri!
Metóda getItem()
v adaptéri musí vrátiť novú inštanciu fragmentu, ktorý
obsiahne dáta práve zobrazenej položky.
@Override
public Fragment getItem(int i) {
return RegionDetailFragment.newInstance(regionNames[i]);
}
Ako vidno, využili sme pseudokonštruktor, do ktorého uvedieme reťazec s názvom kraja, ktorý vytiahneme z poľa podľa indexu aktuálne zobrazenej položky z kolekcie krajov.
Už teraz môžeme spustiť aplikáciu a kochať sa, ako ťahaním prsta posúvame orloj s veľkými názvami krajov.
Ak máte príliš veľa položiek, ľahko sa v nich stratíte. Najmä keď je fragment tvorený z viacerých komponentov. Našťastie, existujú rovno dva spôsoby, ktorými môžete naznačiť používateľovi, ktorá položka je vľavo či vpravo od práve zobrazovaného fragmentu.
PagerTitleStrip
je neinteraktívny úzky pruh, kde sa v strede zobrazuje názov aktuálneho fragmentu a vľavo i vpravo sú titulky susediacich fragmentov.PagerTabStrip
je interaktívny pruh, kde používateľ nielen vidí, ale môže i klikať na titulky susediacich fragmentov.Pre použitie stačí vložiť deklaráciu príslušného komponentu do layoutu, pod
element <android.support.v4.view.ViewPager>
.
<android.support.v4.view.PagerTitleStrip
android:id="@+id/pager_title_strip"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:background="#33b5e5"
android:textColor="#fff"
android:paddingTop="4dp"
android:paddingBottom="4dp" />
Dôležité je nastaviť šírku (na celý displej), výšku (primeranú výške položiek) a uviesť pozíciu pruhu pomocou layout_gravity
: buď tradične hore (top
) alebo dole (bottom
). Ostatné nastavenia sú estetické: farba pozadia, textu, či priestor medzi textom a horným či dolným okrajom.
Na to, aby to naozaj fungovalo, je nutné implementovať v adaptéri ešte jednu metódu, ktorá vráti titulok fragmentu zobrazený v pruhu:
this.viewPager.setAdapter(new FragmentStatePagerAdapter(getSupportFragmentManager()) {
...
@Override
public CharSequence getPageTitle(int position) {
return regionNames[position];
}
});
Vyskúšajte si meniť typy pruhov, je to zábava! (A vôbec nemusíte meniť Java kód.)
View pagers nemusia zobrazovať aktuálne pozície len cez pruhy Pager___Strip
.
Druhou možnosťou je prepojiť ich s kartami na lište akcií!
Komunikácia medzi lištou akcií a stránkovačom pobeží dvoma drôtmi:
ActionBar.TabListener
.Úlohu oboch poslucháčov vie zastúpiť aktivita implementujúca oba interfejsy.
public class RegionBrowserActivity extends ActionBarActivity
implements ActionBar.TabListener,
ViewPager.OnPageChangeListener {
}
Následne musí implementovať šesť metód: s prefixom onTab____
patria poslucháčovi
zmien výberu kariet, a onPage____
patria poslucháčovi zmien stránkovača.
Metódy kariet sú známe z minulých dielov, ale u nás stačí implementovať
onTabSelected()
, kde nastavíme aktuálne vybratú položku v stránkovači
na základe pozície karty v lište aktivít:
@Override
public void onTabSelected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
this.viewPager.setCurrentItem(tab.getPosition());
}
Ostatné dve metódy, onTabUnselected()
a onTabReselected()
nechajme prázdne.
Stránkovač poskytuje tiež tri metódy: onPageSelected()
sa zavolá pri
zmene aktuálne zobrazenej položky v stránkovači, a práve tam vyvoláme zmenu
aktuálne vybratej karty v lište akcií:
@Override
public void onPageSelected(int pageIndex) {
getSupportActionBar().setSelectedNavigationItem(pageIndex);
}
Ostatné dve metódy slúžia na podrobné zisťovanie stavu zámen položiek
v stránkovači: onPageScrolled()
počúva na zmeny v posune karty a
onPageScrollStateChanged()
počúva na zmeny stavu stránkovača, kde možno zistiť, či používateľ práve
posúva prstom stránky (a vymieňa položky), alebo už je zámena položiek
dokončená. Nad tým sa ale nemusíme trápiť: obe metódy môžeme nechať prázdne.
@Override
public void onPageScrolled(int i, float v, int i2) {
}
@Override
public void onPageScrollStateChanged(int i) {
Aby to naozaj fungovalo, musíme ručne vytvoriť karty podobne, ako sme to robili pri tutoriáli o kartách. V tomto prípade budú karty kreované s titulkami podľa jednotlivých krajov:
protected void onCreate(Bundle savedInstanceState) {
...
ActionBar actionBar = getSupportActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
for (String regionName : regionNames) {
ActionBar.Tab tab = actionBar.newTab()
.setText(regionName)
.setTabListener(this);
actionBar.addTab(tab);
}
}
Vyskúšajte si hotovú verziu! Klikaním na karty meníte výber a posúvaním stránkovača zároveň posúvate karty.
Výsledná aplikácia je k dispozícii na Githube, v repozitári novotnyr/android-viewpager-demo-2015
.