1、简介

ViewPager 是 Android 中的类,这个类可以让用户实现左右切换当前的 view,许多的 APP 在安装完后进入的都是一个APP引导图,是一些图文并茂的关于 APP 的功能的介绍或推荐,用户需要滑动切换浏览完所有的view,就可以使用 APP,这里的功能实现毫无疑问用到了 ViewPager。

ViewPager类继承自 ViewGroup类,它是一个容器类,可以在其中添加需要的 view。

ViewPager类是容器类,与 ListView 有相同之处,都需要使用适配器来管理数据。ViewPager 需要的是一个 PagerAdapter 的适配器。

PagerAdapter用来完成页面和数据的绑定,这个 PagerAdapter 是一个基类适配器,使用简单,上文所述的APP引导图就是用它来实现。它的子类有 FragmentPagerAdapter 和 FragmentStatePagerAdapter,这两个子类适配器用于和 Fragment 一起使用,在 Android 应用中出现的相当频繁。

2、PagerAdapter

PagerAdapter 是一个抽象类,主要是 ViewPager 的适配器,要想继承 或直接使用这个类,需要覆盖四个方法。

在每次创建 ViewPager 或滑动过程中,以下四个方法都会被调用,而 instantiateItem() 和 destroyItem() 方法要自己添加。

/** 获取要滑动的 page 的数量,在这里我们以滑动的广告栏为例,
   那么这里就应该是展示的广告图片的 ImageView 数量  **/

  public abstract int getCount();
/** 当要显示的图片可以进行缓存的时候,会调用这个方法进行显示图片的初始化,
   我们将要显示的 ImageView 加入到 ViewGroup 中,然后作为返回值返回即可  **/

  public Object instantiateItem(View container, int position)
/** PagerAdapter 只缓存三张要显示的图片,如果滑动的图片超出了缓存的范围,
   就会调用这个方法,将图片销毁  **/

  public void destroyItem(ViewGroup container, int position, Object object)

最后一个方法只说明方法有些不明其意,我们来结合源码来说说它的功能。

public abstract boolean isViewFromObject(View view, Object object)

在清楚这个方法的作用前,我们首先要了解 view 和 object 这两个值分别代表了什么。

在 ViewPager 的源码中我们搜索 isViewFromObject() 这个方法,我们看到在 infoForChild() 这个方法中被调用了,它返回的是 ItemInfo 这个变量,在源码中观察这个类型的结构我们可以知道它记录了整个 Page 的信息,其中的 Object 对象,它的值就是 instantiateItem() 所返回的,比如说初始化好的 view。而在源码中我们看到往 isViewFromObject() 方法中的传递的就是 ItemInfo 的 object,所以这个 object 就是某一个 page。

ItemInfo infoForChild(View child) {
        for (int i = 0; i < mItems.size(); i++) {
            ItemInfo ii = mItems.get(i);
            if (mAdapter.isViewFromObject(child, ii.object)) {
                return ii;
            }
        }
        return null;
    }

ViewPager里面用了一个 ArrayList集合叫做 mItems 来存储每个 page 的信息(ItemInfo),当界面要展示或者发生变化时,需要依据 page 的当前信息来调整。通过当前聚焦的位置确认到当前的 page 对象,这个对象的值也就是方法中另一个变量 view,我们只能通过view来查找,遍历 mItems 通过比较 view 和 object 来找到对应的 ItemInfo,所以这个方法通常都是返回 view == object。

最后 infoForChild() 返回找到的 ItemInfo 对象,去完成下面的处理。

3、实例

上面我们讲解了可用于 ViewPager 的适配器 PagerAdapter,现在我们就用它来实现 ViewPager 的滑动图片的效果。

public class MainActivity extends AppCompatActivity {

    private ViewPager mViewPager;

    private int[] mImgIds = new int[] {
            R.drawable.pic1, R.drawable.pic2, R.drawable.pic3, R.drawable.pic4
    };

    private List<ImageView> mImages = new ArrayList<ImageView>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mViewPager = (ViewPager) findViewById(.id_viewpager);
        mViewPager.setAdapter(new PagerAdapter() {

            @Override
            public Object instantiateItem(ViewGroup container, int position) {

                ImageView imageView = new ImageView(MainActivity.this);
                imageView.setImageResource(mImgIds[position]);
                imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
                container.addView(imageView);
                mImages.add(imageView);

                return imageView;
            }

            @Override
            public void destroyItem(ViewGroup container, int position, Object object) {
                container.removeView(mImages.get(position));
            }

            @Override
            public int getCount() {
                return mImgIds.length;
            }

            @Override
            public boolean isViewFromObject(View view, Object object) {
                return view == object;
            }
        });
    }
}

在这个例子中,布局文件里就是很简单的一个 ViewPager,资源就是准备好的四张图。

上面介绍的时候说过当创建 ViewPager 的时候就会调用这四个方法,getCount() 很简单,因为是 ViewPager 的页数,而我们这个例子是个简单的图片浏览,所以页数就是图片的数目。

instantiateItem() 是初始化的方法,我们在这个方法里把确定好图片资源,各种属性的 ImageView 放入 ViewGroup 中,返回 ImageView,这就设置好了当前 position 的 page。

destroyItem() 就是将对应 position 上的 ImageView 移除即可,isViewFromObject() 返回 view 和 object 的比较值。

android viewpager 类文件 android viewpager详解_android

这个是不是跟我们平常使用的APP的引导很像。

4、切换卡顿的简单优化

在我们介绍 PageAdapter 的四个方法时,说过 PageAdapter 最多只缓存三页,也就是说只会保存当前页和左右两页,每次切换的时候都会将左右中没有缓存的 page 加载好,也就是在 destoryItem() 方法中每次销毁一个 page,滑动时又在 instantiateItem() 方法中创建一个 page。

如果按照我们上面的写法,每次在调用 instantiateItem() 方法时都会创建一个新的 ImageView,但其实我们的图片资源是固定的,每次重新初始化页面的时候也创建一个 ImageView,这样重复创建使得程序运行缓慢。

@Override
public Object instantiateItem(ViewGroup container, int position) {

    ImageView imageView = new ImageView(MainActivity.this);
    imageView.setImageResource(mImgIds[position]);
    imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
    mImages.add(imageView);

    Log.i("TAG", "position=" + position + "  mImages.size=" + mImages.size());
    container.addView(mImages.get(position));
    return mImages.get(position);
}

android viewpager 类文件 android viewpager详解_Android_02

可以看到每次加载新页的时候 ImageView 的集合都会增大,也就是创建了新的 ImageView,但这些 ImageView 有相同的在集合里,所以在 instantiateItem() 方法创建新的 ImageView 对象前先进行判断。

@Override
public Object instantiateItem(ViewGroup container, int position) {

    if (mImages.size() <= position || mImages.get(position) == null) {
        ImageView imageView = new ImageView(MainActivity.this);
        imageView.setImageResource(mImgIds[position]);
        imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
        mImages.add(imageView);
    }

    Log.i("TAG", "position=" + position + "  mImages.size=" + mImages.size());
    container.addView(mImages.get(position));
    return mImages.get(position);
}

android viewpager 类文件 android viewpager详解_Android_03

5、切换动画

接下来介绍下 ViewPager 的切换动画,在 Android 3.0 后要为 ViewPager 添加切换动画,只需要使用 setPageTransformer() 这个方法即可,因为用到了属性动画,所以 Android 3.0 以下是不能用的。

setPageTransformer(boolean reverseDrawingOrder, PageTransformer transformer)

前面的布尔值代表 PageTransformer 画 view 时是正序还是倒序,true 则是倒序,false 则是正序,一般设为 true 即可。

transformer 就是设置动画的 PageTransformer 类,在网上有许多的例子,谷歌也给了我们几种动画效果。

PageTransformer 是一个接口,里面只有一个需要我们实现的方法:

void transformPage(View page, float position);

这个方法的作用是应用属性动画到一个指定的页面。page 就是缓存中的页面,每次滑动其实就是在移动缓存中的 page,每一个 page 都会去做 PageTransformer 给的动画。

position 表示相对于当前页正中的位置,0表示在正中的这个页面,1表示右边一个完整的页面,-1表示左边一个完整的页面。所以如果 position 为0说明当前页面完整的出现在屏幕上。所以要想自定义动画效果,最应该关注的就是各个 page 的 position 的变化。

这里就贴一个谷歌给的案例,案例网址

public class DepthPageTransformer implements ViewPager.PageTransformer {
    private static final float MIN_SCALE = 0.75f;

    public void transformPage(View view, float position) {
        int pageWidth = view.getWidth();

        if (position < -1) { // [-Infinity,-1)
            // This page is way off-screen to the left.
            view.setAlpha(0);

        } else if (position <= 0) { // [-1,0]
            // Use the default slide transition when moving to the left page
            view.setAlpha(1);
            view.setTranslationX(0);
            view.setScaleX(1);
            view.setScaleY(1);

        } else if (position <= 1) { // (0,1]
            // Fade the page out.
            view.setAlpha(1 - position);

            // Counteract the default slide transition
            view.setTranslationX(pageWidth * -position);

            // Scale the page down (between MIN_SCALE and 1)
            float scaleFactor = MIN_SCALE
                    + (1 - MIN_SCALE) * (1 - Math.abs(position));
            view.setScaleX(scaleFactor);
            view.setScaleY(scaleFactor);

        } else { // (1,+Infinity]
            // This page is way off-screen to the right.
            view.setAlpha(0);
        }
    }
}

可以看到只是用了很简单的 Android 动画的知识。

mViewPager.setPageTransformer(true, new DepthPageTransformer());

把这个类应用上我们的 ViewPager 的切换动画就实现了。

android viewpager 类文件 android viewpager详解_Android_04

ViewPager 的知识还有很多,并且ViewPager 更多的是和 Fragment 一起使用,在这篇博客中就不说明了。

结束语:本文仅用来学习记录,参考查阅。