ViewPager PageTransformer探索

很多人对于ViewPager PageTransformer一直都是停留在用的阶段,都是把别的写好的能够实现一些效果的PageTransformer直接拿过来给set上,可是如果有一天你的产品经理突然给你来了一个你从未见过的切换效果要你实现。于是各种百度、Google,无奈并没有什么卵用,根本搜不到这种效果。这时候怎么办?

当然是朝这里看过来了

废话不多说,我们先看一个效果图


这种切换效果相信大家都在很多地方见过,没错这就是Google的官方示例_DepthPageTransformer,其实现代码如下:

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

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

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

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

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

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

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

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

可以看出来,这里当position位于(-∞,-1)和(1,∞)这两个区间的的时候,给page的透明度设为0,在[-1,1]这个区间根据一些相关极限对page进行了一些缩放和平移的操作。然后只需要viewPager.setPageTransformer(new DepthPageTransformer());就能实现如上的效果了。关键就是这里计算的这些公式是怎么的出来的呢?这便是我们今天要讨论的重点了。

废话不多收,直接进入我们今天的主题

好了,先来看一下这两个参数的解释

public interface PageTransformer {
    /**
     * Apply a property transformation to the given page.
     * 对给定的page施加一个属性切换效果
     *
     * @param page Apply the transformation to this page
                   将转换效果引用到此page
     * @param position Position of page relative to the current front-and-center                 
     *                 position of the pager. 0 is front and center. 1 is one full             
     *                 page position to the right, and -1 is one page position to the left.
     *                 相对于当前处于中心显示的page的postion。
     *                 0是当前页面。1是右侧页面。
     *                 -1是左侧页面。
     */
    void transformPage(View page, float position);
}

虽然解释还算挺清楚的,不过好像并没有什么卵用啊。根本没说滑动的时候page是如何变化的,position又是如何变化的。对我自己要实现一个切换效果根本没什么帮助啊。那么怎么办?好吧,没办法我们打一下log,看一下是如何变化的:

  1. 布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v4.view.ViewPager
        android:id="@+id/viewPager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</LinearLayout>
  1. MainActivity
public class MainActivity extends AppCompatActivity {

    private List<View> views;

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

        initViews();

        final ViewPager viewPager = (ViewPager) findViewById(R.id.viewPager);
        // 为了更容易看出position的变化规律,我们让pageLimit由3改为5
        viewPager.setOffscreenPageLimit(5);

        viewPager.setAdapter(new PagerAdapter() {
            @Override
            public int getCount() {
                return views.size();
            }

            @Override
            public Object instantiateItem(ViewGroup container, int position) {
                container.addView(views.get(position));
                return views.get(position);
            }

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

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

        viewPager.setPageTransformer(false, new ViewPager.PageTransformer() {
            @Override
            public void transformPage(View page, float position) {
                Log.e("=====", position + "");
            }
        });
    }

    private void initViews() {
        views = new ArrayList<>();
        TextView left2 = new TextView(this);
        left2.setGravity(Gravity.CENTER);
        left2.setText("左2");
        left2.setTextSize(30);
        left2.setBackgroundColor(Color.parseColor("#33b5e5"));//浅蓝色
        views.add(left2);

        TextView left1 = new TextView(this);
        left1.setGravity(Gravity.CENTER);
        left1.setText("左1");
        left1.setTextSize(30);
        left1.setBackgroundColor(Color.parseColor("#99cc00"));//浅绿色
        views.add(left1);

        TextView middle = new TextView(this);
        middle.setGravity(Gravity.CENTER);
        middle.setText("中");
        middle.setTextSize(30);
        middle.setBackgroundColor(Color.parseColor("#cc0000"));//浅红色
        views.add(middle);

        TextView right1 = new TextView(this);
        right1.setGravity(Gravity.CENTER);
        right1.setText("右1");
        right1.setTextSize(30);
        right1.setBackgroundColor(Color.parseColor("#aa66cc"));//紫色
        views.add(right1);

        TextView right2 = new TextView(this);
        right2.setGravity(Gravity.CENTER);
        right2.setText("右2");
        right2.setTextSize(30);
        right2.setBackgroundColor(Color.parseColor("#ffbb33"));//浅橘色
        views.add(right2);
    }
}

接下来我们运行,效果如下:


这是很正常的一个ViewPager切换,我们看一下log





我一共滑了三次,截取了三个log,你能看出什么规律来吗?讲道理,这能看出来?

诶!有人说了每次最后都有5个整数,没错!这还真是一条规律。
为了让我们的方便找出规律,我对5个page做了一些处理

left2.setTag("left2");
left1.setTag("left1");
middle.setTag("middle");
right1.setTag("right1");
right2.setTag("right3");

对log的输出方式也做了一些处理

Log.e((String) page.getTag(), position + "");

在来看一下


好像还是看不出来什么,不过从现在打出的log中能发现,其实滑动的时候是每个page都在动,transformPage方法回调的page并非只是当前滑动的page而是所有滑动的page,postion也并非是当前滑动page的postion变化,而是每个page的position变化。这个时候怎么办?这里有个小技巧



这是就只会看到左2页面的position变化了,可以看出来是一个有-4 -> -3的变化过程,同样我们也能得到其他每个页面的变化规律来。

下面是我列举的一张log表格,给大家展示一下每次滑动,每个page所对应的postion变化,
这里我们以page为当前页面进行左右滑动。

左2 左1 中(当前页面) 右1 右2
右滑(左1页面变为中的过程) -1.9833333
-1.7740741
-1.5592593
-1.3277777
-1.1722223
-1.0592593
-1.0166667
-1.0018518
-1.0
-0.98333335
-0.7740741
-0.55925924
-0.32777777
-0.17222223
-0.05925926
-0.016666668
-0.0018518518
0.0
0.016666668
0.22592592
0.44074073
0.6722222
0.8277778
0.94074076
0.98333335
0.99814814
1.0
1.0666667
1.2907407
1.4388889
1.4944445
1.812963
1.9425926
1.9888889
1.9981482
2.0
2.0666666
2.2907407
2.4388888
2.4944444
2.812963
2.9425926
2.988889
2.9981482
3.0
右滑规律 -2 -> -1 -1 -> 0 0 -> 1 1 -> 2 2-> 3
左滑(右1页面变为中的过程) -2.2277777
-2.338889
-2.4074075
-2.6814816
-2.85
-2.9388888
-2.9777777
-2.9944444
-3.0
-1.2277777
-1.3388889
-1.4074074
-1.6814815
-1.85
-1.9388889
-1.9777777
-1.9944445
-2.0
-0.22777778
-0.33888888
-0.4074074
-0.6814815
-0.85
-0.9388889
-0.9777778
-0.99444443
-1.0
0.7722222
0.6611111
0.5925926
0.31851852
0.15
0.06111111
0.022222223
0.0055555557
0.0
1.7722223
1.6611111
1.5925926
1.3185185
1.15
1.0611111
1.0222223
1.0055555
1.0
左滑规律 -2 -> -3 -1 -> -2 0 -> -1 1 -> 0 2 -> 1

观察这个表格,是不就就可以得出一些结论了(无论滑动前后当前页面都为显示在屏幕正中间页面):

  • 滑动开始前每个页面的postion是一个整数,当前页面为0,左侧页面position值为0 - 相对于当前页面偏移页面数,右侧页面postion值 0 + 相对于当前页面偏移页面数
  • 滑动结束后,每个页面的postion依然是一个整数,数值依然符合上一条规则,不过当前页面已经改变。也可以说左滑全部 -1,右滑全部 +1,postion变为0的页面成为新的当前页面
  • 每个页面的postion右滑时都在增大,左滑时都在减小,滑动后结果为滑动前结果 ±1

有了这些结论,我们是不是就可以按部就班来实现一开始的那个效果了!
先来做一波分析,这个效果一个有三种变换:透明度缩放平移。其中平移可能有些人不太理解,我会在后面解释

先来看看透明度

我们希望当前页面(显示在正中间的页面)不透,两侧页面在变为当前页面的过程中,慢慢变为不透,当前页面变为两侧页面的过程中,慢慢变为一个最小透明度(比如0.3f)
通过上面的结论其实处于变得三个页面当前页面左1右1的变化范围只会在[-1,1]这个区间里而在这意外的页面其实并不能看见,那么是不是可以这样?

if (position < -1 || position > 1) {
    page.setAlpha(0f);
} else {
   // ...
}

接下来就是对变化区间的处理了(我们以右滑为例进行分析)
-1 -> 0 : 左1变为当前页面
对于左1的透明度是不是由 minAlpha -> 1 啊?
0 -> 1 : 当前页面变为右1
对于当前页面透明度是不是由 1 -> minAlpha 啊?
可能你还没有什么感觉,我给你一个提示:

             position                    position
      -1 ----------------> 0       0 ---------------> 1

               x1                           x2
minAlpha ----------------> 1       1 ---------------> minAlpha

是不是很熟悉?这个x是什么?
——这TM不就是初中数学等比运算嘛。这个x不就是我们要求的变化过程中的透明度值嘛。
好的!开始解题:

解:设所求变量“透明度”为x。
由题可得:
  -1 - position      position - 0        0 - position       position - 1
 --------------- = ----------------  , ---------------- =  ----------------
  minAlpha - x1          x1 - 1              1 - x2          x2 - minAlpha
解得:
x1 = 1 + position - minAlpha*position, x2 = 1 + minAlpha*position - position

之后在调用page.setAlpha(float alpha)传入对应的值 是不是就可以了?

viewPager.setPageTransformer(true, new ViewPager.PageTransformer() {

    float MIN_ALPHA = 0.1f;

    @Override
    public void transformPage(View page, float position) {
        Log.e((String) page.getTag(), position + "");

        if (position < -1 || position > 1) {
            page.setAlpha(0f);
        } else {
            if (-1 <= position && position < 0) {
                page.setAlpha(1 + position - MIN_ALPHA * position);
            } else if (0 < position && position <= 1) {
                page.setAlpha(1 - position + MIN_ALPHA * position);
            } else {
                page.setAlpha(1f);
            }
        }
    }
});

这个效果我就不单独截屏了,等到缩放效果实现后一并查看(不要纠结为什么跟DepthPageTransformer代码为何不一样,因为它的效果在(-1,0)区间本就是不透所以是1,在(1,0)区间的小最小透明度是0,你可以试试吧MIN_ALPHA换成0再看看是不是一样的)

再来看看缩放的实现

和上面一同,可以算出来最终的x = 1 - position + minScale * position,同样也不要纠结为何不一样,你可以尝试把DepthPageTransformer在这个(0,1)这个区间的缩放公式化简出来再看看。

再来看看现在的效果图



是不是既有透明度的变化,也有缩放的变化了?

最后就是平移的变化了(这个是需要解释一番的)

首先来看看一开始那张图:右侧页面的进出都是以屏幕为中心只有缩放,何来平移啊?
再来看看我们现在的效果:我们已经实现了透明度和缩放的变化,为何与一开始的效果并不一样呢?
以下纯属个人理解,如有大神觉得不妥,欢迎指出:以右滑为例,右侧页面在这个过程中,本身就有一个往右平移的过程,如果我们手动给这个页面一个向左的平移来抵消这个向右的平移,那么是不是就只有缩放效果了呢?

原本position的变化:0 -> 1
原本translationX的变化:0 -> pageWidth
既然我们要给他一个想反方向的X轴平移,那么我们需要的translationX的变化是否就是:0 -> - pageWidth?

      position
0 ---------------> 1                  0 - position        position - 1
                              ===>  ---------------- = ------------------
         x                                0 - x          x - (-pageWith)
0 ---------------> -pageWith

==> x = - pageWidth * position

我们发现,跟DepthPageTransformer果然是一样的,现在修改一下代码:

if (position < -1 || position > 1) {
    page.setAlpha(0f);
} else {
    if (-1 <= position && position < 0) {
        page.setAlpha(1 + position - MIN_ALPHA * position);

        page.setScaleX(1);
        page.setScaleY(1);

        page.setTranslationX(0);
    } else if (0 < position && position <= 1) {
        page.setAlpha(1 - position + MIN_ALPHA * position);

        float scaleFactor = 1 - position + MIN_SCALE * position;
        page.setScaleX(scaleFactor);
        page.setScaleY(scaleFactor);

        page.setTranslationX(-page.getWidth() * position);
    } else {
        page.setAlpha(1f);
    }
}

现在的效果,就和开始的是一模一样了。

相信大家看完以后对于position是如果变化的都能了解了,也都知道如何自己去实现出各种效果了。

最后奉上一个翻转切换的效果图


4.gif

CSDN
Github

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

推荐阅读更多精彩内容