自定义实现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,集体内容就看下篇博客吧~

发布了45 篇原创文章 · 获赞 18 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/Ever69/article/details/92830093