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