效果图
基本原理
要实现如上展示的两种效果,我们需要两个东西来帮助实现——clipChildren和PagerTransformer
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,集体内容就看下篇博客吧~