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  */
16 package android.transition;
17 
18 import android.animation.Animator;
19 import android.animation.TimeInterpolator;
20 import android.content.Context;
21 import android.graphics.Rect;
22 import android.util.AttributeSet;
23 import android.view.View;
24 import android.view.ViewGroup;
25 import android.view.animation.AccelerateInterpolator;
26 import android.view.animation.DecelerateInterpolator;
27 
28 import 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  */
40 public 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 
Explode()48     public Explode() {
49         setPropagation(new CircularPropagation());
50     }
51 
Explode(Context context, AttributeSet attrs)52     public Explode(Context context, AttributeSet attrs) {
53         super(context, attrs);
54         setPropagation(new CircularPropagation());
55     }
56 
captureValues(TransitionValues transitionValues)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
captureStartValues(TransitionValues transitionValues)68     public void captureStartValues(TransitionValues transitionValues) {
69         super.captureStartValues(transitionValues);
70         captureValues(transitionValues);
71     }
72 
73     @Override
captureEndValues(TransitionValues transitionValues)74     public void captureEndValues(TransitionValues transitionValues) {
75         super.captureEndValues(transitionValues);
76         captureValues(transitionValues);
77     }
78 
79     @Override
onAppear(ViewGroup sceneRoot, View view, TransitionValues startValues, TransitionValues endValues)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
onDisappear(ViewGroup sceneRoot, View view, TransitionValues startValues, TransitionValues endValues)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 
calculateOut(View sceneRoot, Rect bounds, int[] outVector)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 
calculateMaxDistance(View sceneRoot, int focalX, int focalY)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