blob: 1a6736a8677d3542a96ab1e02b47a3bfd749eb0f [file] [log] [blame]
Alan Viverette5435a302015-01-29 10:25:34 -08001/*
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 */
16package com.android.internal.transition;
17
18import android.animation.Animator;
George Mount3c75db92015-03-05 14:09:33 -080019import android.animation.AnimatorListenerAdapter;
Alan Viverette95888c02015-04-16 13:27:50 -070020import android.animation.AnimatorSet;
Alan Viverette5435a302015-01-29 10:25:34 -080021import android.animation.ObjectAnimator;
22import android.animation.RectEvaluator;
Alan Viverette95888c02015-04-16 13:27:50 -070023import android.animation.TimeInterpolator;
Alan Viverette5435a302015-01-29 10:25:34 -080024import android.content.Context;
Alan Viverette95888c02015-04-16 13:27:50 -070025import android.content.res.TypedArray;
Alan Viverette5435a302015-01-29 10:25:34 -080026import android.graphics.Rect;
27import android.transition.TransitionValues;
28import android.transition.Visibility;
29import android.util.AttributeSet;
Alan Viverette95888c02015-04-16 13:27:50 -070030import android.util.Property;
Alan Viverette5435a302015-01-29 10:25:34 -080031import android.view.View;
32import android.view.ViewGroup;
Alan Viverette95888c02015-04-16 13:27:50 -070033import android.view.animation.AnimationUtils;
34import android.view.animation.PathInterpolator;
35
36import com.android.internal.R;
Alan Viverette5435a302015-01-29 10:25:34 -080037
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 */
43public 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 Viverette95888c02015-04-16 13:27:50 -070047 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 Viverette5435a302015-01-29 10:25:34 -080056
57 public EpicenterClipReveal(Context context, AttributeSet attrs) {
58 super(context, attrs);
Alan Viverette95888c02015-04-16 13:27:50 -070059
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 Viverette5435a302015-01-29 10:25:34 -080080 }
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 Viverette5435a302015-01-29 10:25:34 -0800116 final Rect end = getBestRect(endValues);
George Mount3c75db92015-03-05 14:09:33 -0800117 final Rect start = getEpicenterOrCenter(end);
Alan Viverette5435a302015-01-29 10:25:34 -0800118
119 // Prepare the view.
120 view.setClipBounds(start);
121
Alan Viverette95888c02015-04-16 13:27:50 -0700122 return createRectAnimator(view, start, end, endValues, mInterpolatorX, mInterpolatorY);
Alan Viverette5435a302015-01-29 10:25:34 -0800123 }
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 Mount3c75db92015-03-05 14:09:33 -0800133 final Rect end = getEpicenterOrCenter(start);
Alan Viverette5435a302015-01-29 10:25:34 -0800134
135 // Prepare the view.
136 view.setClipBounds(start);
137
Alan Viverette95888c02015-04-16 13:27:50 -0700138 return createRectAnimator(view, start, end, endValues, mInterpolatorX, mInterpolatorY);
George Mount3c75db92015-03-05 14:09:33 -0800139 }
140
141 private Rect getEpicenterOrCenter(Rect bestRect) {
142 final Rect epicenter = getEpicenter();
143 if (epicenter != null) {
Alan Viverette95888c02015-04-16 13:27:50 -0700144 // 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 Mount3c75db92015-03-05 14:09:33 -0800150 return epicenter;
151 }
152
Alan Viverette95888c02015-04-16 13:27:50 -0700153 final int centerX = bestRect.centerX();
154 final int centerY = bestRect.centerY();
George Mount3c75db92015-03-05 14:09:33 -0800155 return new Rect(centerX, centerY, centerX, centerY);
Alan Viverette5435a302015-01-29 10:25:34 -0800156 }
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 Viverette95888c02015-04-16 13:27:50 -0700166 private static Animator createRectAnimator(final View view, Rect start, Rect end,
167 TransitionValues endValues, TimeInterpolator interpolatorX,
168 TimeInterpolator interpolatorY) {
Alan Viverette5435a302015-01-29 10:25:34 -0800169 final RectEvaluator evaluator = new RectEvaluator(new Rect());
Alan Viverette95888c02015-04-16 13:27:50 -0700170 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 Mount3c75db92015-03-05 14:09:33 -0800187 @Override
188 public void onAnimationEnd(Animator animation) {
189 view.setClipBounds(terminalClip);
190 }
191 });
Alan Viverette95888c02015-04-16 13:27:50 -0700192 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 Viverette5435a302015-01-29 10:25:34 -0800232 }
233}