- 利用ViewPage的PagerTransformer定製頁面切換效果
- ViewPager動態添加刪除及刷新頁面
- ViewPager打造真正意義的無限輪播
- ViewPage 聯動效果自帶角標
- ViewPager禁止滑動和修改滑動速度
1. 簡述
你是不是覺得 ViewPager 默認的切換效果有些平淡?其實,我們可以定製 ViewPager 的頁面切換效果。定製 ViewPager 的頁面切換效果,只需用到 ViewPager 的一個方法setPageTransformer(boolean reverseDrawingOrder, @Nullable PageTransformer transformer)
,實現一個接口 PageTransformer
。
2. PageTransformer
PageTransformer
是 ViewPager
內部一個接口,源碼如下:
/**
* A PageTransformer is invoked whenever a visible/attached page is scrolled.
* This offers an opportunity for the application to apply a custom transformation
* to the page views using animation properties.
* 每當滾動到 可見/附屬 頁面時,都會調用 PageTransformer 。
* 這爲應用程序提供了使用動畫屬性對頁面視圖應用自定義轉換的機會。
* <p>As property animation is only supported as of Android 3.0 and forward,
* setting a PageTransformer on a ViewPager on earlier platform versions will
* be ignored.</p>
*/
public interface PageTransformer {
/**
* Apply a property transformation to the given page.
* 爲指定頁面提供一個屬性轉換。
* @param page Apply the transformation to this page
* @param position Position of page relative to the current front-and-center
* position of the pager. 0 is front and center. 1 is one full
* page position to the right, and -1 is one page position to the left.
* 頁面相對於當前正中位置的位置。0代表正中。1 是右邊的一個完整頁面位置,
* -1 是左邊的一個頁面位置。
*/
void transformPage(@NonNull View page, float position);
}
從源碼註釋我們可以看出, PageTransformer
就是爲自定義頁面切換效果而生的。
使用 PageTransformer
需要實現方法 void transformPage(@NonNull View page, float position)
,這個方法有兩個參數,兩個參數是對應關係,position 表示頁面相對於正中(0)的位置,0代表正中,1 是右邊的一個完整頁面位置,-1 是左邊的一個頁面位置。
如下圖所示,ViewPager 頁面滑動過程中,屏幕中顯示 2 個頁面,即左邊頁面(綠色)的一部分和右邊頁面(藍色)的一部分。以虛線和頁面左邊緣作爲位置參考,那麼左邊頁面位置在 (-1, 0) 區間,右邊頁面位置在 (0, 1) 區間。它們的值是:position左 = (x左 - x0) / width,position右 = (x右 - x0) / width。
一般情況下我們最多隻能看到如上所示兩個頁面,所以多數情況下,我們可以把 position 分爲 3 段。
- (-Infinity, -1) :左邊不可見頁面。
- [-1, 1]:中間可見頁面,其中 [-1, 0) 表示左邊可見頁面,[0, 1] 表示右邊可見頁面。
- (1, +Infinity) :右邊不可見頁面。
當頁面的 position = 0,此頁面正中顯示。
可以調用 ViewPager 的 setOffscreenPageLimit(int)
方法設置了離屏緩存頁面數量(空閒時,當前頁面左右保留頁面數量)。
如果想在 ViewPager 中顯示多個頁面,可以:
- 調用
setOffscreenPageLimit(int)
設置離屏緩存頁面數量 大於 1; - 給 ViewPager 外層控件以及 ViewPager 都設置
android:clipChildren="false"
; - 給 ViewPager 設置 適當的 leftMargin 和 rightMargin 。
3. 例子和效果
1、手風琴效果
其實就是水平縮放效果。在頁面滑動時,
左邊頁面 position < 0,右邊頁面 position > 0;
左邊頁面以頁面右邊緣爲縮放中心,右邊頁面以左邊緣爲縮放中心。
代碼如下所示:
/**
* 手風琴效果(水平方向縮放)
*/
public class AccordionTransformer implements ViewPager.PageTransformer {
@Override
public void transformPage(@NonNull View page, float position) {
if (position < 0f) {
page.setPivotX(page.getWidth());
page.setScaleX(1f + position * 0.5f);
} else if (position < 1f) {
page.setPivotX(0f);
page.setScaleX(1f - position * 0.5f);
}
}
}
2、下弧形效果
實現此效果需以頁面下邊緣某一點爲旋轉中心旋轉:
- position < -1 時,旋轉到最大角度,旋轉中心爲右下角;
- -1 < position < 0 時,position 越靠近 0 ,旋轉角度越小,旋轉中心向下邊緣中心靠攏;
- 0 <= position <= 1 時,position 越靠近 0 ,旋轉角度越小,旋轉中心向下邊緣中心靠攏;
- position > 1 時,旋轉到最大角度,旋轉中心爲左下角。
代碼如下所示:
/**
* 下弧形切換效果
*/
public class ArcDownTransformer implements ViewPager.PageTransformer {
private static final float DEF_MAX_ROTATE = 12.0f;
private float mMaxRotate = DEF_MAX_ROTATE;
@Override
public void transformPage(@NonNull View page, float position) {
page.setPivotY( page.getHeight());
if (position < -1f) {//[-Infinity, -1)
page.setRotation(-mMaxRotate);
page.setPivotX(page.getWidth());
} else if (position <= 1f) {//[-1, 1]
if (position < 0f) {//[-1, 0)
page.setRotation(mMaxRotate * position);
page.setPivotX(page.getWidth() * (0.5f - 0.5f * position));
} else { //[0, 1]
page.setRotation(mMaxRotate * position);
page.setPivotX(page.getWidth() * (0.5f - 0.5f * position));
}
} else {//(1, +Infinity]
page.setRotation(mMaxRotate);
page.setPivotX(0f);
}
}
}
3、上弧形效果
與下弧形相反,旋轉中心以上邊緣某一點:
- position < -1 時,旋轉到最大角度,旋轉中心爲右下角;
- -1 < position < 0 時,position 越靠近 0 ,旋轉角度越小,旋轉中心向上邊緣中心靠攏;
- 0 <= position <= 1 時,position 越靠近 0 ,旋轉角度越小,旋轉中心向上邊緣中心靠攏;
- position > 1 時,旋轉到最大角度,旋轉中心爲左下角。
代碼如下:
/**
* 上弧形切換效果
*/
public class ArcUpTransformer implements ViewPager.PageTransformer {
private static final float DEF_MAX_ROTATE = 12.0f;
private float mMaxRotate = DEF_MAX_ROTATE;
@Override
public void transformPage(@NonNull View page, float position) {
page.setPivotY(0f);
if (position < -1f) {//[-Infinity, -1)
page.setRotation(mMaxRotate);
page.setPivotX(page.getWidth());
} else if (position <= 1f) {//[-1, 1]
if (position < 0f) {//[-1, 0)
page.setRotation(-mMaxRotate * position);
page.setPivotX(page.getWidth() * (0.5f - 0.5f * position));
} else { //[0, 1]
page.setRotation(-mMaxRotate * position);
page.setPivotX(page.getWidth() * (0.5f - 0.5f * position));
}
} else {//(1, +Infinity]
page.setRotation(-mMaxRotate);
page.setPivotX(0f);
}
}
}
4、立方翻轉-外
其實是繞 Y 軸旋轉,再加上縮放效果。繞 Y 軸旋轉,用到了 View 的 setRotationY(float)
方法,此方法可以設置繞 Y 軸的旋轉角度。
- position < -1,逆時針旋轉到最大角度,旋轉中心爲頁面右邊緣;
- -1 <= position < 0,旋轉中心爲頁面右邊緣,position 越靠近 0,旋轉角度越小,頁面先縮小後放大,縮放值是關於 position 開口向上 對稱線爲 position = -0.5 的拋物線;
- 0 <= position <= 1,旋轉中心爲左邊緣,position 越靠近 0,旋轉角度越小,頁面先縮小後放大,縮放值是關於 position 開口向上 對稱線爲 position = 0.5 的拋物線;
- position > 1,順時針旋轉到最大角度,旋轉中心爲左邊緣。
引入拋物線計算縮放值,是爲了讓頁面在滑動到一半(position 爲 -0.5 和 0.5)時,縮放到最小。
需要注意 的是鏡頭距離,即圖像與屏幕距離,距離較小時,旋轉時會有較大的失真,效果很差,需要設置一下鏡頭距離(Camera Distance,參考 View#setCameraDistance(float))。
代碼如下:
/**
* 立方體翻轉效果
*/
public class CubicOverturnTransformer implements ViewPager.PageTransformer {
public static final float DEFAULT_MAX_ROTATION = 60f;
public static final float DEF_MIN_SCALE = 0.86f;
/**
* 最大旋轉角度
*/
private float mMaxRotation = DEFAULT_MAX_ROTATION;
/**
* 最小縮放
*/
private float mMinScale = DEF_MIN_SCALE;
public CubicOverturnTransformer() {
this(DEFAULT_MAX_ROTATION);
}
public CubicOverturnTransformer(float maxRotation) {
this(maxRotation, DEF_MIN_SCALE);
}
public CubicOverturnTransformer(float maxRotation, float minScale) {
mMaxRotation = maxRotation;
this.mMinScale = minScale;
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Override
public void transformPage(@NonNull View page, float position) {
page.setPivotY(page.getHeight() / 2f);
float distance = getCameraDistance();
page.setCameraDistance(distance);//設置 View 的鏡頭距離,可以防止旋轉大角度時出現圖像失真或不顯示。
if (position < -1) { // [-Infinity,-1)
page.setRotationY(-mMaxRotation);
page.setPivotX(page.getWidth());
} else if (position <= 1) { // [-1,1]
page.setRotationY(position * mMaxRotation);
if (position < 0) {//[0,-1]
page.setPivotX(page.getWidth());
float scale = DEF_MIN_SCALE + 4f * (1f - DEF_MIN_SCALE) * (position + 0.5f) * (position + 0.5f);
page.setScaleX(scale);
page.setScaleY(scale);
} else {//[1,0]
page.setPivotX(0);
float scale = DEF_MIN_SCALE + 4f * (1f - DEF_MIN_SCALE) * (position - 0.5f) * (position - 0.5f);
page.setScaleX(scale);
page.setScaleY(scale);
}
} else { // (1,+Infinity]
page.setRotationY(mMaxRotation);
page.setPivotX(0);
}
}
/**
* 獲得鏡頭距離(圖像與屏幕距離)。參考{@link View#setCameraDistance(float)},小距離表示小視角,
* 大距離表示大視角。這個距離較小時,在 3D 變換(如圍繞X和Y軸的旋轉)時,會導致更大的失真。
* 如果改變 rotationX 或 rotationY 屬性,使得此 View 很大 (超過屏幕尺寸的一半),則建議始終使用
* 大於此時圖高度 (X 軸旋轉)或 寬度(Y 軸旋轉)的鏡頭距離。
* @return 鏡頭距離 distance
*
* @see {@link View#setCameraDistance(float)}
*/
private float getCameraDistance() {
DisplayMetrics displayMetrics = VpsApplication.getAppContext().getResources().getDisplayMetrics();
float density = displayMetrics.density;
int widthPixels = displayMetrics.widthPixels;
int heightPixels = displayMetrics.heightPixels;
return 1.5f*Math.max(widthPixels, heightPixels)*density;
}
}
使用:
mPageTransformer = new CubicOverturnTransformer(90f, 0.6f);
mVpImgs.setPageTransformer(reverseDrawingOrder, mPageTransformer);
最大旋轉角度 90 度,最小縮放到 0.6 。
5、立方翻轉-內
與上一個效果是同一套代碼,繞 Y 軸旋轉方向相反,使用時,讓 最大旋轉角度小於 0 即可,如下代碼所示,最大旋轉角度改爲 -90f 就是內部翻轉:
使用:
mPageTransformer = new CubicOverturnTransformer(-90f, 0.6f);
mVpImgs.setPageTransformer(reverseDrawingOrder, mPageTransformer);
最大旋轉角度 90 度,最小縮放到 0.6 。
6、下沉效果
特殊的縮放效果,有最小縮放值,頁面縮到最小值不再縮小,同時有透明度的變化(可以去掉透明度變化)。
旋轉中心位置的調整主要是爲了調整頁面間隙。
/**
* 下沉效果
*/
public class DipInTransformer implements ViewPager.PageTransformer {
private static final float MIN_SCALE = 0.85f;
private float mMinScale = MIN_SCALE;
private static final float MIN_ALPHA = 0.5f;
private float mMinAlpha = MIN_ALPHA;
@Override
public void transformPage(@NonNull View page, float position) {
Log.i("DipInTransformer", "transformPage: id = " + page.getId() + ", position = " + position);
int pageWidth = page.getWidth();
int pageHeight = page.getHeight();
// page.setPivotX(pageWidth * 0.5f);
page.setPivotY(pageHeight * 0.5f);
if (position < -1f) {//(-Infinity, -1]
page.setAlpha(mMinAlpha);
page.setScaleX(mMinScale);
page.setScaleY(mMinScale);
page.setPivotX(pageWidth*1f);
} else if (position <= 1f) {//(-1, 1)
float scaleFactor = Math.max(mMinScale, 1 - Math.abs(position));
page.setScaleX(scaleFactor);
page.setScaleY(scaleFactor);
if (position < 0) {
page.setPivotX(pageWidth * (0.5f + 0.5f * scaleFactor));
} else {
page.setPivotX(pageWidth * (0.5f - 0.5f * scaleFactor));
}
page.setAlpha(mMinAlpha + (scaleFactor - mMinScale) / (1f - mMinScale) * (1f - mMinAlpha));
} else {//(1, +Infinity)
page.setAlpha(mMinAlpha);
page.setScaleX(mMinScale);
page.setScaleY(mMinScale);
page.setPivotX(pageWidth * 0f);
}
}
}
7、淡入淡出效果(透明度)
不多作解釋,就是透明度屬性動畫。代碼如下:
/**
* 淡入淡出
*/
public class FadeInOutTransformer implements ViewPager.PageTransformer {
private static final float DEF_MIN_ALPHA =0.5f;
private float mMinAlpha = DEF_MIN_ALPHA;
@Override
public void transformPage(@NonNull View page, float position) {
if (position < -1f) {//[-Infinity, -1)
page.setAlpha(mMinAlpha);
} else if (position <= 1f) {//[-1, 1]
if (position < 0f) {//[-1, 0)
page.setAlpha(1f + (1f - mMinAlpha) * position);
} else { //[0, 1]
page.setAlpha(1f - (1f - mMinAlpha) * position);
}
} else {//(1, +Infinity]
page.setAlpha(mMinAlpha);
}
}
}
8、水平翻轉效果(左右翻轉)
即以水平中心線爲旋轉中心,繞 Y 軸旋轉。代碼如下所示,同樣要注意鏡頭距離:
/**
* 水平翻轉效果
*/
public class FlipHorizontalTransformer implements ViewPager.PageTransformer {
@Override
public void transformPage(@NonNull View page, float position) {
page.setCameraDistance(getCameraDistance());
page.setTranslationX(-page.getWidth() * position);
float rotation = 180f * position;
page.setAlpha(rotation > 90f || rotation < -90f ? 0f : 1f);
page.setPivotX(page.getWidth() * 0.5f);
page.setPivotY(page.getHeight() * 0.5f);
page.setRotationY(rotation);
if (position > -0.5f && position < 0.5f) {
page.setVisibility(View.VISIBLE);
} else {
page.setVisibility(View.INVISIBLE);
}
}
private float getCameraDistance() {
DisplayMetrics displayMetrics = VpsApplication.getAppContext().getResources().getDisplayMetrics();
float density = displayMetrics.density;
int widthPixels = displayMetrics.widthPixels;
int heightPixels = displayMetrics.heightPixels;
return 1.5f * Math.max(widthPixels, heightPixels) * density;
}
}
9、豎直翻轉效果(上下翻轉)
即以豎直中心線爲旋轉中心,繞 X 軸旋轉。代碼如下所示,同樣要注意鏡頭距離:
/**
* 豎直翻轉效果
*/
public class FlipVerticalTransformer implements ViewPager.PageTransformer {
@Override
public void transformPage(@NonNull View page, float position) {
page.setCameraDistance(getCameraDistance());
page.setTranslationX(-page.getWidth() * position);
float rotation = 180f * position;
page.setAlpha(rotation > 90f || rotation < -90f ? 0f : 1f);
page.setPivotX(page.getWidth() * 0.5f);
page.setPivotY(page.getHeight() * 0.5f);
page.setRotationX(rotation);
if (position > -0.5f && position < 0.5f) {
page.setVisibility(View.VISIBLE);
} else {
page.setVisibility(View.INVISIBLE);
}
}
private float getCameraDistance() {
DisplayMetrics displayMetrics = VpsApplication.getAppContext().getResources().getDisplayMetrics();
float density = displayMetrics.density;
int widthPixels = displayMetrics.widthPixels;
int heightPixels = displayMetrics.heightPixels;
return 1.5f * Math.max(widthPixels, heightPixels) * density;
}
}
10、浮出效果
讓所有右邊頁面都移動到 正中位置,從右向左滑動切換頁面時,左邊從右向左滑出,右邊頁面放大淡入。
/**
* 浮出效果
*/
public class RiseInTransformer implements ViewPager.PageTransformer {
private static final float DEF_MIN_SCALE = 0.72f;
private float mMinScale = DEF_MIN_SCALE;
private static final float DEF_MIN_ALPHA = 0.5f;
public RiseInTransformer() {
}
public RiseInTransformer(float minScale) {
this.mMinScale = minScale;
}
public float getMinScale() {
return mMinScale;
}
public void setMinScale(float minScale) {
this.mMinScale = minScale;
}
@Override
public void transformPage(@NonNull View page, float position) {
if (position < 0f) {
page.setTranslationX(0f);
} else if (position <= 1) {
page.setTranslationX(-position * page.getWidth());
page.setScaleX(1f - (1f - mMinScale) * position);
page.setScaleY(1f - (1f - mMinScale) * position);
page.setAlpha(1f - (1f - DEF_MIN_ALPHA) * position);
} else {
page.setTranslationX(-position * page.getWidth());
page.setScaleX(mMinScale);
page.setScaleY(mMinScale);
page.setAlpha(DEF_MIN_ALPHA);
}
}
}
調用時需要反轉繪製順序,即 reverseDrawingOrder = true,使左邊頁面先繪製,右邊頁面後繪製,否則效果無法實現。
int reverseDrawingOrder = true;
mVpImgs.setPageTransformer(reverseDrawingOrder , mPageTransformer);
11、下潛效果
讓所有左邊頁面都移動到 正中位置,切換頁面時,左邊頁面縮小淡出,右面頁面從右向左滑入正中。
/**
* 下潛效果
*/
public class DiveOutTransformer implements ViewPager.PageTransformer {
private static final float DEF_MIN_SCALE = 0.72f;
private float mMinScale = DEF_MIN_SCALE;
private static final float DEF_MIN_ALPHA = 0.5f;
public DiveOutTransformer() {
}
public DiveOutTransformer(float minScale) {
this.mMinScale = minScale;
}
public float getMinScale() {
return mMinScale;
}
public void setMinScale(float minScale) {
this.mMinScale = minScale;
}
@Override
public void transformPage(@NonNull View page, float position) {
if (position < -1f) {
page.setScaleX(mMinScale);
page.setScaleY(mMinScale);
page.setAlpha(DEF_MIN_ALPHA);
page.setTranslationX(-position * page.getWidth());
} else if (position <= 0) {
page.setTranslationX(-position * page.getWidth());
page.setScaleX(1f + (1f - mMinScale) * position);
page.setScaleY(1f + (1f - mMinScale) * position);
page.setAlpha(1f + (1f - DEF_MIN_ALPHA) * position);
} else {
page.setTranslationX(0f);
}
}
}
調用時不需要反轉繪製順序,即 reverseDrawingOrder = false,使右邊頁面先繪製,左邊頁面後繪製,否則效果無法實現。
int reverseDrawingOrder = false;
mVpImgs.setPageTransformer(reverseDrawingOrder , mPageTransformer);
12、堆疊效果
所有右邊頁面移動到正中位置,即 0 位置,滑動時,把最上面頁面滑掉,代碼如下所示:
/**
* 堆疊效果
*/
public class StackTransformer implements ViewPager.PageTransformer {
@Override
public void transformPage(@NonNull View page, float position) {
page.setTranslationX(position < 0 ? 0f : -page.getWidth() * position);
}
}
調用時需要反轉繪製順序,即 reverseDrawingOrder = true,使左邊頁面先繪製,右邊頁面後繪製,否則效果無法實現。
int reverseDrawingOrder = true;
mVpImgs.setPageTransformer(reverseDrawingOrder , mPageTransformer);
13、縮放效果
比較簡單,與淡入淡出效果類似,不多作解釋:
/**
* 縮放效果
*/
public class ZoomInOutTransformer implements ViewPager.PageTransformer {
private static final float DEF_MIN_SCALE = 0.9f;
private float mMinScale = DEF_MIN_SCALE;
@Override
public void transformPage(@NonNull View page, float position) {
if (position < -1f) {//[-Infinity, -1)
page.setScaleX(mMinScale);
page.setScaleY(mMinScale);
} else if (position <= 1f) {//[-1, 1]
if (position < 0f) {//[-1, 0)
page.setScaleX(1f + (1f - mMinScale) * position);
page.setScaleY(1f + (1f - mMinScale) * position);
} else { //[0, 1]
page.setScaleX(1f - (1f - mMinScale) * position);
page.setScaleY(1f - (1f - mMinScale) * position);
}
} else {//(1, +Infinity]
page.setScaleX(mMinScale);
page.setScaleY(mMinScale);
}
}
}
並行覆蓋效果
通過調用 View 的 setScrollX() 方法,使頁面內容隨着 position 移動。
public class ParallaxTransformer implements ViewPager.PageTransformer {
@Override
public void transformPage(@NonNull View page, float position) {
int width = page.getWidth();
if (position <= -1f) {
page.setScrollX(0);
} else if (position < 1f) {
if (position < 0f) {
page.setScrollX((int) (width * 0.75f * position));
} else {
page.setScrollX((int) (width * 0.75f * position));
}
} else {
page.setScrollX(0);
}
}
}
調用時需要反轉繪製順序,即 reverseDrawingOrder = true,使左邊頁面先繪製,右邊頁面後繪製,否則效果無法實現。
int reverseDrawingOrder = true;
mVpImgs.setPageTransformer(reverseDrawingOrder , mPageTransformer);
項目地址
https://github.com/wangzhengyangNo1/ViewPagerSerialDemo
4. 參考
【1】巧用ViewPager 打造不一樣的廣告輪播切換效果
【2】ViewPager 超詳解:玩出十八般花樣
【3】關於ViewPager.PageTransformer的一些理解
【4】一個卡片式的ViewPager,帶你玩轉ViewPager的PageTransformer屬性!