一. 簡介
ViewPager 是support v4 包提供的控件眯停,可以實(shí)現(xiàn)一組View 切換顯示的效果济舆。
二. 使用
使用ViewPager 也比較簡單,主要有以下幾步:
獲取ViewPager 實(shí)例莺债,包括從XML 或直接new
自定義ViewPager 的adapter滋觉,并為ViewPager 設(shè)置adapter
設(shè)置ViewPager 的切換效果(可選)
設(shè)置ViewPager 的事件監(jiān)聽(可選)
三. 自定義Adapter
ViewPager 的adapter 類型為PagerAdapter,它是一個(gè)抽象類齐邦,support 包提供了兩種實(shí)現(xiàn)椎侠,一個(gè)FragmentPagerAdapter,另一個(gè)是FragmentStatePagerAdapter侄旬,這兩個(gè)Adapter 是ViewPager 和Fragment 結(jié)合使用時(shí)所用肺蔚,稍后介紹。
現(xiàn)在介紹一下儡羔,繼承PagerAdapter 的自定義Adapter宣羊。繼承PagerAdapter 必須要重寫兩個(gè)方法,即 getCount汰蜘、isViewFromObject仇冯,同時(shí)為了保證功能的實(shí)現(xiàn),還應(yīng)該重寫instantiateItem 和 destroyItem 方法族操,示例代碼如下:
private class MyPagerAdapter extends PagerAdapter {
private List<Uri> data;
MyPagerAdapter(List<Uri> data) {
this.data = data;
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
// 創(chuàng)建 Item, 本例為顯示圖片
View view = LayoutInflater.from(container.getContext()).inflate(R.layout.item_two, container, false);
ImageView image = view.findViewById(R.id.show_image_iv);
Picasso.with(container.getContext())
.load(data.get(position))
.into(image);
container.addView(view);
return view;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
if (object instanceof View) {
container.removeView((View)object);
}
}
@Override
public int getItemPosition(Object object) {
return POSITION_UNCHANGED; // 默認(rèn)返回苛坚,與刷新有關(guān)
}
@Override
public boolean isViewFromObject(View view, Object object) {
return object == view;
}
@Override
public int getCount() {
return data.size();
}
public void setData(List<Uri> data) {
this.data = data;
notifyDataSetChanged();
}
}
效果:
PagerAdapter 工作流程:
PagerAdapter 作為 ViewPager 的適配器,無論 ViewPager 有多少頁色难,PagerAdapter 在初始化時(shí)也只初始化開始的2個(gè) View泼舱,即調(diào)用2次instantiateItem 方法。而接下來每當(dāng) ViewPager 滑動時(shí)枷莉,PagerAdapter 都會調(diào)用 destroyItem 方法將距離該頁2個(gè)步幅以上的那個(gè) View 銷毀娇昙,以此保證 PagerAdapter 最多只管轄3個(gè) View,且當(dāng)前 View 是3個(gè)中的中間一個(gè)笤妙,如果當(dāng)前 View 缺少兩邊的 View冒掌,那么就 instantiateItem噪裕,如里有超過2個(gè)步幅的就 destroyItem。
在實(shí)現(xiàn)Adapter 時(shí)股毫,有幾點(diǎn)需要注意膳音,可能會出坑。
第一個(gè)就是destroyItem 方法铃诬,如果使用 container.removeViewAt(index) 方法祭陷,可能會導(dǎo)致出現(xiàn) 越界 crash,所以應(yīng)該使用removeView(object) 方法氧急。
第二個(gè)是getItemPosition 方法颗胡,默認(rèn)的返回值是 POSITION_UNCHANGED毫深,還有另外一種值是 POSITION_NONE吩坝,這兩個(gè)值的區(qū)別在于刷新數(shù)據(jù)時(shí),
Viewpager 的刷新過程是這樣的哑蔫,在每次調(diào)用 PagerAdapter 的 notifyDataSetChanged() 方法時(shí)钉寝,都會激活 getItemPosition(Object object) 方法,該方法會遍歷 ViewPager 的所有 Item(由緩存的 Item 數(shù)量決定闸迷,默認(rèn)為當(dāng)前頁和其左右加起來共3頁嵌纲,這個(gè)可以自行設(shè)定,但是至少會緩存2頁)腥沽,為每個(gè) Item 返回一個(gè)狀態(tài)值(POSITION_NONE/POSITION_UNCHANGED)逮走,
如果是 POSITION_NONE,那么該 Item 會被 destroyItem(ViewGroup container, int position, Object object) 方法 remove 掉今阳,然后重新加載师溅,
如果是 POSITION_UNCHANGED,就不會重新加載盾舌,默認(rèn)是 POSITION_UNCHANGED墓臭,所以如果不重寫 getItemPosition(Object object),修改返回值妖谴,就無法看到 notifyDataSetChanged() 的刷新效果窿锉。
注:當(dāng)ViewPager 的Item 是Fragment 時(shí),系統(tǒng)提供了FragmentPagerAdapter 和 FragmentStatePagerAdapter膝舅,但是動態(tài)修改時(shí)可能還是會有一些問題嗡载,需要進(jìn)行一些特殊處理。
四. 切換效果
有時(shí)候我們可能覺得默認(rèn)的切換太平淡仍稀,想來點(diǎn)花哨的效果洼滚,Android 提供了一個(gè)接口 PageTransformer,實(shí)現(xiàn)這個(gè)接口琳轿,并為ViewPager 設(shè)置Transformer 即可實(shí)現(xiàn)切換特效判沟。
PageTransformer 接口只有一個(gè)方法耿芹,即 public void transformPage(View page, float position)
,它有兩個(gè)參數(shù)挪哄,
page
表示 ViewPager 中的一頁吧秕,
position
表示page
當(dāng)前的位置,[-1, 0)表示屏幕左邊的page
(部分可見)迹炼,[0, 0]表示屏幕上的page
(完全可見)砸彬,(0, 1]表示屏幕右邊的page
(部分可見),具體看下圖:
當(dāng)page
向左邊滑動時(shí)斯入,position
從0向-1變化砂碉,當(dāng)position==-1
時(shí)完全不可見;當(dāng)page
向右滑動時(shí)刻两,position
從0向1變化增蹭,當(dāng)position==1
時(shí)完全不可見。這樣磅摹,我們根據(jù)position 的變化來對View 進(jìn)行相關(guān)的操作滋迈,比如旋轉(zhuǎn)、縮放户誓、透明度等饼灿,就可以實(shí)現(xiàn)不同的切換效果。如下例帝美,
private class GalleryTransformer implements ViewPager.PageTransformer {
private static final float MAX_ROTATION = 20.0F;
private static final float MIN_SCALE = 0.75f;
private static final float MAX_TRANSLATE = 20.0F;
private int width = UiUtils.getScreenWidth(ViewActivity.this);
private boolean firstPage; // 是否第一頁
private boolean firstTime = true; // 是否第一遍執(zhí)行
private int count; // 執(zhí)行次數(shù), 與setOffscreenPageLimit 有關(guān)
GalleryTransformer() {
firstPage = true;
}
// 第一頁和第一遍的邏輯碍彭,用于處理一頁顯示多個(gè)Item 時(shí),初始狀態(tài)第二頁的狀態(tài)
@Override
public void transformPage(View page, float position) {
if (count++ == 3) {
firstTime = false; // 第一遍執(zhí)行完
}
float offset = (float)(width - page.getWidth()) / 2; // 因?yàn)橐豁擄@示多個(gè)Item
if (page.getWidth() != 0) {
position = position - offset / page.getWidth(); // 所以要對position 進(jìn)行相關(guān)偏移悼潭,否則將顯示異常
}
if (position < -1.0) { // (-∞, -1)
page.setTranslationX(MAX_TRANSLATE);
page.setScaleX(MIN_SCALE);
page.setScaleY(MIN_SCALE);
page.setRotationY(-MAX_ROTATION);
} else if (firstPage || (!firstTime && position <= 1.0)) { // [-1, 1]
firstPage = false;
page.setTranslationX(-MAX_TRANSLATE * position);
float scale = MIN_SCALE + (1-MIN_SCALE) * (1.0f - Math.abs(position));
page.setScaleX(scale); // 縮放
page.setScaleY(scale);
page.setRotationY(MAX_ROTATION * position); // 旋轉(zhuǎn)
} else if (firstTime || position > 1.0){ // (1, +∞)
page.setTranslationX(-MAX_TRANSLATE);
page.setScaleX(MIN_SCALE);
page.setScaleY(MIN_SCALE);
page.setRotationY(MAX_ROTATION);
}
}
}
形成的效果如下: