Alan Viverette | 5435a30 | 2015-01-29 10:25:34 -0800 | [diff] [blame] | 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; |
George Mount | 3c75db9 | 2015-03-05 14:09:33 -0800 | [diff] [blame] | 19 | import android.animation.AnimatorListenerAdapter; |
Alan Viverette | 95888c0 | 2015-04-16 13:27:50 -0700 | [diff] [blame] | 20 | import android.animation.AnimatorSet; |
Alan Viverette | 5435a30 | 2015-01-29 10:25:34 -0800 | [diff] [blame] | 21 | import android.animation.ObjectAnimator; |
| 22 | import android.animation.RectEvaluator; |
Alan Viverette | 95888c0 | 2015-04-16 13:27:50 -0700 | [diff] [blame] | 23 | import android.animation.TimeInterpolator; |
Alan Viverette | 5435a30 | 2015-01-29 10:25:34 -0800 | [diff] [blame] | 24 | import android.content.Context; |
Alan Viverette | 95888c0 | 2015-04-16 13:27:50 -0700 | [diff] [blame] | 25 | import android.content.res.TypedArray; |
Alan Viverette | 5435a30 | 2015-01-29 10:25:34 -0800 | [diff] [blame] | 26 | import android.graphics.Rect; |
| 27 | import android.transition.TransitionValues; |
| 28 | import android.transition.Visibility; |
| 29 | import android.util.AttributeSet; |
Alan Viverette | 95888c0 | 2015-04-16 13:27:50 -0700 | [diff] [blame] | 30 | import android.util.Property; |
Alan Viverette | 5435a30 | 2015-01-29 10:25:34 -0800 | [diff] [blame] | 31 | import android.view.View; |
| 32 | import android.view.ViewGroup; |
Alan Viverette | 95888c0 | 2015-04-16 13:27:50 -0700 | [diff] [blame] | 33 | import android.view.animation.AnimationUtils; |
| 34 | import android.view.animation.PathInterpolator; |
| 35 | |
| 36 | import com.android.internal.R; |
Alan Viverette | 5435a30 | 2015-01-29 10:25:34 -0800 | [diff] [blame] | 37 | |
| 38 | /** |
| 39 | * EpicenterClipReveal captures the {@link View#getClipBounds()} before and |
| 40 | * after the scene change and animates between those and the epicenter bounds |
| 41 | * during a visibility transition. |
| 42 | */ |
| 43 | public class EpicenterClipReveal extends Visibility { |
| 44 | private static final String PROPNAME_CLIP = "android:epicenterReveal:clip"; |
| 45 | private static final String PROPNAME_BOUNDS = "android:epicenterReveal:bounds"; |
| 46 | |
Alan Viverette | 95888c0 | 2015-04-16 13:27:50 -0700 | [diff] [blame] | 47 | private final TimeInterpolator mInterpolatorX; |
| 48 | private final TimeInterpolator mInterpolatorY; |
| 49 | private final boolean mCenterClipBounds; |
| 50 | |
| 51 | public EpicenterClipReveal() { |
| 52 | mInterpolatorX = null; |
| 53 | mInterpolatorY = null; |
| 54 | mCenterClipBounds = false; |
| 55 | } |
Alan Viverette | 5435a30 | 2015-01-29 10:25:34 -0800 | [diff] [blame] | 56 | |
| 57 | public EpicenterClipReveal(Context context, AttributeSet attrs) { |
| 58 | super(context, attrs); |
Alan Viverette | 95888c0 | 2015-04-16 13:27:50 -0700 | [diff] [blame] | 59 | |
| 60 | final TypedArray a = context.obtainStyledAttributes(attrs, |
| 61 | R.styleable.EpicenterClipReveal, 0, 0); |
| 62 | |
| 63 | mCenterClipBounds = a.getBoolean(R.styleable.EpicenterClipReveal_centerClipBounds, false); |
| 64 | |
| 65 | final int interpolatorX = a.getResourceId(R.styleable.EpicenterClipReveal_interpolatorX, 0); |
| 66 | if (interpolatorX != 0) { |
| 67 | mInterpolatorX = AnimationUtils.loadInterpolator(context, interpolatorX); |
| 68 | } else { |
| 69 | mInterpolatorX = TransitionConstants.LINEAR_OUT_SLOW_IN; |
| 70 | } |
| 71 | |
| 72 | final int interpolatorY = a.getResourceId(R.styleable.EpicenterClipReveal_interpolatorY, 0); |
| 73 | if (interpolatorY != 0) { |
| 74 | mInterpolatorY = AnimationUtils.loadInterpolator(context, interpolatorY); |
| 75 | } else { |
| 76 | mInterpolatorY = TransitionConstants.FAST_OUT_SLOW_IN; |
| 77 | } |
| 78 | |
| 79 | a.recycle(); |
Alan Viverette | 5435a30 | 2015-01-29 10:25:34 -0800 | [diff] [blame] | 80 | } |
| 81 | |
| 82 | @Override |
| 83 | public void captureStartValues(TransitionValues transitionValues) { |
| 84 | super.captureStartValues(transitionValues); |
| 85 | captureValues(transitionValues); |
| 86 | } |
| 87 | |
| 88 | @Override |
| 89 | public void captureEndValues(TransitionValues transitionValues) { |
| 90 | super.captureEndValues(transitionValues); |
| 91 | captureValues(transitionValues); |
| 92 | } |
| 93 | |
| 94 | private void captureValues(TransitionValues values) { |
| 95 | final View view = values.view; |
| 96 | if (view.getVisibility() == View.GONE) { |
| 97 | return; |
| 98 | } |
| 99 | |
| 100 | final Rect clip = view.getClipBounds(); |
| 101 | values.values.put(PROPNAME_CLIP, clip); |
| 102 | |
| 103 | if (clip == null) { |
| 104 | final Rect bounds = new Rect(0, 0, view.getWidth(), view.getHeight()); |
| 105 | values.values.put(PROPNAME_BOUNDS, bounds); |
| 106 | } |
| 107 | } |
| 108 | |
| 109 | @Override |
| 110 | public Animator onAppear(ViewGroup sceneRoot, View view, |
| 111 | TransitionValues startValues, TransitionValues endValues) { |
| 112 | if (endValues == null) { |
| 113 | return null; |
| 114 | } |
| 115 | |
Alan Viverette | 5435a30 | 2015-01-29 10:25:34 -0800 | [diff] [blame] | 116 | final Rect end = getBestRect(endValues); |
George Mount | 3c75db9 | 2015-03-05 14:09:33 -0800 | [diff] [blame] | 117 | final Rect start = getEpicenterOrCenter(end); |
Alan Viverette | 5435a30 | 2015-01-29 10:25:34 -0800 | [diff] [blame] | 118 | |
| 119 | // Prepare the view. |
| 120 | view.setClipBounds(start); |
| 121 | |
Alan Viverette | 95888c0 | 2015-04-16 13:27:50 -0700 | [diff] [blame] | 122 | return createRectAnimator(view, start, end, endValues, mInterpolatorX, mInterpolatorY); |
Alan Viverette | 5435a30 | 2015-01-29 10:25:34 -0800 | [diff] [blame] | 123 | } |
| 124 | |
| 125 | @Override |
| 126 | public Animator onDisappear(ViewGroup sceneRoot, View view, |
| 127 | TransitionValues startValues, TransitionValues endValues) { |
| 128 | if (startValues == null) { |
| 129 | return null; |
| 130 | } |
| 131 | |
| 132 | final Rect start = getBestRect(startValues); |
George Mount | 3c75db9 | 2015-03-05 14:09:33 -0800 | [diff] [blame] | 133 | final Rect end = getEpicenterOrCenter(start); |
Alan Viverette | 5435a30 | 2015-01-29 10:25:34 -0800 | [diff] [blame] | 134 | |
| 135 | // Prepare the view. |
| 136 | view.setClipBounds(start); |
| 137 | |
Alan Viverette | 95888c0 | 2015-04-16 13:27:50 -0700 | [diff] [blame] | 138 | return createRectAnimator(view, start, end, endValues, mInterpolatorX, mInterpolatorY); |
George Mount | 3c75db9 | 2015-03-05 14:09:33 -0800 | [diff] [blame] | 139 | } |
| 140 | |
| 141 | private Rect getEpicenterOrCenter(Rect bestRect) { |
| 142 | final Rect epicenter = getEpicenter(); |
| 143 | if (epicenter != null) { |
Alan Viverette | 95888c0 | 2015-04-16 13:27:50 -0700 | [diff] [blame] | 144 | // Translate the clip bounds to be centered within the target bounds. |
| 145 | if (mCenterClipBounds) { |
| 146 | final int offsetX = bestRect.centerX() - epicenter.centerX(); |
| 147 | final int offsetY = bestRect.centerY() - epicenter.centerY(); |
| 148 | epicenter.offset(offsetX, offsetY); |
| 149 | } |
George Mount | 3c75db9 | 2015-03-05 14:09:33 -0800 | [diff] [blame] | 150 | return epicenter; |
| 151 | } |
| 152 | |
Alan Viverette | 95888c0 | 2015-04-16 13:27:50 -0700 | [diff] [blame] | 153 | final int centerX = bestRect.centerX(); |
| 154 | final int centerY = bestRect.centerY(); |
George Mount | 3c75db9 | 2015-03-05 14:09:33 -0800 | [diff] [blame] | 155 | return new Rect(centerX, centerY, centerX, centerY); |
Alan Viverette | 5435a30 | 2015-01-29 10:25:34 -0800 | [diff] [blame] | 156 | } |
| 157 | |
| 158 | private Rect getBestRect(TransitionValues values) { |
| 159 | final Rect clipRect = (Rect) values.values.get(PROPNAME_CLIP); |
| 160 | if (clipRect == null) { |
| 161 | return (Rect) values.values.get(PROPNAME_BOUNDS); |
| 162 | } |
| 163 | return clipRect; |
| 164 | } |
| 165 | |
Alan Viverette | 95888c0 | 2015-04-16 13:27:50 -0700 | [diff] [blame] | 166 | private static Animator createRectAnimator(final View view, Rect start, Rect end, |
| 167 | TransitionValues endValues, TimeInterpolator interpolatorX, |
| 168 | TimeInterpolator interpolatorY) { |
Alan Viverette | 5435a30 | 2015-01-29 10:25:34 -0800 | [diff] [blame] | 169 | final RectEvaluator evaluator = new RectEvaluator(new Rect()); |
Alan Viverette | 95888c0 | 2015-04-16 13:27:50 -0700 | [diff] [blame] | 170 | final Rect terminalClip = (Rect) endValues.values.get(PROPNAME_CLIP); |
| 171 | |
| 172 | final ClipDimenProperty propX = new ClipDimenProperty(ClipDimenProperty.TARGET_X); |
| 173 | final ObjectAnimator animX = ObjectAnimator.ofObject(view, propX, evaluator, start, end); |
| 174 | if (interpolatorX != null) { |
| 175 | animX.setInterpolator(interpolatorX); |
| 176 | } |
| 177 | |
| 178 | final ClipDimenProperty propY = new ClipDimenProperty(ClipDimenProperty.TARGET_Y); |
| 179 | final ObjectAnimator animY = ObjectAnimator.ofObject(view, propY, evaluator, start, end); |
| 180 | if (interpolatorY != null) { |
| 181 | animY.setInterpolator(interpolatorY); |
| 182 | } |
| 183 | |
| 184 | final AnimatorSet animSet = new AnimatorSet(); |
| 185 | animSet.playTogether(animX, animY); |
| 186 | animSet.addListener(new AnimatorListenerAdapter() { |
George Mount | 3c75db9 | 2015-03-05 14:09:33 -0800 | [diff] [blame] | 187 | @Override |
| 188 | public void onAnimationEnd(Animator animation) { |
| 189 | view.setClipBounds(terminalClip); |
| 190 | } |
| 191 | }); |
Alan Viverette | 95888c0 | 2015-04-16 13:27:50 -0700 | [diff] [blame] | 192 | return animSet; |
| 193 | } |
| 194 | |
| 195 | private static class ClipDimenProperty extends Property<View, Rect> { |
| 196 | public static final char TARGET_X = 'x'; |
| 197 | public static final char TARGET_Y = 'y'; |
| 198 | |
| 199 | private final Rect mTempRect = new Rect(); |
| 200 | |
| 201 | private final int mTargetDimension; |
| 202 | |
| 203 | public ClipDimenProperty(char targetDimension) { |
| 204 | super(Rect.class, "clip_bounds_" + targetDimension); |
| 205 | |
| 206 | mTargetDimension = targetDimension; |
| 207 | } |
| 208 | |
| 209 | @Override |
| 210 | public Rect get(View object) { |
| 211 | final Rect tempRect = mTempRect; |
| 212 | if (!object.getClipBounds(tempRect)) { |
| 213 | tempRect.setEmpty(); |
| 214 | } |
| 215 | return tempRect; |
| 216 | } |
| 217 | |
| 218 | @Override |
| 219 | public void set(View object, Rect value) { |
| 220 | final Rect tempRect = mTempRect; |
| 221 | if (object.getClipBounds(tempRect)) { |
| 222 | if (mTargetDimension == TARGET_X) { |
| 223 | tempRect.left = value.left; |
| 224 | tempRect.right = value.right; |
| 225 | } else { |
| 226 | tempRect.top = value.top; |
| 227 | tempRect.bottom = value.bottom; |
| 228 | } |
| 229 | object.setClipBounds(tempRect); |
| 230 | } |
| 231 | } |
Alan Viverette | 5435a30 | 2015-01-29 10:25:34 -0800 | [diff] [blame] | 232 | } |
| 233 | } |