Home | History | Annotate | Download | only in editor
      1 /*
      2  * Copyright (C) 2012 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.contacts.editor;
     18 
     19 import android.animation.Animator;
     20 import android.animation.Animator.AnimatorListener;
     21 import android.animation.AnimatorListenerAdapter;
     22 import android.animation.AnimatorSet;
     23 import android.animation.ObjectAnimator;
     24 import android.view.View;
     25 import android.view.ViewGroup;
     26 import android.view.ViewParent;
     27 import android.widget.LinearLayout;
     28 
     29 import com.android.contacts.util.SchedulingUtils;
     30 import com.google.common.collect.Lists;
     31 
     32 import java.util.List;
     33 
     34 /**
     35  * Configures animations for typical use-cases
     36  */
     37 public class EditorAnimator {
     38     private static EditorAnimator sInstance = new EditorAnimator();
     39 
     40     public static  EditorAnimator getInstance() {
     41         return sInstance;
     42     }
     43 
     44     /** Private constructor for singleton */
     45     private EditorAnimator() { }
     46 
     47     private AnimatorRunner mRunner = new AnimatorRunner();
     48 
     49     public void removeEditorView(final View victim) {
     50         mRunner.endOldAnimation();
     51         final int offset = victim.getHeight();
     52 
     53         final List<View> viewsToMove = getViewsBelowOf(victim);
     54         final List<Animator> animators = Lists.newArrayList();
     55 
     56         // Fade out
     57         final ObjectAnimator fadeOutAnimator =
     58                 ObjectAnimator.ofFloat(victim, View.ALPHA, 1.0f, 0.0f);
     59         fadeOutAnimator.setDuration(200);
     60         animators.add(fadeOutAnimator);
     61 
     62         // Translations
     63         translateViews(animators, viewsToMove, 0.0f, -offset, 100, 200);
     64 
     65         mRunner.run(animators, new AnimatorListenerAdapter() {
     66             @Override
     67             public void onAnimationEnd(Animator animation) {
     68                 // Clean up: Remove all the translations
     69                 for (int i = 0; i < viewsToMove.size(); i++) {
     70                     final View view = viewsToMove.get(i);
     71                     view.setTranslationY(0.0f);
     72                 }
     73                 // Remove our target view (if parent is null, we were run several times by quick
     74                 // fingers. Just ignore)
     75                 final ViewGroup victimParent = (ViewGroup) victim.getParent();
     76                 if (victimParent != null) {
     77                     victimParent.removeView(victim);
     78                 }
     79             }
     80         });
     81     }
     82 
     83     public void expandOrganization(final View addOrganizationButton,
     84             final ViewGroup organizationSectionViewContainer) {
     85         mRunner.endOldAnimation();
     86         // Make the new controls visible and do one layout pass (so that we can measure)
     87         organizationSectionViewContainer.setVisibility(View.VISIBLE);
     88         organizationSectionViewContainer.setAlpha(0.0f);
     89         organizationSectionViewContainer.requestFocus();
     90         SchedulingUtils.doAfterLayout(addOrganizationButton, new Runnable() {
     91             @Override
     92             public void run() {
     93                 // How many pixels extra do we need?
     94                 final int offset = organizationSectionViewContainer.getHeight() -
     95                         addOrganizationButton.getHeight();
     96 
     97                 final List<Animator> animators = Lists.newArrayList();
     98 
     99                 // Fade out
    100                 final ObjectAnimator fadeOutAnimator = ObjectAnimator.ofFloat(
    101                         addOrganizationButton, View.ALPHA, 1.0f, 0.0f);
    102                 fadeOutAnimator.setDuration(200);
    103                 animators.add(fadeOutAnimator);
    104 
    105                 // Translations
    106                 final List<View> viewsToMove = getViewsBelowOf(organizationSectionViewContainer);
    107                 translateViews(animators, viewsToMove, -offset, 0.0f, 0, 200);
    108 
    109                 // Fade in
    110                 final ObjectAnimator fadeInAnimator = ObjectAnimator.ofFloat(
    111                         organizationSectionViewContainer, View.ALPHA, 0.0f, 1.0f);
    112                 fadeInAnimator.setDuration(200);
    113                 fadeInAnimator.setStartDelay(200);
    114                 animators.add(fadeInAnimator);
    115 
    116                 mRunner.run(animators);
    117             }
    118         });
    119     }
    120 
    121     public void showAddFieldFooter(final View view) {
    122         mRunner.endOldAnimation();
    123         if (view.getVisibility() == View.VISIBLE) return;
    124         // Make the new controls visible and do one layout pass (so that we can measure)
    125         view.setVisibility(View.VISIBLE);
    126         view.setAlpha(0.0f);
    127         SchedulingUtils.doAfterLayout(view, new Runnable() {
    128             @Override
    129             public void run() {
    130                 // How many pixels extra do we need?
    131                 final int offset = view.getHeight();
    132 
    133                 final List<Animator> animators = Lists.newArrayList();
    134 
    135                 // Translations
    136                 final List<View> viewsToMove = getViewsBelowOf(view);
    137                 translateViews(animators, viewsToMove, -offset, 0.0f, 0, 200);
    138 
    139                 // Fade in
    140                 final ObjectAnimator fadeInAnimator = ObjectAnimator.ofFloat(
    141                         view, View.ALPHA, 0.0f, 1.0f);
    142                 fadeInAnimator.setDuration(200);
    143                 fadeInAnimator.setStartDelay(200);
    144                 animators.add(fadeInAnimator);
    145 
    146                 mRunner.run(animators);
    147             }
    148         });
    149     }
    150 
    151     public void hideAddFieldFooter(final View victim) {
    152         mRunner.endOldAnimation();
    153         if (victim.getVisibility() == View.GONE) return;
    154         final int offset = victim.getHeight();
    155 
    156         final List<View> viewsToMove = getViewsBelowOf(victim);
    157         final List<Animator> animators = Lists.newArrayList();
    158 
    159         // Fade out
    160         final ObjectAnimator fadeOutAnimator =
    161                 ObjectAnimator.ofFloat(victim, View.ALPHA, 1.0f, 0.0f);
    162         fadeOutAnimator.setDuration(200);
    163         animators.add(fadeOutAnimator);
    164 
    165         // Translations
    166         translateViews(animators, viewsToMove, 0.0f, -offset, 100, 200);
    167 
    168         // Combine
    169         mRunner.run(animators, new AnimatorListenerAdapter() {
    170             @Override
    171             public void onAnimationEnd(Animator animation) {
    172                 // Clean up: Remove all the translations
    173                 for (int i = 0; i < viewsToMove.size(); i++) {
    174                     final View view = viewsToMove.get(i);
    175                     view.setTranslationY(0.0f);
    176                 }
    177 
    178                 // Restore alpha (for next time), but hide the view for good now
    179                 victim.setAlpha(1.0f);
    180                 victim.setVisibility(View.GONE);
    181             }
    182         });
    183     }
    184 
    185     /**
    186      * Creates a translation-animation for the given views
    187      */
    188     private static void translateViews(List<Animator> animators, List<View> views, float fromY,
    189             float toY, int startDelay, int duration) {
    190         for (int i = 0; i < views.size(); i++) {
    191             final View child = views.get(i);
    192             final ObjectAnimator translateAnimator =
    193                     ObjectAnimator.ofFloat(child, View.TRANSLATION_Y, fromY, toY);
    194             translateAnimator.setStartDelay(startDelay);
    195             translateAnimator.setDuration(duration);
    196             animators.add(translateAnimator);
    197         }
    198     }
    199 
    200     /**
    201      * Traverses up the view hierarchy and returns all views below this item. Stops
    202      * once a parent is not a vertical LinearLayout
    203      *
    204      * @return List of views that are below the given view. Empty list if parent of view is null.
    205      */
    206     private static List<View> getViewsBelowOf(View view) {
    207         final ViewGroup victimParent = (ViewGroup) view.getParent();
    208         final List<View> result = Lists.newArrayList();
    209         if (victimParent != null) {
    210             final int index = victimParent.indexOfChild(view);
    211             getViewsBelowOfRecursive(result, victimParent, index + 1);
    212         }
    213         return result;
    214     }
    215 
    216     private static void getViewsBelowOfRecursive(List<View> result, ViewGroup container,
    217             int index) {
    218         for (int i = index; i < container.getChildCount(); i++) {
    219             result.add(container.getChildAt(i));
    220         }
    221 
    222         final ViewParent parent = container.getParent();
    223         if (parent instanceof LinearLayout) {
    224             final LinearLayout parentLayout = (LinearLayout) parent;
    225             if (parentLayout.getOrientation() == LinearLayout.VERTICAL) {
    226                 int containerIndex = parentLayout.indexOfChild(container);
    227                 getViewsBelowOfRecursive(result, parentLayout, containerIndex+1);
    228             }
    229         }
    230     }
    231 
    232     /**
    233      * Keeps a reference to the last animator, so that we can end that early if the user
    234      * quickly pushes buttons. Removes the reference once the animation has finished
    235      */
    236     /* package */ static class AnimatorRunner extends AnimatorListenerAdapter {
    237         private Animator mLastAnimator;
    238 
    239         @Override
    240         public void onAnimationEnd(Animator animation) {
    241             mLastAnimator = null;
    242         }
    243 
    244         public void run(List<Animator> animators) {
    245             run(animators, null);
    246         }
    247 
    248         public void run(List<Animator> animators, AnimatorListener listener) {
    249             final AnimatorSet set = new AnimatorSet();
    250             set.playTogether(animators);
    251             if (listener != null) set.addListener(listener);
    252             set.addListener(this);
    253             mLastAnimator = set;
    254             set.start();
    255         }
    256 
    257         public void endOldAnimation() {
    258             if (mLastAnimator != null) {
    259                 mLastAnimator.end();
    260             }
    261         }
    262     }
    263 }
    264