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