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 EpicenterTranslateClipReveal()54 public EpicenterTranslateClipReveal() { 55 mInterpolatorX = null; 56 mInterpolatorY = null; 57 mInterpolatorZ = null; 58 } 59 EpicenterTranslateClipReveal(Context context, AttributeSet attrs)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 captureStartValues(TransitionValues transitionValues)94 public void captureStartValues(TransitionValues transitionValues) { 95 super.captureStartValues(transitionValues); 96 captureValues(transitionValues); 97 } 98 99 @Override captureEndValues(TransitionValues transitionValues)100 public void captureEndValues(TransitionValues transitionValues) { 101 super.captureEndValues(transitionValues); 102 captureValues(transitionValues); 103 } 104 captureValues(TransitionValues values)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 onAppear(ViewGroup sceneRoot, View view, TransitionValues startValues, TransitionValues endValues)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 onDisappear(ViewGroup sceneRoot, View view, TransitionValues startValues, TransitionValues endValues)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 getEpicenterOrCenter(Rect bestRect)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 getBestRect(TransitionValues values)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 createRectAnimator(final View view, State startX, State startY, float startZ, State endX, State endY, float endZ, TransitionValues endValues, TimeInterpolator interpolatorX, TimeInterpolator interpolatorY, TimeInterpolator interpolatorZ)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 State()252 public State() {} 253 State(int lower, int upper, float trans)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 evaluate(float fraction, State startValue, State endValue)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 StateProperty(char targetDimension)282 public StateProperty(char targetDimension) { 283 super(State.class, "state_" + targetDimension); 284 285 mTargetDimension = targetDimension; 286 } 287 288 @Override get(View object)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 set(View object, State value)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