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.graphics.Rect;
19 import android.transition.Slide.GravityFlag;
20 import android.view.Gravity;
21 import android.view.View;
22 import android.view.ViewGroup;
23 
24 /**
25  * A <code>TransitionPropagation</code> that propagates based on the distance to the side
26  * and, orthogonally, the distance to epicenter. If the transitioning View is visible in
27  * the start of the transition, then it will transition sooner when closer to the side and
28  * later when farther. If the view is not visible in the start of the transition, then
29  * it will transition later when closer to the side and sooner when farther from the edge.
30  * This is the default TransitionPropagation used with {@link android.transition.Slide}.
31  */
32 public class SidePropagation extends VisibilityPropagation {
33     private static final String TAG = "SlidePropagation";
34 
35     private float mPropagationSpeed = 3.0f;
36     private int mSide = Gravity.BOTTOM;
37 
38     /**
39      * Sets the side that is used to calculate the transition propagation. If the transitioning
40      * View is visible in the start of the transition, then it will transition sooner when
41      * closer to the side and later when farther. If the view is not visible in the start of
42      * the transition, then it will transition later when closer to the side and sooner when
43      * farther from the edge. The default is {@link Gravity#BOTTOM}.
44      *
45      * @param side The side that is used to calculate the transition propagation. Must be one of
46      *             {@link Gravity#LEFT}, {@link Gravity#TOP}, {@link Gravity#RIGHT},
47      *             {@link Gravity#BOTTOM}, {@link Gravity#START}, or {@link Gravity#END}.
48      */
setSide(@ravityFlag int side)49     public void setSide(@GravityFlag int side) {
50         mSide = side;
51     }
52 
53     /**
54      * Sets the speed at which transition propagation happens, relative to the duration of the
55      * Transition. A <code>propagationSpeed</code> of 1 means that a View centered at the side
56      * set in {@link #setSide(int)} and View centered at the opposite edge will have a difference
57      * in start delay of approximately the duration of the Transition. A speed of 2 means the
58      * start delay difference will be approximately half of the duration of the transition. A
59      * value of 0 is illegal, but negative values will invert the propagation.
60      *
61      * @param propagationSpeed The speed at which propagation occurs, relative to the duration
62      *                         of the transition. A speed of 4 means it works 4 times as fast
63      *                         as the duration of the transition. May not be 0.
64      */
setPropagationSpeed(float propagationSpeed)65     public void setPropagationSpeed(float propagationSpeed) {
66         if (propagationSpeed == 0) {
67             throw new IllegalArgumentException("propagationSpeed may not be 0");
68         }
69         mPropagationSpeed = propagationSpeed;
70     }
71 
72     @Override
getStartDelay(ViewGroup sceneRoot, Transition transition, TransitionValues startValues, TransitionValues endValues)73     public long getStartDelay(ViewGroup sceneRoot, Transition transition,
74             TransitionValues startValues, TransitionValues endValues) {
75         if (startValues == null && endValues == null) {
76             return 0;
77         }
78         int directionMultiplier = 1;
79         Rect epicenter = transition.getEpicenter();
80         TransitionValues positionValues;
81         if (endValues == null || getViewVisibility(startValues) == View.VISIBLE) {
82             positionValues = startValues;
83             directionMultiplier = -1;
84         } else {
85             positionValues = endValues;
86         }
87 
88         int viewCenterX = getViewX(positionValues);
89         int viewCenterY = getViewY(positionValues);
90 
91         int[] loc = new int[2];
92         sceneRoot.getLocationOnScreen(loc);
93         int left = loc[0] + Math.round(sceneRoot.getTranslationX());
94         int top = loc[1] + Math.round(sceneRoot.getTranslationY());
95         int right = left + sceneRoot.getWidth();
96         int bottom = top + sceneRoot.getHeight();
97 
98         int epicenterX;
99         int epicenterY;
100         if (epicenter != null) {
101             epicenterX = epicenter.centerX();
102             epicenterY = epicenter.centerY();
103         } else {
104             epicenterX = (left + right) / 2;
105             epicenterY = (top + bottom) / 2;
106         }
107 
108         float distance = distance(sceneRoot, viewCenterX, viewCenterY, epicenterX, epicenterY,
109                 left, top, right, bottom);
110         float maxDistance = getMaxDistance(sceneRoot);
111         float distanceFraction = distance/maxDistance;
112 
113         long duration = transition.getDuration();
114         if (duration < 0) {
115             duration = 300;
116         }
117 
118         return Math.round(duration * directionMultiplier / mPropagationSpeed * distanceFraction);
119     }
120 
distance(View sceneRoot, int viewX, int viewY, int epicenterX, int epicenterY, int left, int top, int right, int bottom)121     private int distance(View sceneRoot, int viewX, int viewY, int epicenterX, int epicenterY,
122             int left, int top, int right, int bottom) {
123         final int side;
124         if (mSide == Gravity.START) {
125             final boolean isRtl = sceneRoot.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
126             side = isRtl ? Gravity.RIGHT : Gravity.LEFT;
127         } else if (mSide == Gravity.END) {
128             final boolean isRtl = sceneRoot.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
129             side = isRtl ? Gravity.LEFT : Gravity.RIGHT;
130         } else {
131             side = mSide;
132         }
133         int distance = 0;
134         switch (side) {
135             case Gravity.LEFT:
136                 distance = right - viewX + Math.abs(epicenterY - viewY);
137                 break;
138             case Gravity.TOP:
139                 distance = bottom - viewY + Math.abs(epicenterX - viewX);
140                 break;
141             case Gravity.RIGHT:
142                 distance = viewX - left + Math.abs(epicenterY - viewY);
143                 break;
144             case Gravity.BOTTOM:
145                 distance = viewY - top + Math.abs(epicenterX - viewX);
146                 break;
147         }
148         return distance;
149     }
150 
getMaxDistance(ViewGroup sceneRoot)151     private int getMaxDistance(ViewGroup sceneRoot) {
152         switch (mSide) {
153             case Gravity.LEFT:
154             case Gravity.RIGHT:
155             case Gravity.START:
156             case Gravity.END:
157                 return sceneRoot.getWidth();
158             default:
159                 return sceneRoot.getHeight();
160         }
161     }
162 }
163