blob: 8053bffa808c6a05a86acf973657fbb53be6b384 [file] [log] [blame]
Chet Haasefaebd8f2012-05-18 14:17:57 -07001/*
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 */
Chet Haase6ebe3de2013-06-17 16:50:50 -070016
Chet Haased82c8ac2013-08-26 14:20:16 -070017package android.transition;
Chet Haasefaebd8f2012-05-18 14:17:57 -070018
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.ObjectAnimator;
22import android.animation.PropertyValuesHolder;
23import android.animation.RectEvaluator;
24import android.graphics.Bitmap;
25import android.graphics.Canvas;
26import android.graphics.Rect;
27import android.graphics.drawable.BitmapDrawable;
28import android.view.View;
29import android.view.ViewGroup;
30
Chet Haase08735182013-06-04 10:44:40 -070031import java.util.Map;
Chet Haasefaebd8f2012-05-18 14:17:57 -070032
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.
Chet Haased82c8ac2013-08-26 14:20:16 -070036 *
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>
Chet Haasefaebd8f2012-05-18 14:17:57 -070040 */
Chet Haased82c8ac2013-08-26 14:20:16 -070041public class ChangeBounds extends Transition {
Chet Haasefaebd8f2012-05-18 14:17:57 -070042
Chet Haased82c8ac2013-08-26 14:20:16 -070043 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";
Chet Haaseaf78bdd2013-08-27 16:06:26 -070047 private static final String[] sTransitionProperties = {
Chet Haase199acdf2013-07-24 18:40:55 -070048 PROPNAME_BOUNDS,
49 PROPNAME_PARENT,
50 PROPNAME_WINDOW_X,
51 PROPNAME_WINDOW_Y
52 };
53
Chet Haasefaebd8f2012-05-18 14:17:57 -070054 int[] tempLocation = new int[2];
55 boolean mResizeClip = false;
56 boolean mReparent = false;
Chet Haased82c8ac2013-08-26 14:20:16 -070057 private static final String LOG_TAG = "ChangeBounds";
Chet Haasefaebd8f2012-05-18 14:17:57 -070058
59 private static RectEvaluator sRectEvaluator = new RectEvaluator();
60
Chet Haase199acdf2013-07-24 18:40:55 -070061 @Override
62 public String[] getTransitionProperties() {
63 return sTransitionProperties;
64 }
65
Chet Haasefaebd8f2012-05-18 14:17:57 -070066 public void setResizeClip(boolean resizeClip) {
67 mResizeClip = resizeClip;
68 }
69
70 /**
Chet Haased82c8ac2013-08-26 14:20:16 -070071 * Setting this flag tells ChangeBounds to track the before/after parent
Chet Haasefaebd8f2012-05-18 14:17:57 -070072 * 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 */
80 public void setReparent(boolean reparent) {
81 mReparent = reparent;
82 }
83
Chet Haased82c8ac2013-08-26 14:20:16 -070084 private void captureValues(TransitionValues values) {
Chet Haasefaebd8f2012-05-18 14:17:57 -070085 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
Chet Haased82c8ac2013-08-26 14:20:16 -070095 public void captureStartValues(TransitionValues transitionValues) {
96 captureValues(transitionValues);
97 }
98
99 @Override
100 public void captureEndValues(TransitionValues transitionValues) {
101 captureValues(transitionValues);
102 }
103
104 @Override
105 public Animator createAnimator(final ViewGroup sceneRoot, TransitionValues startValues,
Chet Haasefaebd8f2012-05-18 14:17:57 -0700106 TransitionValues endValues) {
107 if (startValues == null || endValues == null) {
108 return null;
109 }
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700110 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);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700114 if (startParent == null || endParent == null) {
115 return null;
116 }
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700117 final View view = endValues.view;
Chet Haasefaebd8f2012-05-18 14:17:57 -0700118 boolean parentsEqual = (startParent == endParent) ||
119 (startParent.getId() == endParent.getId());
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700120 // TODO: Might want reparenting to be separate/subclass transition, or at least
Chet Haased82c8ac2013-08-26 14:20:16 -0700121 // triggered by a property on ChangeBounds. Otherwise, we're forcing the requirement that
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700122 // all parents in layouts have IDs to avoid layout-inflation resulting in a side-effect
123 // of reparenting the views.
Chet Haasefaebd8f2012-05-18 14:17:57 -0700124 if (!mReparent || parentsEqual) {
Chet Haasefaebd8f2012-05-18 14:17:57 -0700125 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) {
Chet Haasefaebd8f2012-05-18 14:17:57 -0700141 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;
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700150 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);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700154 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);
Chet Haase199acdf2013-07-24 18:40:55 -0700172 TransitionListener transitionListener = new TransitionListenerAdapter() {
173 boolean mCanceled = false;
174
Chet Haasefaebd8f2012-05-18 14:17:57 -0700175 @Override
Chet Haase199acdf2013-07-24 18:40:55 -0700176 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) {
Chet Haasefaebd8f2012-05-18 14:17:57 -0700190 parent.suppressLayout(false);
191 }
Chet Haase199acdf2013-07-24 18:40:55 -0700192
193 @Override
194 public void onTransitionResume(Transition transition) {
195 parent.suppressLayout(true);
196 }
197 };
198 addListener(transitionListener);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700199 }
200 return anim;
201 } else {
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700202 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);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700209 // 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);
Chet Haase199acdf2013-07-24 18:40:55 -0700238 TransitionListener transitionListener = new TransitionListenerAdapter() {
239 boolean mCanceled = false;
240
Chet Haasefaebd8f2012-05-18 14:17:57 -0700241 @Override
Chet Haase199acdf2013-07-24 18:40:55 -0700242 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) {
Chet Haasefaebd8f2012-05-18 14:17:57 -0700256 parent.suppressLayout(false);
257 }
Chet Haase199acdf2013-07-24 18:40:55 -0700258
259 @Override
260 public void onTransitionResume(Transition transition) {
261 parent.suppressLayout(true);
262 }
263 };
264 addListener(transitionListener);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700265 }
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 {
Chet Haasefaebd8f2012-05-18 14:17:57 -0700276 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);
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700290 Rect startBounds1 = new Rect(startX - tempLocation[0], startY - tempLocation[1],
Chet Haasefaebd8f2012-05-18 14:17:57 -0700291 startX - tempLocation[0] + view.getWidth(),
292 startY - tempLocation[1] + view.getHeight());
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700293 Rect endBounds1 = new Rect(endX - tempLocation[0], endY - tempLocation[1],
Chet Haasefaebd8f2012-05-18 14:17:57 -0700294 endX - tempLocation[0] + view.getWidth(),
295 endY - tempLocation[1] + view.getHeight());
296 ObjectAnimator anim = ObjectAnimator.ofObject(drawable, "bounds",
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700297 sRectEvaluator, startBounds1, endBounds1);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700298 anim.addListener(new AnimatorListenerAdapter() {
299 @Override
300 public void onAnimationEnd(Animator animation) {
301 sceneRoot.getOverlay().remove(drawable);
302 view.setVisibility(View.VISIBLE);
303 }
304 });
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700305 return anim;
Chet Haasefaebd8f2012-05-18 14:17:57 -0700306 }
307 }
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700308 return null;
Chet Haasefaebd8f2012-05-18 14:17:57 -0700309 }
310}