Solving advanced animation problems


SDK Version: 
M3

As we have seen in the previous animations tutorial it is easy to create some funny animations defined by XML.

Unfortunately in some cases we face troublesome limitations. Lest see them through an example:
We will create a simple animation that moves a buttom from the top left corner of the screen to the center when it is clicked, then remains there, and when clicked again it moves back to its original position

After you read the documentation http://developer.android.com/guide/topics/resources/animation-resource.h... it may seem easy to solve. We can define the animation realative to the parents size in percent (50%p), and the fillAfter attribute makes the button remain there, insted of jumping back to its original position. We can even define all of this in XML. So most of the problem seems to be solved with the few line of XML below:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <translate xmlns:android="http://schemas.android.com/apk/res/android"
  3.     android:fromXDelta="0%p" android:fromYDelta="0%p"
  4.     android:toXDelta="50%p" android:toYDelta="50%p"
  5.     android:duration="1000"
  6.     android:fillAfter="true" />

But when you try this out, you will face two problems. First, the button will not be properly centered, the buttons top left corner will be in the center. Second, after it moved to the center it wont be clickable any more. The clickable area of the button does not move with the animation, if you try to click at the original position of the button you will be able to trigger the onclick again. Unfortunatelly such a simple code, like the xml above will not be anough to fix this difficulties.

To properly center the button we must compute where the buttons top left corner should be. We when the button is clicked get the buttons size, an its parent views size, compute where it should move, and create the proper animation by code. Somehow we have to track where the button is, if it is already in the middle, we have to reverse the animation. This can be done like this:

  1. public void onCreate(Bundle savedInstanceState) {
  2.   super.onCreate(savedInstanceState);
  3.   setContentView(R.layout.main);
  4.   final Button button = (Button) findViewById(R.id.button);
  5.   final RelativeLayout mainContainer = (RelativeLayout) findViewById(R.id.mainContainer);
  6.  
  7.   button.setOnClickListener(new OnClickListener() {
  8.     @Override
  9.     public void onClick(View v) {
  10.       //have to get the sizes here instead of onCreate, in onCreate size is not computed yet
  11.       int buttonHeight = button.getHeight();
  12.       int parentHeight = mainContainer.getHeight();
  13.       int buttonWidth = button.getWidth ();
  14.       int parentWidth  = mainContainer.getWidth();
  15.      
  16.       //detect if have to move to center, or already there and have to move back
  17.       boolean isInCenter = button.getLeft()>0;
  18.       final int fromX = isInCenter?(parentWidth/2)-(buttonWidth/2):0;
  19.       final int fromY = isInCenter?(parentHeight/2)-(buttonHeight/2):0;
  20.       final int toX = isInCenter?0:(parentWidth/2)-(buttonWidth/2);
  21.       final int toY = isInCenter?0:(parentHeight/2)-(buttonHeight/2);
  22.      
  23.       //create the animation
  24.       TranslateAnimation moveAnim = new TranslateAnimation(
  25.           TranslateAnimation.ABSOLUTE, fromX, TranslateAnimation.ABSOLUTE, toX,
  26.           TranslateAnimation.ABSOLUTE, fromY, TranslateAnimation.ABSOLUTE, toY);
  27.       moveAnim.setDuration(1000);
  28.       moveAnim.setFillAfter(true);

To solve the clickinkig problem we have to really move the button when the animation ended. This can be done by setting up an animation listener to detect the end of the animation. In the listener we set up margins to do the move. Because of this we have to remove this margins the naxt time we animate button or, the margins will mess it up. So we also add a listener to detect the start of the animation, and remove the margins there.

  1. moveAnim.setAnimationListener(new AnimationListener() {
  2.   @Override
  3.   public void onAnimationEnd(Animation animation) {
  4.     //when the animation ended the button is really moved, to remain clickable
  5.     LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
  6.     params.leftMargin = toX;
  7.     params.topMargin = toY;
  8.     button.setLayoutParams(params);
  9.     //remove the animation, its no longer needed, since button is really there
  10.     button.clearAnimation();
  11.   }
  12.  
  13.   @Override
  14.   public void onAnimationStart(Animation animation) {
  15.     //remove the margins used to really move the button, or it messes up the animation
  16.     LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
  17.     params.leftMargin = 0;
  18.     params.topMargin = 0;
  19.     button.setLayoutParams(params);
  20.   }
  21.  
  22.   @Override
  23.   public void onAnimationRepeat(Animation animation) {
  24.     //do nothing
  25.   }
  26. });

A complete project to try this out can be downloaded here.