自定義實現ViewPager切換動畫

效果圖

基本原理

要實現如上展示的兩種效果,我們需要兩個東西來幫助實現——clipChildrenPagerTransformer

1)clipChildren

這個屬性見得不多,可能很多小夥伴不熟悉,這個屬性是個布爾值,clip中文爲裁剪的意思,clipChildren即爲裁剪孩子(硬核翻譯)。一般修飾在ViewGroup上,它可以表示是否限制處於容器內部的子控件可以越界繪製,默認爲true。這麼說還是有點懵逼,直接看圖吧。

相信大家經常見到閒魚這種樣式的底部Tab欄,那麼這是如何實現的呢,肯定有人想過用佈局嵌套並設置底層佈局背景透明的這種方法,這種方法的確可行,可是稍嫌麻煩,如果是使用clipChildren,則要簡單許多。

比如,我現在實現一個類似效果的Tab欄,不使用clipChildren是這樣的。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    ...>

    <LinearLayout
        android:layout_height="60dp"
        ...>

        <ImageView
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:src="@mipmap/ic_launcher" />

        <ImageView
            ... />

        <ImageView
            android:layout_width="0dp"
            android:layout_height="80dp"
            android:layout_gravity="bottom"
            android:layout_weight="1"
            android:src="@mipmap/ic_launcher" />

        <ImageView
            ... />

        <ImageView
            .../>
    </LinearLayout>
</RelativeLayout>

可以看見,中間的tab明顯被裁減了,那麼加上clipChildren屬性呢?

android:clipChildren="false"

當我們爲根節點設置clipChildren爲false時,神奇的一幕出現了,中間的tab竟然長出頭了(滑稽)。通過這個例子,你應該瞭解了clipChildren的作用了。

2)PagerTransformer

PagerTransformer是ViewPager暴露給我們的一個接口,它內部含有一個transformPage(view:View,position:Float)方法,通過這個方法我們可以拿它的兩個參數針對view實現各種切換效果。這裏着重講一下方法的第二個參數,它表示每個view的下標,但是注意它是一個Float類型的變量,它不像我們以往見的下標那樣,1就是1,2就是2,它是會變的,動態的。剛纔爲0的下標,滑動過後可能會變成1或-1,也就是在[1-,0],[0,1]之間變化,0並不是他的最小值並且當前展示的view下標永遠爲0。

話不多說,上圖

就記住一句話,對於下標,當前展示永爲0,左爲負遞減,右爲正遞增(總結的我都想給自己個贊。。)。

對於PagerTransformer,官方有倆個實現,有興趣的可以去看看。傳送門

動手實現

爲了實現文章開篇兩幅圖的效果,首先搞定vp單屏顯示多頁效果。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clipChildren="false"
    android:gravity="center_horizontal"
    tools:context=".MainActivity">

    <android.support.v4.view.ViewPager
        android:id="@+id/view_pager"
        android:overScrollMode="never"
        android:layout_width="240dp"
        android:clipChildren="false"
        android:layout_height="wrap_content" />

</LinearLayout>

因爲vp默認只顯示一頁內容,所以需要兩個根節點都要加上clipChildren屬性。如果需要每頁有間隔的可以通過代碼設置

view_pager.pageMargin = 15

畫廊效果

private const val MIN_SCALE_Y = 0.75f
private const val MIN_SCALE_X = 0.95f
private const val MIN_ALPHA = 0.7f

class MyTransform : ViewPager.PageTransformer {

    override fun transformPage(view: View, position: Float) {
        view.apply {
            when {
                position < -1 -> { // [-∞,-1)
                    // 屏幕左邊的view
                    alpha = MIN_ALPHA
                    scaleX = MIN_SCALE_X
                    scaleY = MIN_SCALE_Y
                }
                position <= 1 -> { // [-1,1]
                    //X/Y方向上的縮放比例,跟隨下標變化,介於分別的最小值和1之間
                    val scaleFactorY = (MIN_SCALE_Y + (1 - MIN_SCALE_Y) * (1 - Math.abs(position)))
                    val scaleFactorX = (MIN_SCALE_X + (1 - MIN_SCALE_X) * (1 - Math.abs(position)))
                    scaleX = scaleFactorX
                    scaleY = scaleFactorY
                    // 隨着下標變化的透明度
                    alpha = (MIN_ALPHA + ((1 - Math.abs(position)) * (1 - MIN_ALPHA)))
                }
                else -> { // (1,+∞]
                    // 屏幕右邊的view
                    alpha = MIN_ALPHA
                    scaleX = MIN_SCALE_X
                    scaleY = MIN_SCALE_Y
                }
            }
        }
    }
}

屏幕兩側看不到的view的透明度和縮放大小永遠是固定的,避免它們從兩側出現時大小和透明度的突然變化帶來的落差感。屏幕顯示的view的透明度和大小隨着手指切換時在一個區間動態改變。

畫廊式效果圖

側旋式效果

private const val MIN_SCALE = 0.75f

class DashTransform : ViewPager.PageTransformer {
    override fun transformPage(view: View, position: Float) {
        view.apply {
            val pageWidth = width
            when {
                position < -1 -> { // [-∞,-1)
                    alpha = 0f
                }
                position <= 0 -> { // [-1,0] 移動到左側的view
                    alpha = 1f - Math.abs(position)
                    scaleX = 1f
                    scaleY = 1f
                    rotation = 30 * position
                }
                position <= 1 -> { // (0,1] 從右側移動過來的view
                    alpha = 1 - position
                    // 抵消掉原來的位移,讓view固定住
                    translationX = pageWidth * -position
                    // 縮放漸變大小
                    val scaleFactor = (MIN_SCALE + (1 - MIN_SCALE) * (1 - Math.abs(position)))
                    scaleX = scaleFactor
                    scaleY = scaleFactor
                }
                else -> { // (1,+∞]
                    alpha = 0f
                }
            }
        }
    }
}

向左移動的view透明度隨着下標逐漸透明,並且加了一個30度以內的旋轉,原本從右側移動過來的view被固定在了向左移動的view的下面,因爲代碼設置的位移抵消掉了它原本的移動距離,使它看起來像是一隻固定在一個位置。

側旋式效果

最後

其實很多炫酷的效果,乍一看以爲很難,其實無非就是通過旋轉、位移、縮放、漸變組合起來實現的,只要找到合適的地方和方法,合理利用組合就能實現非常棒的效果,看了這篇文章,對vp的切換效果感興趣的小夥伴可以動手實現一個自己的效果,學以致用,加深對動畫的理解。

擴展

基於本次博客的內容,我們還可以再次添加修改,定製實現一個帶切換效果的banner,集體內容就看下篇博客吧~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章