ViewPager 的使用

一. 简介

ViewPager 是support v4 包提供的控件,可以实现一组View 切换显示的效果。

二. 使用

使用ViewPager 也比较简单,主要有以下几步:

  1. 获取ViewPager 实例,包括从XML 或直接new

  2. 自定义ViewPager 的adapter,并为ViewPager 设置adapter

  3. 设置ViewPager 的切换效果(可选)

  4. 设置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(); 
        } 
    }

效果:


pager.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(部分可见),具体看下图:

image.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); 
            } 
        } 
    }

形成的效果如下:


share3.gif
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,657评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,662评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,143评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,732评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,837评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,036评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,126评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,868评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,315评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,641评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,773评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,859评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,584评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,676评论 2 351

推荐阅读更多精彩内容