天天看點

ViewPager PageTransformer探索

很多人對于ViewPager PageTransformer一直都是停留在用的階段,都是把别的寫好的能夠實作一些效果的PageTransformer直接拿過來給set上,可是如果有一天你的産品經理突然給你來了一個你從未見過的切換效果要你實作。于是各種百度、Google,無奈并沒有什麼卵用,根本搜不到這種效果。這時候怎麼辦?

當然是朝這裡看過來了

廢話不多說,我們先看一個效果圖

ViewPager PageTransformer探索

這種切換效果相信大家都在很多地方見過,沒錯這就是Google的官方示例_DepthPageTransformer,其實作代碼如下:

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

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

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

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

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

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

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

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

可以看出來,這裡當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.  is front and center.  is one full             
     *                 page position to the right, and - is one page position to the left.
     *                 相對于目前處于中心顯示的page的postion。
     *                 是目前頁面。是右側頁面。
     *                 -是左側頁面。
     */
    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();

        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();
        left2.setBackgroundColor(Color.parseColor("#33b5e5"));//淺藍色
        views.add(left2);

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

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

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

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

接下來我們運作,效果如下:

ViewPager PageTransformer探索

這是很正常的一個ViewPager切換,我們看一下log

ViewPager PageTransformer探索
ViewPager PageTransformer探索
ViewPager PageTransformer探索

我一共滑了三次,截取了三個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 + "");
           

在來看一下

ViewPager PageTransformer探索

好像還是看不出來什麼,不過從現在打出的log中能發現,其實滑動的時候是每個page都在動,transformPage方法回調的page并非隻是目前滑動的page而是所有滑動的page,postion也并非是目前滑動page的postion變化,而是每個page的position變化。這個時候怎麼辦?這裡有個小技巧

ViewPager PageTransformer探索

這是就隻會看到左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 -> -1 -1 -> 0 0 -> 1 1 -> 2
左滑(右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

左滑規律 -2 -> -3 -1 -> -2 0 -> -1 1 -> 0

觀察這個表格,是不就就可以得出一些結論了(無論滑動前後目前頁面都為顯示在螢幕正中間頁面):

- 滑動開始前每個頁面的postion是一個整數,目前頁面為0,左側頁面position值為0 - 相對于目前頁面偏移頁面數,右側頁面postion值 0 + 相對于目前頁面偏移頁面數

- 滑動結束後,每個頁面的postion依然是一個整數,數值依然符合上一條規則,不過目前頁面已經改變。也可以說左滑全部 -1,右滑全部 +1,postion變為0的頁面成為新的目前頁面

- 每個頁面的postion右滑時都在增大,左滑時都在減小,滑動後結果為滑動前結果 ±1

有了這些結論,我們是不是就可以按部就班來實作一開始的那個效果了!

先來做一波分析,這個效果一個有三種變換:透明度、縮放、平移。其中平移可能有些人不太了解,我會在後面解釋

先來看看透明度

我們希望目前頁面(顯示在正中間的頁面)不透,兩側頁面在變為目前頁面的過程中,慢慢變為不透,目前頁面變為兩側頁面的過程中,慢慢變為一個最小透明度(比如0.3f)

通過上面的結論其實處于變得三個頁面目前頁面、左1、右1的變化範圍隻會在[-1,1]這個區間裡而在這意外的頁面其實并不能看見,那麼是不是可以這樣?

if (position < - || position > ) {
    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          1x - 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 = f;

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

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

這個效果我就不單獨截屏了,等到縮放效果實作後一并檢視(不要糾結為什麼跟DepthPageTransformer代碼為何不一樣,因為它的效果在(-1,0)區間本就是不透是以是1,在(1,0)區間的小最小透明度是0,你可以試試吧MIN_ALPHA換成0再看看是不是一樣的)

再來看看縮放的實作

和上面一同,可以算出來最終的x = 1 - position + minScale * position,同樣也不要糾結為何不一樣,你可以嘗試把DepthPageTransformer在這個(0,1)這個區間的縮放公式化簡出來再看看。

再來看看現在的效果圖

ViewPager PageTransformer探索

是不是既有透明度的變化,也有縮放的變化了?

最後就是平移的變化了(這個是需要解釋一番的)

首先來看看一開始那張圖:右側頁面的進出都是以螢幕為中心隻有縮放,何來平移啊?

再來看看我們現在的效果:我們已經實作了透明度和縮放的變化,為何與一開始的效果并不一樣呢?

以下純屬個人了解,如有大神覺得不妥,歡迎指出:以右滑為例,右側頁面在這個過程中,本身就有一個往右平移的過程,如果我們手動給這個頁面一個向左的平移來抵消這個向右的平移,那麼是不是就隻有縮放效果了呢?

原本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 < - || position > ) {
    page.setAlpha(f);
} else {
    if (- <= position && position < ) {
        page.setAlpha( + position - MIN_ALPHA * position);

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

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

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

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

現在的效果,就和開始的是一模一樣了。

相信大家看完以後對于position是如果變化的都能了解了,也都知道如何自己去實作出各種效果了。

最後奉上一張翻轉效果的切換圖

ViewPager PageTransformer探索

簡書

Github

繼續閱讀