1/*
2 * Copyright (C) 2017 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
17package android.support.transition;
18
19import android.animation.Animator;
20import android.animation.TimeInterpolator;
21import android.content.Context;
22import android.graphics.Rect;
23import android.support.annotation.NonNull;
24import android.util.AttributeSet;
25import android.view.View;
26import android.view.ViewGroup;
27import android.view.animation.AccelerateInterpolator;
28import android.view.animation.DecelerateInterpolator;
29
30/**
31 * This transition tracks changes to the visibility of target views in the
32 * start and end scenes and moves views in or out from the edges of the
33 * scene. Visibility is determined by both the
34 * {@link View#setVisibility(int)} state of the view as well as whether it
35 * is parented in the current view hierarchy. Disappearing Views are
36 * limited as described in {@link Visibility#onDisappear(android.view.ViewGroup,
37 * TransitionValues, int, TransitionValues, int)}.
38 * <p>Views move away from the focal View or the center of the Scene if
39 * no epicenter was provided.</p>
40 */
41public class Explode extends Visibility {
42
43    private static final TimeInterpolator sDecelerate = new DecelerateInterpolator();
44    private static final TimeInterpolator sAccelerate = new AccelerateInterpolator();
45    private static final String PROPNAME_SCREEN_BOUNDS = "android:explode:screenBounds";
46
47    private int[] mTempLoc = new int[2];
48
49    public Explode() {
50        setPropagation(new CircularPropagation());
51    }
52
53    public Explode(Context context, AttributeSet attrs) {
54        super(context, attrs);
55        setPropagation(new CircularPropagation());
56    }
57
58    private void captureValues(TransitionValues transitionValues) {
59        View view = transitionValues.view;
60        view.getLocationOnScreen(mTempLoc);
61        int left = mTempLoc[0];
62        int top = mTempLoc[1];
63        int right = left + view.getWidth();
64        int bottom = top + view.getHeight();
65        transitionValues.values.put(PROPNAME_SCREEN_BOUNDS, new Rect(left, top, right, bottom));
66    }
67
68    @Override
69    public void captureStartValues(@NonNull TransitionValues transitionValues) {
70        super.captureStartValues(transitionValues);
71        captureValues(transitionValues);
72    }
73
74    @Override
75    public void captureEndValues(@NonNull TransitionValues transitionValues) {
76        super.captureEndValues(transitionValues);
77        captureValues(transitionValues);
78    }
79
80    @Override
81    public Animator onAppear(ViewGroup sceneRoot, View view,
82            TransitionValues startValues, TransitionValues endValues) {
83        if (endValues == null) {
84            return null;
85        }
86        Rect bounds = (Rect) endValues.values.get(PROPNAME_SCREEN_BOUNDS);
87        float endX = view.getTranslationX();
88        float endY = view.getTranslationY();
89        calculateOut(sceneRoot, bounds, mTempLoc);
90        float startX = endX + mTempLoc[0];
91        float startY = endY + mTempLoc[1];
92
93        return TranslationAnimationCreator.createAnimation(view, endValues, bounds.left, bounds.top,
94                startX, startY, endX, endY, sDecelerate);
95    }
96
97    @Override
98    public Animator onDisappear(ViewGroup sceneRoot, View view,
99            TransitionValues startValues, TransitionValues endValues) {
100        if (startValues == null) {
101            return null;
102        }
103        Rect bounds = (Rect) startValues.values.get(PROPNAME_SCREEN_BOUNDS);
104        int viewPosX = bounds.left;
105        int viewPosY = bounds.top;
106        float startX = view.getTranslationX();
107        float startY = view.getTranslationY();
108        float endX = startX;
109        float endY = startY;
110        int[] interruptedPosition = (int[]) startValues.view.getTag(R.id.transition_position);
111        if (interruptedPosition != null) {
112            // We want to have the end position relative to the interrupted position, not
113            // the position it was supposed to start at.
114            endX += interruptedPosition[0] - bounds.left;
115            endY += interruptedPosition[1] - bounds.top;
116            bounds.offsetTo(interruptedPosition[0], interruptedPosition[1]);
117        }
118        calculateOut(sceneRoot, bounds, mTempLoc);
119        endX += mTempLoc[0];
120        endY += mTempLoc[1];
121
122        return TranslationAnimationCreator.createAnimation(view, startValues,
123                viewPosX, viewPosY, startX, startY, endX, endY, sAccelerate);
124    }
125
126    private void calculateOut(View sceneRoot, Rect bounds, int[] outVector) {
127        sceneRoot.getLocationOnScreen(mTempLoc);
128        int sceneRootX = mTempLoc[0];
129        int sceneRootY = mTempLoc[1];
130        int focalX;
131        int focalY;
132
133        Rect epicenter = getEpicenter();
134        if (epicenter == null) {
135            focalX = sceneRootX + (sceneRoot.getWidth() / 2)
136                    + Math.round(sceneRoot.getTranslationX());
137            focalY = sceneRootY + (sceneRoot.getHeight() / 2)
138                    + Math.round(sceneRoot.getTranslationY());
139        } else {
140            focalX = epicenter.centerX();
141            focalY = epicenter.centerY();
142        }
143
144        int centerX = bounds.centerX();
145        int centerY = bounds.centerY();
146        float xVector = centerX - focalX;
147        float yVector = centerY - focalY;
148
149        if (xVector == 0 && yVector == 0) {
150            // Random direction when View is centered on focal View.
151            xVector = (float) (Math.random() * 2) - 1;
152            yVector = (float) (Math.random() * 2) - 1;
153        }
154        float vectorSize = calculateDistance(xVector, yVector);
155        xVector /= vectorSize;
156        yVector /= vectorSize;
157
158        float maxDistance =
159                calculateMaxDistance(sceneRoot, focalX - sceneRootX, focalY - sceneRootY);
160
161        outVector[0] = Math.round(maxDistance * xVector);
162        outVector[1] = Math.round(maxDistance * yVector);
163    }
164
165    private static float calculateMaxDistance(View sceneRoot, int focalX, int focalY) {
166        int maxX = Math.max(focalX, sceneRoot.getWidth() - focalX);
167        int maxY = Math.max(focalY, sceneRoot.getHeight() - focalY);
168        return calculateDistance(maxX, maxY);
169    }
170
171    private static float calculateDistance(float x, float y) {
172        return (float) Math.sqrt((x * x) + (y * y));
173    }
174
175}
176