Home | History | Annotate | Download | only in transition
      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 }
    177