blob: ceda5a5f6f6e648903ba124d7cec80db0bc3f7d4 [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 Haasefaebd8f2012-05-18 14:17:57 -070017package android.view.transition;
18
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;
Chet Haase08735182013-06-04 10:44:40 -070028import android.util.ArrayMap;
Chet Haase867a8662013-06-03 07:30:21 -070029import android.util.Log;
Chet Haasefaebd8f2012-05-18 14:17:57 -070030import android.view.View;
31import android.view.ViewGroup;
32
Chet Haase08735182013-06-04 10:44:40 -070033import java.util.Map;
Chet Haasefaebd8f2012-05-18 14:17:57 -070034
35/**
36 * This transition captures the layout bounds of target views before and after
37 * the scene change and animates those changes during the transition.
38 */
39public class Move extends Transition {
40
41 private static final String PROPNAME_BOUNDS = "android:move:bounds";
42 private static final String PROPNAME_PARENT = "android:move:parent";
43 private static final String PROPNAME_WINDOW_X = "android:move:windowX";
44 private static final String PROPNAME_WINDOW_Y = "android:move:windowY";
45 int[] tempLocation = new int[2];
46 boolean mResizeClip = false;
47 boolean mReparent = false;
Chet Haase867a8662013-06-03 07:30:21 -070048 private static final String LOG_TAG = "Move";
Chet Haasefaebd8f2012-05-18 14:17:57 -070049
50 private static RectEvaluator sRectEvaluator = new RectEvaluator();
51
52 public void setResizeClip(boolean resizeClip) {
53 mResizeClip = resizeClip;
54 }
55
56 /**
57 * Setting this flag tells Move to track the before/after parent
58 * of every view using this transition. The flag is not enabled by
59 * default because it requires the parent instances to be the same
60 * in the two scenes or else all parents must use ids to allow
61 * the transition to determine which parents are the same.
62 *
63 * @param reparent true if the transition should track the parent
64 * container of target views and animate parent changes.
65 */
66 public void setReparent(boolean reparent) {
67 mReparent = reparent;
68 }
69
70 @Override
71 protected void captureValues(TransitionValues values, boolean start) {
72 View view = values.view;
73 values.values.put(PROPNAME_BOUNDS, new Rect(view.getLeft(), view.getTop(),
74 view.getRight(), view.getBottom()));
75 values.values.put(PROPNAME_PARENT, values.view.getParent());
76 values.view.getLocationInWindow(tempLocation);
77 values.values.put(PROPNAME_WINDOW_X, tempLocation[0]);
78 values.values.put(PROPNAME_WINDOW_Y, tempLocation[1]);
79 }
80
81 @Override
82 protected Animator play(final ViewGroup sceneRoot, TransitionValues startValues,
83 TransitionValues endValues) {
84 if (startValues == null || endValues == null) {
85 return null;
86 }
Chet Haase2ea7f8b2013-06-21 15:00:05 -070087 Map<String, Object> startParentVals = startValues.values;
88 Map<String, Object> endParentVals = endValues.values;
89 ViewGroup startParent = (ViewGroup) startParentVals.get(PROPNAME_PARENT);
90 ViewGroup endParent = (ViewGroup) endParentVals.get(PROPNAME_PARENT);
Chet Haasefaebd8f2012-05-18 14:17:57 -070091 if (startParent == null || endParent == null) {
92 return null;
93 }
Chet Haase2ea7f8b2013-06-21 15:00:05 -070094 final View view = endValues.view;
Chet Haasefaebd8f2012-05-18 14:17:57 -070095 boolean parentsEqual = (startParent == endParent) ||
96 (startParent.getId() == endParent.getId());
Chet Haase2ea7f8b2013-06-21 15:00:05 -070097 // TODO: Might want reparenting to be separate/subclass transition, or at least
98 // triggered by a property on Move. Otherwise, we're forcing the requirement that
99 // all parents in layouts have IDs to avoid layout-inflation resulting in a side-effect
100 // of reparenting the views.
Chet Haasefaebd8f2012-05-18 14:17:57 -0700101 if (!mReparent || parentsEqual) {
Chet Haasefaebd8f2012-05-18 14:17:57 -0700102 Rect startBounds = (Rect) startValues.values.get(PROPNAME_BOUNDS);
103 Rect endBounds = (Rect) endValues.values.get(PROPNAME_BOUNDS);
104 int startLeft = startBounds.left;
105 int endLeft = endBounds.left;
106 int startTop = startBounds.top;
107 int endTop = endBounds.top;
108 int startRight = startBounds.right;
109 int endRight = endBounds.right;
110 int startBottom = startBounds.bottom;
111 int endBottom = endBounds.bottom;
112 int startWidth = startRight - startLeft;
113 int startHeight = startBottom - startTop;
114 int endWidth = endRight - endLeft;
115 int endHeight = endBottom - endTop;
116 int numChanges = 0;
117 if (startWidth != 0 && startHeight != 0 && endWidth != 0 && endHeight != 0) {
Chet Haasefaebd8f2012-05-18 14:17:57 -0700118 if (startLeft != endLeft) ++numChanges;
119 if (startTop != endTop) ++numChanges;
120 if (startRight != endRight) ++numChanges;
121 if (startBottom != endBottom) ++numChanges;
122 }
123 if (numChanges > 0) {
124 if (!mResizeClip) {
125 PropertyValuesHolder pvh[] = new PropertyValuesHolder[numChanges];
126 int pvhIndex = 0;
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700127 if (startLeft != endLeft) view.setLeft(startLeft);
128 if (startTop != endTop) view.setTop(startTop);
129 if (startRight != endRight) view.setRight(startRight);
130 if (startBottom != endBottom) view.setBottom(startBottom);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700131 if (startLeft != endLeft) {
132 pvh[pvhIndex++] = PropertyValuesHolder.ofInt("left", startLeft, endLeft);
133 }
134 if (startTop != endTop) {
135 pvh[pvhIndex++] = PropertyValuesHolder.ofInt("top", startTop, endTop);
136 }
137 if (startRight != endRight) {
138 pvh[pvhIndex++] = PropertyValuesHolder.ofInt("right",
139 startRight, endRight);
140 }
141 if (startBottom != endBottom) {
142 pvh[pvhIndex++] = PropertyValuesHolder.ofInt("bottom",
143 startBottom, endBottom);
144 }
145 ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(view, pvh);
146 if (view.getParent() instanceof ViewGroup) {
147 final ViewGroup parent = (ViewGroup) view.getParent();
148 parent.suppressLayout(true);
149 anim.addListener(new AnimatorListenerAdapter() {
150 @Override
151 public void onAnimationEnd(Animator animation) {
152 parent.suppressLayout(false);
153 }
154 });
155 }
156 return anim;
157 } else {
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700158 if (startWidth != endWidth) view.setRight(endLeft +
159 Math.max(startWidth, endWidth));
160 if (startHeight != endHeight) view.setBottom(endTop +
161 Math.max(startHeight, endHeight));
162 // TODO: don't clobber TX/TY
163 if (startLeft != endLeft) view.setTranslationX(startLeft - endLeft);
164 if (startTop != endTop) view.setTranslationY(startTop - endTop);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700165 // Animate location with translationX/Y and size with clip bounds
166 float transXDelta = endLeft - startLeft;
167 float transYDelta = endTop - startTop;
168 int widthDelta = endWidth - startWidth;
169 int heightDelta = endHeight - startHeight;
170 numChanges = 0;
171 if (transXDelta != 0) numChanges++;
172 if (transYDelta != 0) numChanges++;
173 if (widthDelta != 0 || heightDelta != 0) numChanges++;
174 PropertyValuesHolder pvh[] = new PropertyValuesHolder[numChanges];
175 int pvhIndex = 0;
176 if (transXDelta != 0) {
177 pvh[pvhIndex++] = PropertyValuesHolder.ofFloat("translationX",
178 view.getTranslationX(), 0);
179 }
180 if (transYDelta != 0) {
181 pvh[pvhIndex++] = PropertyValuesHolder.ofFloat("translationY",
182 view.getTranslationY(), 0);
183 }
184 if (widthDelta != 0 || heightDelta != 0) {
185 Rect tempStartBounds = new Rect(0, 0, startWidth, startHeight);
186 Rect tempEndBounds = new Rect(0, 0, endWidth, endHeight);
187 pvh[pvhIndex++] = PropertyValuesHolder.ofObject("clipBounds",
188 sRectEvaluator, tempStartBounds, tempEndBounds);
189 }
190 ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(view, pvh);
191 if (view.getParent() instanceof ViewGroup) {
192 final ViewGroup parent = (ViewGroup) view.getParent();
193 parent.suppressLayout(true);
194 anim.addListener(new AnimatorListenerAdapter() {
195 @Override
196 public void onAnimationEnd(Animator animation) {
197 parent.suppressLayout(false);
198 }
199 });
200 }
201 anim.addListener(new AnimatorListenerAdapter() {
202 @Override
203 public void onAnimationEnd(Animator animation) {
204 view.setClipBounds(null);
205 }
206 });
207 return anim;
208 }
209 }
210 } else {
Chet Haasefaebd8f2012-05-18 14:17:57 -0700211 int startX = (Integer) startValues.values.get(PROPNAME_WINDOW_X);
212 int startY = (Integer) startValues.values.get(PROPNAME_WINDOW_Y);
213 int endX = (Integer) endValues.values.get(PROPNAME_WINDOW_X);
214 int endY = (Integer) endValues.values.get(PROPNAME_WINDOW_Y);
215 // TODO: also handle size changes: check bounds and animate size changes
216 if (startX != endX || startY != endY) {
217 sceneRoot.getLocationInWindow(tempLocation);
218 Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(),
219 Bitmap.Config.ARGB_8888);
220 Canvas canvas = new Canvas(bitmap);
221 view.draw(canvas);
222 final BitmapDrawable drawable = new BitmapDrawable(bitmap);
223 view.setVisibility(View.INVISIBLE);
224 sceneRoot.getOverlay().add(drawable);
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700225 Rect startBounds1 = new Rect(startX - tempLocation[0], startY - tempLocation[1],
Chet Haasefaebd8f2012-05-18 14:17:57 -0700226 startX - tempLocation[0] + view.getWidth(),
227 startY - tempLocation[1] + view.getHeight());
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700228 Rect endBounds1 = new Rect(endX - tempLocation[0], endY - tempLocation[1],
Chet Haasefaebd8f2012-05-18 14:17:57 -0700229 endX - tempLocation[0] + view.getWidth(),
230 endY - tempLocation[1] + view.getHeight());
231 ObjectAnimator anim = ObjectAnimator.ofObject(drawable, "bounds",
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700232 sRectEvaluator, startBounds1, endBounds1);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700233 anim.addListener(new AnimatorListenerAdapter() {
234 @Override
235 public void onAnimationEnd(Animator animation) {
236 sceneRoot.getOverlay().remove(drawable);
237 view.setVisibility(View.VISIBLE);
238 }
239 });
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700240 return anim;
Chet Haasefaebd8f2012-05-18 14:17:57 -0700241 }
242 }
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700243 return null;
Chet Haasefaebd8f2012-05-18 14:17:57 -0700244 }
245}