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