一直都很喜欢Instagram的快拍(Story)功能,也很喜欢他们的翻转效果,是一种简单的3D翻转效果。大致效果如下:
貌似最近微博也出了一个差不多的Story的功能,用的翻转动画也是和Instagram一样。
思路
看到这样的效果,很容易想到用ViewPager的Transformer动画来实现。当然这种翻转效果网上也有相应的实现,就是以View的左边或右边为旋转轴进行空间上Y轴的旋转。
于是很容易我们可以写出下面的代码
public class StereoPagerTransformer implements ViewPager.PageTransformer {
private static final float MAX_ROTATE_Y = 90;
private final float pageWidth;
public StereoPagerTransformer(float pageWidth) {
this.pageWidth = pageWidth;
}
public void transformPage(View view, float position) {
view.setPivotY(view.getHeight() / 2);
if (position < -1) { // [-Infinity,-1)
// This page is way off-screen to the left.
view.setPivotX(0);
view.setRotationY(90);
} else if (position <= 0) { // [-1,0]
view.setPivotX(pageWidth);
view.setRotationY(position * MAX_ROTATE_Y);
} else if (position <= 1) { // (0,1]
view.setPivotX(0);
view.setRotationY(position * MAX_ROTATE_Y);
} else { // (1,+Infinity]
// This page is way off-screen to the right.
view.setPivotX(0);
view.setRotationY(90);
}
}
}
然后运行代码看一下我们实现的效果:
额,总觉哪里不对,嗯,就是动画的速度不对,上面的写法就是一个匀速进行的动画,效果非常不理想,这时我们就要重新写一个插值器(TimeInterpolator ),在尝试了系统自带的差值器后,发现效果仍然不是很理想。于是决定自己写一个插值器。
关于插值器
根据TimeInterpolator代码中的文档可以得知,插值器用于控制动画进行的速度,其输入值为01,输出值也为01。
/**
* A time interpolator defines the rate of change of an animation. This allows animations
* to have non-linear motion, such as acceleration and deceleration.
*/
public interface TimeInterpolator {
/**
* Maps a value representing the elapsed fraction of an animation to a value that represents
* the interpolated fraction. This interpolated value is then multiplied by the change in
* value of an animation to derive the animated value at the current elapsed animation time.
*
* @param input A value between 0 and 1.0 indicating our current point
* in the animation where 0 represents the start and 1.0 represents
* the end
* @return The interpolation value. This value can be more than 1.0 for
* interpolators which overshoot their targets, or less than 0 for
* interpolators that undershoot their targets.
*/
float getInterpolation(float input);
}
经过简单的分析,这次我们的动画应该在动画前半段时进行缓慢一些(也就是输入值在0到某个值之间),在后半段时(也就是输入值在某个值到1之间)进行的快速一些。
经过简单的调整,最终我写了如下的插值器
private static final TimeInterpolator sInterpolator = new TimeInterpolator() {
@Override
public float getInterpolation(float input) {
if (input < 0.7) {
return input * (float) pow(0.7, 3) * MAX_ROTATE_Y;
} else {
return (float) pow(input, 4) * MAX_ROTATE_Y;
}
}
};
再次运行代码,这次效果看上去好多了,哈哈,一个简单又好看的效果就完成了。
最后附上完整的代码:
import android.animation.TimeInterpolator;
import android.support.v4.view.ViewPager;
import android.view.View;
import static java.lang.Math.pow;
/**
* @author wupanjie
*/
public class StereoPagerTransformer implements ViewPager.PageTransformer {
private static final float MAX_ROTATE_Y = 90;
private static final TimeInterpolator sInterpolator = new TimeInterpolator() {
@Override
public float getInterpolation(float input) {
if (input < 0.7) {
return input * (float) pow(0.7, 3) * MAX_ROTATE_Y;
} else {
return (float) pow(input, 4) * MAX_ROTATE_Y;
}
}
};
private final float pageWidth;
public StereoPagerTransformer(float pageWidth) {
this.pageWidth = pageWidth;
}
public void transformPage(View view, float position) {
view.setPivotY(view.getHeight() / 2);
if (position < -1) { // [-Infinity,-1)
// This page is way off-screen to the left.
view.setPivotX(0);
view.setRotationY(90);
} else if (position <= 0) { // [-1,0]
view.setPivotX(pageWidth);
view.setRotationY(-sInterpolator.getInterpolation(-position));
} else if (position <= 1) { // (0,1]
view.setPivotX(0);
view.setRotationY(sInterpolator.getInterpolation(position));
} else { // (1,+Infinity]
// This page is way off-screen to the right.
view.setPivotX(0);
view.setRotationY(90);
}
}
}
总结
动画的灵魂在于它的插值器,插值器控制了动画进行的速度,这次我选择自己写了一个插值器作为练手,其实我这次写的插值器效果仍不是很平滑,动画的插值器也应该用贝塞尔曲线来制作,这样我们的动画就会进行的更平滑。具体大家可以参考自带的PathInterpolator,是在API 21以后引入的,当然它也有对应的兼容包。
完整项目地址: https://github.com/wuapnjie/DailyAndroid/tree/master/StereoPageTransformer