Home | History | Annotate | Download | only in transition
      1 /*
      2  * Copyright (C) 2015 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 com.android.internal.transition;
     17 
     18 import android.animation.Animator;
     19 import android.animation.AnimatorListenerAdapter;
     20 import android.animation.AnimatorSet;
     21 import android.animation.ObjectAnimator;
     22 import android.animation.TimeInterpolator;
     23 import android.animation.TypeEvaluator;
     24 import android.content.Context;
     25 import android.content.res.TypedArray;
     26 import android.graphics.Rect;
     27 import android.transition.TransitionValues;
     28 import android.transition.Visibility;
     29 import android.util.AttributeSet;
     30 import android.util.Property;
     31 import android.view.View;
     32 import android.view.ViewGroup;
     33 import android.view.animation.AnimationUtils;
     34 
     35 import com.android.internal.R;
     36 
     37 /**
     38  * EpicenterTranslateClipReveal captures the clip bounds and translation values
     39  * before and after the scene change and animates between those and the
     40  * epicenter bounds during a visibility transition.
     41  */
     42 public class EpicenterTranslateClipReveal extends Visibility {
     43     private static final String PROPNAME_CLIP = "android:epicenterReveal:clip";
     44     private static final String PROPNAME_BOUNDS = "android:epicenterReveal:bounds";
     45     private static final String PROPNAME_TRANSLATE_X = "android:epicenterReveal:translateX";
     46     private static final String PROPNAME_TRANSLATE_Y = "android:epicenterReveal:translateY";
     47     private static final String PROPNAME_TRANSLATE_Z = "android:epicenterReveal:translateZ";
     48     private static final String PROPNAME_Z = "android:epicenterReveal:z";
     49 
     50     private final TimeInterpolator mInterpolatorX;
     51     private final TimeInterpolator mInterpolatorY;
     52     private final TimeInterpolator mInterpolatorZ;
     53 
     54     public EpicenterTranslateClipReveal() {
     55         mInterpolatorX = null;
     56         mInterpolatorY = null;
     57         mInterpolatorZ = null;
     58     }
     59 
     60     public EpicenterTranslateClipReveal(Context context, AttributeSet attrs) {
     61         super(context, attrs);
     62 
     63         final TypedArray a = context.obtainStyledAttributes(attrs,
     64                 R.styleable.EpicenterTranslateClipReveal, 0, 0);
     65 
     66         final int interpolatorX = a.getResourceId(
     67                 R.styleable.EpicenterTranslateClipReveal_interpolatorX, 0);
     68         if (interpolatorX != 0) {
     69             mInterpolatorX = AnimationUtils.loadInterpolator(context, interpolatorX);
     70         } else {
     71             mInterpolatorX = TransitionConstants.LINEAR_OUT_SLOW_IN;
     72         }
     73 
     74         final int interpolatorY = a.getResourceId(
     75                 R.styleable.EpicenterTranslateClipReveal_interpolatorY, 0);
     76         if (interpolatorY != 0) {
     77             mInterpolatorY = AnimationUtils.loadInterpolator(context, interpolatorY);
     78         } else {
     79             mInterpolatorY = TransitionConstants.FAST_OUT_SLOW_IN;
     80         }
     81 
     82         final int interpolatorZ = a.getResourceId(
     83                 R.styleable.EpicenterTranslateClipReveal_interpolatorZ, 0);
     84         if (interpolatorZ != 0) {
     85             mInterpolatorZ = AnimationUtils.loadInterpolator(context, interpolatorZ);
     86         } else {
     87             mInterpolatorZ = TransitionConstants.FAST_OUT_SLOW_IN;
     88         }
     89 
     90         a.recycle();
     91     }
     92 
     93     @Override
     94     public void captureStartValues(TransitionValues transitionValues) {
     95         super.captureStartValues(transitionValues);
     96         captureValues(transitionValues);
     97     }
     98 
     99     @Override
    100     public void captureEndValues(TransitionValues transitionValues) {
    101         super.captureEndValues(transitionValues);
    102         captureValues(transitionValues);
    103     }
    104 
    105     private void captureValues(TransitionValues values) {
    106         final View view = values.view;
    107         if (view.getVisibility() == View.GONE) {
    108             return;
    109         }
    110 
    111         final Rect bounds = new Rect(0, 0, view.getWidth(), view.getHeight());
    112         values.values.put(PROPNAME_BOUNDS, bounds);
    113         values.values.put(PROPNAME_TRANSLATE_X, view.getTranslationX());
    114         values.values.put(PROPNAME_TRANSLATE_Y, view.getTranslationY());
    115         values.values.put(PROPNAME_TRANSLATE_Z, view.getTranslationZ());
    116         values.values.put(PROPNAME_Z, view.getZ());
    117 
    118         final Rect clip = view.getClipBounds();
    119         values.values.put(PROPNAME_CLIP, clip);
    120     }
    121 
    122     @Override
    123     public Animator onAppear(ViewGroup sceneRoot, View view,
    124             TransitionValues startValues, TransitionValues endValues) {
    125         if (endValues == null) {
    126             return null;
    127         }
    128 
    129         final Rect endBounds = (Rect) endValues.values.get(PROPNAME_BOUNDS);
    130         final Rect startBounds = getEpicenterOrCenter(endBounds);
    131         final float startX = startBounds.centerX() - endBounds.centerX();
    132         final float startY = startBounds.centerY() - endBounds.centerY();
    133         final float startZ = 0 - (float) endValues.values.get(PROPNAME_Z);
    134 
    135         // Translate the view to be centered on the epicenter.
    136         view.setTranslationX(startX);
    137         view.setTranslationY(startY);
    138         view.setTranslationZ(startZ);
    139 
    140         final float endX = (float) endValues.values.get(PROPNAME_TRANSLATE_X);
    141         final float endY = (float) endValues.values.get(PROPNAME_TRANSLATE_Y);
    142         final float endZ = (float) endValues.values.get(PROPNAME_TRANSLATE_Z);
    143 
    144         final Rect endClip = getBestRect(endValues);
    145         final Rect startClip = getEpicenterOrCenter(endClip);
    146 
    147         // Prepare the view.
    148         view.setClipBounds(startClip);
    149 
    150         final State startStateX = new State(startClip.left, startClip.right, startX);
    151         final State endStateX = new State(endClip.left, endClip.right, endX);
    152         final State startStateY = new State(startClip.top, startClip.bottom, startY);
    153         final State endStateY = new State(endClip.top, endClip.bottom, endY);
    154 
    155         return createRectAnimator(view, startStateX, startStateY, startZ, endStateX, endStateY,
    156                 endZ, endValues, mInterpolatorX, mInterpolatorY, mInterpolatorZ);
    157     }
    158 
    159     @Override
    160     public Animator onDisappear(ViewGroup sceneRoot, View view,
    161             TransitionValues startValues, TransitionValues endValues) {
    162         if (startValues == null) {
    163             return null;
    164         }
    165 
    166         final Rect startBounds = (Rect) endValues.values.get(PROPNAME_BOUNDS);
    167         final Rect endBounds = getEpicenterOrCenter(startBounds);
    168         final float endX = endBounds.centerX() - startBounds.centerX();
    169         final float endY = endBounds.centerY() - startBounds.centerY();
    170         final float endZ = 0 - (float) startValues.values.get(PROPNAME_Z);
    171 
    172         final float startX = (float) endValues.values.get(PROPNAME_TRANSLATE_X);
    173         final float startY = (float) endValues.values.get(PROPNAME_TRANSLATE_Y);
    174         final float startZ = (float) endValues.values.get(PROPNAME_TRANSLATE_Z);
    175 
    176         final Rect startClip = getBestRect(startValues);
    177         final Rect endClip = getEpicenterOrCenter(startClip);
    178 
    179         // Prepare the view.
    180         view.setClipBounds(startClip);
    181 
    182         final State startStateX = new State(startClip.left, startClip.right, startX);
    183         final State endStateX = new State(endClip.left, endClip.right, endX);
    184         final State startStateY = new State(startClip.top, startClip.bottom, startY);
    185         final State endStateY = new State(endClip.top, endClip.bottom, endY);
    186 
    187         return createRectAnimator(view, startStateX, startStateY, startZ, endStateX, endStateY,
    188                 endZ, endValues, mInterpolatorX, mInterpolatorY, mInterpolatorZ);
    189     }
    190 
    191     private Rect getEpicenterOrCenter(Rect bestRect) {
    192         final Rect epicenter = getEpicenter();
    193         if (epicenter != null) {
    194             return epicenter;
    195         }
    196 
    197         final int centerX = bestRect.centerX();
    198         final int centerY = bestRect.centerY();
    199         return new Rect(centerX, centerY, centerX, centerY);
    200     }
    201 
    202     private Rect getBestRect(TransitionValues values) {
    203         final Rect clipRect = (Rect) values.values.get(PROPNAME_CLIP);
    204         if (clipRect == null) {
    205             return (Rect) values.values.get(PROPNAME_BOUNDS);
    206         }
    207         return clipRect;
    208     }
    209 
    210     private static Animator createRectAnimator(final View view, State startX, State startY,
    211             float startZ, State endX, State endY, float endZ, TransitionValues endValues,
    212             TimeInterpolator interpolatorX, TimeInterpolator interpolatorY,
    213             TimeInterpolator interpolatorZ) {
    214         final StateEvaluator evaluator = new StateEvaluator();
    215 
    216         final ObjectAnimator animZ = ObjectAnimator.ofFloat(view, View.TRANSLATION_Z, startZ, endZ);
    217         if (interpolatorZ != null) {
    218             animZ.setInterpolator(interpolatorZ);
    219         }
    220 
    221         final StateProperty propX = new StateProperty(StateProperty.TARGET_X);
    222         final ObjectAnimator animX = ObjectAnimator.ofObject(view, propX, evaluator, startX, endX);
    223         if (interpolatorX != null) {
    224             animX.setInterpolator(interpolatorX);
    225         }
    226 
    227         final StateProperty propY = new StateProperty(StateProperty.TARGET_Y);
    228         final ObjectAnimator animY = ObjectAnimator.ofObject(view, propY, evaluator, startY, endY);
    229         if (interpolatorY != null) {
    230             animY.setInterpolator(interpolatorY);
    231         }
    232 
    233         final Rect terminalClip = (Rect) endValues.values.get(PROPNAME_CLIP);
    234         final AnimatorListenerAdapter animatorListener = new AnimatorListenerAdapter() {
    235             @Override
    236             public void onAnimationEnd(Animator animation) {
    237                 view.setClipBounds(terminalClip);
    238             }
    239         };
    240 
    241         final AnimatorSet animSet = new AnimatorSet();
    242         animSet.playTogether(animX, animY, animZ);
    243         animSet.addListener(animatorListener);
    244         return animSet;
    245     }
    246 
    247     private static class State {
    248         int lower;
    249         int upper;
    250         float trans;
    251 
    252         public State() {}
    253 
    254         public State(int lower, int upper, float trans) {
    255             this.lower = lower;
    256             this.upper = upper;
    257             this.trans = trans;
    258         }
    259     }
    260 
    261     private static class StateEvaluator implements TypeEvaluator<State> {
    262         private final State mTemp = new State();
    263 
    264         @Override
    265         public State evaluate(float fraction, State startValue, State endValue) {
    266             mTemp.upper = startValue.upper + (int) ((endValue.upper - startValue.upper) * fraction);
    267             mTemp.lower = startValue.lower + (int) ((endValue.lower - startValue.lower) * fraction);
    268             mTemp.trans = startValue.trans + (int) ((endValue.trans - startValue.trans) * fraction);
    269             return mTemp;
    270         }
    271     }
    272 
    273     private static class StateProperty extends Property<View, State> {
    274         public static final char TARGET_X = 'x';
    275         public static final char TARGET_Y = 'y';
    276 
    277         private final Rect mTempRect = new Rect();
    278         private final State mTempState = new State();
    279 
    280         private final int mTargetDimension;
    281 
    282         public StateProperty(char targetDimension) {
    283             super(State.class, "state_" + targetDimension);
    284 
    285             mTargetDimension = targetDimension;
    286         }
    287 
    288         @Override
    289         public State get(View object) {
    290             final Rect tempRect = mTempRect;
    291             if (!object.getClipBounds(tempRect)) {
    292                 tempRect.setEmpty();
    293             }
    294             final State tempState = mTempState;
    295             if (mTargetDimension == TARGET_X) {
    296                 tempState.trans = object.getTranslationX();
    297                 tempState.lower = tempRect.left + (int) tempState.trans;
    298                 tempState.upper = tempRect.right + (int) tempState.trans;
    299             } else {
    300                 tempState.trans = object.getTranslationY();
    301                 tempState.lower = tempRect.top + (int) tempState.trans;
    302                 tempState.upper = tempRect.bottom + (int) tempState.trans;
    303             }
    304             return tempState;
    305         }
    306 
    307         @Override
    308         public void set(View object, State value) {
    309             final Rect tempRect = mTempRect;
    310             if (object.getClipBounds(tempRect)) {
    311                 if (mTargetDimension == TARGET_X) {
    312                     tempRect.left = value.lower - (int) value.trans;
    313                     tempRect.right = value.upper - (int) value.trans;
    314                 } else {
    315                     tempRect.top = value.lower - (int) value.trans;
    316                     tempRect.bottom = value.upper - (int) value.trans;
    317                 }
    318                 object.setClipBounds(tempRect);
    319             }
    320 
    321             if (mTargetDimension == TARGET_X) {
    322                 object.setTranslationX(value.trans);
    323             } else {
    324                 object.setTranslationY(value.trans);
    325             }
    326         }
    327     }
    328 }
    329