ViewPager的PageTransformer 实现各种Page变换动画效果分析

一,PageTransformer 的简单介绍
从Android 3.0开始,ViewPager提供了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.
 *
 * <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.
      */
     public void transformPage(View page, float position);
}

从上面可以看出ViewPager实现各种Page页面变换的动画效果只要实现transformPage这一个方法即可,其中:
(1)page
用来表示当前被滑动的页面所对应的view,通过实际的使用会发现,滑动的时候其返回的page并不都是同一个对象,因为滑动不仅导致当前界面慢慢滑出屏幕,同时导致相关的新界面慢慢滑入屏幕,下面是通过源码查看其被调用的地方:

protected void onPageScrolled(int position, float offset, int offsetPixels) {
     ...
     if (mPageTransformer != null) {
         final int scrollX = getScrollX();
         final int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
             final View child = getChildAt(i);
             final LayoutParams lp = (LayoutParams) child.getLayoutParams();
             if (lp.isDecor) continue;
             final float transformPos = (float) (child.getLeft() - scrollX) / getClientWidth();
             mPageTransformer.transformPage(child, transformPos);
         }
     }
     ...
}

可以看出,每次滑动时,都会依次取ViewPager中每个child然后调用transformPage方法,验证了上面我们说明的每次滑动时返回的page是可能不同的。
(2)position
给定界面的位置相对于屏幕中心的偏移量。在用户滑动界面的时候,是动态变化的。假设ViewPager中有A,B,C页面,当前停留在B界面,则B界面此时的position为0,A界面的position为-1,C界面的position为1,而后向左滑动界面(C -> B),此过程中A的position在区间(-Infinity,-1)变化,B的position在区间[-1,0)变化,C的position在区间[0,1)变化,并且会发现B的position跟C的position绝对值之和为1,而正负则表示了滑入和滑出状态。理解清楚了position的变化过程对后面如何利用PageTransformer实现各种变化是很有用的。
当应用实现好了PageTransformer接口后,就可以通过ViewPager提供的setPageTransformer()设置进去。

    public void setPageTransformer(boolean reverseDrawingOrder, PageTransformer transformer)

reverseDrawingOrder用来指定是否要改变page的绘制顺序,在getChildDrawingOrder()时会用到,true则表示从最后一个开始绘制。
通过对position的介绍可知,我们在实现PageTransformer时主要还是关注[-1,1] 区间的值。
首先拿最简单的AlphaPageTransformer变换来说,其实现如下:

public class AlphaPageTransformer implements ViewPager.PageTransformer {
    private float mMinAlpha = 0.5f;

    public void transformPage(View view, float position) {
        if (position < -1) {
            view.setAlpha(mMinAlpha);
        } else if (position <= 0) {
            float factor = mMinAlpha + (1 - mMinAlpha) * (1 + position);
            view.setAlpha(factor);
        } else if(position <= 1){
            float factor = mMinAlpha + (1 - mMinAlpha) * (1 - position);
            view.setAlpha(factor);
        } else {
            view.setAlpha(mMinAlpha);
        }
    }
}

可以总结出一套通用简单的规律:

public class DepthPageTransformer implements ViewPager.PageTransformer {
    public void transformPage(View view, float position) {
        int pageWidth = view.getWidth();

        if (position < -1) {
           //具体实现,此时page在界面的左边并且已经不显示在当前界面
        } else if (position <= 0) {
            //具体实现,此时page正从中间往左侧移动
        } else if (position <= 1) {
           //具体实现,此时page正从右侧往中间移动
        } else {
          //具体实现,此时page在界面的右边并且已经不显示在当前界面
        }
    }
}

三,如何实现视差滚动效果
视差滚动效果也是最近几年在手机上比较流行,一般应用场景是首次启动应用时的导航界面,根据上面的规律,结合自己的效果实现动画就可以了。只需在PageTransformer中先找到相关的view,然后根据position设置不同的水平平移距离即可,大致的伪代码如下所示:

public void transformPage(View view, float position) {
    int pageWidth = view.getWidth();

    if (position < -1) { // [-Infinity,-1)
        // This page is way off-screen to the left.
        view.setAlpha(0);

    } else if (position <= 1) { // [-1,1]

        mBlur.setTranslationX((float) (-(1 - position) * 0.5 * pageWidth));
        mBlurLabel.setTranslationX((float) (-(1 - position) * 0.5 * pageWidth));

        mDim.setTranslationX((float) (-(1 - position) * pageWidth));
        mDimLabel.setTranslationX((float) (-(1 - position) * pageWidth));

        mCheck.setTranslationX((float) (-(1 - position) * 1.5 * pageWidth));
        mDoneButton.setTranslationX((float) (-(1 - position) * 1.7 * pageWidth)); 
        // The 0.5, 1.5, 1.7 values you see here are what makes the view move in a different speed.
        // The bigger the number, the faster the view will translate.
        // The result float is preceded by a minus because the views travel in the opposite direction of the movement.

        mFirstColor.setTranslationX((position) * (pageWidth / 4));

        mSecondColor.setTranslationX((position) * (pageWidth / 1));

        mTint.setTranslationX((position) * (pageWidth / 2));

        mDesaturate.setTranslationX((position) * (pageWidth / 1));
        // This is another way to do it


    } else { // (1,+Infinity]
        // This page is way off-screen to the right.
        view.setAlpha(0);
    }
}

四,与OnPageChangeListener的不同
OnPageChangeListener是很早就在ViewPager上提供的接口,其主要提供了在Page滑动时的一些回调,方便应用进行相关逻辑处理。

   /**
     * Callback interface for responding to changing state of the selected page.
     */
    public interface OnPageChangeListener {

        /**
         * This method will be invoked when the current page is scrolled, either as part
         * of a programmatically initiated smooth scroll or a user initiated touch scroll.
         *
         * @param position Position index of the first page currently being displayed.
         *                 Page position+1 will be visible if positionOffset is nonzero.
         * @param positionOffset Value from [0, 1) indicating the offset from the page at position.
         * @param positionOffsetPixels Value in pixels indicating the offset from position.
         */
        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels);

        /**
         * This method will be invoked when a new page becomes selected. Animation is not
         * necessarily complete.
         *
         * @param position Position index of the new selected page.
         */
        public void onPageSelected(int position);

        /**
         * Called when the scroll state changes. Useful for discovering when the user
         * begins dragging, when the pager is automatically settling to the current page,
         * or when it is fully stopped/idle.
         *
         * @param state The new scroll state.
         * @see ViewPager#SCROLL_STATE_IDLE
         * @see ViewPager#SCROLL_STATE_DRAGGING
         * @see ViewPager#SCROLL_STATE_SETTLING
         */
        public void onPageScrollStateChanged(int state);
    }

里面的onPageScrolled()方法也会在page滚动时会被实时调用,但是里面的position只是当前正在滚动页所在的position。按照上节动画的实现过程,一般来说,需要先找当前页和相邻页,然后根据偏移量对其添加动画,而onPageScrolled并不能提供所有相关页的信息,仅仅只是当前页的position,如果需要知道相邻页,就需要额外采取一些手段,更加详细的介绍可以参考http://blog.csdn.net/lmj623565791/article/details/38026503 里面的说明。当然如果非要通过OnPageChangeListener来实现以上的动画效果,也是可以的,这方面最出名的开源项目要数JazzyViewPager(https://github.com/jfeinstein10/JazzyViewPager) ,作者会先在ViewPager的Adapter中维护一个position与page对应关系的HashMap,然后在onPageScrolled()方法中就可以根据这个HashMap很方便的找到当前页以及相邻的页面,从而进行相关动画的设置。
正是因为通过onPageScrolled()来实现动画比较困难,所以后来ViewPager提供了PageTransformer ,更加方便应用来实现各种动画效果
相关参考文档:
http://blog.csdn.net/lmj623565791/article/details/38026503
http://blog.csdn.net/lmj623565791/article/details/51339751
http://www.lightskystreet.com/2014/12/15/viewpager-anim/
http://ryanhoo.github.io/blog/2014/07/16/step-by-step-implement-parallax-animation-for-splash-screen-of-zhihu/

各种炫酷的page变换效果
https://github.com/hongyangAndroid/MagicViewPager
https://github.com/daimajia/AndroidImageSlider
https://github.com/jfeinstein10/JazzyViewPager

各种视差滚动效果
https://github.com/Cleveroad/slidingtutorial-android
https://github.com/stephentuso/welcome-android
https://github.com/MoshDev/BackgroundViewPager
https://github.com/andraskindler/parallaxviewpager

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值