1ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki/*
2ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki * Copyright (C) 2017 The Android Open Source Project
3ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki *
4ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki * Licensed under the Apache License, Version 2.0 (the "License");
5ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki * you may not use this file except in compliance with the License.
6ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki * You may obtain a copy of the License at
7ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki *
8ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki *      http://www.apache.org/licenses/LICENSE-2.0
9ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki *
10ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki * Unless required by applicable law or agreed to in writing, software
11ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki * distributed under the License is distributed on an "AS IS" BASIS,
12ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki * See the License for the specific language governing permissions and
14ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki * limitations under the License.
15ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki */
16ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki
17ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Arakipackage android.support.transition;
18ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki
19ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Arakiimport android.graphics.Rect;
20ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Arakiimport android.support.v4.view.ViewCompat;
21ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Arakiimport android.view.Gravity;
22ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Arakiimport android.view.View;
23ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Arakiimport android.view.ViewGroup;
24ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki
25ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki/**
26ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki * A <code>TransitionPropagation</code> that propagates based on the distance to the side
27ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki * and, orthogonally, the distance to epicenter. If the transitioning View is visible in
28ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki * the start of the transition, then it will transition sooner when closer to the side and
29ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki * later when farther. If the view is not visible in the start of the transition, then
30ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki * it will transition later when closer to the side and sooner when farther from the edge.
31ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki * This is the default TransitionPropagation used with {@link android.transition.Slide}.
32ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki */
33ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Arakipublic class SidePropagation extends VisibilityPropagation {
34ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki
35ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki    private float mPropagationSpeed = 3.0f;
36ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki    private int mSide = Gravity.BOTTOM;
37ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki
38ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki    /**
39ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki     * Sets the side that is used to calculate the transition propagation. If the transitioning
40ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki     * View is visible in the start of the transition, then it will transition sooner when
41ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki     * closer to the side and later when farther. If the view is not visible in the start of
42ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki     * the transition, then it will transition later when closer to the side and sooner when
43ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki     * farther from the edge. The default is {@link Gravity#BOTTOM}.
44ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki     *
45ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki     * @param side The side that is used to calculate the transition propagation. Must be one of
46ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki     *             {@link Gravity#LEFT}, {@link Gravity#TOP}, {@link Gravity#RIGHT},
47ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki     *             {@link Gravity#BOTTOM}, {@link Gravity#START}, or {@link Gravity#END}.
48ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki     */
49ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki    public void setSide(@Slide.GravityFlag int side) {
50ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki        mSide = side;
51ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki    }
52ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki
53ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki    /**
54ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki     * Sets the speed at which transition propagation happens, relative to the duration of the
55ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki     * Transition. A <code>propagationSpeed</code> of 1 means that a View centered at the side
56ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki     * set in {@link #setSide(int)} and View centered at the opposite edge will have a difference
57ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki     * in start delay of approximately the duration of the Transition. A speed of 2 means the
58ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki     * start delay difference will be approximately half of the duration of the transition. A
59ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki     * value of 0 is illegal, but negative values will invert the propagation.
60ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki     *
61ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki     * @param propagationSpeed The speed at which propagation occurs, relative to the duration
62ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki     *                         of the transition. A speed of 4 means it works 4 times as fast
63ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki     *                         as the duration of the transition. May not be 0.
64ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki     */
65ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki    public void setPropagationSpeed(float propagationSpeed) {
66ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki        if (propagationSpeed == 0) {
67ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki            throw new IllegalArgumentException("propagationSpeed may not be 0");
68ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki        }
69ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki        mPropagationSpeed = propagationSpeed;
70ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki    }
71ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki
72ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki    @Override
73ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki    public long getStartDelay(ViewGroup sceneRoot, Transition transition,
74ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki            TransitionValues startValues, TransitionValues endValues) {
75ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki        if (startValues == null && endValues == null) {
76ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki            return 0;
77ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki        }
78ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki        int directionMultiplier = 1;
79ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki        Rect epicenter = transition.getEpicenter();
80ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki        TransitionValues positionValues;
81ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki        if (endValues == null || getViewVisibility(startValues) == View.VISIBLE) {
82ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki            positionValues = startValues;
83ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki            directionMultiplier = -1;
84ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki        } else {
85ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki            positionValues = endValues;
86ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki        }
87ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki
88ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki        int viewCenterX = getViewX(positionValues);
89ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki        int viewCenterY = getViewY(positionValues);
90ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki
91ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki        int[] loc = new int[2];
92ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki        sceneRoot.getLocationOnScreen(loc);
93ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki        int left = loc[0] + Math.round(sceneRoot.getTranslationX());
94ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki        int top = loc[1] + Math.round(sceneRoot.getTranslationY());
95ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki        int right = left + sceneRoot.getWidth();
96ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki        int bottom = top + sceneRoot.getHeight();
97ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki
98ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki        int epicenterX;
99ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki        int epicenterY;
100ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki        if (epicenter != null) {
101ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki            epicenterX = epicenter.centerX();
102ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki            epicenterY = epicenter.centerY();
103ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki        } else {
104ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki            epicenterX = (left + right) / 2;
105ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki            epicenterY = (top + bottom) / 2;
106ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki        }
107ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki
108ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki        float distance = distance(sceneRoot, viewCenterX, viewCenterY, epicenterX, epicenterY,
109ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki                left, top, right, bottom);
110ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki        float maxDistance = getMaxDistance(sceneRoot);
111ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki        float distanceFraction = distance / maxDistance;
112ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki
113ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki        long duration = transition.getDuration();
114ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki        if (duration < 0) {
115ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki            duration = 300;
116ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki        }
117ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki
118ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki        return Math.round(duration * directionMultiplier / mPropagationSpeed * distanceFraction);
119ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki    }
120ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki
121ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki    private int distance(View sceneRoot, int viewX, int viewY, int epicenterX, int epicenterY,
122ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki            int left, int top, int right, int bottom) {
123ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki        final int side;
124ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki        if (mSide == Gravity.START) {
125ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki            final boolean isRtl = ViewCompat.getLayoutDirection(sceneRoot)
126ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki                    == ViewCompat.LAYOUT_DIRECTION_RTL;
127ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki            side = isRtl ? Gravity.RIGHT : Gravity.LEFT;
128ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki        } else if (mSide == Gravity.END) {
129ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki            final boolean isRtl = ViewCompat.getLayoutDirection(sceneRoot)
130ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki                    == ViewCompat.LAYOUT_DIRECTION_RTL;
131ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki            side = isRtl ? Gravity.LEFT : Gravity.RIGHT;
132ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki        } else {
133ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki            side = mSide;
134ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki        }
135ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki        int distance = 0;
136ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki        switch (side) {
137ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki            case Gravity.LEFT:
138ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki                distance = right - viewX + Math.abs(epicenterY - viewY);
139ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki                break;
140ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki            case Gravity.TOP:
141ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki                distance = bottom - viewY + Math.abs(epicenterX - viewX);
142ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki                break;
143ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki            case Gravity.RIGHT:
144ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki                distance = viewX - left + Math.abs(epicenterY - viewY);
145ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki                break;
146ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki            case Gravity.BOTTOM:
147ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki                distance = viewY - top + Math.abs(epicenterX - viewX);
148ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki                break;
149ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki        }
150ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki        return distance;
151ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki    }
152ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki
153ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki    private int getMaxDistance(ViewGroup sceneRoot) {
154ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki        switch (mSide) {
155ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki            case Gravity.LEFT:
156ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki            case Gravity.RIGHT:
157ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki            case Gravity.START:
158ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki            case Gravity.END:
159ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki                return sceneRoot.getWidth();
160ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki            default:
161ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki                return sceneRoot.getHeight();
162ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki        }
163ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki    }
164ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki
165ae4925aede60914bcca5ed47d7ce868ae14313a2Yuichi Araki}
166