一. 简介
ViewPager 是support v4 包提供的控件,可以实现一组View 切换显示的效果。
二. 使用
使用ViewPager 也比较简单,主要有以下几步:
获取ViewPager 实例,包括从XML 或直接new
自定义ViewPager 的adapter,并为ViewPager 设置adapter
设置ViewPager 的切换效果(可选)
设置ViewPager 的事件监听(可选)
三. 自定义Adapter
ViewPager 的adapter 类型为PagerAdapter,它是一个抽象类,support 包提供了两种实现,一个FragmentPagerAdapter,另一个是FragmentStatePagerAdapter,这两个Adapter 是ViewPager 和Fragment 结合使用时所用,稍后介绍。
现在介绍一下,继承PagerAdapter 的自定义Adapter。继承PagerAdapter 必须要重写两个方法,即 getCount、isViewFromObject,同时为了保证功能的实现,还应该重写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) {
// 创建 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; // 默认返回,与刷新有关
}
@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 在初始化时也只初始化开始的2个 View,即调用2次instantiateItem 方法。而接下来每当 ViewPager 滑动时,PagerAdapter 都会调用 destroyItem 方法将距离该页2个步幅以上的那个 View 销毁,以此保证 PagerAdapter 最多只管辖3个 View,且当前 View 是3个中的中间一个,如果当前 View 缺少两边的 View,那么就 instantiateItem,如里有超过2个步幅的就 destroyItem。
在实现Adapter 时,有几点需要注意,可能会出坑。
第一个就是destroyItem 方法,如果使用 container.removeViewAt(index) 方法,可能会导致出现 越界 crash,所以应该使用removeView(object) 方法。
第二个是getItemPosition 方法,默认的返回值是 POSITION_UNCHANGED,还有另外一种值是 POSITION_NONE,这两个值的区别在于刷新数据时,
Viewpager 的刷新过程是这样的,在每次调用 PagerAdapter 的 notifyDataSetChanged() 方法时,都会激活 getItemPosition(Object object) 方法,该方法会遍历 ViewPager 的所有 Item(由缓存的 Item 数量决定,默认为当前页和其左右加起来共3页,这个可以自行设定,但是至少会缓存2页),为每个 Item 返回一个状态值(POSITION_NONE/POSITION_UNCHANGED),
如果是 POSITION_NONE,那么该 Item 会被 destroyItem(ViewGroup container, int position, Object object) 方法 remove 掉,然后重新加载,
如果是 POSITION_UNCHANGED,就不会重新加载,默认是 POSITION_UNCHANGED,所以如果不重写 getItemPosition(Object object),修改返回值,就无法看到 notifyDataSetChanged() 的刷新效果。
注:当ViewPager 的Item 是Fragment 时,系统提供了FragmentPagerAdapter 和 FragmentStatePagerAdapter,但是动态修改时可能还是会有一些问题,需要进行一些特殊处理。
四. 切换效果
有时候我们可能觉得默认的切换太平淡,想来点花哨的效果,Android 提供了一个接口 PageTransformer,实现这个接口,并为ViewPager 设置Transformer 即可实现切换特效。
PageTransformer 接口只有一个方法,即 public void transformPage(View page, float position)
,它有两个参数,
page
表示 ViewPager 中的一页,
position
表示page
当前的位置,[-1, 0)表示屏幕左边的page
(部分可见),[0, 0]表示屏幕上的page
(完全可见),(0, 1]表示屏幕右边的page
(部分可见),具体看下图:
当page
向左边滑动时,position
从0向-1变化,当position==-1
时完全不可见;当page
向右滑动时,position
从0向1变化,当position==1
时完全不可见。这样,我们根据position 的变化来对View 进行相关的操作,比如旋转、缩放、透明度等,就可以实现不同的切换效果。如下例,
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; // 是否第一遍执行
private int count; // 执行次数, 与setOffscreenPageLimit 有关
GalleryTransformer() {
firstPage = true;
}
// 第一页和第一遍的逻辑,用于处理一页显示多个Item 时,初始状态第二页的状态
@Override
public void transformPage(View page, float position) {
if (count++ == 3) {
firstTime = false; // 第一遍执行完
}
float offset = (float)(width - page.getWidth()) / 2; // 因为一页显示多个Item
if (page.getWidth() != 0) {
position = position - offset / page.getWidth(); // 所以要对position 进行相关偏移,否则将显示异常
}
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); // 旋转
} else if (firstTime || position > 1.0){ // (1, +∞)
page.setTranslationX(-MAX_TRANSLATE);
page.setScaleX(MIN_SCALE);
page.setScaleY(MIN_SCALE);
page.setRotationY(MAX_ROTATION);
}
}
}
形成的效果如下: