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

来源:互联网 发布:数据库建库 编辑:程序博客网 时间:2024/06/14 09:53

一,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

0 0