一. 简介
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();
}
}
效果:
![](https://img.haomeiwen.com/i6025289/a7127320b43778fb.gif)
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
(部分可见),具体看下图:
![](https://img.haomeiwen.com/i6025289/64c60adb598cbeb0.png)
当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);
}
}
}
形成的效果如下:
![](https://img.haomeiwen.com/i6025289/f272c9213be44dec.gif)
网友评论