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.Animator.AnimatorListener;
     21 import android.animation.Animator.AnimatorPauseListener;
     22 import android.annotation.IntDef;
     23 import android.content.Context;
     24 import android.content.res.TypedArray;
     25 import android.util.AttributeSet;
     26 import android.view.View;
     27 import android.view.ViewGroup;
     28 import android.view.ViewGroupOverlay;
     29 
     30 import com.android.internal.R;
     31 
     32 import java.lang.annotation.Retention;
     33 import java.lang.annotation.RetentionPolicy;
     34 
     35 /**
     36  * This transition tracks changes to the visibility of target views in the
     37  * start and end scenes. Visibility is determined not just by the
     38  * {@link View#setVisibility(int)} state of views, but also whether
     39  * views exist in the current view hierarchy. The class is intended to be a
     40  * utility for subclasses such as {@link Fade}, which use this visibility
     41  * information to determine the specific animations to run when visibility
     42  * changes occur. Subclasses should implement one or both of the methods
     43  * {@link #onAppear(ViewGroup, TransitionValues, int, TransitionValues, int)},
     44  * {@link #onDisappear(ViewGroup, TransitionValues, int, TransitionValues, int)} or
     45  * {@link #onAppear(ViewGroup, View, TransitionValues, TransitionValues)},
     46  * {@link #onDisappear(ViewGroup, View, TransitionValues, TransitionValues)}.
     47  */
     48 public abstract class Visibility extends Transition {
     49 
     50     static final String PROPNAME_VISIBILITY = "android:visibility:visibility";
     51     private static final String PROPNAME_PARENT = "android:visibility:parent";
     52     private static final String PROPNAME_SCREEN_LOCATION = "android:visibility:screenLocation";
     53 
     54     /** @hide */
     55     @Retention(RetentionPolicy.SOURCE)
     56     @IntDef(flag = true, value = {
     57             MODE_IN,
     58             MODE_OUT,
     59             Fade.IN,
     60             Fade.OUT
     61     })
     62     @interface VisibilityMode {}
     63 
     64     /**
     65      * Mode used in {@link #setMode(int)} to make the transition
     66      * operate on targets that are appearing. Maybe be combined with
     67      * {@link #MODE_OUT} to target Visibility changes both in and out.
     68      */
     69     public static final int MODE_IN = 0x1;
     70 
     71     /**
     72      * Mode used in {@link #setMode(int)} to make the transition
     73      * operate on targets that are disappearing. Maybe be combined with
     74      * {@link #MODE_IN} to target Visibility changes both in and out.
     75      */
     76     public static final int MODE_OUT = 0x2;
     77 
     78     private static final String[] sTransitionProperties = {
     79             PROPNAME_VISIBILITY,
     80             PROPNAME_PARENT,
     81     };
     82 
     83     private static class VisibilityInfo {
     84         boolean visibilityChange;
     85         boolean fadeIn;
     86         int startVisibility;
     87         int endVisibility;
     88         ViewGroup startParent;
     89         ViewGroup endParent;
     90     }
     91 
     92     private int mMode = MODE_IN | MODE_OUT;
     93     private boolean mSuppressLayout = true;
     94 
     95     public Visibility() {}
     96 
     97     public Visibility(Context context, AttributeSet attrs) {
     98         super(context, attrs);
     99         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.VisibilityTransition);
    100         int mode = a.getInt(R.styleable.VisibilityTransition_transitionVisibilityMode, 0);
    101         a.recycle();
    102         if (mode != 0) {
    103             setMode(mode);
    104         }
    105     }
    106 
    107     /**
    108      * This tells the Visibility transition to suppress layout during the transition and release
    109      * the suppression after the transition.
    110      * @hide
    111      */
    112     public void setSuppressLayout(boolean suppress) {
    113         this.mSuppressLayout = suppress;
    114     }
    115 
    116     /**
    117      * Changes the transition to support appearing and/or disappearing Views, depending
    118      * on <code>mode</code>.
    119      *
    120      * @param mode The behavior supported by this transition, a combination of
    121      *             {@link #MODE_IN} and {@link #MODE_OUT}.
    122      * @attr ref android.R.styleable#VisibilityTransition_transitionVisibilityMode
    123      */
    124     public void setMode(@VisibilityMode int mode) {
    125         if ((mode & ~(MODE_IN | MODE_OUT)) != 0) {
    126             throw new IllegalArgumentException("Only MODE_IN and MODE_OUT flags are allowed");
    127         }
    128         mMode = mode;
    129     }
    130 
    131     /**
    132      * Returns whether appearing and/or disappearing Views are supported.
    133      *
    134      * Returns whether appearing and/or disappearing Views are supported. A combination of
    135      *         {@link #MODE_IN} and {@link #MODE_OUT}.
    136      * @attr ref android.R.styleable#VisibilityTransition_transitionVisibilityMode
    137      */
    138     @VisibilityMode
    139     public int getMode() {
    140         return mMode;
    141     }
    142 
    143     @Override
    144     public String[] getTransitionProperties() {
    145         return sTransitionProperties;
    146     }
    147 
    148     private void captureValues(TransitionValues transitionValues) {
    149         int visibility = transitionValues.view.getVisibility();
    150         transitionValues.values.put(PROPNAME_VISIBILITY, visibility);
    151         transitionValues.values.put(PROPNAME_PARENT, transitionValues.view.getParent());
    152         int[] loc = new int[2];
    153         transitionValues.view.getLocationOnScreen(loc);
    154         transitionValues.values.put(PROPNAME_SCREEN_LOCATION, loc);
    155     }
    156 
    157     @Override
    158     public void captureStartValues(TransitionValues transitionValues) {
    159         captureValues(transitionValues);
    160     }
    161 
    162     @Override
    163     public void captureEndValues(TransitionValues transitionValues) {
    164         captureValues(transitionValues);
    165     }
    166 
    167     /**
    168      * Returns whether the view is 'visible' according to the given values
    169      * object. This is determined by testing the same properties in the values
    170      * object that are used to determine whether the object is appearing or
    171      * disappearing in the {@link
    172      * Transition#createAnimator(ViewGroup, TransitionValues, TransitionValues)}
    173      * method. This method can be called by, for example, subclasses that want
    174      * to know whether the object is visible in the same way that Visibility
    175      * determines it for the actual animation.
    176      *
    177      * @param values The TransitionValues object that holds the information by
    178      * which visibility is determined.
    179      * @return True if the view reference by <code>values</code> is visible,
    180      * false otherwise.
    181      */
    182     public boolean isVisible(TransitionValues values) {
    183         if (values == null) {
    184             return false;
    185         }
    186         int visibility = (Integer) values.values.get(PROPNAME_VISIBILITY);
    187         View parent = (View) values.values.get(PROPNAME_PARENT);
    188 
    189         return visibility == View.VISIBLE && parent != null;
    190     }
    191 
    192     private static VisibilityInfo getVisibilityChangeInfo(TransitionValues startValues,
    193             TransitionValues endValues) {
    194         final VisibilityInfo visInfo = new VisibilityInfo();
    195         visInfo.visibilityChange = false;
    196         visInfo.fadeIn = false;
    197         if (startValues != null && startValues.values.containsKey(PROPNAME_VISIBILITY)) {
    198             visInfo.startVisibility = (Integer) startValues.values.get(PROPNAME_VISIBILITY);
    199             visInfo.startParent = (ViewGroup) startValues.values.get(PROPNAME_PARENT);
    200         } else {
    201             visInfo.startVisibility = -1;
    202             visInfo.startParent = null;
    203         }
    204         if (endValues != null && endValues.values.containsKey(PROPNAME_VISIBILITY)) {
    205             visInfo.endVisibility = (Integer) endValues.values.get(PROPNAME_VISIBILITY);
    206             visInfo.endParent = (ViewGroup) endValues.values.get(PROPNAME_PARENT);
    207         } else {
    208             visInfo.endVisibility = -1;
    209             visInfo.endParent = null;
    210         }
    211         if (startValues != null && endValues != null) {
    212             if (visInfo.startVisibility == visInfo.endVisibility &&
    213                     visInfo.startParent == visInfo.endParent) {
    214                 return visInfo;
    215             } else {
    216                 if (visInfo.startVisibility != visInfo.endVisibility) {
    217                     if (visInfo.startVisibility == View.VISIBLE) {
    218                         visInfo.fadeIn = false;
    219                         visInfo.visibilityChange = true;
    220                     } else if (visInfo.endVisibility == View.VISIBLE) {
    221                         visInfo.fadeIn = true;
    222                         visInfo.visibilityChange = true;
    223                     }
    224                     // no visibilityChange if going between INVISIBLE and GONE
    225                 } else if (visInfo.startParent != visInfo.endParent) {
    226                     if (visInfo.endParent == null) {
    227                         visInfo.fadeIn = false;
    228                         visInfo.visibilityChange = true;
    229                     } else if (visInfo.startParent == null) {
    230                         visInfo.fadeIn = true;
    231                         visInfo.visibilityChange = true;
    232                     }
    233                 }
    234             }
    235         } else if (startValues == null && visInfo.endVisibility == View.VISIBLE) {
    236             visInfo.fadeIn = true;
    237             visInfo.visibilityChange = true;
    238         } else if (endValues == null && visInfo.startVisibility == View.VISIBLE) {
    239             visInfo.fadeIn = false;
    240             visInfo.visibilityChange = true;
    241         }
    242         return visInfo;
    243     }
    244 
    245     @Override
    246     public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
    247             TransitionValues endValues) {
    248         VisibilityInfo visInfo = getVisibilityChangeInfo(startValues, endValues);
    249         if (visInfo.visibilityChange
    250                 && (visInfo.startParent != null || visInfo.endParent != null)) {
    251             if (visInfo.fadeIn) {
    252                 return onAppear(sceneRoot, startValues, visInfo.startVisibility,
    253                         endValues, visInfo.endVisibility);
    254             } else {
    255                 return onDisappear(sceneRoot, startValues, visInfo.startVisibility,
    256                         endValues, visInfo.endVisibility
    257                 );
    258             }
    259         }
    260         return null;
    261     }
    262 
    263     /**
    264      * The default implementation of this method calls
    265      * {@link #onAppear(ViewGroup, View, TransitionValues, TransitionValues)}.
    266      * Subclasses should override this method or
    267      * {@link #onAppear(ViewGroup, View, TransitionValues, TransitionValues)}.
    268      * if they need to create an Animator when targets appear.
    269      * The method should only be called by the Visibility class; it is
    270      * not intended to be called from external classes.
    271      *
    272      * @param sceneRoot The root of the transition hierarchy
    273      * @param startValues The target values in the start scene
    274      * @param startVisibility The target visibility in the start scene
    275      * @param endValues The target values in the end scene
    276      * @param endVisibility The target visibility in the end scene
    277      * @return An Animator to be started at the appropriate time in the
    278      * overall transition for this scene change. A null value means no animation
    279      * should be run.
    280      */
    281     public Animator onAppear(ViewGroup sceneRoot,
    282             TransitionValues startValues, int startVisibility,
    283             TransitionValues endValues, int endVisibility) {
    284         if ((mMode & MODE_IN) != MODE_IN || endValues == null) {
    285             return null;
    286         }
    287         if (startValues == null) {
    288             VisibilityInfo parentVisibilityInfo = null;
    289             View endParent = (View) endValues.view.getParent();
    290             TransitionValues startParentValues = getMatchedTransitionValues(endParent,
    291                                                                             false);
    292             TransitionValues endParentValues = getTransitionValues(endParent, false);
    293             parentVisibilityInfo =
    294                 getVisibilityChangeInfo(startParentValues, endParentValues);
    295             if (parentVisibilityInfo.visibilityChange) {
    296                 return null;
    297             }
    298         }
    299         return onAppear(sceneRoot, endValues.view, startValues, endValues);
    300     }
    301 
    302     /**
    303      * The default implementation of this method returns a null Animator. Subclasses should
    304      * override this method to make targets appear with the desired transition. The
    305      * method should only be called from
    306      * {@link #onAppear(ViewGroup, TransitionValues, int, TransitionValues, int)}.
    307      *
    308      * @param sceneRoot The root of the transition hierarchy
    309      * @param view The View to make appear. This will be in the target scene's View hierarchy and
    310      *             will be VISIBLE.
    311      * @param startValues The target values in the start scene
    312      * @param endValues The target values in the end scene
    313      * @return An Animator to be started at the appropriate time in the
    314      * overall transition for this scene change. A null value means no animation
    315      * should be run.
    316      */
    317     public Animator onAppear(ViewGroup sceneRoot, View view, TransitionValues startValues,
    318             TransitionValues endValues) {
    319         return null;
    320     }
    321 
    322     /**
    323      * Subclasses should override this method or
    324      * {@link #onDisappear(ViewGroup, View, TransitionValues, TransitionValues)}
    325      * if they need to create an Animator when targets disappear.
    326      * The method should only be called by the Visibility class; it is
    327      * not intended to be called from external classes.
    328      * <p>
    329      * The default implementation of this method attempts to find a View to use to call
    330      * {@link #onDisappear(ViewGroup, View, TransitionValues, TransitionValues)},
    331      * based on the situation of the View in the View hierarchy. For example,
    332      * if a View was simply removed from its parent, then the View will be added
    333      * into a {@link android.view.ViewGroupOverlay} and passed as the <code>view</code>
    334      * parameter in {@link #onDisappear(ViewGroup, View, TransitionValues, TransitionValues)}.
    335      * If a visible View is changed to be {@link View#GONE} or {@link View#INVISIBLE},
    336      * then it can be used as the <code>view</code> and the visibility will be changed
    337      * to {@link View#VISIBLE} for the duration of the animation. However, if a View
    338      * is in a hierarchy which is also altering its visibility, the situation can be
    339      * more complicated. In general, if a view that is no longer in the hierarchy in
    340      * the end scene still has a parent (so its parent hierarchy was removed, but it
    341      * was not removed from its parent), then it will be left alone to avoid side-effects from
    342      * improperly removing it from its parent. The only exception to this is if
    343      * the previous {@link Scene} was {@link Scene#getSceneForLayout(ViewGroup, int,
    344      * android.content.Context) created from a layout resource file}, then it is considered
    345      * safe to un-parent the starting scene view in order to make it disappear.</p>
    346      *
    347      * @param sceneRoot The root of the transition hierarchy
    348      * @param startValues The target values in the start scene
    349      * @param startVisibility The target visibility in the start scene
    350      * @param endValues The target values in the end scene
    351      * @param endVisibility The target visibility in the end scene
    352      * @return An Animator to be started at the appropriate time in the
    353      * overall transition for this scene change. A null value means no animation
    354      * should be run.
    355      */
    356     public Animator onDisappear(ViewGroup sceneRoot,
    357             TransitionValues startValues, int startVisibility,
    358             TransitionValues endValues, int endVisibility) {
    359         if ((mMode & MODE_OUT) != MODE_OUT) {
    360             return null;
    361         }
    362 
    363         if (startValues == null) {
    364             // startValues(and startView) will never be null for disappear transition.
    365             return null;
    366         }
    367 
    368         final View startView = startValues.view;
    369         final View endView = (endValues != null) ? endValues.view : null;
    370         View overlayView = null;
    371         View viewToKeep = null;
    372         boolean reusingOverlayView = false;
    373 
    374         View savedOverlayView = (View) startView.getTag(R.id.transition_overlay_view_tag);
    375         if (savedOverlayView != null) {
    376             // we've already created overlay for the start view.
    377             // it means that we are applying two visibility
    378             // transitions for the same view
    379             overlayView = savedOverlayView;
    380             reusingOverlayView = true;
    381         } else {
    382             boolean needOverlayForStartView = false;
    383 
    384             if (endView == null || endView.getParent() == null) {
    385                 if (endView != null) {
    386                     // endView was removed from its parent - add it to the overlay
    387                     overlayView = endView;
    388                 } else {
    389                     needOverlayForStartView = true;
    390                 }
    391             } else {
    392                 // visibility change
    393                 if (endVisibility == View.INVISIBLE) {
    394                     viewToKeep = endView;
    395                 } else {
    396                     // Becoming GONE
    397                     if (startView == endView) {
    398                         viewToKeep = endView;
    399                     } else {
    400                         needOverlayForStartView = true;
    401                     }
    402                 }
    403             }
    404 
    405             if (needOverlayForStartView) {
    406                 // endView does not exist. Use startView only under certain
    407                 // conditions, because placing a view in an overlay necessitates
    408                 // it being removed from its current parent
    409                 if (startView.getParent() == null) {
    410                     // no parent - safe to use
    411                     overlayView = startView;
    412                 } else if (startView.getParent() instanceof View) {
    413                     View startParent = (View) startView.getParent();
    414                     TransitionValues startParentValues = getTransitionValues(startParent, true);
    415                     TransitionValues endParentValues = getMatchedTransitionValues(startParent,
    416                             true);
    417                     VisibilityInfo parentVisibilityInfo =
    418                             getVisibilityChangeInfo(startParentValues, endParentValues);
    419                     if (!parentVisibilityInfo.visibilityChange) {
    420                         overlayView = TransitionUtils.copyViewImage(sceneRoot, startView,
    421                                 startParent);
    422                     } else {
    423                         int id = startParent.getId();
    424                         if (startParent.getParent() == null && id != View.NO_ID
    425                                 && sceneRoot.findViewById(id) != null && mCanRemoveViews) {
    426                             // no parent, but its parent is unparented  but the parent
    427                             // hierarchy has been replaced by a new hierarchy with the same id
    428                             // and it is safe to un-parent startView
    429                             overlayView = startView;
    430                         } else {
    431                             // TODO: Handle this case as well
    432                         }
    433                     }
    434                 }
    435             }
    436         }
    437 
    438         if (overlayView != null) {
    439             // TODO: Need to do this for general case of adding to overlay
    440             final ViewGroupOverlay overlay;
    441             if (!reusingOverlayView) {
    442                 overlay = sceneRoot.getOverlay();
    443                 int[] screenLoc = (int[]) startValues.values.get(PROPNAME_SCREEN_LOCATION);
    444                 int screenX = screenLoc[0];
    445                 int screenY = screenLoc[1];
    446                 int[] loc = new int[2];
    447                 sceneRoot.getLocationOnScreen(loc);
    448                 overlayView.offsetLeftAndRight((screenX - loc[0]) - overlayView.getLeft());
    449                 overlayView.offsetTopAndBottom((screenY - loc[1]) - overlayView.getTop());
    450                 overlay.add(overlayView);
    451             } else {
    452                 overlay = null;
    453             }
    454             Animator animator = onDisappear(sceneRoot, overlayView, startValues, endValues);
    455             if (!reusingOverlayView) {
    456                 if (animator == null) {
    457                     overlay.remove(overlayView);
    458                 } else {
    459                     startView.setTagInternal(R.id.transition_overlay_view_tag, overlayView);
    460                     final View finalOverlayView = overlayView;
    461                     addListener(new TransitionListenerAdapter() {
    462 
    463                         @Override
    464                         public void onTransitionPause(Transition transition) {
    465                             overlay.remove(finalOverlayView);
    466                         }
    467 
    468                         @Override
    469                         public void onTransitionResume(Transition transition) {
    470                             if (finalOverlayView.getParent() == null) {
    471                                 overlay.add(finalOverlayView);
    472                             } else {
    473                                 cancel();
    474                             }
    475                         }
    476 
    477                         @Override
    478                         public void onTransitionEnd(Transition transition) {
    479                             startView.setTagInternal(R.id.transition_overlay_view_tag, null);
    480                             overlay.remove(finalOverlayView);
    481                             transition.removeListener(this);
    482                         }
    483                     });
    484                 }
    485             }
    486             return animator;
    487         }
    488 
    489         if (viewToKeep != null) {
    490             int originalVisibility = viewToKeep.getVisibility();
    491             viewToKeep.setTransitionVisibility(View.VISIBLE);
    492             Animator animator = onDisappear(sceneRoot, viewToKeep, startValues, endValues);
    493             if (animator != null) {
    494                 DisappearListener disappearListener = new DisappearListener(viewToKeep,
    495                         endVisibility, mSuppressLayout);
    496                 animator.addListener(disappearListener);
    497                 animator.addPauseListener(disappearListener);
    498                 addListener(disappearListener);
    499             } else {
    500                 viewToKeep.setTransitionVisibility(originalVisibility);
    501             }
    502             return animator;
    503         }
    504         return null;
    505     }
    506 
    507     @Override
    508     public boolean isTransitionRequired(TransitionValues startValues, TransitionValues newValues) {
    509         if (startValues == null && newValues == null) {
    510             return false;
    511         }
    512         if (startValues != null && newValues != null &&
    513                 newValues.values.containsKey(PROPNAME_VISIBILITY) !=
    514                         startValues.values.containsKey(PROPNAME_VISIBILITY)) {
    515             // The transition wasn't targeted in either the start or end, so it couldn't
    516             // have changed.
    517             return false;
    518         }
    519         VisibilityInfo changeInfo = getVisibilityChangeInfo(startValues, newValues);
    520         return changeInfo.visibilityChange && (changeInfo.startVisibility == View.VISIBLE ||
    521                 changeInfo.endVisibility == View.VISIBLE);
    522     }
    523 
    524     /**
    525      * The default implementation of this method returns a null Animator. Subclasses should
    526      * override this method to make targets disappear with the desired transition. The
    527      * method should only be called from
    528      * {@link #onDisappear(ViewGroup, TransitionValues, int, TransitionValues, int)}.
    529      *
    530      * @param sceneRoot The root of the transition hierarchy
    531      * @param view The View to make disappear. This will be in the target scene's View
    532      *             hierarchy or in an {@link android.view.ViewGroupOverlay} and will be
    533      *             VISIBLE.
    534      * @param startValues The target values in the start scene
    535      * @param endValues The target values in the end scene
    536      * @return An Animator to be started at the appropriate time in the
    537      * overall transition for this scene change. A null value means no animation
    538      * should be run.
    539      */
    540     public Animator onDisappear(ViewGroup sceneRoot, View view, TransitionValues startValues,
    541             TransitionValues endValues) {
    542         return null;
    543     }
    544 
    545     private static class DisappearListener
    546             extends TransitionListenerAdapter implements AnimatorListener, AnimatorPauseListener {
    547         private final View mView;
    548         private final int mFinalVisibility;
    549         private final ViewGroup mParent;
    550         private final boolean mSuppressLayout;
    551 
    552         private boolean mLayoutSuppressed;
    553         boolean mCanceled = false;
    554 
    555         public DisappearListener(View view, int finalVisibility, boolean suppressLayout) {
    556             this.mView = view;
    557             this.mFinalVisibility = finalVisibility;
    558             this.mParent = (ViewGroup) view.getParent();
    559             this.mSuppressLayout = suppressLayout;
    560             // Prevent a layout from including mView in its calculation.
    561             suppressLayout(true);
    562         }
    563 
    564         @Override
    565         public void onAnimationPause(Animator animation) {
    566             if (!mCanceled) {
    567                 mView.setTransitionVisibility(mFinalVisibility);
    568             }
    569         }
    570 
    571         @Override
    572         public void onAnimationResume(Animator animation) {
    573             if (!mCanceled) {
    574                 mView.setTransitionVisibility(View.VISIBLE);
    575             }
    576         }
    577 
    578         @Override
    579         public void onAnimationCancel(Animator animation) {
    580             mCanceled = true;
    581         }
    582 
    583         @Override
    584         public void onAnimationRepeat(Animator animation) {
    585         }
    586 
    587         @Override
    588         public void onAnimationStart(Animator animation) {
    589         }
    590 
    591         @Override
    592         public void onAnimationEnd(Animator animation) {
    593             hideViewWhenNotCanceled();
    594         }
    595 
    596         @Override
    597         public void onTransitionEnd(Transition transition) {
    598             hideViewWhenNotCanceled();
    599             transition.removeListener(this);
    600         }
    601 
    602         @Override
    603         public void onTransitionPause(Transition transition) {
    604             suppressLayout(false);
    605         }
    606 
    607         @Override
    608         public void onTransitionResume(Transition transition) {
    609             suppressLayout(true);
    610         }
    611 
    612         private void hideViewWhenNotCanceled() {
    613             if (!mCanceled) {
    614                 // Recreate the parent's display list in case it includes mView.
    615                 mView.setTransitionVisibility(mFinalVisibility);
    616                 if (mParent != null) {
    617                     mParent.invalidate();
    618                 }
    619             }
    620             // Layout is allowed now that the View is in its final state
    621             suppressLayout(false);
    622         }
    623 
    624         private void suppressLayout(boolean suppress) {
    625             if (mSuppressLayout && mLayoutSuppressed != suppress && mParent != null) {
    626                 mLayoutSuppressed = suppress;
    627                 mParent.suppressLayout(suppress);
    628             }
    629         }
    630     }
    631 }
    632