Home | History | Annotate | Download | only in transition
      1 /*
      2  * Copyright (C) 2013 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 android.transition;
     18 
     19 import android.animation.Animator;
     20 import android.animation.AnimatorListenerAdapter;
     21 import android.animation.ObjectAnimator;
     22 import android.animation.PropertyValuesHolder;
     23 import android.animation.RectEvaluator;
     24 import android.graphics.Bitmap;
     25 import android.graphics.Canvas;
     26 import android.graphics.Rect;
     27 import android.graphics.drawable.BitmapDrawable;
     28 import android.view.View;
     29 import android.view.ViewGroup;
     30 
     31 import java.util.Map;
     32 
     33 /**
     34  * This transition captures the layout bounds of target views before and after
     35  * the scene change and animates those changes during the transition.
     36  *
     37  * <p>A ChangeBounds transition can be described in a resource file by using the
     38  * tag <code>changeBounds</code>, along with the other standard
     39  * attributes of {@link android.R.styleable#Transition}.</p>
     40  */
     41 public class ChangeBounds extends Transition {
     42 
     43     private static final String PROPNAME_BOUNDS = "android:changeBounds:bounds";
     44     private static final String PROPNAME_PARENT = "android:changeBounds:parent";
     45     private static final String PROPNAME_WINDOW_X = "android:changeBounds:windowX";
     46     private static final String PROPNAME_WINDOW_Y = "android:changeBounds:windowY";
     47     private static final String[] sTransitionProperties = {
     48             PROPNAME_BOUNDS,
     49             PROPNAME_PARENT,
     50             PROPNAME_WINDOW_X,
     51             PROPNAME_WINDOW_Y
     52     };
     53 
     54     int[] tempLocation = new int[2];
     55     boolean mResizeClip = false;
     56     boolean mReparent = false;
     57     private static final String LOG_TAG = "ChangeBounds";
     58 
     59     private static RectEvaluator sRectEvaluator = new RectEvaluator();
     60 
     61     @Override
     62     public String[] getTransitionProperties() {
     63         return sTransitionProperties;
     64     }
     65 
     66     public void setResizeClip(boolean resizeClip) {
     67         mResizeClip = resizeClip;
     68     }
     69 
     70     /**
     71      * Setting this flag tells ChangeBounds to track the before/after parent
     72      * of every view using this transition. The flag is not enabled by
     73      * default because it requires the parent instances to be the same
     74      * in the two scenes or else all parents must use ids to allow
     75      * the transition to determine which parents are the same.
     76      *
     77      * @param reparent true if the transition should track the parent
     78      * container of target views and animate parent changes.
     79      */
     80     public void setReparent(boolean reparent) {
     81         mReparent = reparent;
     82     }
     83 
     84     private void captureValues(TransitionValues values) {
     85         View view = values.view;
     86         values.values.put(PROPNAME_BOUNDS, new Rect(view.getLeft(), view.getTop(),
     87                 view.getRight(), view.getBottom()));
     88         values.values.put(PROPNAME_PARENT, values.view.getParent());
     89         values.view.getLocationInWindow(tempLocation);
     90         values.values.put(PROPNAME_WINDOW_X, tempLocation[0]);
     91         values.values.put(PROPNAME_WINDOW_Y, tempLocation[1]);
     92     }
     93 
     94     @Override
     95     public void captureStartValues(TransitionValues transitionValues) {
     96         captureValues(transitionValues);
     97     }
     98 
     99     @Override
    100     public void captureEndValues(TransitionValues transitionValues) {
    101         captureValues(transitionValues);
    102     }
    103 
    104     @Override
    105     public Animator createAnimator(final ViewGroup sceneRoot, TransitionValues startValues,
    106             TransitionValues endValues) {
    107         if (startValues == null || endValues == null) {
    108             return null;
    109         }
    110         Map<String, Object> startParentVals = startValues.values;
    111         Map<String, Object> endParentVals = endValues.values;
    112         ViewGroup startParent = (ViewGroup) startParentVals.get(PROPNAME_PARENT);
    113         ViewGroup endParent = (ViewGroup) endParentVals.get(PROPNAME_PARENT);
    114         if (startParent == null || endParent == null) {
    115             return null;
    116         }
    117         final View view = endValues.view;
    118         boolean parentsEqual = (startParent == endParent) ||
    119                 (startParent.getId() == endParent.getId());
    120         // TODO: Might want reparenting to be separate/subclass transition, or at least
    121         // triggered by a property on ChangeBounds. Otherwise, we're forcing the requirement that
    122         // all parents in layouts have IDs to avoid layout-inflation resulting in a side-effect
    123         // of reparenting the views.
    124         if (!mReparent || parentsEqual) {
    125             Rect startBounds = (Rect) startValues.values.get(PROPNAME_BOUNDS);
    126             Rect endBounds = (Rect) endValues.values.get(PROPNAME_BOUNDS);
    127             int startLeft = startBounds.left;
    128             int endLeft = endBounds.left;
    129             int startTop = startBounds.top;
    130             int endTop = endBounds.top;
    131             int startRight = startBounds.right;
    132             int endRight = endBounds.right;
    133             int startBottom = startBounds.bottom;
    134             int endBottom = endBounds.bottom;
    135             int startWidth = startRight - startLeft;
    136             int startHeight = startBottom - startTop;
    137             int endWidth = endRight - endLeft;
    138             int endHeight = endBottom - endTop;
    139             int numChanges = 0;
    140             if (startWidth != 0 && startHeight != 0 && endWidth != 0 && endHeight != 0) {
    141                 if (startLeft != endLeft) ++numChanges;
    142                 if (startTop != endTop) ++numChanges;
    143                 if (startRight != endRight) ++numChanges;
    144                 if (startBottom != endBottom) ++numChanges;
    145             }
    146             if (numChanges > 0) {
    147                 if (!mResizeClip) {
    148                     PropertyValuesHolder pvh[] = new PropertyValuesHolder[numChanges];
    149                     int pvhIndex = 0;
    150                     if (startLeft != endLeft) view.setLeft(startLeft);
    151                     if (startTop != endTop) view.setTop(startTop);
    152                     if (startRight != endRight) view.setRight(startRight);
    153                     if (startBottom != endBottom) view.setBottom(startBottom);
    154                     if (startLeft != endLeft) {
    155                         pvh[pvhIndex++] = PropertyValuesHolder.ofInt("left", startLeft, endLeft);
    156                     }
    157                     if (startTop != endTop) {
    158                         pvh[pvhIndex++] = PropertyValuesHolder.ofInt("top", startTop, endTop);
    159                     }
    160                     if (startRight != endRight) {
    161                         pvh[pvhIndex++] = PropertyValuesHolder.ofInt("right",
    162                                 startRight, endRight);
    163                     }
    164                     if (startBottom != endBottom) {
    165                         pvh[pvhIndex++] = PropertyValuesHolder.ofInt("bottom",
    166                                 startBottom, endBottom);
    167                     }
    168                     ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(view, pvh);
    169                     if (view.getParent() instanceof ViewGroup) {
    170                         final ViewGroup parent = (ViewGroup) view.getParent();
    171                         parent.suppressLayout(true);
    172                         TransitionListener transitionListener = new TransitionListenerAdapter() {
    173                             boolean mCanceled = false;
    174 
    175                             @Override
    176                             public void onTransitionCancel(Transition transition) {
    177                                 parent.suppressLayout(false);
    178                                 mCanceled = true;
    179                             }
    180 
    181                             @Override
    182                             public void onTransitionEnd(Transition transition) {
    183                                 if (!mCanceled) {
    184                                     parent.suppressLayout(false);
    185                                 }
    186                             }
    187 
    188                             @Override
    189                             public void onTransitionPause(Transition transition) {
    190                                 parent.suppressLayout(false);
    191                             }
    192 
    193                             @Override
    194                             public void onTransitionResume(Transition transition) {
    195                                 parent.suppressLayout(true);
    196                             }
    197                         };
    198                         addListener(transitionListener);
    199                     }
    200                     return anim;
    201                 } else {
    202                     if (startWidth != endWidth) view.setRight(endLeft +
    203                             Math.max(startWidth, endWidth));
    204                     if (startHeight != endHeight) view.setBottom(endTop +
    205                             Math.max(startHeight, endHeight));
    206                     // TODO: don't clobber TX/TY
    207                     if (startLeft != endLeft) view.setTranslationX(startLeft - endLeft);
    208                     if (startTop != endTop) view.setTranslationY(startTop - endTop);
    209                     // Animate location with translationX/Y and size with clip bounds
    210                     float transXDelta = endLeft - startLeft;
    211                     float transYDelta = endTop - startTop;
    212                     int widthDelta = endWidth - startWidth;
    213                     int heightDelta = endHeight - startHeight;
    214                     numChanges = 0;
    215                     if (transXDelta != 0) numChanges++;
    216                     if (transYDelta != 0) numChanges++;
    217                     if (widthDelta != 0 || heightDelta != 0) numChanges++;
    218                     PropertyValuesHolder pvh[] = new PropertyValuesHolder[numChanges];
    219                     int pvhIndex = 0;
    220                     if (transXDelta != 0) {
    221                         pvh[pvhIndex++] = PropertyValuesHolder.ofFloat("translationX",
    222                                 view.getTranslationX(), 0);
    223                     }
    224                     if (transYDelta != 0) {
    225                         pvh[pvhIndex++] = PropertyValuesHolder.ofFloat("translationY",
    226                                 view.getTranslationY(), 0);
    227                     }
    228                     if (widthDelta != 0 || heightDelta != 0) {
    229                         Rect tempStartBounds = new Rect(0, 0, startWidth, startHeight);
    230                         Rect tempEndBounds = new Rect(0, 0, endWidth, endHeight);
    231                         pvh[pvhIndex++] = PropertyValuesHolder.ofObject("clipBounds",
    232                                 sRectEvaluator, tempStartBounds, tempEndBounds);
    233                     }
    234                     ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(view, pvh);
    235                     if (view.getParent() instanceof ViewGroup) {
    236                         final ViewGroup parent = (ViewGroup) view.getParent();
    237                         parent.suppressLayout(true);
    238                         TransitionListener transitionListener = new TransitionListenerAdapter() {
    239                             boolean mCanceled = false;
    240 
    241                             @Override
    242                             public void onTransitionCancel(Transition transition) {
    243                                 parent.suppressLayout(false);
    244                                 mCanceled = true;
    245                             }
    246 
    247                             @Override
    248                             public void onTransitionEnd(Transition transition) {
    249                                 if (!mCanceled) {
    250                                     parent.suppressLayout(false);
    251                                 }
    252                             }
    253 
    254                             @Override
    255                             public void onTransitionPause(Transition transition) {
    256                                 parent.suppressLayout(false);
    257                             }
    258 
    259                             @Override
    260                             public void onTransitionResume(Transition transition) {
    261                                 parent.suppressLayout(true);
    262                             }
    263                         };
    264                         addListener(transitionListener);
    265                     }
    266                     anim.addListener(new AnimatorListenerAdapter() {
    267                         @Override
    268                         public void onAnimationEnd(Animator animation) {
    269                             view.setClipBounds(null);
    270                         }
    271                     });
    272                     return anim;
    273                 }
    274             }
    275         } else {
    276             int startX = (Integer) startValues.values.get(PROPNAME_WINDOW_X);
    277             int startY = (Integer) startValues.values.get(PROPNAME_WINDOW_Y);
    278             int endX = (Integer) endValues.values.get(PROPNAME_WINDOW_X);
    279             int endY = (Integer) endValues.values.get(PROPNAME_WINDOW_Y);
    280             // TODO: also handle size changes: check bounds and animate size changes
    281             if (startX != endX || startY != endY) {
    282                 sceneRoot.getLocationInWindow(tempLocation);
    283                 Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(),
    284                         Bitmap.Config.ARGB_8888);
    285                 Canvas canvas = new Canvas(bitmap);
    286                 view.draw(canvas);
    287                 final BitmapDrawable drawable = new BitmapDrawable(bitmap);
    288                 view.setVisibility(View.INVISIBLE);
    289                 sceneRoot.getOverlay().add(drawable);
    290                 Rect startBounds1 = new Rect(startX - tempLocation[0], startY - tempLocation[1],
    291                         startX - tempLocation[0] + view.getWidth(),
    292                         startY - tempLocation[1] + view.getHeight());
    293                 Rect endBounds1 = new Rect(endX - tempLocation[0], endY - tempLocation[1],
    294                         endX - tempLocation[0] + view.getWidth(),
    295                         endY - tempLocation[1] + view.getHeight());
    296                 ObjectAnimator anim = ObjectAnimator.ofObject(drawable, "bounds",
    297                         sRectEvaluator, startBounds1, endBounds1);
    298                 anim.addListener(new AnimatorListenerAdapter() {
    299                     @Override
    300                     public void onAnimationEnd(Animator animation) {
    301                         sceneRoot.getOverlay().remove(drawable);
    302                         view.setVisibility(View.VISIBLE);
    303                     }
    304                 });
    305                 return anim;
    306             }
    307         }
    308         return null;
    309     }
    310 }
    311