blob: 0da5fb6d91bc99992aa512dcf3a15b466a9eb3fb [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
George Mountecd857b2014-06-19 07:51:08 -070019import android.content.Context;
20import android.graphics.PointF;
21
Chet Haasefaebd8f2012-05-18 14:17:57 -070022import android.animation.Animator;
23import android.animation.AnimatorListenerAdapter;
24import android.animation.ObjectAnimator;
25import android.animation.PropertyValuesHolder;
26import android.animation.RectEvaluator;
27import android.graphics.Bitmap;
28import android.graphics.Canvas;
George Mountecd857b2014-06-19 07:51:08 -070029import android.graphics.Path;
Chet Haasefaebd8f2012-05-18 14:17:57 -070030import android.graphics.Rect;
31import android.graphics.drawable.BitmapDrawable;
George Mountecd857b2014-06-19 07:51:08 -070032import android.graphics.drawable.Drawable;
33import android.util.AttributeSet;
George Mountff8e6d82014-08-28 16:58:01 -070034import android.util.IntProperty;
George Mountecd857b2014-06-19 07:51:08 -070035import android.util.Property;
Chet Haasefaebd8f2012-05-18 14:17:57 -070036import android.view.View;
37import android.view.ViewGroup;
38
Chet Haase08735182013-06-04 10:44:40 -070039import java.util.Map;
Chet Haasefaebd8f2012-05-18 14:17:57 -070040
41/**
42 * This transition captures the layout bounds of target views before and after
43 * the scene change and animates those changes during the transition.
Chet Haased82c8ac2013-08-26 14:20:16 -070044 *
45 * <p>A ChangeBounds transition can be described in a resource file by using the
46 * tag <code>changeBounds</code>, along with the other standard
47 * attributes of {@link android.R.styleable#Transition}.</p>
Chet Haasefaebd8f2012-05-18 14:17:57 -070048 */
Chet Haased82c8ac2013-08-26 14:20:16 -070049public class ChangeBounds extends Transition {
Chet Haasefaebd8f2012-05-18 14:17:57 -070050
Chet Haased82c8ac2013-08-26 14:20:16 -070051 private static final String PROPNAME_BOUNDS = "android:changeBounds:bounds";
52 private static final String PROPNAME_PARENT = "android:changeBounds:parent";
53 private static final String PROPNAME_WINDOW_X = "android:changeBounds:windowX";
54 private static final String PROPNAME_WINDOW_Y = "android:changeBounds:windowY";
Chet Haaseaf78bdd2013-08-27 16:06:26 -070055 private static final String[] sTransitionProperties = {
Chet Haase199acdf2013-07-24 18:40:55 -070056 PROPNAME_BOUNDS,
57 PROPNAME_PARENT,
58 PROPNAME_WINDOW_X,
59 PROPNAME_WINDOW_Y
60 };
61
George Mountecd857b2014-06-19 07:51:08 -070062 private static final Property<Drawable, PointF> DRAWABLE_ORIGIN_PROPERTY =
63 new Property<Drawable, PointF>(PointF.class, "boundsOrigin") {
64 private Rect mBounds = new Rect();
65
66 @Override
67 public void set(Drawable object, PointF value) {
68 object.copyBounds(mBounds);
69 mBounds.offsetTo(Math.round(value.x), Math.round(value.y));
70 object.setBounds(mBounds);
71 }
72
73 @Override
74 public PointF get(Drawable object) {
75 object.copyBounds(mBounds);
76 return new PointF(mBounds.left, mBounds.top);
77 }
78 };
79
Chet Haasefaebd8f2012-05-18 14:17:57 -070080 int[] tempLocation = new int[2];
81 boolean mResizeClip = false;
82 boolean mReparent = false;
Chet Haased82c8ac2013-08-26 14:20:16 -070083 private static final String LOG_TAG = "ChangeBounds";
Chet Haasefaebd8f2012-05-18 14:17:57 -070084
85 private static RectEvaluator sRectEvaluator = new RectEvaluator();
86
George Mountecd857b2014-06-19 07:51:08 -070087 public ChangeBounds() {}
88
89 public ChangeBounds(Context context, AttributeSet attrs) {
90 super(context, attrs);
91 }
92
Chet Haase199acdf2013-07-24 18:40:55 -070093 @Override
94 public String[] getTransitionProperties() {
95 return sTransitionProperties;
96 }
97
Chet Haasefaebd8f2012-05-18 14:17:57 -070098 public void setResizeClip(boolean resizeClip) {
99 mResizeClip = resizeClip;
100 }
101
102 /**
Chet Haased82c8ac2013-08-26 14:20:16 -0700103 * Setting this flag tells ChangeBounds to track the before/after parent
Chet Haasefaebd8f2012-05-18 14:17:57 -0700104 * of every view using this transition. The flag is not enabled by
105 * default because it requires the parent instances to be the same
106 * in the two scenes or else all parents must use ids to allow
107 * the transition to determine which parents are the same.
108 *
109 * @param reparent true if the transition should track the parent
110 * container of target views and animate parent changes.
Dake Guc94e2b32014-07-22 14:53:53 -0700111 * @deprecated Use {@link android.transition.ChangeTransform} to handle
112 * transitions between different parents.
Chet Haasefaebd8f2012-05-18 14:17:57 -0700113 */
114 public void setReparent(boolean reparent) {
115 mReparent = reparent;
116 }
117
Chet Haased82c8ac2013-08-26 14:20:16 -0700118 private void captureValues(TransitionValues values) {
Chet Haasefaebd8f2012-05-18 14:17:57 -0700119 View view = values.view;
Chris Craikf7917032014-10-06 14:50:59 -0700120
George Mount5ac9b202014-10-10 13:22:36 -0700121 if (view.isLaidOut() || view.getWidth() != 0 || view.getHeight() != 0) {
Chris Craikf7917032014-10-06 14:50:59 -0700122 values.values.put(PROPNAME_BOUNDS, new Rect(view.getLeft(), view.getTop(),
123 view.getRight(), view.getBottom()));
124 values.values.put(PROPNAME_PARENT, values.view.getParent());
125 if (mReparent) {
126 values.view.getLocationInWindow(tempLocation);
127 values.values.put(PROPNAME_WINDOW_X, tempLocation[0]);
128 values.values.put(PROPNAME_WINDOW_Y, tempLocation[1]);
129 }
George Mount4d1ecf52014-07-29 11:15:54 -0700130 }
Chet Haasefaebd8f2012-05-18 14:17:57 -0700131 }
132
133 @Override
Chet Haased82c8ac2013-08-26 14:20:16 -0700134 public void captureStartValues(TransitionValues transitionValues) {
135 captureValues(transitionValues);
136 }
137
138 @Override
139 public void captureEndValues(TransitionValues transitionValues) {
140 captureValues(transitionValues);
141 }
142
George Mount4d1ecf52014-07-29 11:15:54 -0700143 private boolean parentMatches(View startParent, View endParent) {
144 boolean parentMatches = true;
145 if (mReparent) {
146 TransitionValues endValues = getMatchedTransitionValues(startParent, true);
147 if (endValues == null) {
148 parentMatches = startParent == endParent;
149 } else {
150 parentMatches = endParent == endValues.view;
151 }
152 }
153 return parentMatches;
154 }
155
Chet Haased82c8ac2013-08-26 14:20:16 -0700156 @Override
157 public Animator createAnimator(final ViewGroup sceneRoot, TransitionValues startValues,
Chet Haasefaebd8f2012-05-18 14:17:57 -0700158 TransitionValues endValues) {
159 if (startValues == null || endValues == null) {
160 return null;
161 }
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700162 Map<String, Object> startParentVals = startValues.values;
163 Map<String, Object> endParentVals = endValues.values;
164 ViewGroup startParent = (ViewGroup) startParentVals.get(PROPNAME_PARENT);
165 ViewGroup endParent = (ViewGroup) endParentVals.get(PROPNAME_PARENT);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700166 if (startParent == null || endParent == null) {
167 return null;
168 }
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700169 final View view = endValues.view;
George Mount4d1ecf52014-07-29 11:15:54 -0700170 if (parentMatches(startParent, endParent)) {
Chet Haasefaebd8f2012-05-18 14:17:57 -0700171 Rect startBounds = (Rect) startValues.values.get(PROPNAME_BOUNDS);
172 Rect endBounds = (Rect) endValues.values.get(PROPNAME_BOUNDS);
173 int startLeft = startBounds.left;
174 int endLeft = endBounds.left;
175 int startTop = startBounds.top;
176 int endTop = endBounds.top;
177 int startRight = startBounds.right;
178 int endRight = endBounds.right;
179 int startBottom = startBounds.bottom;
180 int endBottom = endBounds.bottom;
181 int startWidth = startRight - startLeft;
182 int startHeight = startBottom - startTop;
183 int endWidth = endRight - endLeft;
184 int endHeight = endBottom - endTop;
185 int numChanges = 0;
George Mount2cacfe32014-07-24 09:47:02 -0700186 if ((startWidth != 0 && startHeight != 0) || (endWidth != 0 && endHeight != 0)) {
George Mountecd857b2014-06-19 07:51:08 -0700187 if (startLeft != endLeft || startTop != endTop) ++numChanges;
188 if (startRight != endRight || startBottom != endBottom) ++numChanges;
Chet Haasefaebd8f2012-05-18 14:17:57 -0700189 }
190 if (numChanges > 0) {
191 if (!mResizeClip) {
George Mountff8e6d82014-08-28 16:58:01 -0700192 Animator anim;
193 if (startWidth == endWidth && startHeight == endHeight) {
194 view.offsetLeftAndRight(startLeft - view.getLeft());
195 view.offsetTopAndBottom(startTop - view.getTop());
196 Path positionPath = getPathMotion().getPath(0, 0, endLeft - startLeft,
197 endTop - startTop);
198 anim = ObjectAnimator.ofInt(view, new HorizontalOffsetProperty(),
199 new VerticalOffsetProperty(), positionPath);
200 } else {
201 if (startLeft != endLeft) view.setLeft(startLeft);
202 if (startTop != endTop) view.setTop(startTop);
203 if (startRight != endRight) view.setRight(startRight);
204 if (startBottom != endBottom) view.setBottom(startBottom);
205 ObjectAnimator topLeftAnimator = null;
206 if (startLeft != endLeft || startTop != endTop) {
207 Path topLeftPath = getPathMotion().getPath(startLeft, startTop,
208 endLeft, endTop);
209 topLeftAnimator = ObjectAnimator
210 .ofInt(view, "left", "top", topLeftPath);
211 }
212 ObjectAnimator bottomRightAnimator = null;
213 if (startRight != endRight || startBottom != endBottom) {
214 Path bottomRightPath = getPathMotion().getPath(startRight, startBottom,
215 endRight, endBottom);
216 bottomRightAnimator = ObjectAnimator.ofInt(view, "right", "bottom",
217 bottomRightPath);
218 }
219 anim = TransitionUtils.mergeAnimators(topLeftAnimator,
220 bottomRightAnimator);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700221 }
Chet Haasefaebd8f2012-05-18 14:17:57 -0700222 if (view.getParent() instanceof ViewGroup) {
223 final ViewGroup parent = (ViewGroup) view.getParent();
224 parent.suppressLayout(true);
Chet Haase199acdf2013-07-24 18:40:55 -0700225 TransitionListener transitionListener = new TransitionListenerAdapter() {
226 boolean mCanceled = false;
227
Chet Haasefaebd8f2012-05-18 14:17:57 -0700228 @Override
Chet Haase199acdf2013-07-24 18:40:55 -0700229 public void onTransitionCancel(Transition transition) {
230 parent.suppressLayout(false);
231 mCanceled = true;
232 }
233
234 @Override
235 public void onTransitionEnd(Transition transition) {
236 if (!mCanceled) {
237 parent.suppressLayout(false);
238 }
239 }
240
241 @Override
242 public void onTransitionPause(Transition transition) {
Chet Haasefaebd8f2012-05-18 14:17:57 -0700243 parent.suppressLayout(false);
244 }
Chet Haase199acdf2013-07-24 18:40:55 -0700245
246 @Override
247 public void onTransitionResume(Transition transition) {
248 parent.suppressLayout(true);
249 }
250 };
251 addListener(transitionListener);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700252 }
253 return anim;
254 } else {
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700255 if (startWidth != endWidth) view.setRight(endLeft +
256 Math.max(startWidth, endWidth));
257 if (startHeight != endHeight) view.setBottom(endTop +
258 Math.max(startHeight, endHeight));
259 // TODO: don't clobber TX/TY
260 if (startLeft != endLeft) view.setTranslationX(startLeft - endLeft);
261 if (startTop != endTop) view.setTranslationY(startTop - endTop);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700262 // Animate location with translationX/Y and size with clip bounds
263 float transXDelta = endLeft - startLeft;
264 float transYDelta = endTop - startTop;
265 int widthDelta = endWidth - startWidth;
266 int heightDelta = endHeight - startHeight;
267 numChanges = 0;
268 if (transXDelta != 0) numChanges++;
269 if (transYDelta != 0) numChanges++;
270 if (widthDelta != 0 || heightDelta != 0) numChanges++;
George Mountecd857b2014-06-19 07:51:08 -0700271 ObjectAnimator translationAnimator = null;
272 if (transXDelta != 0 || transYDelta != 0) {
273 Path topLeftPath = getPathMotion().getPath(0, 0, transXDelta, transYDelta);
274 translationAnimator = ObjectAnimator.ofFloat(view, View.TRANSLATION_X,
275 View.TRANSLATION_Y, topLeftPath);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700276 }
George Mountecd857b2014-06-19 07:51:08 -0700277 ObjectAnimator clipAnimator = null;
Chet Haasefaebd8f2012-05-18 14:17:57 -0700278 if (widthDelta != 0 || heightDelta != 0) {
279 Rect tempStartBounds = new Rect(0, 0, startWidth, startHeight);
280 Rect tempEndBounds = new Rect(0, 0, endWidth, endHeight);
George Mountecd857b2014-06-19 07:51:08 -0700281 clipAnimator = ObjectAnimator.ofObject(view, "clipBounds", sRectEvaluator,
282 tempStartBounds, tempEndBounds);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700283 }
Paul Soulos2dcab182014-07-22 14:10:16 -0700284 Animator anim = TransitionUtils.mergeAnimators(translationAnimator,
285 clipAnimator);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700286 if (view.getParent() instanceof ViewGroup) {
287 final ViewGroup parent = (ViewGroup) view.getParent();
288 parent.suppressLayout(true);
Chet Haase199acdf2013-07-24 18:40:55 -0700289 TransitionListener transitionListener = new TransitionListenerAdapter() {
290 boolean mCanceled = false;
291
Chet Haasefaebd8f2012-05-18 14:17:57 -0700292 @Override
Chet Haase199acdf2013-07-24 18:40:55 -0700293 public void onTransitionCancel(Transition transition) {
294 parent.suppressLayout(false);
295 mCanceled = true;
296 }
297
298 @Override
299 public void onTransitionEnd(Transition transition) {
300 if (!mCanceled) {
301 parent.suppressLayout(false);
302 }
303 }
304
305 @Override
306 public void onTransitionPause(Transition transition) {
Chet Haasefaebd8f2012-05-18 14:17:57 -0700307 parent.suppressLayout(false);
308 }
Chet Haase199acdf2013-07-24 18:40:55 -0700309
310 @Override
311 public void onTransitionResume(Transition transition) {
312 parent.suppressLayout(true);
313 }
314 };
315 addListener(transitionListener);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700316 }
317 anim.addListener(new AnimatorListenerAdapter() {
318 @Override
319 public void onAnimationEnd(Animator animation) {
320 view.setClipBounds(null);
321 }
322 });
323 return anim;
324 }
325 }
326 } else {
Chet Haasefaebd8f2012-05-18 14:17:57 -0700327 int startX = (Integer) startValues.values.get(PROPNAME_WINDOW_X);
328 int startY = (Integer) startValues.values.get(PROPNAME_WINDOW_Y);
329 int endX = (Integer) endValues.values.get(PROPNAME_WINDOW_X);
330 int endY = (Integer) endValues.values.get(PROPNAME_WINDOW_Y);
331 // TODO: also handle size changes: check bounds and animate size changes
332 if (startX != endX || startY != endY) {
333 sceneRoot.getLocationInWindow(tempLocation);
334 Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(),
335 Bitmap.Config.ARGB_8888);
336 Canvas canvas = new Canvas(bitmap);
337 view.draw(canvas);
338 final BitmapDrawable drawable = new BitmapDrawable(bitmap);
George Mountb5ef7f82014-07-09 14:55:03 -0700339 final float transitionAlpha = view.getTransitionAlpha();
340 view.setTransitionAlpha(0);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700341 sceneRoot.getOverlay().add(drawable);
George Mountecd857b2014-06-19 07:51:08 -0700342 Path topLeftPath = getPathMotion().getPath(startX - tempLocation[0],
343 startY - tempLocation[1], endX - tempLocation[0], endY - tempLocation[1]);
344 PropertyValuesHolder origin = PropertyValuesHolder.ofObject(
345 DRAWABLE_ORIGIN_PROPERTY, null, topLeftPath);
346 ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(drawable, origin);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700347 anim.addListener(new AnimatorListenerAdapter() {
348 @Override
349 public void onAnimationEnd(Animator animation) {
350 sceneRoot.getOverlay().remove(drawable);
George Mountb5ef7f82014-07-09 14:55:03 -0700351 view.setTransitionAlpha(transitionAlpha);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700352 }
353 });
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700354 return anim;
Chet Haasefaebd8f2012-05-18 14:17:57 -0700355 }
356 }
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700357 return null;
Chet Haasefaebd8f2012-05-18 14:17:57 -0700358 }
George Mountff8e6d82014-08-28 16:58:01 -0700359
360 private abstract static class OffsetProperty extends IntProperty<View> {
361 int mPreviousValue;
362
363 public OffsetProperty(String name) {
364 super(name);
365 }
366
367 @Override
368 public void setValue(View view, int value) {
369 int offset = value - mPreviousValue;
370 offsetBy(view, offset);
371 mPreviousValue = value;
372 }
373
374 @Override
375 public Integer get(View object) {
376 return null;
377 }
378
379 protected abstract void offsetBy(View view, int by);
380 }
381
382 private static class HorizontalOffsetProperty extends OffsetProperty {
383 public HorizontalOffsetProperty() {
384 super("offsetLeftAndRight");
385 }
386
387 @Override
388 protected void offsetBy(View view, int by) {
389 view.offsetLeftAndRight(by);
390 }
391 }
392
393 private static class VerticalOffsetProperty extends OffsetProperty {
394 public VerticalOffsetProperty() {
395 super("offsetTopAndBottom");
396 }
397
398 @Override
399 protected void offsetBy(View view, int by) {
400 view.offsetTopAndBottom(by);
401 }
402 }
Chet Haasefaebd8f2012-05-18 14:17:57 -0700403}