1. 程式人生 > >一步步實現Viewpager卡片翻頁效果

一步步實現Viewpager卡片翻頁效果

這個CardStackViewpager的靈感來自Github上面的 FlippableStackView開源專案,而我想實現的效果方向上恰好與FlippableStackView相反,並且細節上也有些區別,詳見下面的效果對比圖:

FlippableStackView執行效果圖:

enter image description here

CardStackViewpager執行效果圖:

enter image description here

這裡講一個小插曲,自己嘗試實現CardStackViewpager的過程中,由於一開始對PageTransformeronTransform(View page, float position)實在很困惑,於是我用自己小學般的英語寫了一封郵件給FlippableStackView

的開發者,尷尬的是,至今他沒回我郵件。

迴歸正題,下面我就來具體講一下CardStackViewpager的實現思路,其實整個核心就在下面這一段程式碼,把下面這段程式碼搞懂了,就可以通過自定義自己的PageTransformer實現各種各樣想要的Viewpager效果了。

核心的VerticalStackTransformer的onTransform方法最終版

    @Override
    protected void onTransform(View page, float position) {
        if (position <= 0.0f) {
            page.setAlpha(1.0f
); Log.e("onTransform", "position <= 0.0f ==>" + position); page.setTranslationY(0f); //控制停止滑動切換的時候,只有最上面的一張卡片可以點選 page.setClickable(true); } else if (position <= 3.0f) { Log.e("onTransform", "position <= 3.0f ==>" + position); float
scale = (float) (page.getWidth() - ScreenUtils.dp2px(context, spaceBetweenFirAndSecWith * position)) / (float) (page.getWidth()); //控制下面卡片的可見度 page.setAlpha(1.0f); //控制停止滑動切換的時候,只有最上面的一張卡片可以點選 page.setClickable(false); page.setPivotX(page.getWidth() / 2f); page.setPivotY(page.getHeight() / 2f); page.setScaleX(scale); page.setScaleY(scale); page.setTranslationY(-page.getHeight() * position + (page.getHeight() * 0.5f) * (1 - scale) + ScreenUtils.dp2px(context, spaceBetweenFirAndSecHeight) * position); } }
在分析上面的程式碼之前,我們需要有以下幾個知識準備:
  1. Viewpager的setPageTransformer(boolean reverseDrawingOrder, ViewPager.PageTransformer transformer)方法的第一個引數,用來控制加入到Viewpager的Views物件是正序的還是倒序的,這裡為了實現我們想要的效果,需要讓第一個新增到佈局的View來到第一個展示,所以傳入true
  2. Viewpager的setOffscreenPageLimit(int limit)方法,設定有多少的快取Views,這個將決定我們的卡片重疊展示的效果顯示幾層卡片效果。

現在我們繼續看上面的onTransform(View page, float position)方法,這個方法設計的很巧妙,當初我在探索的時候,通過列印日誌來判斷這個方法是如何執行的時候,發現這這個position的值看似毫無規律,後來我想到以前數學裡推理定理時的方法,從特殊情況入手,再一點點分析其他情況,然後一步步的實現上面的程式碼。

第一步,分析應用初始化進來的時候的position

此時的onTransform(View page, float position)方法如下:

    @Override
    protected void onTransform(View page, float position) {
        Log.e("onTransform","position  ==>"+position);
        //設定每個卡片y方向偏移量,這樣可以使卡片都完全疊加起來
        page.setTranslationY(-page.getHeight() * position);
    }

對應日誌如下:

enter image description here

根據這個日誌很明顯的可以判斷得到:由於我現在設定的setOffscreenPageLimit(int limit)值為4,所以可以看到position有上面幾種情況,顯而易見,每個position對應了一張卡片,這個時候介面的效果如圖:

enter image description here

現在猜想2,3,4,5號卡片就在1號卡片下面,現在要想個法子證實我們的猜想,將onTransform(View page, float position)方法改成下面這樣:

    @Override
    protected void onTransform(View page, float position) {
        Log.e("onTransform","position  ==>"+position);
        //設定卡片透明度
        page.setAlpha(0.5f);
        //設定縮放中點
        page.setPivotX(page.getWidth() / 2f);
        page.setPivotY(page.getHeight() / 2f);
        //設定縮放的比例 此處設定兩個相鄰的卡片的縮放比率為0.9f
        page.setScaleX((float) Math.pow(0.9f,position));
        page.setScaleY((float) Math.pow(0.9f,position));
        //設定每個卡片y方向偏移量,這樣可以使卡片都完全疊加起來
        page.setTranslationY(-page.getHeight() * position);
    }

執行起來之後,證實了我們的想法:

enter image description here

第二步,實現卡片疊加的最終效果

分析上面的圖片效果,可以發現,把第二張卡片往下移動一段距離之後,就可以形成一個卡片疊加的初步效果了,變成下面這樣:

enter image description here

其他的卡片,道理一樣,那麼如何實現這個向下偏移的值呢,這個值如何以一個表示式表現出來呢,先看下面的A,B,C步驟的分析圖:

enter image description here

顯而易見,相隔兩張卡片的偏移量為:(H2-H1)+d1,我們稍微改變一下onTransform(View page, float position)方法如下:

@Override
    protected void onTransform(View page, float position) {
        Log.e("onTransform", "position  ==>" + position);
        page.setAlpha(0.5f);
        page.setPivotX(page.getWidth() / 2f);
        page.setPivotY(page.getHeight() / 2f);
        page.setScaleX((float) Math.pow(0.9f, position));
        page.setScaleY((float) Math.pow(0.9f, position));
        //修改過的程式碼
        page.setTranslationY(-page.getHeight() * position + (page.getHeight() * 0.5f) * (1 - (float) Math.pow(0.9f, position)) + ScreenUtils.dp2px(context, 10));
    }

此時的效果圖如下:

enter image description here

卡片半透明的時候,效果還不是特別的明顯,把page.setAlpha(0.5f)改為page.setAlpha(1.0f)再試一次:

enter image description here

驚喜的發現這不就是卡片疊加效果嘛,雖然現在的效果細節還有點問題,我們不急,這個細節問題簡單分析一下就會想到,是我們的縮放比例問題導致的,繼續下一步的優化,我們將會解決這個問題。

第三步,根據相鄰卡片的間距值動態設定縮放值

上面的onTransform(View page, float position)方法中,我們的x,y縮放比例都是寫的一個固定值0.9f,這個顯然不能滿足日常需求,我這裡是設定上下兩張卡片的寬度比來作為最終想要的縮放比例,修改onTransform(View page, float position)方法如下:

    @Override
    protected void onTransform(View page, float position) {
        Log.e("onTransform", "position  ==>" + position);
        float scale = (float) (page.getWidth() - ScreenUtils.dp2px(context, 20 * position)) / (float) (page.getWidth());
        page.setAlpha(1.0f);
        page.setPivotX(page.getWidth() / 2f);
        page.setPivotY(page.getHeight() / 2f);
        page.setScaleX(scale);
        page.setScaleY(scale);
        //修改過的程式碼
        page.setTranslationY(-page.getHeight() * position + (page.getHeight() * 0.5f) * (1 - scale) + ScreenUtils.dp2px(context, 10) * position);
    }

再跑一下程式,完美的卡片效果就出現了:

enter image description here

第四步,特殊到一般,實現最終的卡片滑動效果

此時,我們嘗試一下滑動Viewpager,發現卡片的切換效果並沒有如期的出現,通過多次嘗試和分析,我發現,由於我們這裡沒有對當前滑動過去的那張卡片做特殊處理,這裡的特殊處理指的是:為了實現卡片抽動的切換效果,當前滑動的卡片應該不用執行任何縮放和偏移的操作,修改為page.setTranslationY(0f);,具體程式碼如下:

    @Override
    protected void onTransform(View page, float position) {
        Log.e("onTransform", "position  ==>" + position);
        if (position <= 0.0f) {
            page.setAlpha(1.0f);
            //出現卡片抽動效果的關鍵程式碼
            page.setTranslationY(0f);
        } else {
            float scale = (float) (page.getWidth() - ScreenUtils.dp2px(context, 20 * position)) / (float) (page.getWidth());
            page.setAlpha(1.0f);
            page.setPivotX(page.getWidth() / 2f);
            page.setPivotY(page.getHeight() / 2f);
            page.setScaleX(scale);
            page.setScaleY(scale);
            //修改過的程式碼
            page.setTranslationY(-page.getHeight() * position + (page.getHeight() * 0.5f) * (1 - scale) + ScreenUtils.dp2px(context, 10) * position);
        }
    }

至此,已經可以實現文章開頭的動畫效果了。回頭想一下,我們一直在基於特殊的情況寫程式碼,最後發現其實他就是所有一般情況中的一種,只不過特殊情況由於他的特殊性最容易進行分析總結,更有利於我們編寫出易懂的程式碼。

最後補充下,在實際專案中,在每張卡片上可能還有有點選區域,更可能整張卡片都是一個點選區域,這個時候就會發現一個問題,當處於這種情況的時候:

enter image description here

我不但可以點到卡片1,也會點到卡片2,卡片3。。。這樣肯定不行的,所以我們再次回到onTransform(View page, float position)方法,在裡面加一個控制:

   @Override
    protected void onTransform(View page, float position) {
        Log.e("onTransform", "position  ==>" + position);
        if (position <= 0.0f) {
            //最上面的卡片可以點選
            page.setClickable(true);
            .......
        } else {
            //下面的卡片不可點選
            page.setClickable(false);
            ........
        }
    }

另外我們可能只需要4張卡片重疊的效果就行,這個時候改變一下判斷條件即可:

   @Override
    protected void onTransform(View page, float position) {
        Log.e("onTransform", "position  ==>" + position);
        if (position <= 0.0f) {
            ......
        //控制顯示幾張卡片
        } else if(position <= 3.0f) {
            ......
        }
    }

至此這邊文章就要結束了,這是我的總結,希望能幫助大家對onTransform(View page, float position)方法有一個更深的理解。