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