Justin Klaassen | 4d01eea | 2018-04-03 23:21:57 -0400 | [diff] [blame] | 1 | /* |
| 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 | |
| 17 | package androidx.transition; |
| 18 | |
| 19 | import android.animation.Animator; |
| 20 | import android.animation.TimeInterpolator; |
| 21 | import android.content.Context; |
| 22 | import android.graphics.Rect; |
| 23 | import android.util.AttributeSet; |
| 24 | import android.view.View; |
| 25 | import android.view.ViewGroup; |
| 26 | import android.view.animation.AccelerateInterpolator; |
| 27 | import android.view.animation.DecelerateInterpolator; |
| 28 | |
| 29 | import 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 | */ |
| 42 | public 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 | } |