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 */
16package com.android.internal.transition;
17
18import android.animation.Animator;
19import android.animation.AnimatorListenerAdapter;
20import android.animation.AnimatorSet;
21import android.animation.ObjectAnimator;
22import android.animation.TimeInterpolator;
23import android.animation.TypeEvaluator;
24import android.content.Context;
25import android.content.res.TypedArray;
26import android.graphics.Rect;
27import android.transition.TransitionValues;
28import android.transition.Visibility;
29import android.util.AttributeSet;
30import android.util.Property;
31import android.view.View;
32import android.view.ViewGroup;
33import android.view.animation.AnimationUtils;
34
35import 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 */
42public 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
54    public EpicenterTranslateClipReveal() {
55        mInterpolatorX = null;
56        mInterpolatorY = null;
57        mInterpolatorZ = null;
58    }
59
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
94    public void captureStartValues(TransitionValues transitionValues) {
95        super.captureStartValues(transitionValues);
96        captureValues(transitionValues);
97    }
98
99    @Override
100    public void captureEndValues(TransitionValues transitionValues) {
101        super.captureEndValues(transitionValues);
102        captureValues(transitionValues);
103    }
104
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
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
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
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
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
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
252        public State() {}
253
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
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
282        public StateProperty(char targetDimension) {
283            super(State.class, "state_" + targetDimension);
284
285            mTargetDimension = targetDimension;
286        }
287
288        @Override
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
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