ViewPager 超详解:玩出十八般花样

受权声明:本文已受权微信公众号:鸿洋(hongyangAndroid)原创首发。其余转载请再咨询做者许可!android

虽然没有 RecyclerView 这种列表控件经常使用些,可是在开发中你ViewPager 确定也是不可或缺的控件,引导页、轮播图、卡片画廊等效果老是缺乏不了 ViewPager 的身影。
相信每一位朋友对 ViewPager 的基础使用都已经很熟练了,今天在这里就从简至繁将 ViewPager 的每一个用法都梳理一边。git

主要包括如下内容:github

  • ViewPager 基本使用(简介、适配器)
  • ViewPager + TabLayout + Fragment 的使用
  • ViewPager 轮播图的使用(指示器、标题、自动轮播、首尾循环)
  • ViewPager 的切换效果(PageTransformer)
  • ViewPager 切换效果进阶

ViewPager 的基础使用

对于 ViewPager ,官方的描述大概是这样的:页面容许左右滑动的布局管理器,而不一样页面带有不一样的数据。缓存

这里简单归结以下:微信

  • ViewPager 是 v4 包中的一个类。
  • ViewPager 类直接继承了 ViewGroup 类,它是一个容器类,能够在其中添加其余的 view 。
  • 相似于 ListView,也有本身的适配器,用来填充数据页面。

关于 ViewPager 在布局文件中的声明,这里就再也不说了。实际上是没什么好说的,并无什么能够直接声明的特殊属性,因为继承于 ViewGroup 有的也都是些 ViewGroup 的属性。ide

这里值得介绍的也就是几个能够动态设置方法了,经常使用的有如下几个:布局

  • setAdapter(PagerAdapter adapter) 设置适配器
  • setOffscreenPageLimit(int limit) 设置缓存的页面个数,默认是 1
  • setCurrentItem(int item) 跳转到特定的页面
  • setOnPageChangeListener(..) 设置页面滑动时的监听器(如今API中建议使用 addOnPageChangeListener(..)
  • setPageTransformer(..PageTransformer) 设置页面切换时的动画效果
  • setPageMargin(int marginPixels) 设置不一样页面之间的间隔
  • setPageMarginDrawable(..) 设置不一样页面间隔之间的装饰图也就是 divide ,要想显示设置的图片,须要同时设置 setPageMargin()

谨记上面这几个方法,玩转 ViewPager 其实都是围绕它们进行的,能不能玩出花样,就看你把它们运用的怎么样了。post

上面的方面大多一看说明就明白了,这里值得一提的就是 ViewPager 的适配器了。测试

PagerAdapter

PagerAdapter 是抽象的类,因此使用时只能使用它的子类,实现子类必需要实现如下四个方法:动画

  • getCount(); 是获取当前窗体界面数,也就是数据的个数。
  • isViewFromObject(View view, Object object); 这个方法用于判断是否由对象生成界面,官方建议直接返回 return view == object;
  • instantiateItem(View container, int position); 要显示的页面或须要缓存的页面,会调用这个方法进行布局的初始化。
  • destroyItem(ViewGroup container, int position, Object object); 若是页面不是当前显示的页面也不是要缓存的页面,会调用这个方法,将页面销毁。

相信你们对上面这些方法的实现并不陌生,这里就不详细介绍了。另外咱们知道官方给咱们提供的还有 PagerAdapter 的两个直接子类 FragmentPagerAdapter 和 FragmentStatePagerAdapter 。而咱们经常会在 ViewPager 和 Fragment 结合使用的时候来使用这两个适配器。具体的用法和它们之间的区别,咱们在下个章节讲。

ViewPager + TabLayout + Fragment 的结合使用

在引导页中咱们经常用 ViewPager 和 Fragment 结合使用,而像新闻分类的页面咱们会再加上一个 TabLayout 三者联动使用。而此时,咱们不会再使用 PagerAdapter 了,而是直接使用官方提供的专门用于与 Fragment 结合使用的 FragmentPagerAdapter。

FragmentPagerAdapter 它将每个页面表示为一个 Fragment,而且每个 Fragment 都将会保存到 FragmentManager 当中。并且,当用户没可能再次回到页面的时候,FragmentManager 才会将这个 Fragment 销毁。

使用 FragmentPagerAdapter 须要实现两个方法:

  • public Fragment getItem(int position) 返回的是对应的 Fragment 实例,通常咱们在使用时,会经过构造传入一个要显示的 Fragment 的集合,咱们只要在这里把对应的 Fragment 返回就好了。
  • public int getCount() 这个上面介绍过了返回的是页面的个数,咱们只要返回传入集合的长度就好了。

使用起来是很是简单的,FragmentStatePagerAdapter 的使用也和上面同样,那二者到底有什么区别呢?

区别以下:

  • FragmentPagerAdapter:对于再也不须要的 fragment,选择调用 onDetach() 方法,仅销毁视图,并不会销毁 fragment 实例。
  • FragmentStatePagerAdapter:会销毁再也不须要的 fragment,当当前事务提交之后,会完全的将 fragmeng 从当前 Activity 的FragmentManager 中移除,state 标明,销毁时,会将其 onSaveInstanceState(Bundle outState) 中的 bundle 信息保存下来,当用户切换回来,能够经过该 bundle 恢复生成新的 fragment,也就是说,你能够在 onSaveInstanceState(Bundle outState) 方法中保存一些数据,在 onCreate 中进行恢复建立。

由上总结:
使用 FragmentStatePagerAdapter 更省内存,可是销毁后新建也是须要时间的。通常状况下,若是你是制做主页面,就 三、4 个 Tab,那么能够选择使用 FragmentPagerAdapter,若是你是用于 ViewPager 展现数量特别多的条目时,那么建议使用 FragmentStatePagerAdapter。

那 Tablayout 如何和 Viewpager 联动呢?因为咱们这里主要是讲解 ViewPager 的,所谓 “术业有专攻” 因此关于 TabLayout 的使用咱们就再也不掺和了。
第一步,初始化 TabLayout 和 ViewPager 后只要经过调用 TabLayout 的 tabLayout.setupWithViewPager(viewPager) 方法就将二者绑定在一块儿了。
第二步,重写 PagerAdapter 的 public CharSequence getPageTitle(int position) 方法,而 TabLayout 也正是经过 setupWithViewPager() 方法底部会调用 PagerAdapter 中的getPageTitle() 方法来实现联动的。

ViewPager 轮播图的使用

关于此章本想给你们细细到来,才写上面两章都这么多篇幅了,咱们还有给下面两章重点讲的部分留点空间呢。若是非得想看还不嫌我啰嗦,那我有时间再把这段给不出来。

这章就这样结束,固然没有,虽然不负责任,可是也不能撩完妹子就闪人啊!这里仍是要基本原理给你们论道论道的。

Banner 元素组成图

从上图咱们能够知道,通常咱们使用 ViewPager 作 Banner 时主要有以上几个元素:

标题 & 指示器
咱们能够把标题和指示器直接写在咱们 Banner 的 item 的布局中,这样经过在 PageAdapter 的 instantiateItem() 方法初始化页面时,直接设置。可是通常咱们不会这样作(若是标题没有阴影的话,能够如上面说的那样),由于这样在页面滑动的时候,会显得特别生硬,尤为是指示器。
那该如何呢?通常咱们会在 ViewPager 所在的布局文件中,声明指示器和标题布局,以下:

<FrameLayout
     ...>

    <android.support.v4.view.ViewPager
        android:id="@+id/viewPager"
        ..."/>

    <LinearLayout
        android:layout_gravity="bottom"
        ...>
		<!--指示器布局,由于不知道 item 的个数,因此会动态的把指示器的View添加到这里-->
        <LinearLayout
            android:id="@+id/bannerIndicators"
            .../>
		<!--标题-->
        <TextView
            android:id="@+id/bannerTitle"
            .../>
    </LinearLayout>

</FrameLayout>
复制代码

那如何才能实现当页面滑动时,标题和指示器伴随改变呢?还记不记得,一开始介绍 ViewPager 时,它有一个能够设置监听页面改变的方法 addOnPageChangeListener(),在 OnPageChangeListener 监听器中有一个页面滑动结束时的回调方法 onPageSelected(int position) ,咱们只须要在这个方法中,来设置标题和指示器跟随变化就好了。

自动轮播
实现自动轮播的原理其实更简单,只要咱们每隔必定时间发送一个切换页面的事件就好了。实现这个功能有不少种方法,相信做为 Android 开发者,你最快想到的就是 Handler.sendEmptyMessageDelayed(int what, long delayMillis) 了吧。

Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            if (mAutoPlay) {
                //mViewPager.setCurrentItem(mViewPager.getCurrentItem() + 1);//无限轮播时
				mViewPager.setCurrentItem((mViewPager.getCurrentItem()+1) % mViewPagerItemCount)
                this.sendEmptyMessageDelayed(MSG_WHAT, delayMillis);
            }
        }
    };
复制代码

固然不要忘了在外部,初始化完成后调用一次mHandler.sendEmptyMessageDelayed(MSG_WHAT, delayMillis);

首尾循环无限轮播
固然,咱们在设置自动轮播时,已经作到了首尾循环无限轮播了呀。

其实这里所说的无限轮播是指:当咱们手动滑到最后一个页面时,依然能够向后伴随手指滑动,并跳转显示的是第一个页面;反之滑到首个页面也是同样。
旁边那位脑子灵光的大兄弟又说了,那还不简单,在页面监听器 OnPageChangeListener 里,经过 position 分辨滑动的是否是首页或者最后一页,而后经过 setCurrentItem() 设置一下不就好了,还那么麻烦。
大胸弟,你且消消气!这里并不使用这种方式是由于,这种方式的跳转是十分生硬的,同时是不能实现“伴随手指滑动”这个条件的。

那究竟如何如何才能实现呢?目前江湖流传的有一下两种方法:

  1. 使 adapter 的 getCount() 返回 Integer.MAX_VALUE,再在初始化时设置当前页面为几千页(如:ViewPager.setCurrentItem(1000*data.size)),其实就是障眼法,大爷心情好的话,向前滑动几千页也不是不可能的;
  2. 经过监听 viewpager 的滑动来设置页面。如当前有数据 123,则设置页面为 31231,当页面滑动到第一个 3 时,设置当前页面为第二个 3,那么左右均可以滑动,当其滑动到第二个 1 时同理。

关于第一种是目前流行最广的方法,若是你们想查看详细的说明能够参考下面这篇文章(网上随便找的,对可靠性不做担保啊):
ViewPager真正的无限轮播

关于第二种方式,严格意义上分析是会在滑动过程当中产生生硬的跳动的。不过有位江湖义士声称已经解决了这种不和谐状况的发生,附上文章地址(可靠性更不做担保啊):
打造真正的无限循环viewpager (不负责的我真的没有测试这个可靠性,你们闲的测试下,若是效果很差的话,告诉我,我赶快把这个连接删除~~)

自定义 ViewPager 的切换效果

原本最近封装一个了 ViewPager 十八般花样、样样都有的 PageTransformer 动效库,想着前面少啰嗦点,而后把这章做为重点来说的。没想到前面仍是啰里啰嗦这么多(恍然间,我好像找到本身一直撩妹不成功单身的缘由了~),好了,步入正题。

关于 ViewPager 的切换动画,官方提供了一个内部接口 ViewPager.PageTransformer 来供咱们实现自定义切换动效。这个接口里只提供了一个方法 public void transformPage(View view, float position),可是千万不要小看了这两个方法,这里面的道道有不少呢。

transformPage 方法两个参数,一个是 View ,这个好理解就是当前要设置动效的页面。这个页面并不仅仅是指当前显示的页面,即将滑出的页面、即将滑入的页面、已经隐藏的页面,也就是说这个 View 是指全部的页面。那如何分辨 View 究竟是指哪一个页面呢,这个须要根据第二个参数 position 来辨别。

你千万不要把 position 理解成了 ViewPager 页面的下标,必定要看仔细,这个 position 但是 float 类型,下标怎么多是浮点型呢!

从 doc 注释来看,当前选中的 item 的 position 永远是 0 ,被选中 item 的前一个为 -1,被选中 item 的后一个为 1。

其实这里文档的描述并非彻底正确的,先后 item position 为 -1 和 1 的前提是你没有给 ViewPager 设置 pageMargin。
若是你设置了 pageMargin,先后 item 的 position 须要分别加上(或减去,前减后加)一个偏移量(偏移量的计算方式为 pageMargin / pageWidth)。

在用户滑动界面的时候,position 是动态变化的,下面以左滑为例(以向左为正方向):

  • 选中 item 的 position:从 0 渐至 -1 - offset (pageMargin / pageWidth)
  • 前一个 item 的 position:从 -1 渐至 -1 - offset (pageMargin / pageWidth)
  • 前两个 item 的 position:从 -2 渐至 -2 - offset (pageMargin / pageWidth),再往前就以此类推
  • 后一个 item 的 position:从 1 + offset (pageMargin / pageWidth) 渐至 0,再日后就以此类推

每一次滑动,每一个 View 对应的 position 是一个在一个区间范围内动态渐变的过程,因此咱们能够将 position 的值应用于 setAlpha(), setTranslationX(), 或者 setScaleY() 等等方法,从而实现自定义的切换动画有一个渐变的效果。

这里给你们举一个视差切换动效的实现方式,咱们先来看一下效果:

其实实现起来很简单,就是滑动时给页面再设置一个页面横向滑动的动画,让页面实现滑动的速度慢于手指滑动的速度,这样就会有种视差的效果:

@Override
public void transformPage(View page, float position) {
    int width = page.getWidth();
	//咱们给不一样状态的页面设置不一样的效果
	//经过position的值来分辨页面所处于的状态
    if (position < -1) {//滑出的页面
        page.setScrollX((int) (width * 0.75 * -1));
    } else if (position <= 1) {//[-1,1]
        if (position < 0) {//[-1,0]
            page.setScrollX((int) (width * 0.75 * position));
        } else {//[0,1]
            page.setScrollX((int) (width * 0.75 * position));
        }
    } else {//即将滑入的页面
        page.setScrollX((int) (width * 0.75));
    }
}
复制代码

其实这里处于大于1或者小于-1的状态的页面都好理解。须要详细讲解的就是 [-1,1] 这个区间状态的页面。你们能够想象一下:

像左滑动

由上图能够看出,当滑动时,(若是没有偏移量)界面上最多出现两个 item,一个即将滑出即将隐藏的页面(postion变化为:从0渐到-1),一个滑入即将彻底显示的页面(postion变化为:从1渐到0)。

这样给每一个不一样状态的页面设置不一样的动效就达到咱们想要的目的了。回到上面例子中的代码,刚才那位大兄弟又说话了,你的代码明明 [0 -> -1] 和 [1 -> 0] 两个状态的 item 设置的是同样的动效啊。这里只是代码同样,动效其实是不同的,由于一个position 是大于 0 的,一个 position 是小于 0 的,滑动的方向天然是相反的。难道你非得让我写成 page.setScrollX((int) (width * -0.75 * -position)) 这样吗?

想实现更多炫酷的动效,能够查看为你们封装好的 PageTransformerHelp 库,GitHub地址:github.com/OCNYang/Pag…

ViewPager 切换效果进阶

其实上面已经把该讲的自定义的方面都讲的很清楚了;整个梳理下来,上面一开始给你们列举的着重强调的几个 ViewPager 的动态设置的方法就剩 setPageMargin(int marginPixels) 没有说了,那么这个方法又会给咱们带来什么样的神奇效果呢?

界面可以同时看到多个 item

那是如何实现上面这种效果呢,能够确定的是:两边两个 item 被缩小且透明度变低,是经过设置上面所说的自定义动效完成的。那如何在一个界面可以同时看到 3 个 item 呢?那么就经过下图的分析和解释告诉实现的原理:

卡片式轮播效果实现原理分析

看了上面的解释有没有一种恍然大悟,火烧眉毛想试试的冲动?若是经过上图仍然略有疑惑能够看看鸿洋大神的这篇文章,能够说是很全面了:
巧用ViewPager 打造不同的广告轮播切换效果

结尾

到此,ViewPager 的基本使用方式已经讲的差很少了。想查看更多 切换动画 的效果,能够到本文的源码地址进行查看。

源码地址github.com/OCNYang/Pag…

参考文章:
blog.csdn.net/lmj62356579…
blog.csdn.net/qq_30716173…

相关文章
相关标签/搜索