Android ViewPager 切换动画详解:PageTransformer 从原理到实战
ViewPager 默认的页面切换只是"水平平移",朴素到几乎没存在感。想做出"缩放淡化""3D 深度""旋转翻转"这类炫酷效果,靠的就是 PageTransformer——一个只有单个方法的接口,却是 ViewPager 动画的灵魂。本文从 position 参数的数学含义讲起,到两个官方经典实现、再到 ViewPager2 的差异,带你彻底搞懂 ViewPager 切换动画。
1、PageTransformer 是什么
ViewPager.PageTransformer 是一个回调接口,签名极简:在滑动过程中,它会对每个"参与切换的页面"持续调用 transformPage(View page, float position),让你根据 position 给 page 设置旋转、缩放、透明度、位移等变换,从而实时构造动画。本质上,它把"切换效果"完全交给开发者自定义,框架只负责把 position(页面相对位置)喂给你。
public interface PageTransformer {
/**
* @param page 被切换的页面 View(当前页、相邻页都会收到回调)
* @param position 该页面相对正中的位置,随滑动实时变化(见下一节)
*/
void transformPage(@NonNull View page, float position);
}它的适用范围很广:既适用于老的 ViewPager(androidx.viewpager.widget),也适用于新的 ViewPager2(androidx.viewpager2.widget),两者用同一套 transformPage 思路,只是设置方法和接口包名略有差异。下面先啃下最关键的 position 参数。
2、核心:position 参数详解
理解 PageTransformer,90% 就是理解 position。它表示"这个 page 相对于屏幕正中"的位置,是一个随手指滑动实时变化的浮点数:
position 取值区间与含义(屏幕正中 = 0):
position < -1 : 该页在屏幕左侧一屏之外(不可见)
position = -1 : 该页紧贴屏幕左边界(左侧完全滑入)
-1 < position < 0 : 该页在左侧、正滑向正中(左页进入)
position = 0 : 该页正居中、完全显示
0 < position < 1 : 该页在右侧、正滑离正中(右页离开)
position = 1 : 该页紧贴屏幕右边界(右侧完全滑出)
position > 1 : 该页在屏幕右侧一屏之外(不可见)一个关键认知:transformPage 会被对每个相关页面调用,而且 position 是连续变化的。比如从第 0 页滑到第 1 页时,第 0 页的 position 会从 0 平滑变到 -1(向左离开),第 1 页的 position 会从 1 平滑变到 0(从右侧滑入正中)。你在这个回调里根据 position 计算"缩放比例""旋转角度""透明度"并 setXxx 给 page,连续的回调就拼出了流畅动画。所以写 Transformer 的套路就是:if/else 分段处理 position,每段算出对应变换。
3、setPageTransformer 与绘制顺序
写好 Transformer 后,通过 setPageTransformer 装到 ViewPager 上。注意它有两个参数,第一个 reverseDrawingOrder 控制"是否反转页面绘制顺序":
// ViewPager(androidx.viewpager.widget)
viewPager.setPageTransformer(true, new ZoomOutPageTransformer());
// ^^^^
// reverseDrawingOrder = true:反转绘制顺序,保证"即将进入的页面"画在最上层,
// 否则缩放/位移后的页面可能被相邻页遮挡,看起来怪异。
// 多数自定义 Transformer 都传 true。
// 取消自定义动画,恢复默认平移:
viewPager.setPageTransformer(true, null);一个常见坑:Transformer 里如果只做"缩放/透明"不涉及 z-order,传 true 或 false 差别不大;但一旦做了"位移"让页面重叠,就必须用 true 反转绘制顺序,否则进入页会被压在底下。
4、经典效果一:ZoomOutPageTransformer(缩放淡化)
这是官方示例里最经典的一个,效果是"页面滑出时逐渐缩小并变透明",离正中越远越小越淡。它的实现就是按 position 分段:屏幕外 alpha=0;正中位置不缩放;中间按距离算缩放与透明度。
public class ZoomOutPageTransformer implements ViewPager.PageTransformer {
private static final float MIN_SCALE = 0.85f; // 最小缩放
private static final float MIN_ALPHA = 0.5f; // 最小透明度
@Override
public void transformPage(View view, float position) {
int pageWidth = view.getWidth();
int pageHeight = view.getHeight();
if (position < -1) { // 屏幕左侧外:完全隐藏
view.setAlpha(0f);
} else if (position <= 1) { // [-1,1]:可见区间
// 离正中越远,缩得越小(不低于 MIN_SCALE)
float scaleFactor = Math.max(MIN_SCALE, 1 - Math.abs(position));
float vertMargin = pageHeight * (1 - scaleFactor) / 2;
float horzMargin = pageWidth * (1 - scaleFactor) / 2;
if (position < 0) {
view.setTranslationX(horzMargin - vertMargin / 2);
} else {
view.setTranslationX(-horzMargin + vertMargin / 2);
}
view.setScaleX(scaleFactor);
view.setScaleY(scaleFactor);
// 透明度:缩到最小时降到 MIN_ALPHA
view.setAlpha(MIN_ALPHA + (scaleFactor - MIN_SCALE)
/ (1 - MIN_SCALE) * (1 - MIN_ALPHA));
} else { // 屏幕右侧外:完全隐藏
view.setAlpha(0f);
}
}
}理解要点:核心是 scaleFactor = max(MIN_SCALE, 1 - |position|)——用"距离正中的远近"直接换算成"缩放比例";再用缩放差算出留白边距(horzMargin/vertMargin),让缩小的页面居中显示而非贴边。这个"距离换变换"的思路,是所有缩放类 Transformer 的通用模板。
5、经典效果二:DepthPageTransformer(3D 深度)
另一个官方示例,效果是"当前页从右滑入、停留在正中;右侧页在它身后缩小淡化",有明显的 3D 纵深感。它对左页(position < 0)保持原样、对右页(0~1)做淡出+缩放+左移,分段更分明:
public class DepthPageTransformer implements ViewPager.PageTransformer {
private static final float MIN_SCALE = 0.75f;
@Override
public void transformPage(View view, float position) {
int pageWidth = view.getWidth();
if (position < -1) { // 左侧外:隐藏
view.setAlpha(0f);
} else if (position <= 0) { // [-1,0]:当前/左页,原样显示
view.setAlpha(1f);
view.setTranslationX(0f);
view.setScaleX(1f);
view.setScaleY(1f);
} else if (position <= 1) { // (0,1]:右页,淡化+左移+缩小
view.setAlpha(1 - position);
view.setTranslationX(pageWidth * -position);
float scaleFactor = MIN_SCALE + (1 - MIN_SCALE) * (1 - Math.abs(position));
view.setScaleX(scaleFactor);
view.setScaleY(scaleFactor);
} else { // 右侧外:隐藏
view.setAlpha(0f);
}
}
}对比 ZoomOut:ZoomOut 是"两边都缩",Depth 是"只缩右侧(身后页)",所以 Depth 的正中页始终"顶在最前"、身后页渐渐退后,纵深感更强。把第 4、5 节两个实现轮流换着用,就能直观感受 Transformer 的威力。
6、更多效果:旋转、3D 翻转、卡片堆叠
掌握了"按 position 分段算变换"的套路,各种效果都好写。最简洁的是旋转类——一行 setRotationY 就有强烈的 3D 翻转感:
// 绕 Y 轴翻转:position=0 时正面朝上,向两侧逐渐翻转 90 度
public void transformPage(View page, float position) {
// 限制只在 [-1,1] 内处理,避免屏幕外浪费
if (position < -1 || position > 1) {
page.setAlpha(0f);
return;
}
page.setCameraDistance(page.getWidth() * 20); // 拉远相机,避免翻转失真
page.setRotationY(position * -90); // ±90 度翻转
page.setAlpha(1 - Math.abs(position));
}
// 卡片堆叠:当前页滑出时向下/向右"塌"到一堆里
// 思路:page.setTranslationY(position * pageHeight * 0.3f) + 缩小 + 半透明小技巧:做 3D 旋转时记得 setCameraDistance 把"相机"拉远,否则近大远小的透视会让翻转中的页面严重变形。把 rotation/translation/scale/alpha 组合起来,就能演变出翻转、堆叠、抽屉、画廊等几十种效果——本质都是在 transformPage 里按 position 算几个变换。
7、ViewPager2 的差异与用法
新项目多用 ViewPager2(基于 RecyclerView,更现代)。它的 PageTransformer 思路完全一致,但接口更简洁——setPageTransformer 只剩一个参数(绘制顺序由框架自动处理,不用手动 reverse),并且官方提供了 MarginPageTransformer 用于"页面间距",还支持纵向滑动:
// ViewPager2(androidx.viewpager2.widget)
// 自定义动画:复用第 4/5 节的 Transformer(接口名变成 ViewPager2.PageTransformer)
viewPager2.setPageTransformer(new ZoomOutTransformer());
// 页面间距:相邻页面之间留 40px 缝隙(官方内置,无需自己写)
viewPager2.setPageTransformer(new MarginPageTransformer(40));
// 纵向滑动(上下翻页),配合 Transformer 能做"垂直卡片"效果
viewPager2.setOrientation(ViewPager2.ORIENTATION_VERTICAL);
// 注意:ViewPager2.PageTransformer 与 ViewPager.PageTransformer 接口结构相同,
// 都是 transformPage(View page, float position),旧 Transformer 改个 import/接口名即可迁移。迁移要点:旧 Transformer 的逻辑(按 position 分段算变换)可以原样复用,只需把实现的接口从 `androidx.viewpager.widget.ViewPager.PageTransformer` 换成 `androidx.viewpager2.widget.ViewPager2.PageTransformer`,再去掉 reverseDrawingOrder 这个参数即可。
8、实战与最佳实践
- 选效果:引导页/相册用 ZoomOut(轻柔),主 Tab 切换用 Depth 或不加 Transformer(稳重),卡片展示用旋转/堆叠(炫酷)
- 性能:Transformer 在滑动期间高频回调,内部只做"几个 setXxx",避免创建对象、避免复杂计算;3D 旋转依赖硬件加速,确保页面未关闭硬件层
- 边界:务必处理 position < -1 与 > 1(屏幕外),及时 setAlpha(0) 隐藏,否则可能渲染不可见页面、浪费性能
- 新项目优先 ViewPager2 + FragmentStateAdapter,Transformer 可平滑迁移
- 调参套路:先把官方 ZoomOut/Depth 拷进去跑通,再调 MIN_SCALE/MIN_ALPHA 两个常量找到顺眼的"缩放感"和"淡出感"
9、总结
ViewPager 的切换动画,本质就是把"页面相对位置"换算成"页面变换"。PageTransformer 只有一个 transformPage 方法,难点全在理解 position——0 是正中、-1/1 是左右屏外、中间连续变化。在这个回调里按 position 分段算缩放/旋转/位移/透明度,就拼出了 ZoomOut、Depth、翻转、堆叠等所有效果。
关键要点:
- PageTransformer.transformPage(page, position):position 0=正中,±1=屏外,连续变化
- 套路:if/else 分段处理 position,每段 setScaleX/setRotationY/setTranslationX/setAlpha
- ZoomOut:两边都缩;Depth:只缩身后页、纵深感强;setPageTransformer 传 true 反转绘制
- ViewPager2:setPageTransformer 单参数、官方 MarginPageTransformer、支持纵向;旧 Transformer 可平滑迁移
- 性能:高频回调内别创建对象、处理好屏外边界(setAlpha(0))
对于做引导页、Banner、卡片浏览这类需要"动起来"的 Android 界面而言,PageTransformer 是性价比极高的工具——几十行代码就能把朴素的平移切换变成有质感的高级动画。把 position 的数学含义吃透、再备好 ZoomOut 与 Depth 两个"基准实现",绝大多数切换效果你都能信手拈来。下次产品要"切换酷一点",别再满世界找轮子,自己写个 Transformer 往往更快、更可控。
学习资料:

