ViewPager with gradual Background Transition

Recently I came across one of those tutorial screens in a newly installed app. It would be quite ordinary use of the Android’s ViewPager if not for a smooth gradual background change while swiping between pages.

I searched the web but couldn’t find a nice solution anywhere, so I set off to create a solution of my own.

What are we creating

Before we dive into code, let’s show off the finished effect (the source and sample apk can be found at the bottom of the article):

</img>

As you can see, the background of the ViewPager animates gradually, so you can’t see the edges of adjacent views.

The basics

First we have to set up an Activity (in my case FragmentActivity for compatibility) with a ViewPager.

public class MainActivity extends FragmentActivity {

SectionsPagerAdapter mSectionsPagerAdapter;
ValueAnimator mColorAnimation;
ViewPager mViewPager;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
mViewPager = (ViewPager) findViewById(R.id.pager);
mViewPager.setAdapter(mSectionsPagerAdapter);
}
}

SectionsPagerAdapter is just a class extending the FragmentPagerAdapter which provides the ViewPager with placeholder fragments for each page.

Changing the background

Now that we have set up the foundation for out project, we can move on to actually changing the background of the ViewPager.

Changing the color is a matter of one method call:

mViewPager.setBackgroundColor(Color.BLACK);

The only remaining problems are:

Solution #1 - using the ValueAnimator

To determine the right color, which is a blend of Color A and Color B, I at first decided to use the ValueAnimator of ArgbEvaluator object.

ValueAnimator does all the job of calculating the correct color over time for you. You only have to pass the colors you want to iterate over - in my case 3, because my SectionsPagerAdapter returns only 3 pages.

mColorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), color1, color2, color3);

To change the background of the ViewPager each time the animation updates, we have to set a listener.

mColorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

@Override
public void onAnimationUpdate(ValueAnimator animator) {
mViewPager.setBackgroundColor((Integer)animator.getAnimatedValue());
}

});

And at last, we set the duration of our animation:

// (3 - 1) = number of pages minus 1
mColorAnimation.setDuration((3 - 1) * 10000000000l);

To explain the magic number 10000000000l, setDuration() method only accepts a long value, where as we’re going to be working with float values while calculation the position of pages in the ViewPager. To keep the precision, we will multiple the float values by this number, so that for instance number 0.9876543219 will be converted to 9876543219.

Normally when using ValueAnimator you would set the duration and start the animation by calling ValueAnimator#start(), but we’re only going to use the animator for determining the right color in each state of swiping between pages.


To calculate the color we need to know what is the ViewPager currently showing. To do that we create our listener that implements ViewPager.OnPageChangeListener.

private class CustomOnPageChangeListener implements ViewPager.OnPageChangeListener {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
mColorAnimation.setCurrentPlayTime((long)((positionOffset + position)* 10000000000l));
}
}

In previous section we set the duration of the animation for 3 pages to 2 * 10000000000. Method onPageScrolled() will recieve value for position parameter in a range of [0,3) and for positionOffset in [0,1) range.

An example: if position is 0 and positionOffset is equal to 0.98, it means that we’re swiping to/from second page and 98% of the first page is already hidden.

Therefore, by using the formula ((positionOffset + position) * 10000000000l)) we are moving on a timeline from 0 to 20000000000 (in this example).

The last step already showed in the code above is to call ValueAnimator#setCurrentPlayTime() which will set the current position of the animation and will also trigger an animation update which will call our listener that will update our ViewPager background.

Solution #2 - getting rid of the ValueAnimator

The ValueAnimator’s use wasn’t at all necessary. Using ArgbEvaluator directly will produce a cleaner code, but you’ll have to check for some boundary cases.

Each time we’re going to call ArgbEvaluator.evaluate(), we’re going to need to pass the 2 colors we want to blend. That means we have to keep an array of all the colors;

Integer[] colors = {color1, color2, color3};

Unlike in the first solution, we’ll set the color of the background right in the ViewPager.OnPageChangeListener.

@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

if(position < (mSectionsPagerAdapter.getCount() -1) && position < (colors.length - 1)) {

mViewPager.setBackgroundColor((Integer) argbEvaluator.evaluate(positionOffset, colors[position], colors[position + 1]));

} else {

mViewPager.setBackgroundColor(colors[colors.length - 1]);

}
}

As you can see, we have to check for the last page to avoid OutOfBoundsException. However we can skip all the hassle with converting floats to ints and setting the Animators length.

This approach will even work for cases where you supply an array with less colors than actual pages. For those pages the last color will always be set.

Sample App

If you’d like you can download a sample app to try the effect on your device.

Or view the source on GitHub.


Acknowledgements: In the sample app, PagerSlidingTabStrip was used for the ViewPager tabs.


comments powered by Disqus