A Rotating Page Transformer

This adventure in Android came about as part of my work for Chatous.  As part of a visual redesign, I wanted to create the effect of cards rotating to animate from one to the other.  This is a bit hard to describe, so I recommend checking out this video to see what I’m talking about.

Rotating Page Transformer Demo from Steven Kideckel on Vimeo.

O20140406_215120ne thing I really liked about this project was that it gave me a chance to do some math.  It was simply trigonometry that I’d learned in high school, but it was still nice to have an application for this stuff.  You can see some of my work on the side, but the underlying question was this:

For a regular polygon with side length x and inner degrees all equal to d, what is the distance from an outer edge to the centre in terms of x?

On the right you can see what this shape looks like for an inner degree of 160, this shape has 18 sides.  By drawing lines from a vertex and the middle of the edge to the centre, we form a right triangle.  We can then use the properties of right triangles to find that this distance is generally equal to x \cdot \frac{tan(d/2)}{2}.  This will be useful later.

The key to achieving the effect is to use a PageTransformer attached to a ViewPager.  This is part of the support package, but since it uses property animation, it will only work with Honeycomb (Api 11) and higher.  There are two notable aspects to the transformation: 1) as views shift, they rotate in and out, and 2) the views are also shifted over, so that they appear on the edges of the centred view.

The latter effect is achieved by setting padding on the Fragments shown in the ViewPager and using a negative value for the setPageMargin function of ViewPager.  I derived my implementation of this from a post by Paul Lammertsma, and hats off to him for making this simple and elegant solution.

To create the rotation involves using the math above.  The centre of the imaginary polygon, upon whose edges these views are resting, is given by that expression, and is used as the pivot centre for our rotation.  The full code of my PageTransformer class is this

package com.example.animalflashcards;

import android.annotation.TargetApi;
import android.os.Build;
import android.support.v4.view.ViewPager;
import android.view.View;
/**
 * This page transformer will create the effect that each page is an edge of a regular polygon
 * that is rotated to show each page. Can only be used on devices with Honeycomb or higher api
 *
 * Example usage:
 *
 *
<pre> 
 * if(BUILD.VERSION.SDK_INT >= BUILD.VERSION_CODES.HONEYCOMB){
 * 	mPager.setPageTransformer(true, new RotationPageTransformer(DEGREES_BETWEEN_CARDS));
 * 	mPager.setOffscreenPageLimit(mPagerAdapter.getCount());
 * 	mPager.setPageMargin(-2 * paddingOnPages);
 * 	mPager.setClipChildren(false);
 * }
</pre>
*
 * @author Steven Kideckel
 */
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public class RotationPageTransformer implements ViewPager.PageTransformer{
	private float minAlpha;
	private int degrees;
	private float distanceToCentreFactor;

	/**
	 * Creates a RotationPageTransformer
	 * @param degrees the inner angle between two edges in the "polygon" that the pages are on.
	 * Note, this will only work with an obtuse angle
	 */
	public RotationPageTransformer(int degrees){
		this(degrees, 0.7f);
	}

	/**
	 * Creates a RotationPageTransformer
	 * @param degrees the inner angle between two edges in the "polygon" that the pages are on.
	 * Note, this will only work with an obtuse angle
	 * @param minAlpha the least faded out that the side
	 */
	public RotationPageTransformer(int degrees, float minAlpha){
		this.degrees = degrees;
		distanceToCentreFactor = (float) Math.tan(Math.toRadians(degrees / 2))/2;
		this.minAlpha = minAlpha;
	}

	public void transformPage(View view, float position){
		int pageWidth = view.getWidth();
		int pageHeight = view.getHeight();
		view.setPivotX((float) pageWidth / 2);
		view.setPivotY((float) (pageHeight + pageWidth * distanceToCentreFactor)); 

		if(position < -1){ //[-infinity,1)
			//off to the left by a lot
			view.setRotation(0);
			view.setAlpha(0);
		}else if(position <= 1){ //[-1,1]
			view.setTranslationX((-position) * pageWidth); //shift the view over
			view.setRotation(position * (180 - degrees)); //rotate it
			// Fade the page relative to its distance from the center
			view.setAlpha(Math.max(minAlpha, 1 - Math.abs(position)/3));
		}else{ //(1, +infinity]
			//off to the right by a lot
			view.setRotation(0);
			view.setAlpha(0);
		}
	}
}

The full source code and project for the sample application can be found at https://github.com/steviek/AnimalFlashCards

 

 

Advertisements