• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.util.Log;
23 import android.view.View;
24 import android.view.ViewGroup;
25 
26 /**
27  * This transition tracks changes to the visibility of target views in the
28  * start and end scenes and fades views in or out when they become visible
29  * or non-visible. Visibility is determined by both the
30  * {@link View#setVisibility(int)} state of the view as well as whether it
31  * is parented in the current view hierarchy.
32  *
33  * <p>The ability of this transition to fade out a particular view, and the
34  * way that that fading operation takes place, is based on
35  * the situation of the view in the view hierarchy. For example, if a view was
36  * simply removed from its parent, then the view will be added into a {@link
37  * android.view.ViewGroupOverlay} while fading. If a visible view is
38  * changed to be {@link View#GONE} or {@link View#INVISIBLE}, then the
39  * visibility will be changed to {@link View#VISIBLE} for the duration of
40  * the animation. However, if a view is in a hierarchy which is also altering
41  * its visibility, the situation can be more complicated. In general, if a
42  * view that is no longer in the hierarchy in the end scene still has a
43  * parent (so its parent hierarchy was removed, but it was not removed from
44  * its parent), then it will be left alone to avoid side-effects from
45  * improperly removing it from its parent. The only exception to this is if
46  * the previous {@link Scene} was
47  * {@link Scene#getSceneForLayout(android.view.ViewGroup, int, android.content.Context)
48  * created from a layout resource file}, then it is considered safe to un-parent
49  * the starting scene view in order to fade it out.</p>
50  *
51  * <p>A Fade transition can be described in a resource file by using the
52  * tag <code>fade</code>, along with the standard
53  * attributes of {@link android.R.styleable#Fade} and
54  * {@link android.R.styleable#Transition}.</p>
55 
56  */
57 public class Fade extends Visibility {
58 
59     private static boolean DBG = Transition.DBG && false;
60 
61     private static final String LOG_TAG = "Fade";
62     private static final String PROPNAME_SCREEN_X = "android:fade:screenX";
63     private static final String PROPNAME_SCREEN_Y = "android:fade:screenY";
64 
65     /**
66      * Fading mode used in {@link #Fade(int)} to make the transition
67      * operate on targets that are appearing. Maybe be combined with
68      * {@link #OUT} to fade both in and out.
69      */
70     public static final int IN = 0x1;
71     /**
72      * Fading mode used in {@link #Fade(int)} to make the transition
73      * operate on targets that are disappearing. Maybe be combined with
74      * {@link #IN} to fade both in and out.
75      */
76     public static final int OUT = 0x2;
77 
78     private int mFadingMode;
79 
80     /**
81      * Constructs a Fade transition that will fade targets in and out.
82      */
Fade()83     public Fade() {
84         this(IN | OUT);
85     }
86 
87     /**
88      * Constructs a Fade transition that will fade targets in
89      * and/or out, according to the value of fadingMode.
90      *
91      * @param fadingMode The behavior of this transition, a combination of
92      * {@link #IN} and {@link #OUT}.
93      */
Fade(int fadingMode)94     public Fade(int fadingMode) {
95         mFadingMode = fadingMode;
96     }
97 
98     /**
99      * Utility method to handle creating and running the Animator.
100      */
createAnimation(View view, float startAlpha, float endAlpha, AnimatorListenerAdapter listener)101     private Animator createAnimation(View view, float startAlpha, float endAlpha,
102             AnimatorListenerAdapter listener) {
103         if (startAlpha == endAlpha) {
104             // run listener if we're noop'ing the animation, to get the end-state results now
105             if (listener != null) {
106                 listener.onAnimationEnd(null);
107             }
108             return null;
109         }
110         final ObjectAnimator anim = ObjectAnimator.ofFloat(view, "transitionAlpha", startAlpha,
111                 endAlpha);
112         if (DBG) {
113             Log.d(LOG_TAG, "Created animator " + anim);
114         }
115         if (listener != null) {
116             anim.addListener(listener);
117             anim.addPauseListener(listener);
118         }
119         return anim;
120     }
121 
captureValues(TransitionValues transitionValues)122     private void captureValues(TransitionValues transitionValues) {
123         int[] loc = new int[2];
124         transitionValues.view.getLocationOnScreen(loc);
125         transitionValues.values.put(PROPNAME_SCREEN_X, loc[0]);
126         transitionValues.values.put(PROPNAME_SCREEN_Y, loc[1]);
127     }
128 
129     @Override
captureStartValues(TransitionValues transitionValues)130     public void captureStartValues(TransitionValues transitionValues) {
131         super.captureStartValues(transitionValues);
132         captureValues(transitionValues);
133     }
134 
135     @Override
onAppear(ViewGroup sceneRoot, TransitionValues startValues, int startVisibility, TransitionValues endValues, int endVisibility)136     public Animator onAppear(ViewGroup sceneRoot,
137             TransitionValues startValues, int startVisibility,
138             TransitionValues endValues, int endVisibility) {
139         if ((mFadingMode & IN) != IN || endValues == null) {
140             return null;
141         }
142         final View endView = endValues.view;
143         if (DBG) {
144             View startView = (startValues != null) ? startValues.view : null;
145             Log.d(LOG_TAG, "Fade.onAppear: startView, startVis, endView, endVis = " +
146                     startView + ", " + startVisibility + ", " + endView + ", " + endVisibility);
147         }
148         endView.setTransitionAlpha(0);
149         TransitionListener transitionListener = new TransitionListenerAdapter() {
150             boolean mCanceled = false;
151             float mPausedAlpha;
152 
153             @Override
154             public void onTransitionCancel(Transition transition) {
155                 endView.setTransitionAlpha(1);
156                 mCanceled = true;
157             }
158 
159             @Override
160             public void onTransitionEnd(Transition transition) {
161                 if (!mCanceled) {
162                     endView.setTransitionAlpha(1);
163                 }
164             }
165 
166             @Override
167             public void onTransitionPause(Transition transition) {
168                 mPausedAlpha = endView.getTransitionAlpha();
169                 endView.setTransitionAlpha(1);
170             }
171 
172             @Override
173             public void onTransitionResume(Transition transition) {
174                 endView.setTransitionAlpha(mPausedAlpha);
175             }
176         };
177         addListener(transitionListener);
178         return createAnimation(endView, 0, 1, null);
179     }
180 
181     @Override
onDisappear(ViewGroup sceneRoot, TransitionValues startValues, int startVisibility, TransitionValues endValues, int endVisibility)182     public Animator onDisappear(ViewGroup sceneRoot,
183             TransitionValues startValues, int startVisibility,
184             TransitionValues endValues, int endVisibility) {
185         if ((mFadingMode & OUT) != OUT) {
186             return null;
187         }
188         View view = null;
189         View startView = (startValues != null) ? startValues.view : null;
190         View endView = (endValues != null) ? endValues.view : null;
191         if (DBG) {
192             Log.d(LOG_TAG, "Fade.onDisappear: startView, startVis, endView, endVis = " +
193                         startView + ", " + startVisibility + ", " + endView + ", " + endVisibility);
194         }
195         View overlayView = null;
196         View viewToKeep = null;
197         if (endView == null || endView.getParent() == null) {
198             if (endView != null) {
199                 // endView was removed from its parent - add it to the overlay
200                 view = overlayView = endView;
201             } else if (startView != null) {
202                 // endView does not exist. Use startView only under certain
203                 // conditions, because placing a view in an overlay necessitates
204                 // it being removed from its current parent
205                 if (startView.getParent() == null) {
206                     // no parent - safe to use
207                     view = overlayView = startView;
208                 } else if (startView.getParent() instanceof View &&
209                         startView.getParent().getParent() == null) {
210                     View startParent = (View) startView.getParent();
211                     int id = startParent.getId();
212                     if (id != View.NO_ID && sceneRoot.findViewById(id) != null && mCanRemoveViews) {
213                         // no parent, but its parent is unparented  but the parent
214                         // hierarchy has been replaced by a new hierarchy with the same id
215                         // and it is safe to un-parent startView
216                         view = overlayView = startView;
217                     }
218                 }
219             }
220         } else {
221             // visibility change
222             if (endVisibility == View.INVISIBLE) {
223                 view = endView;
224                 viewToKeep = view;
225             } else {
226                 // Becoming GONE
227                 if (startView == endView) {
228                     view = endView;
229                     viewToKeep = view;
230                 } else {
231                     view = startView;
232                     overlayView = view;
233                 }
234             }
235         }
236         final int finalVisibility = endVisibility;
237         // TODO: add automatic facility to Visibility superclass for keeping views around
238         if (overlayView != null) {
239             // TODO: Need to do this for general case of adding to overlay
240             int screenX = (Integer) startValues.values.get(PROPNAME_SCREEN_X);
241             int screenY = (Integer) startValues.values.get(PROPNAME_SCREEN_Y);
242             int[] loc = new int[2];
243             sceneRoot.getLocationOnScreen(loc);
244             overlayView.offsetLeftAndRight((screenX - loc[0]) - overlayView.getLeft());
245             overlayView.offsetTopAndBottom((screenY - loc[1]) - overlayView.getTop());
246             sceneRoot.getOverlay().add(overlayView);
247             // TODO: add automatic facility to Visibility superclass for keeping views around
248             final float startAlpha = 1;
249             float endAlpha = 0;
250             final View finalView = view;
251             final View finalOverlayView = overlayView;
252             final View finalViewToKeep = viewToKeep;
253             final ViewGroup finalSceneRoot = sceneRoot;
254             final AnimatorListenerAdapter endListener = new AnimatorListenerAdapter() {
255                 @Override
256                 public void onAnimationEnd(Animator animation) {
257                     finalView.setTransitionAlpha(startAlpha);
258                     // TODO: restore view offset from overlay repositioning
259                     if (finalViewToKeep != null) {
260                         finalViewToKeep.setVisibility(finalVisibility);
261                     }
262                     if (finalOverlayView != null) {
263                         finalSceneRoot.getOverlay().remove(finalOverlayView);
264                     }
265                 }
266 
267                 @Override
268                 public void onAnimationPause(Animator animation) {
269                     if (finalOverlayView != null) {
270                         finalSceneRoot.getOverlay().remove(finalOverlayView);
271                     }
272                 }
273 
274                 @Override
275                 public void onAnimationResume(Animator animation) {
276                     if (finalOverlayView != null) {
277                         finalSceneRoot.getOverlay().add(finalOverlayView);
278                     }
279                 }
280             };
281             return createAnimation(view, startAlpha, endAlpha, endListener);
282         }
283         if (viewToKeep != null) {
284             // TODO: find a different way to do this, like just changing the view to be
285             // VISIBLE for the duration of the transition
286             viewToKeep.setVisibility((View.VISIBLE));
287             // TODO: add automatic facility to Visibility superclass for keeping views around
288             final float startAlpha = 1;
289             float endAlpha = 0;
290             final View finalView = view;
291             final View finalOverlayView = overlayView;
292             final View finalViewToKeep = viewToKeep;
293             final ViewGroup finalSceneRoot = sceneRoot;
294             final AnimatorListenerAdapter endListener = new AnimatorListenerAdapter() {
295                 boolean mCanceled = false;
296                 float mPausedAlpha = -1;
297 
298                 @Override
299                 public void onAnimationPause(Animator animation) {
300                     if (finalViewToKeep != null && !mCanceled) {
301                         finalViewToKeep.setVisibility(finalVisibility);
302                     }
303                     mPausedAlpha = finalView.getTransitionAlpha();
304                     finalView.setTransitionAlpha(startAlpha);
305                 }
306 
307                 @Override
308                 public void onAnimationResume(Animator animation) {
309                     if (finalViewToKeep != null && !mCanceled) {
310                         finalViewToKeep.setVisibility(View.VISIBLE);
311                     }
312                     finalView.setTransitionAlpha(mPausedAlpha);
313                 }
314 
315                 @Override
316                 public void onAnimationCancel(Animator animation) {
317                     mCanceled = true;
318                     if (mPausedAlpha >= 0) {
319                         finalView.setTransitionAlpha(mPausedAlpha);
320                     }
321                 }
322 
323                 @Override
324                 public void onAnimationEnd(Animator animation) {
325                     if (!mCanceled) {
326                         finalView.setTransitionAlpha(startAlpha);
327                     }
328                     // TODO: restore view offset from overlay repositioning
329                     if (finalViewToKeep != null && !mCanceled) {
330                         finalViewToKeep.setVisibility(finalVisibility);
331                     }
332                     if (finalOverlayView != null) {
333                         finalSceneRoot.getOverlay().remove(finalOverlayView);
334                     }
335                 }
336             };
337             return createAnimation(view, startAlpha, endAlpha, endListener);
338         }
339         return null;
340     }
341 
342 }