Android学习感悟之ViewPager

本篇包含:实现一屏多页、自定义PageTransformer、OnPageChangeListener方法参数的解释和应用

使用

ViewPager在开发过程中的使用就不用多说了,那肯定是很多场景都会用到,其实使用它很简单,就两部就能使用了:

  • 拿到一个ViewPager实例
  • 设置一个适配器

其中适配器的实现方式有三种:

方式 场景
PagerAdapter 主要是用在比较单一的View时比较方便
FragmentPagerAdapter 主要是用在内容为Fragment时,当这些Fragment比较少时使用
FragmentStatePagerAdapter 主要是用在内容为Fragment时,当Fragment比较多时使用,可保存Fragment的状态

具体的使用方式这里就不介绍了,都比较简单。

注:对于Fragment的创建,尽量做到需要创建时再创建

一屏多页

这里可以先看看实现的效果

pager_1.png

难看是难看了点,但是效果是有了,就好比麻雀虽丑,五脏还是俱全的。其实这个知识点,挺简单,并没有想象中那么难,要做到的其实也就几步:

  • 给ViewPager的父容器添加 android:clipChildren="false" 和
    android:layerType="software"属性
  • 为ViewPager添加layout_marginLeft和layout_marginRight,并设一个值
  • 再拿到实例,设置Adapter即可

这样基本就能出效果,就能看到下一页的内容能显示在屏幕中了,这两点的含义也比较简单,clipChildren就是是否裁剪子View,默认为true的,layerType这个是用来关闭硬件加速的;第二点的margin是让一页的宽度不要占满整个屏幕,这样才能让下一页或上一页的显示出来。

这时候会发现,每一页都是挨在一起的,我们怎么把他们分开呢,其实在不知道的情况下,我们可以给把自己内容加上一个padding,那么就分开了,但是Android给我们提供了方法pager.setPageMargin,这个就是设置页与页之间的间距的。

这时候细心的同学又会发现一个问题,下一页为什么总是在滚动完之后才被显示出来,会闪一下,这个效果可不友好,这时候其实是由于ViewPager的缓存机制在作怪,我们可以通过pager.setOffscreenPageLimit来解决这个问题,这个值默认是1,所以它每次到下一页都会闪一下,但是不代表它只缓存了一页,其实是三页,这里先不解释为什么,那么我们现在只需要把这个值设置为2,就能让下一页的出来,而且保证不闪一下,可以先这么理解,值为1时,只管当前页一定先加载出来的,值为2时,保证当前页和下一页一定被加载出来了。

好这一部分的实现就到这里。

自定义PageTransformer

这个东西,如果你看过他是什么的话,应该就不怕了

public interface PageTransformer {
    void transformPage(View page, float position);
}

你看,是不是纸老虎,就一个方法,两个参数而已,注释我删了=_=|。

其中page和position是配对的,page是position所对应的那个view,其实最关键就是这个position了,它其实也很简单,你可以先这么理解,当所有页都被加载出来之后,这些页面都会对应着一个position。

当静止不滑动的时候,当前的position就是0,它右边的就是1,2,3...它左边的就是-1,-2,-3...所以我们想操作某一页都可以以当前页为基准,那样就所有页面都能操作了。

当滑动时,position就是变化的了,其中从右往左滑的时候,是所有position都变小,即从position到position-1,反之亦然;Android给我们也提供过一些简单的切换效果,我就假设你知道了,这里呢,其实我还可以来个跟简单点的,就只是透明度的变化,左右两边的我都默认为0.3,中间的是1,所以透明度切换的过程就是从1到0.3或者0.3到1。而postion是从postion到postion-1,这里我们可以看到,这是一个分段函数,都是一元一次方程,分界线是postion为0的点,只管[-1,1]的区间的画,大概是这个样子:

pager_2.jpg

求不吐槽照片,电脑画不太熟,太慢=_=|

这个我们知道position就是x,alpha值就是y,在小于0的部分函数关系就是:
y=0.7(1+x)+0.3
大于0的部分函数关系是:
y=0.7(1-postion)+0.3
0的部分归到哪边都一样,所以我们就能写这一部分的代码了

public class AlphaTransformer implements ViewPager.PageTransformer {
    @Override
    public void transformPage(View page, float position) {
        if (position <= 0) {
            float alpha = 0.7f * (1 + position) + 0.3f;
            page.setAlpha(alpha);
        } else {
            float alpha = 0.7f * (1 - position) + 0.3f;
            page.setAlpha(alpha);
        }
    }
}

然后再调用pager.setPageTransformer(true,new AlphaTransformer());
这样就实现了。

pager_3.png

有没有看到左右两边不那么红了,其实就是因为透明度被改变了,这里就没有弄动态图了,效果已经出来了。有的朋友说等等,间距变大了,对,我这里把间距调大了。。感觉挨在一起不好看,虽然现在也不大好看=_=|。

再来看个复杂点的效果

pager_4.gif

模拟器录的,有点快,但是多看两遍还是可以看出其规律的。

这里我把ViewPager的布局和加载的Fragment换了下,之前的那个就不大好看了,这里也把页面循环滑动去掉了。

好我们来分析一下,它默认是要显示出后边几页,并像卡片一样叠在下边,这里可以看到是总共三张,这里边用到了透明度变化,x方向偏移,y方向偏移,以及x和y方向的缩放,这个其实是项目中的一个卡片式布局的备选方案,结果没用上,用了透明度那个,当然还有背景颜色的变化下文介绍,还有一个页面跳转的动画比较炫之后会介绍。

我们来通过position分析一下,再不滑动的情况下
(-INF,-1)的时候,即左边的第二页及其左边的,其实是看不到的,所以我们只需要将其透明度设置为0,甚至不管都可以。
[-1,0],即左边第一页,若是滑动的话是从当前页往左滑的时候,透明度从1到0,宽高同比缩小。
(0,3],即右边的1,2,3页,首先x都偏移到了正中间,且宽高都有缩放,y向上偏移还与缩放比有关,这里第三页,滑动时进来的,从0到1的透明度变化,小于3的透明度都是1,都显示出来了的。
(3,INF),即右边4及其以后的页,看不到,直接不管或者透明度为0即可。

其实这个到最后就是使用的和动画有关的东西,变化的情况都告诉了,就是一个函数关系而已,这里就不去仔细分析怎么得出来的了,代码是最好的老师,所以这里就直接上代码。还有一点就是画图太不熟悉。

public class CardTransformer implements ViewPager.PageTransformer {
    private static final float MIN_SCALE = 0.9f;

    private static final int PAGE_OFFSET = 40;

    @SuppressLint("NewApi")
    @Override
    public void transformPage(View view, float position) {
        //position从左往右滑时,根据缓存了多少页,每一页的变化都是currPos到currPos+1;反之则为currPos到currPos-1;所以根据pos的大小可以控制当前页的位置,
        //例如下边改变translationX,让后几页一样显示到当前页,当然这里让它能被看到,
        //再调整ViewPager的宽高来控制协作控制其位置
        int pageWidth = view.getWidth();
        int pageHeight = view.getHeight();
        float scaleFactor;
        if (position < -1) { // [-Infinity,-1)
            view.setAlpha(0);
        } else if (position <= 0) {
            view.setAlpha(1 + position);
            scaleFactor = MIN_SCALE + (1 - MIN_SCALE)
                    * (1 - Math.abs(position));
            view.setScaleX(scaleFactor);
            view.setScaleY(scaleFactor);
        } else {
            view.setAlpha(1 + position);
            if (position <= 3) {
                if (position <= 2) {
                    view.setAlpha(1);
                } else {
                    view.setAlpha(1 - (position - 2));
                }
                view.setTranslationX(pageWidth * -position);
            } else {
                view.setAlpha(0);
            }
            scaleFactor = MIN_SCALE + (1 - MIN_SCALE)
                    * (1 - Math.abs(position));
            view.setScaleX(scaleFactor);
            view.setScaleY(scaleFactor);
            view.setTranslationY(-position * PAGE_OFFSET - (pageHeight - pageHeight * scaleFactor) / 2);
        }
    }
}

这样就可以了。

注:其实举了这两个例子,为的就是想要说明一点postion可以代表ViewPager的每一页,可以自己去操控他,唯一需要配合使用的就是一屏多页和mOffscreenPageLimit参数,来控制在显示的时候是否能被加载并且看见。

OnPageChangeListener方法的参数

使用过ViewPager的朋友应该都知道,想要监听ViewPager的滚动情况,我们都可以使用设置这个监听去实现,而实现它需要实现他的三个方法:

  • onPageSelected(int position)
  • onPageScrollStateChanged(int state)
  • onPageScrolled(int position, float positionOffset, int positionOffsetPixels)

第一个用的应该是最多的,就是某一页被确定选中的时候,被回调,注意我的措辞,被确定选中,而不是直接说选中,即意味如果滑了一大半,且方向是滑了一大半的方向,那么虽然还在滑动,但是这个方法就会被回调。大家可以自己验证一下,我自己通过打log发现的,去看源码之后发现确实是这样这样,在手指拿起的时候就会去分发这个被选中的方法。

第二个是,滑动状态改变的时候回调,有三种状态:

  • SCROLL_STATE_IDLE
  • SCROLL_STATE_DRAGGING
  • SCROLL_STATE_SETTLING

依次表示,滑动结束,手指拖着滑动中,手指离开后滑动,这就包含了滑动的所有阶段。

第三个,也是在监听滚动变化情况的时候用的最多的一个方法,这里有三个参数,

  • position:表示变化趋势,滑动完成后如果变大了,那么就是往左滑,反之亦然;在滑动过程中,如果往左滑,position是不变的,直到滑动结束变为1;往右滑,postion是比滑动之前的当前页小1直到结束。
  • positionOffset:这个就厉害了,它从小到大就是往左滑(0,1),反之往右滑(1,0),注意到没有0和1的区间都是开区间,这是因为不管是0到1还是1到0都是无限趋近于1结束或开始,而0是在滑动结束之后都会变成0。
  • positionOffsetPixels:这个就和positionOffset变化情况一致,只不过它不再是0到1的变化,而是0到Pager宽度的变化情况了。

到这里这一部分的概念和含义都介绍完了,下边我再介绍一下onPageScrolled在实际运用中的技巧,其中主要是postion和positionOffset的应用。

在这个方法中,position和position+1分别代表左边的View和右边的View,这一点很神奇,捋一捋之后发现果然是这样,可参照前边position的介绍;而positionOffset是(0,1)的变化,这样就方便我们做变化效果了。这里举个例子,因为要实现一屏多页,那么如果想要很好的实现背景颜色的渐变,那么目前最好的方法我认为就是通过在这个方法中处理。

这里先上个效果,因为代码实在太简单:

pager_5.gif

由于是模拟器录制的,然后mp4转gif总感觉不大理想,大家将就看看吧,就是有个渐变的效果,直接上关键代码:

@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
    int color = mItems.get(position % mItems.size());
    if (positionOffset > 0) {
        color = Utils.getColor(positionOffset, mItems.get(position % mItems.size()),
                mItems.get((position+1) % mItems.size()));
    }
    mRoot.setBackgroundColor(color);
}

/**
 * 根据fraction获取颜色,实现渐变
 */
public static int getColor(float fraction, Object startColor, Object endColor) {
    int startInt = (Integer) startColor;
    int startA = (startInt >> 24) & 0xff;
    int startR = (startInt >> 16) & 0xff;
    int startG = (startInt >> 8) & 0xff;
    int startB = startInt & 0xff;
    int endInt = (Integer) endColor;
    int endA = (endInt >> 24) & 0xff;
    int endR = (endInt >> 16) & 0xff;
    int endG = (endInt >> 8) & 0xff;
    int endB = endInt & 0xff;
    return (int) ((startA + (int) (fraction * (endA - startA))) << 24) |
            (int) ((startR + (int) (fraction * (endR - startR))) << 16) |
            (int) ((startG + (int) (fraction * (endG - startG))) << 8) |
            (int) ((startB + (int) (fraction * (endB - startB))));
}

第一个方法没啥好解释的,就是保证在切换的时候背景颜色从当前页面的背景颜色变化到下一页的颜色,有可能是上一页也有可能是下一页,所以这里的postion和position+1就体现出高级的用处来了,第二个方法,不是我自己写的,我是从Android的颜色变换的估值器ArgbEvaluator中拿过来的。就是一个根据变化情况,去一个开始颜色到结束颜色的其中一个比例值(ARGB),其实去掉位运算之后就是一个直线的一元一次方程而已。

到这里,这篇想说的内容都写完了,若有不对的地方,请指正,最后奉上源码,其中有的地方注释掉了,保留的这版代码是最后效果的代码,但是我相信看完这一篇之后你一定知道怎么还原每个部分的代码。

源码

ViewPagerDemo

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

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,471评论 25 707
  • 很多人对于ViewPager PageTransformer一直都是停留在用的阶段,都是把别的写好的能够实现一些效...
    chuwe1阅读 8,088评论 1 21
  • 做为我viewpager的第一篇,我觉得说一说这个页面变换还是很对头的,这个页面变换可是我们精通viewpager...
    前行的乌龟阅读 2,679评论 0 1
  • 问答题47 /72 常见浏览器兼容性问题与解决方案? 参考答案 (1)浏览器兼容问题一:不同浏览器的标签默认的外补...
    _Yfling阅读 13,611评论 1 92
  • 组件属性 props 组件的属性, 主要作用是让使用该组件的父组件可以传入参数来配置该组件。它是外部传进来的配置参...
    heheheyuanqing阅读 214评论 0 0