blob: 3f5e8e8fbc2011df5bfb07b746891efb75cab668 [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.animation.TypeConverter;
20import android.content.Context;
21import android.graphics.PointF;
22
Chet Haasefaebd8f2012-05-18 14:17:57 -070023import android.animation.Animator;
24import android.animation.AnimatorListenerAdapter;
George Mountecd857b2014-06-19 07:51:08 -070025import android.animation.AnimatorSet;
Chet Haasefaebd8f2012-05-18 14:17:57 -070026import android.animation.ObjectAnimator;
27import android.animation.PropertyValuesHolder;
28import android.animation.RectEvaluator;
29import android.graphics.Bitmap;
30import android.graphics.Canvas;
George Mountecd857b2014-06-19 07:51:08 -070031import android.graphics.Path;
Chet Haasefaebd8f2012-05-18 14:17:57 -070032import android.graphics.Rect;
33import android.graphics.drawable.BitmapDrawable;
George Mountecd857b2014-06-19 07:51:08 -070034import android.graphics.drawable.Drawable;
35import android.util.AttributeSet;
36import android.util.Property;
Chet Haasefaebd8f2012-05-18 14:17:57 -070037import android.view.View;
38import android.view.ViewGroup;
39
Chet Haase08735182013-06-04 10:44:40 -070040import java.util.Map;
Chet Haasefaebd8f2012-05-18 14:17:57 -070041
42/**
43 * This transition captures the layout bounds of target views before and after
44 * the scene change and animates those changes during the transition.
Chet Haased82c8ac2013-08-26 14:20:16 -070045 *
46 * <p>A ChangeBounds transition can be described in a resource file by using the
47 * tag <code>changeBounds</code>, along with the other standard
48 * attributes of {@link android.R.styleable#Transition}.</p>
Chet Haasefaebd8f2012-05-18 14:17:57 -070049 */
Chet Haased82c8ac2013-08-26 14:20:16 -070050public class ChangeBounds extends Transition {
Chet Haasefaebd8f2012-05-18 14:17:57 -070051
Chet Haased82c8ac2013-08-26 14:20:16 -070052 private static final String PROPNAME_BOUNDS = "android:changeBounds:bounds";
53 private static final String PROPNAME_PARENT = "android:changeBounds:parent";
54 private static final String PROPNAME_WINDOW_X = "android:changeBounds:windowX";
55 private static final String PROPNAME_WINDOW_Y = "android:changeBounds:windowY";
Chet Haaseaf78bdd2013-08-27 16:06:26 -070056 private static final String[] sTransitionProperties = {
Chet Haase199acdf2013-07-24 18:40:55 -070057 PROPNAME_BOUNDS,
58 PROPNAME_PARENT,
59 PROPNAME_WINDOW_X,
60 PROPNAME_WINDOW_Y
61 };
62
George Mountecd857b2014-06-19 07:51:08 -070063 private static final Property<Drawable, PointF> DRAWABLE_ORIGIN_PROPERTY =
64 new Property<Drawable, PointF>(PointF.class, "boundsOrigin") {
65 private Rect mBounds = new Rect();
66
67 @Override
68 public void set(Drawable object, PointF value) {
69 object.copyBounds(mBounds);
70 mBounds.offsetTo(Math.round(value.x), Math.round(value.y));
71 object.setBounds(mBounds);
72 }
73
74 @Override
75 public PointF get(Drawable object) {
76 object.copyBounds(mBounds);
77 return new PointF(mBounds.left, mBounds.top);
78 }
79 };
80
Chet Haasefaebd8f2012-05-18 14:17:57 -070081 int[] tempLocation = new int[2];
82 boolean mResizeClip = false;
83 boolean mReparent = false;
Chet Haased82c8ac2013-08-26 14:20:16 -070084 private static final String LOG_TAG = "ChangeBounds";
Chet Haasefaebd8f2012-05-18 14:17:57 -070085
86 private static RectEvaluator sRectEvaluator = new RectEvaluator();
87
George Mountecd857b2014-06-19 07:51:08 -070088 public ChangeBounds() {}
89
90 public ChangeBounds(Context context, AttributeSet attrs) {
91 super(context, attrs);
92 }
93
Chet Haase199acdf2013-07-24 18:40:55 -070094 @Override
95 public String[] getTransitionProperties() {
96 return sTransitionProperties;
97 }
98
Chet Haasefaebd8f2012-05-18 14:17:57 -070099 public void setResizeClip(boolean resizeClip) {
100 mResizeClip = resizeClip;
101 }
102
103 /**
Chet Haased82c8ac2013-08-26 14:20:16 -0700104 * Setting this flag tells ChangeBounds to track the before/after parent
Chet Haasefaebd8f2012-05-18 14:17:57 -0700105 * of every view using this transition. The flag is not enabled by
106 * default because it requires the parent instances to be the same
107 * in the two scenes or else all parents must use ids to allow
108 * the transition to determine which parents are the same.
109 *
110 * @param reparent true if the transition should track the parent
111 * container of target views and animate parent changes.
112 */
113 public void setReparent(boolean reparent) {
114 mReparent = reparent;
115 }
116
Chet Haased82c8ac2013-08-26 14:20:16 -0700117 private void captureValues(TransitionValues values) {
Chet Haasefaebd8f2012-05-18 14:17:57 -0700118 View view = values.view;
119 values.values.put(PROPNAME_BOUNDS, new Rect(view.getLeft(), view.getTop(),
120 view.getRight(), view.getBottom()));
121 values.values.put(PROPNAME_PARENT, values.view.getParent());
122 values.view.getLocationInWindow(tempLocation);
123 values.values.put(PROPNAME_WINDOW_X, tempLocation[0]);
124 values.values.put(PROPNAME_WINDOW_Y, tempLocation[1]);
125 }
126
127 @Override
Chet Haased82c8ac2013-08-26 14:20:16 -0700128 public void captureStartValues(TransitionValues transitionValues) {
129 captureValues(transitionValues);
130 }
131
132 @Override
133 public void captureEndValues(TransitionValues transitionValues) {
134 captureValues(transitionValues);
135 }
136
137 @Override
138 public Animator createAnimator(final ViewGroup sceneRoot, TransitionValues startValues,
Chet Haasefaebd8f2012-05-18 14:17:57 -0700139 TransitionValues endValues) {
140 if (startValues == null || endValues == null) {
141 return null;
142 }
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700143 Map<String, Object> startParentVals = startValues.values;
144 Map<String, Object> endParentVals = endValues.values;
145 ViewGroup startParent = (ViewGroup) startParentVals.get(PROPNAME_PARENT);
146 ViewGroup endParent = (ViewGroup) endParentVals.get(PROPNAME_PARENT);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700147 if (startParent == null || endParent == null) {
148 return null;
149 }
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700150 final View view = endValues.view;
Chet Haasefaebd8f2012-05-18 14:17:57 -0700151 boolean parentsEqual = (startParent == endParent) ||
152 (startParent.getId() == endParent.getId());
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700153 // TODO: Might want reparenting to be separate/subclass transition, or at least
Chet Haased82c8ac2013-08-26 14:20:16 -0700154 // triggered by a property on ChangeBounds. Otherwise, we're forcing the requirement that
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700155 // all parents in layouts have IDs to avoid layout-inflation resulting in a side-effect
156 // of reparenting the views.
Chet Haasefaebd8f2012-05-18 14:17:57 -0700157 if (!mReparent || parentsEqual) {
Chet Haasefaebd8f2012-05-18 14:17:57 -0700158 Rect startBounds = (Rect) startValues.values.get(PROPNAME_BOUNDS);
159 Rect endBounds = (Rect) endValues.values.get(PROPNAME_BOUNDS);
160 int startLeft = startBounds.left;
161 int endLeft = endBounds.left;
162 int startTop = startBounds.top;
163 int endTop = endBounds.top;
164 int startRight = startBounds.right;
165 int endRight = endBounds.right;
166 int startBottom = startBounds.bottom;
167 int endBottom = endBounds.bottom;
168 int startWidth = startRight - startLeft;
169 int startHeight = startBottom - startTop;
170 int endWidth = endRight - endLeft;
171 int endHeight = endBottom - endTop;
172 int numChanges = 0;
173 if (startWidth != 0 && startHeight != 0 && endWidth != 0 && endHeight != 0) {
George Mountecd857b2014-06-19 07:51:08 -0700174 if (startLeft != endLeft || startTop != endTop) ++numChanges;
175 if (startRight != endRight || startBottom != endBottom) ++numChanges;
Chet Haasefaebd8f2012-05-18 14:17:57 -0700176 }
177 if (numChanges > 0) {
178 if (!mResizeClip) {
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700179 if (startLeft != endLeft) view.setLeft(startLeft);
180 if (startTop != endTop) view.setTop(startTop);
181 if (startRight != endRight) view.setRight(startRight);
182 if (startBottom != endBottom) view.setBottom(startBottom);
George Mountecd857b2014-06-19 07:51:08 -0700183 ObjectAnimator topLeftAnimator = null;
184 if (startLeft != endLeft || startTop != endTop) {
185 Path topLeftPath = getPathMotion().getPath(startLeft, startTop,
186 endLeft, endTop);
187 topLeftAnimator = ObjectAnimator.ofInt(view, "left", "top", topLeftPath);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700188 }
George Mountecd857b2014-06-19 07:51:08 -0700189 ObjectAnimator bottomRightAnimator = null;
190 if (startRight != endRight || startBottom != endBottom) {
191 Path bottomRightPath = getPathMotion().getPath(startRight, startBottom,
192 endRight, endBottom);
193 bottomRightAnimator = ObjectAnimator.ofInt(view, "right", "bottom",
194 bottomRightPath);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700195 }
George Mountecd857b2014-06-19 07:51:08 -0700196 Animator anim = mergeAnimators(topLeftAnimator, bottomRightAnimator);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700197 if (view.getParent() instanceof ViewGroup) {
198 final ViewGroup parent = (ViewGroup) view.getParent();
199 parent.suppressLayout(true);
Chet Haase199acdf2013-07-24 18:40:55 -0700200 TransitionListener transitionListener = new TransitionListenerAdapter() {
201 boolean mCanceled = false;
202
Chet Haasefaebd8f2012-05-18 14:17:57 -0700203 @Override
Chet Haase199acdf2013-07-24 18:40:55 -0700204 public void onTransitionCancel(Transition transition) {
205 parent.suppressLayout(false);
206 mCanceled = true;
207 }
208
209 @Override
210 public void onTransitionEnd(Transition transition) {
211 if (!mCanceled) {
212 parent.suppressLayout(false);
213 }
214 }
215
216 @Override
217 public void onTransitionPause(Transition transition) {
Chet Haasefaebd8f2012-05-18 14:17:57 -0700218 parent.suppressLayout(false);
219 }
Chet Haase199acdf2013-07-24 18:40:55 -0700220
221 @Override
222 public void onTransitionResume(Transition transition) {
223 parent.suppressLayout(true);
224 }
225 };
226 addListener(transitionListener);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700227 }
228 return anim;
229 } else {
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700230 if (startWidth != endWidth) view.setRight(endLeft +
231 Math.max(startWidth, endWidth));
232 if (startHeight != endHeight) view.setBottom(endTop +
233 Math.max(startHeight, endHeight));
234 // TODO: don't clobber TX/TY
235 if (startLeft != endLeft) view.setTranslationX(startLeft - endLeft);
236 if (startTop != endTop) view.setTranslationY(startTop - endTop);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700237 // Animate location with translationX/Y and size with clip bounds
238 float transXDelta = endLeft - startLeft;
239 float transYDelta = endTop - startTop;
240 int widthDelta = endWidth - startWidth;
241 int heightDelta = endHeight - startHeight;
242 numChanges = 0;
243 if (transXDelta != 0) numChanges++;
244 if (transYDelta != 0) numChanges++;
245 if (widthDelta != 0 || heightDelta != 0) numChanges++;
George Mountecd857b2014-06-19 07:51:08 -0700246 ObjectAnimator translationAnimator = null;
247 if (transXDelta != 0 || transYDelta != 0) {
248 Path topLeftPath = getPathMotion().getPath(0, 0, transXDelta, transYDelta);
249 translationAnimator = ObjectAnimator.ofFloat(view, View.TRANSLATION_X,
250 View.TRANSLATION_Y, topLeftPath);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700251 }
George Mountecd857b2014-06-19 07:51:08 -0700252 ObjectAnimator clipAnimator = null;
Chet Haasefaebd8f2012-05-18 14:17:57 -0700253 if (widthDelta != 0 || heightDelta != 0) {
254 Rect tempStartBounds = new Rect(0, 0, startWidth, startHeight);
255 Rect tempEndBounds = new Rect(0, 0, endWidth, endHeight);
George Mountecd857b2014-06-19 07:51:08 -0700256 clipAnimator = ObjectAnimator.ofObject(view, "clipBounds", sRectEvaluator,
257 tempStartBounds, tempEndBounds);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700258 }
George Mountecd857b2014-06-19 07:51:08 -0700259 Animator anim = mergeAnimators(translationAnimator, clipAnimator);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700260 if (view.getParent() instanceof ViewGroup) {
261 final ViewGroup parent = (ViewGroup) view.getParent();
262 parent.suppressLayout(true);
Chet Haase199acdf2013-07-24 18:40:55 -0700263 TransitionListener transitionListener = new TransitionListenerAdapter() {
264 boolean mCanceled = false;
265
Chet Haasefaebd8f2012-05-18 14:17:57 -0700266 @Override
Chet Haase199acdf2013-07-24 18:40:55 -0700267 public void onTransitionCancel(Transition transition) {
268 parent.suppressLayout(false);
269 mCanceled = true;
270 }
271
272 @Override
273 public void onTransitionEnd(Transition transition) {
274 if (!mCanceled) {
275 parent.suppressLayout(false);
276 }
277 }
278
279 @Override
280 public void onTransitionPause(Transition transition) {
Chet Haasefaebd8f2012-05-18 14:17:57 -0700281 parent.suppressLayout(false);
282 }
Chet Haase199acdf2013-07-24 18:40:55 -0700283
284 @Override
285 public void onTransitionResume(Transition transition) {
286 parent.suppressLayout(true);
287 }
288 };
289 addListener(transitionListener);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700290 }
291 anim.addListener(new AnimatorListenerAdapter() {
292 @Override
293 public void onAnimationEnd(Animator animation) {
294 view.setClipBounds(null);
295 }
296 });
297 return anim;
298 }
299 }
300 } else {
Chet Haasefaebd8f2012-05-18 14:17:57 -0700301 int startX = (Integer) startValues.values.get(PROPNAME_WINDOW_X);
302 int startY = (Integer) startValues.values.get(PROPNAME_WINDOW_Y);
303 int endX = (Integer) endValues.values.get(PROPNAME_WINDOW_X);
304 int endY = (Integer) endValues.values.get(PROPNAME_WINDOW_Y);
305 // TODO: also handle size changes: check bounds and animate size changes
306 if (startX != endX || startY != endY) {
307 sceneRoot.getLocationInWindow(tempLocation);
308 Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(),
309 Bitmap.Config.ARGB_8888);
310 Canvas canvas = new Canvas(bitmap);
311 view.draw(canvas);
312 final BitmapDrawable drawable = new BitmapDrawable(bitmap);
George Mountb5ef7f82014-07-09 14:55:03 -0700313 final float transitionAlpha = view.getTransitionAlpha();
314 view.setTransitionAlpha(0);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700315 sceneRoot.getOverlay().add(drawable);
George Mountecd857b2014-06-19 07:51:08 -0700316 Path topLeftPath = getPathMotion().getPath(startX - tempLocation[0],
317 startY - tempLocation[1], endX - tempLocation[0], endY - tempLocation[1]);
318 PropertyValuesHolder origin = PropertyValuesHolder.ofObject(
319 DRAWABLE_ORIGIN_PROPERTY, null, topLeftPath);
320 ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(drawable, origin);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700321 anim.addListener(new AnimatorListenerAdapter() {
322 @Override
323 public void onAnimationEnd(Animator animation) {
324 sceneRoot.getOverlay().remove(drawable);
George Mountb5ef7f82014-07-09 14:55:03 -0700325 view.setTransitionAlpha(transitionAlpha);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700326 }
327 });
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700328 return anim;
Chet Haasefaebd8f2012-05-18 14:17:57 -0700329 }
330 }
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700331 return null;
Chet Haasefaebd8f2012-05-18 14:17:57 -0700332 }
George Mountecd857b2014-06-19 07:51:08 -0700333
334 private static Animator mergeAnimators(Animator animator1, Animator animator2) {
335 if (animator1 == null) {
336 return animator2;
337 } else if (animator2 == null) {
338 return animator1;
339 } else {
340 AnimatorSet animatorSet = new AnimatorSet();
341 animatorSet.playTogether(animator1, animator2);
342 return animatorSet;
343 }
344 }
Chet Haasefaebd8f2012-05-18 14:17:57 -0700345}