Android 自定义 PageTransformer 实现 ViewPager 动画切换效果
1. 概述
在 Android 开发中,ViewPager 是进行页面滑动切换的常用组件。除了默认的滑动效果外,通过 setPageTransformer 方法可以设置丰富的页面切换动画。本文将详细介绍如何使用官方提供的 PageTransformer,如何自定义个性化的切换动画,以及如何解决低版本 SDK 的兼容性问题。
本文主要涵盖以下内容:
- 介绍如何使用
setPageTransformer 设置切换动画;
- 自定义 PageTransformer 实现个性的切换动画(如旋转效果);
- 针对 SDK 11 以下版本的向下兼容方案。
2. setPageTransformer 的使用
首先,我们需要搭建一个基础的 ViewPager 环境。
2.1 布局文件
<RelativeLayout 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.support.v4.view.ViewPager
android:id="@+id/id_viewpager"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
</RelativeLayout>
2.2 MainActivity 代码
package com.zhy.demo_zhy_08_viewpageranim;
import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
import android.os.Bundle;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
public class MainActivity extends Activity {
private ViewPager mViewPager;
private int[] mImgIds = new int[] { R.drawable.guide_image1,
R.drawable.guide_image2, R.drawable.guide_image3 };
private List<ImageView> mImageViews = new ArrayList<ImageView>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
initData();
mViewPager = (ViewPager) findViewById(R.id.id_viewpager);
mViewPager.setAdapter(new PagerAdapter() {
@Override
public Object instantiateItem(ViewGroup container, int position) {
container.addView(mImageViews.get(position));
return mImageViews.get(position);
}
@Override
public void destroyItem(ViewGroup container, int position,
Object object) {
container.removeView(mImageViews.get(position));
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
@Override
public int getCount() {
return mImgIds.length;
}
});
}
private void initData() {
for (int imgId : mImgIds) {
ImageView imageView = new ImageView(getApplicationContext());
imageView.setScaleType(ScaleType.CENTER_CROP);
imageView.setImageResource(imgId);
mImageViews.add(imageView);
}
}
}
2.3 添加 PageTransformer
ViewPager 提供了一个 setPageTransformer(boolean reverseDrawingOrder, PageTransformer transformer) 方法用于设置切换时的动画效果。
1. DepthPageTransformer (深度变换)
这是 Google 官方示例之一,模拟卡片堆叠的深度效果。
public class DepthPageTransformer implements ViewPager.PageTransformer {
private static final float MIN_SCALE = 0.75f;
public void transformPage(View view, float position) {
int pageWidth = view.getWidth();
if (position < -1) {
view.setAlpha(0);
} else if (position <= 0) {
view.setAlpha(1);
view.setTranslationX(0);
view.setScaleX(1);
view.setScaleY(1);
} else if (position <= 1) {
view.setAlpha(1 - position);
view.setTranslationX(pageWidth * -position);
float scaleFactor = MIN_SCALE
+ (1 - MIN_SCALE) * ( - Math.abs(position));
view.setScaleX(scaleFactor);
view.setScaleY(scaleFactor);
} {
view.setAlpha();
}
}
}
调用方式:
mViewPager.setPageTransformer(true, new DepthPageTransformer());
2. ZoomOutPageTransformer (缩放退出)
此效果会在滑动时缩小当前页并淡出。
package com.zhy.view;
import android.support.v4.view.ViewPager;
import android.view.View;
public class ZoomOutPageTransformer implements ViewPager.PageTransformer {
private static final float MIN_SCALE = 0.85f;
private static final float MIN_ALPHA = 0.5f;
public void transformPage(View view, float position) {
int pageWidth = view.getWidth();
int pageHeight = view.getHeight();
if (position < -1) {
view.setAlpha(0);
} else if (position <= 1) {
float scaleFactor = Math.max(MIN_SCALE, 1 - Math.abs(position));
float vertMargin = pageHeight * (1 - scaleFactor) / 2;
pageWidth * ( - scaleFactor) / ;
(position < ) {
view.setTranslationX(horzMargin - vertMargin / );
} {
view.setTranslationX(-horzMargin + vertMargin / );
}
view.setScaleX(scaleFactor);
view.setScaleY(scaleFactor);
view.setAlpha(MIN_ALPHA + (scaleFactor - MIN_SCALE)
/ ( - MIN_SCALE) * ( - MIN_ALPHA));
} {
view.setAlpha();
}
}
}
调用方式:
mViewPager.setPageTransformer(true, new ZoomOutPageTransformer());
注意: 上述代码使用了属性动画 API,在 Android 3.0 (API 11) 之前是不生效的。注释中提到:setting a PageTransformer prior to Android 3.0 (API 11) will have no effect。
3. 版本的向下兼容
3.1 不兼容原因分析
在 Android 3.0 之前,系统不支持 View 的属性动画(Property Animation)。因此,直接使用 view.setAlpha() 等方法在旧版本上无效。此外,setPageTransformer 方法内部也有版本判断逻辑,仅当 Build.VERSION.SDK_INT >= 11 时才执行相关逻辑。
3.2 完美向下兼容方案
为了兼容 3.0 以下版本,我们需要做两步处理:
- 引入 NineOldAndroids 库,使用
ViewHelper 替代原生属性动画方法。
- 修改或替换 ViewPager 源码,移除 SDK 版本判断。
第一步:使用 ViewHelper
将代码中的 view.setAlpha() 等替换为 ViewHelper.setAlpha(view, ...)。
public class DepthPageTransformer implements ViewPager.PageTransformer {
private static final float MIN_SCALE = 0.75f;
public void transformPage(View view, float position) {
int pageWidth = view.getWidth();
if (position < -1) {
ViewHelper.setAlpha(view, 0);
} else if (position <= 0) {
ViewHelper.setAlpha(view, 1);
ViewHelper.setTranslationX(view, 0);
ViewHelper.setScaleX(view, 1);
ViewHelper.setScaleY(view, 1);
} else if (position <= 1) {
ViewHelper.setAlpha(view, 1 - position);
ViewHelper.setTranslationX(view, pageWidth * -position);
float scaleFactor = MIN_SCALE + (1 - MIN_SCALE) * (1 - position);
ViewHelper.setScaleX(view, scaleFactor);
ViewHelper.setScaleY(view, scaleFactor);
} else {
ViewHelper.setAlpha(view, 1);
}
}
}
由于 setPageTransformer 内部有 if (Build.VERSION.SDK_INT >= 11) 的判断,直接修改源码是最彻底的方案。我们将 android.support.v4.view.ViewPager 拷贝一份至项目中,重命名为 ViewPagerCompat,并注释掉版本判断语句。
public class ViewPagerCompat extends ViewGroup {
public void setPageTransformer(boolean reverseDrawingOrder, ViewPager.PageTransformer transformer) {
final boolean hasTransformer = transformer != null;
final boolean needsPopulate = hasTransformer != (mPageTransformer != null);
mPageTransformer = transformer;
setChildrenDrawingOrderEnabledCompat(hasTransformer);
if (hasTransformer) {
mDrawingOrder = reverseDrawingOrder ? DRAW_ORDER_REVERSE : DRAW_ORDER_FORWARD;
} else {
mDrawingOrder = DRAW_ORDER_DEFAULT;
}
if (needsPopulate) populate();
}
}
在项目中使用 ViewPagerCompat 替换原有的 ViewPager,即可在 Android 2.3.3 等低版本设备上运行自定义动画。
4. 自定义 PageTransformer 实现个性切换动画
PageTransformer 接口非常简单,只包含一个方法:
public interface PageTransformer {
void transformPage(View page, float position);
}
page: 当前正在变换的 View。
position: 页面相对于中心位置的状态。0 表示正中间,1 表示右侧一整页,-1 表示左侧一整页。
4.1 Position 参数详解
当用户滑动时,transformPage 会被频繁调用。position 的值域通常分为三部分:
[-Infinity, -1): 页面完全在屏幕左侧之外。
(1, +Infinity]: 页面完全在屏幕右侧之外。
[-1, 1]: 页面处于可见区域附近,是动画计算的核心区间。
假设当前显示 A 页,向右滑动出现 B 页:
- A 页的
position 从 0 变为 -1。
- B 页的
position 从 1 变为 0。
4.2 实现旋转动画
我们可以利用 position 的变化来实现页面的旋转效果。例如,让滑出的页面向左旋转,滑入的页面向右旋转。
设计思路:
- 设置旋转中心点:底部中心。
- 根据
position 计算旋转角度。
代码实现:
public class RotatePageTransformer implements ViewPager.PageTransformer {
private static final float ROTATION_MAX = 20.0f;
@Override
public void transformPage(View view, float position) {
view.setPivotX(view.getMeasuredWidth() * 0.5f);
view.setPivotY(view.getMeasuredHeight());
float rotation = ROTATION_MAX * position;
view.setRotation(rotation);
}
}
注意事项:
- 测量时机:在
transformPage 中获取 view.getMeasuredWidth() 可能返回 0,因为此时 View 尚未完成测量。建议将旋转中心的设置放在 onLayout 之后,或者使用 view.post() 确保 View 已测量完毕后再设置 Pivot。
- 性能优化:避免在每次滑动时重复创建对象,尽量复用变量。
5. 总结与最佳实践
通过 PageTransformer,我们可以轻松实现各种复杂的页面切换动画,无需依赖第三方库。但在使用时也需注意以下几点:
- 兼容性:对于需要支持 Android 3.0 以下的老旧设备,必须使用
ViewPagerCompat 方案配合 NineOldAndroids。
- 性能:频繁的 View 属性修改会影响 FPS。尽量使用硬件加速,避免在
transformPage 中进行复杂计算或内存分配。
- 维护性:Google 已推荐使用
ViewPager2 替代旧版 ViewPager。ViewPager2 基于 RecyclerView 构建,提供了更好的性能和更灵活的动画控制(通过 PageTransformer 同样支持)。如果在新项目中开发,建议优先考虑 ViewPager2。
掌握 PageTransformer 的原理,能够帮助开发者创造出更具交互感和视觉吸引力的移动应用界面。