1 /*
2  * Copyright (C) 2015 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 package com.android.internal.transition;
17 
18 import android.animation.Animator;
19 import android.animation.AnimatorListenerAdapter;
20 import android.animation.AnimatorSet;
21 import android.animation.ObjectAnimator;
22 import android.animation.TimeInterpolator;
23 import android.animation.TypeEvaluator;
24 import android.content.Context;
25 import android.content.res.TypedArray;
26 import android.graphics.Rect;
27 import android.transition.TransitionValues;
28 import android.transition.Visibility;
29 import android.util.AttributeSet;
30 import android.util.Property;
31 import android.view.View;
32 import android.view.ViewGroup;
33 import android.view.animation.AnimationUtils;
34 
35 import com.android.internal.R;
36 
37 /**
38  * EpicenterTranslateClipReveal captures the clip bounds and translation values
39  * before and after the scene change and animates between those and the
40  * epicenter bounds during a visibility transition.
41  */
42 public class EpicenterTranslateClipReveal extends Visibility {
43     private static final String PROPNAME_CLIP = "android:epicenterReveal:clip";
44     private static final String PROPNAME_BOUNDS = "android:epicenterReveal:bounds";
45     private static final String PROPNAME_TRANSLATE_X = "android:epicenterReveal:translateX";
46     private static final String PROPNAME_TRANSLATE_Y = "android:epicenterReveal:translateY";
47     private static final String PROPNAME_TRANSLATE_Z = "android:epicenterReveal:translateZ";
48     private static final String PROPNAME_Z = "android:epicenterReveal:z";
49 
50     private final TimeInterpolator mInterpolatorX;
51     private final TimeInterpolator mInterpolatorY;
52     private final TimeInterpolator mInterpolatorZ;
53 
EpicenterTranslateClipReveal()54     public EpicenterTranslateClipReveal() {
55         mInterpolatorX = null;
56         mInterpolatorY = null;
57         mInterpolatorZ = null;
58     }
59 
EpicenterTranslateClipReveal(Context context, AttributeSet attrs)60     public EpicenterTranslateClipReveal(Context context, AttributeSet attrs) {
61         super(context, attrs);
62 
63         final TypedArray a = context.obtainStyledAttributes(attrs,
64                 R.styleable.EpicenterTranslateClipReveal, 0, 0);
65 
66         final int interpolatorX = a.getResourceId(
67                 R.styleable.EpicenterTranslateClipReveal_interpolatorX, 0);
68         if (interpolatorX != 0) {
69             mInterpolatorX = AnimationUtils.loadInterpolator(context, interpolatorX);
70         } else {
71             mInterpolatorX = TransitionConstants.LINEAR_OUT_SLOW_IN;
72         }
73 
74         final int interpolatorY = a.getResourceId(
75                 R.styleable.EpicenterTranslateClipReveal_interpolatorY, 0);
76         if (interpolatorY != 0) {
77             mInterpolatorY = AnimationUtils.loadInterpolator(context, interpolatorY);
78         } else {
79             mInterpolatorY = TransitionConstants.FAST_OUT_SLOW_IN;
80         }
81 
82         final int interpolatorZ = a.getResourceId(
83                 R.styleable.EpicenterTranslateClipReveal_interpolatorZ, 0);
84         if (interpolatorZ != 0) {
85             mInterpolatorZ = AnimationUtils.loadInterpolator(context, interpolatorZ);
86         } else {
87             mInterpolatorZ = TransitionConstants.FAST_OUT_SLOW_IN;
88         }
89 
90         a.recycle();
91     }
92 
93     @Override
captureStartValues(TransitionValues transitionValues)94     public void captureStartValues(TransitionValues transitionValues) {
95         super.captureStartValues(transitionValues);
96         captureValues(transitionValues);
97     }
98 
99     @Override
captureEndValues(TransitionValues transitionValues)100     public void captureEndValues(TransitionValues transitionValues) {
101         super.captureEndValues(transitionValues);
102         captureValues(transitionValues);
103     }
104 
captureValues(TransitionValues values)105     private void captureValues(TransitionValues values) {
106         final View view = values.view;
107         if (view.getVisibility() == View.GONE) {
108             return;
109         }
110 
111         final Rect bounds = new Rect(0, 0, view.getWidth(), view.getHeight());
112         values.values.put(PROPNAME_BOUNDS, bounds);
113         values.values.put(PROPNAME_TRANSLATE_X, view.getTranslationX());
114         values.values.put(PROPNAME_TRANSLATE_Y, view.getTranslationY());
115         values.values.put(PROPNAME_TRANSLATE_Z, view.getTranslationZ());
116         values.values.put(PROPNAME_Z, view.getZ());
117 
118         final Rect clip = view.getClipBounds();
119         values.values.put(PROPNAME_CLIP, clip);
120     }
121 
122     @Override
onAppear(ViewGroup sceneRoot, View view, TransitionValues startValues, TransitionValues endValues)123     public Animator onAppear(ViewGroup sceneRoot, View view,
124             TransitionValues startValues, TransitionValues endValues) {
125         if (endValues == null) {
126             return null;
127         }
128 
129         final Rect endBounds = (Rect) endValues.values.get(PROPNAME_BOUNDS);
130         final Rect startBounds = getEpicenterOrCenter(endBounds);
131         final float startX = startBounds.centerX() - endBounds.centerX();
132         final float startY = startBounds.centerY() - endBounds.centerY();
133         final float startZ = 0 - (float) endValues.values.get(PROPNAME_Z);
134 
135         // Translate the view to be centered on the epicenter.
136         view.setTranslationX(startX);
137         view.setTranslationY(startY);
138         view.setTranslationZ(startZ);
139 
140         final float endX = (float) endValues.values.get(PROPNAME_TRANSLATE_X);
141         final float endY = (float) endValues.values.get(PROPNAME_TRANSLATE_Y);
142         final float endZ = (float) endValues.values.get(PROPNAME_TRANSLATE_Z);
143 
144         final Rect endClip = getBestRect(endValues);
145         final Rect startClip = getEpicenterOrCenter(endClip);
146 
147         // Prepare the view.
148         view.setClipBounds(startClip);
149 
150         final State startStateX = new State(startClip.left, startClip.right, startX);
151         final State endStateX = new State(endClip.left, endClip.right, endX);
152         final State startStateY = new State(startClip.top, startClip.bottom, startY);
153         final State endStateY = new State(endClip.top, endClip.bottom, endY);
154 
155         return createRectAnimator(view, startStateX, startStateY, startZ, endStateX, endStateY,
156                 endZ, endValues, mInterpolatorX, mInterpolatorY, mInterpolatorZ);
157     }
158 
159     @Override
onDisappear(ViewGroup sceneRoot, View view, TransitionValues startValues, TransitionValues endValues)160     public Animator onDisappear(ViewGroup sceneRoot, View view,
161             TransitionValues startValues, TransitionValues endValues) {
162         if (startValues == null) {
163             return null;
164         }
165 
166         final Rect startBounds = (Rect) endValues.values.get(PROPNAME_BOUNDS);
167         final Rect endBounds = getEpicenterOrCenter(startBounds);
168         final float endX = endBounds.centerX() - startBounds.centerX();
169         final float endY = endBounds.centerY() - startBounds.centerY();
170         final float endZ = 0 - (float) startValues.values.get(PROPNAME_Z);
171 
172         final float startX = (float) endValues.values.get(PROPNAME_TRANSLATE_X);
173         final float startY = (float) endValues.values.get(PROPNAME_TRANSLATE_Y);
174         final float startZ = (float) endValues.values.get(PROPNAME_TRANSLATE_Z);
175 
176         final Rect startClip = getBestRect(startValues);
177         final Rect endClip = getEpicenterOrCenter(startClip);
178 
179         // Prepare the view.
180         view.setClipBounds(startClip);
181 
182         final State startStateX = new State(startClip.left, startClip.right, startX);
183         final State endStateX = new State(endClip.left, endClip.right, endX);
184         final State startStateY = new State(startClip.top, startClip.bottom, startY);
185         final State endStateY = new State(endClip.top, endClip.bottom, endY);
186 
187         return createRectAnimator(view, startStateX, startStateY, startZ, endStateX, endStateY,
188                 endZ, endValues, mInterpolatorX, mInterpolatorY, mInterpolatorZ);
189     }
190 
getEpicenterOrCenter(Rect bestRect)191     private Rect getEpicenterOrCenter(Rect bestRect) {
192         final Rect epicenter = getEpicenter();
193         if (epicenter != null) {
194             return epicenter;
195         }
196 
197         final int centerX = bestRect.centerX();
198         final int centerY = bestRect.centerY();
199         return new Rect(centerX, centerY, centerX, centerY);
200     }
201 
getBestRect(TransitionValues values)202     private Rect getBestRect(TransitionValues values) {
203         final Rect clipRect = (Rect) values.values.get(PROPNAME_CLIP);
204         if (clipRect == null) {
205             return (Rect) values.values.get(PROPNAME_BOUNDS);
206         }
207         return clipRect;
208     }
209 
createRectAnimator(final View view, State startX, State startY, float startZ, State endX, State endY, float endZ, TransitionValues endValues, TimeInterpolator interpolatorX, TimeInterpolator interpolatorY, TimeInterpolator interpolatorZ)210     private static Animator createRectAnimator(final View view, State startX, State startY,
211             float startZ, State endX, State endY, float endZ, TransitionValues endValues,
212             TimeInterpolator interpolatorX, TimeInterpolator interpolatorY,
213             TimeInterpolator interpolatorZ) {
214         final StateEvaluator evaluator = new StateEvaluator();
215 
216         final ObjectAnimator animZ = ObjectAnimator.ofFloat(view, View.TRANSLATION_Z, startZ, endZ);
217         if (interpolatorZ != null) {
218             animZ.setInterpolator(interpolatorZ);
219         }
220 
221         final StateProperty propX = new StateProperty(StateProperty.TARGET_X);
222         final ObjectAnimator animX = ObjectAnimator.ofObject(view, propX, evaluator, startX, endX);
223         if (interpolatorX != null) {
224             animX.setInterpolator(interpolatorX);
225         }
226 
227         final StateProperty propY = new StateProperty(StateProperty.TARGET_Y);
228         final ObjectAnimator animY = ObjectAnimator.ofObject(view, propY, evaluator, startY, endY);
229         if (interpolatorY != null) {
230             animY.setInterpolator(interpolatorY);
231         }
232 
233         final Rect terminalClip = (Rect) endValues.values.get(PROPNAME_CLIP);
234         final AnimatorListenerAdapter animatorListener = new AnimatorListenerAdapter() {
235             @Override
236             public void onAnimationEnd(Animator animation) {
237                 view.setClipBounds(terminalClip);
238             }
239         };
240 
241         final AnimatorSet animSet = new AnimatorSet();
242         animSet.playTogether(animX, animY, animZ);
243         animSet.addListener(animatorListener);
244         return animSet;
245     }
246 
247     private static class State {
248         int lower;
249         int upper;
250         float trans;
251 
State()252         public State() {}
253 
State(int lower, int upper, float trans)254         public State(int lower, int upper, float trans) {
255             this.lower = lower;
256             this.upper = upper;
257             this.trans = trans;
258         }
259     }
260 
261     private static class StateEvaluator implements TypeEvaluator<State> {
262         private final State mTemp = new State();
263 
264         @Override
evaluate(float fraction, State startValue, State endValue)265         public State evaluate(float fraction, State startValue, State endValue) {
266             mTemp.upper = startValue.upper + (int) ((endValue.upper - startValue.upper) * fraction);
267             mTemp.lower = startValue.lower + (int) ((endValue.lower - startValue.lower) * fraction);
268             mTemp.trans = startValue.trans + (int) ((endValue.trans - startValue.trans) * fraction);
269             return mTemp;
270         }
271     }
272 
273     private static class StateProperty extends Property<View, State> {
274         public static final char TARGET_X = 'x';
275         public static final char TARGET_Y = 'y';
276 
277         private final Rect mTempRect = new Rect();
278         private final State mTempState = new State();
279 
280         private final int mTargetDimension;
281 
StateProperty(char targetDimension)282         public StateProperty(char targetDimension) {
283             super(State.class, "state_" + targetDimension);
284 
285             mTargetDimension = targetDimension;
286         }
287 
288         @Override
get(View object)289         public State get(View object) {
290             final Rect tempRect = mTempRect;
291             if (!object.getClipBounds(tempRect)) {
292                 tempRect.setEmpty();
293             }
294             final State tempState = mTempState;
295             if (mTargetDimension == TARGET_X) {
296                 tempState.trans = object.getTranslationX();
297                 tempState.lower = tempRect.left + (int) tempState.trans;
298                 tempState.upper = tempRect.right + (int) tempState.trans;
299             } else {
300                 tempState.trans = object.getTranslationY();
301                 tempState.lower = tempRect.top + (int) tempState.trans;
302                 tempState.upper = tempRect.bottom + (int) tempState.trans;
303             }
304             return tempState;
305         }
306 
307         @Override
set(View object, State value)308         public void set(View object, State value) {
309             final Rect tempRect = mTempRect;
310             if (object.getClipBounds(tempRect)) {
311                 if (mTargetDimension == TARGET_X) {
312                     tempRect.left = value.lower - (int) value.trans;
313                     tempRect.right = value.upper - (int) value.trans;
314                 } else {
315                     tempRect.top = value.lower - (int) value.trans;
316                     tempRect.bottom = value.upper - (int) value.trans;
317                 }
318                 object.setClipBounds(tempRect);
319             }
320 
321             if (mTargetDimension == TARGET_X) {
322                 object.setTranslationX(value.trans);
323             } else {
324                 object.setTranslationY(value.trans);
325             }
326         }
327     }
328 }
329