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 
Visibility()95     public Visibility() {}
96 
Visibility(Context context, AttributeSet attrs)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      */
setSuppressLayout(boolean suppress)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      */
setMode(@isibilityMode int mode)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
getMode()139     public int getMode() {
140         return mMode;
141     }
142 
143     @Override
getTransitionProperties()144     public String[] getTransitionProperties() {
145         return sTransitionProperties;
146     }
147 
captureValues(TransitionValues transitionValues)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
captureStartValues(TransitionValues transitionValues)158     public void captureStartValues(TransitionValues transitionValues) {
159         captureValues(transitionValues);
160     }
161 
162     @Override
captureEndValues(TransitionValues transitionValues)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      */
isVisible(TransitionValues values)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 
getVisibilityChangeInfo(TransitionValues startValues, TransitionValues endValues)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
createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues)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      */
onAppear(ViewGroup sceneRoot, TransitionValues startValues, int startVisibility, TransitionValues endValues, int endVisibility)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      */
onAppear(ViewGroup sceneRoot, View view, TransitionValues startValues, TransitionValues endValues)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      */
onDisappear(ViewGroup sceneRoot, TransitionValues startValues, int startVisibility, TransitionValues endValues, int endVisibility)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
isTransitionRequired(TransitionValues startValues, TransitionValues newValues)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      */
onDisappear(ViewGroup sceneRoot, View view, TransitionValues startValues, TransitionValues endValues)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 
DisappearListener(View view, int finalVisibility, boolean suppressLayout)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
onAnimationPause(Animator animation)565         public void onAnimationPause(Animator animation) {
566             if (!mCanceled) {
567                 mView.setTransitionVisibility(mFinalVisibility);
568             }
569         }
570 
571         @Override
onAnimationResume(Animator animation)572         public void onAnimationResume(Animator animation) {
573             if (!mCanceled) {
574                 mView.setTransitionVisibility(View.VISIBLE);
575             }
576         }
577 
578         @Override
onAnimationCancel(Animator animation)579         public void onAnimationCancel(Animator animation) {
580             mCanceled = true;
581         }
582 
583         @Override
onAnimationRepeat(Animator animation)584         public void onAnimationRepeat(Animator animation) {
585         }
586 
587         @Override
onAnimationStart(Animator animation)588         public void onAnimationStart(Animator animation) {
589         }
590 
591         @Override
onAnimationEnd(Animator animation)592         public void onAnimationEnd(Animator animation) {
593             hideViewWhenNotCanceled();
594         }
595 
596         @Override
onTransitionEnd(Transition transition)597         public void onTransitionEnd(Transition transition) {
598             hideViewWhenNotCanceled();
599             transition.removeListener(this);
600         }
601 
602         @Override
onTransitionPause(Transition transition)603         public void onTransitionPause(Transition transition) {
604             suppressLayout(false);
605         }
606 
607         @Override
onTransitionResume(Transition transition)608         public void onTransitionResume(Transition transition) {
609             suppressLayout(true);
610         }
611 
hideViewWhenNotCanceled()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 
suppressLayout(boolean suppress)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