Selim Cinek | 4ffd636 | 2015-12-29 15:12:23 +0100 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2016 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 com.android.systemui.statusbar; |
| 18 | |
Selim Cinek | 8f2f6a6 | 2016-02-23 19:56:31 -0800 | [diff] [blame] | 19 | import android.animation.Animator; |
| 20 | import android.animation.AnimatorListenerAdapter; |
| 21 | import android.animation.ValueAnimator; |
Selim Cinek | 4ffd636 | 2015-12-29 15:12:23 +0100 | [diff] [blame] | 22 | import android.util.ArrayMap; |
Adrian Roos | 7552441 | 2016-06-21 11:56:57 -0700 | [diff] [blame] | 23 | import android.util.ArraySet; |
Selim Cinek | 4ffd636 | 2015-12-29 15:12:23 +0100 | [diff] [blame] | 24 | import android.view.View; |
Selim Cinek | 646d205 | 2016-01-03 14:42:02 +0800 | [diff] [blame] | 25 | import android.view.ViewGroup; |
Selim Cinek | 5be6f33 | 2017-01-19 11:54:04 -0800 | [diff] [blame] | 26 | import android.view.animation.Interpolator; |
Selim Cinek | 4ffd636 | 2015-12-29 15:12:23 +0100 | [diff] [blame] | 27 | |
Selim Cinek | 8f2f6a6 | 2016-02-23 19:56:31 -0800 | [diff] [blame] | 28 | import com.android.systemui.Interpolators; |
Selim Cinek | 646d205 | 2016-01-03 14:42:02 +0800 | [diff] [blame] | 29 | import com.android.systemui.R; |
Selim Cinek | 4ffd636 | 2015-12-29 15:12:23 +0100 | [diff] [blame] | 30 | import com.android.systemui.statusbar.notification.TransformState; |
Rohan Shah | 20790b8 | 2018-07-02 17:21:04 -0700 | [diff] [blame] | 31 | import com.android.systemui.statusbar.notification.stack.StackStateAnimator; |
Selim Cinek | 4ffd636 | 2015-12-29 15:12:23 +0100 | [diff] [blame] | 32 | |
Selim Cinek | 646d205 | 2016-01-03 14:42:02 +0800 | [diff] [blame] | 33 | import java.util.Stack; |
| 34 | |
Selim Cinek | 4ffd636 | 2015-12-29 15:12:23 +0100 | [diff] [blame] | 35 | /** |
| 36 | * A view that can be transformed to and from. |
| 37 | */ |
Selim Cinek | 1d6b50e | 2017-10-27 16:10:57 -0700 | [diff] [blame] | 38 | public class ViewTransformationHelper implements TransformableView, |
| 39 | TransformState.TransformInfo { |
Selim Cinek | 646d205 | 2016-01-03 14:42:02 +0800 | [diff] [blame] | 40 | |
| 41 | private static final int TAG_CONTAINS_TRANSFORMED_VIEW = R.id.contains_transformed_view; |
| 42 | |
Selim Cinek | 4ffd636 | 2015-12-29 15:12:23 +0100 | [diff] [blame] | 43 | private ArrayMap<Integer, View> mTransformedViews = new ArrayMap<>(); |
Selim Cinek | fd3e262 | 2016-01-12 16:02:42 -0800 | [diff] [blame] | 44 | private ArrayMap<Integer, CustomTransformation> mCustomTransformations = new ArrayMap<>(); |
Selim Cinek | 8f2f6a6 | 2016-02-23 19:56:31 -0800 | [diff] [blame] | 45 | private ValueAnimator mViewTransformationAnimation; |
Selim Cinek | 4ffd636 | 2015-12-29 15:12:23 +0100 | [diff] [blame] | 46 | |
| 47 | public void addTransformedView(int key, View transformedView) { |
| 48 | mTransformedViews.put(key, transformedView); |
| 49 | } |
| 50 | |
| 51 | public void reset() { |
| 52 | mTransformedViews.clear(); |
| 53 | } |
| 54 | |
Selim Cinek | fd3e262 | 2016-01-12 16:02:42 -0800 | [diff] [blame] | 55 | public void setCustomTransformation(CustomTransformation transformation, int viewType) { |
| 56 | mCustomTransformations.put(viewType, transformation); |
| 57 | } |
| 58 | |
Selim Cinek | 4ffd636 | 2015-12-29 15:12:23 +0100 | [diff] [blame] | 59 | @Override |
| 60 | public TransformState getCurrentState(int fadingView) { |
| 61 | View view = mTransformedViews.get(fadingView); |
| 62 | if (view != null && view.getVisibility() != View.GONE) { |
Selim Cinek | 1d6b50e | 2017-10-27 16:10:57 -0700 | [diff] [blame] | 63 | return TransformState.createFrom(view, this); |
Selim Cinek | 4ffd636 | 2015-12-29 15:12:23 +0100 | [diff] [blame] | 64 | } |
| 65 | return null; |
| 66 | } |
| 67 | |
| 68 | @Override |
Selim Cinek | 8f2f6a6 | 2016-02-23 19:56:31 -0800 | [diff] [blame] | 69 | public void transformTo(final TransformableView notification, final Runnable endRunnable) { |
| 70 | if (mViewTransformationAnimation != null) { |
| 71 | mViewTransformationAnimation.cancel(); |
| 72 | } |
| 73 | mViewTransformationAnimation = ValueAnimator.ofFloat(0.0f, 1.0f); |
| 74 | mViewTransformationAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { |
| 75 | @Override |
| 76 | public void onAnimationUpdate(ValueAnimator animation) { |
| 77 | transformTo(notification, animation.getAnimatedFraction()); |
| 78 | } |
| 79 | }); |
| 80 | mViewTransformationAnimation.setInterpolator(Interpolators.LINEAR); |
| 81 | mViewTransformationAnimation.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); |
Selim Cinek | 51d9491 | 2016-03-02 15:34:28 -0800 | [diff] [blame] | 82 | mViewTransformationAnimation.addListener(new AnimatorListenerAdapter() { |
| 83 | public boolean mCancelled; |
Selim Cinek | 8f2f6a6 | 2016-02-23 19:56:31 -0800 | [diff] [blame] | 84 | |
Selim Cinek | 51d9491 | 2016-03-02 15:34:28 -0800 | [diff] [blame] | 85 | @Override |
| 86 | public void onAnimationEnd(Animator animation) { |
| 87 | if (!mCancelled) { |
| 88 | if (endRunnable != null) { |
| 89 | endRunnable.run(); |
Selim Cinek | 8f2f6a6 | 2016-02-23 19:56:31 -0800 | [diff] [blame] | 90 | } |
Selim Cinek | 51d9491 | 2016-03-02 15:34:28 -0800 | [diff] [blame] | 91 | setVisible(false); |
Selim Cinek | 1d6b50e | 2017-10-27 16:10:57 -0700 | [diff] [blame] | 92 | mViewTransformationAnimation = null; |
Selim Cinek | 51d9491 | 2016-03-02 15:34:28 -0800 | [diff] [blame] | 93 | } else { |
| 94 | abortTransformations(); |
Selim Cinek | 8f2f6a6 | 2016-02-23 19:56:31 -0800 | [diff] [blame] | 95 | } |
Selim Cinek | 51d9491 | 2016-03-02 15:34:28 -0800 | [diff] [blame] | 96 | } |
Selim Cinek | 8f2f6a6 | 2016-02-23 19:56:31 -0800 | [diff] [blame] | 97 | |
Selim Cinek | 51d9491 | 2016-03-02 15:34:28 -0800 | [diff] [blame] | 98 | @Override |
| 99 | public void onAnimationCancel(Animator animation) { |
| 100 | mCancelled = true; |
| 101 | } |
| 102 | }); |
Selim Cinek | 8f2f6a6 | 2016-02-23 19:56:31 -0800 | [diff] [blame] | 103 | mViewTransformationAnimation.start(); |
| 104 | } |
| 105 | |
| 106 | @Override |
| 107 | public void transformTo(TransformableView notification, float transformationAmount) { |
Selim Cinek | 4ffd636 | 2015-12-29 15:12:23 +0100 | [diff] [blame] | 108 | for (Integer viewType : mTransformedViews.keySet()) { |
| 109 | TransformState ownState = getCurrentState(viewType); |
| 110 | if (ownState != null) { |
Selim Cinek | fd3e262 | 2016-01-12 16:02:42 -0800 | [diff] [blame] | 111 | CustomTransformation customTransformation = mCustomTransformations.get(viewType); |
| 112 | if (customTransformation != null && customTransformation.transformTo( |
Selim Cinek | 8f2f6a6 | 2016-02-23 19:56:31 -0800 | [diff] [blame] | 113 | ownState, notification, transformationAmount)) { |
Selim Cinek | fd3e262 | 2016-01-12 16:02:42 -0800 | [diff] [blame] | 114 | ownState.recycle(); |
Selim Cinek | fd3e262 | 2016-01-12 16:02:42 -0800 | [diff] [blame] | 115 | continue; |
| 116 | } |
Selim Cinek | 4ffd636 | 2015-12-29 15:12:23 +0100 | [diff] [blame] | 117 | TransformState otherState = notification.getCurrentState(viewType); |
| 118 | if (otherState != null) { |
Selim Cinek | 8f2f6a6 | 2016-02-23 19:56:31 -0800 | [diff] [blame] | 119 | ownState.transformViewTo(otherState, transformationAmount); |
Selim Cinek | 4ffd636 | 2015-12-29 15:12:23 +0100 | [diff] [blame] | 120 | otherState.recycle(); |
Selim Cinek | 4ffd636 | 2015-12-29 15:12:23 +0100 | [diff] [blame] | 121 | } else { |
Selim Cinek | 8a71ad0 | 2016-09-07 20:00:25 -0700 | [diff] [blame] | 122 | ownState.disappear(transformationAmount, notification); |
Selim Cinek | 4ffd636 | 2015-12-29 15:12:23 +0100 | [diff] [blame] | 123 | } |
| 124 | ownState.recycle(); |
| 125 | } |
| 126 | } |
Selim Cinek | 4ffd636 | 2015-12-29 15:12:23 +0100 | [diff] [blame] | 127 | } |
| 128 | |
| 129 | @Override |
Selim Cinek | 8f2f6a6 | 2016-02-23 19:56:31 -0800 | [diff] [blame] | 130 | public void transformFrom(final TransformableView notification) { |
| 131 | if (mViewTransformationAnimation != null) { |
| 132 | mViewTransformationAnimation.cancel(); |
| 133 | } |
| 134 | mViewTransformationAnimation = ValueAnimator.ofFloat(0.0f, 1.0f); |
| 135 | mViewTransformationAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { |
| 136 | @Override |
| 137 | public void onAnimationUpdate(ValueAnimator animation) { |
| 138 | transformFrom(notification, animation.getAnimatedFraction()); |
| 139 | } |
| 140 | }); |
| 141 | mViewTransformationAnimation.addListener(new AnimatorListenerAdapter() { |
| 142 | public boolean mCancelled; |
| 143 | |
| 144 | @Override |
| 145 | public void onAnimationEnd(Animator animation) { |
| 146 | if (!mCancelled) { |
| 147 | setVisible(true); |
| 148 | } else { |
| 149 | abortTransformations(); |
| 150 | } |
| 151 | } |
| 152 | |
| 153 | @Override |
| 154 | public void onAnimationCancel(Animator animation) { |
| 155 | mCancelled = true; |
| 156 | } |
| 157 | }); |
| 158 | mViewTransformationAnimation.setInterpolator(Interpolators.LINEAR); |
| 159 | mViewTransformationAnimation.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); |
| 160 | mViewTransformationAnimation.start(); |
| 161 | } |
| 162 | |
| 163 | @Override |
| 164 | public void transformFrom(TransformableView notification, float transformationAmount) { |
Selim Cinek | 4ffd636 | 2015-12-29 15:12:23 +0100 | [diff] [blame] | 165 | for (Integer viewType : mTransformedViews.keySet()) { |
| 166 | TransformState ownState = getCurrentState(viewType); |
| 167 | if (ownState != null) { |
Selim Cinek | fd3e262 | 2016-01-12 16:02:42 -0800 | [diff] [blame] | 168 | CustomTransformation customTransformation = mCustomTransformations.get(viewType); |
| 169 | if (customTransformation != null && customTransformation.transformFrom( |
Selim Cinek | 8f2f6a6 | 2016-02-23 19:56:31 -0800 | [diff] [blame] | 170 | ownState, notification, transformationAmount)) { |
Selim Cinek | fd3e262 | 2016-01-12 16:02:42 -0800 | [diff] [blame] | 171 | ownState.recycle(); |
| 172 | continue; |
| 173 | } |
Selim Cinek | 4ffd636 | 2015-12-29 15:12:23 +0100 | [diff] [blame] | 174 | TransformState otherState = notification.getCurrentState(viewType); |
| 175 | if (otherState != null) { |
Selim Cinek | 8f2f6a6 | 2016-02-23 19:56:31 -0800 | [diff] [blame] | 176 | ownState.transformViewFrom(otherState, transformationAmount); |
Selim Cinek | 4ffd636 | 2015-12-29 15:12:23 +0100 | [diff] [blame] | 177 | otherState.recycle(); |
| 178 | } else { |
Selim Cinek | 8a71ad0 | 2016-09-07 20:00:25 -0700 | [diff] [blame] | 179 | ownState.appear(transformationAmount, notification); |
Selim Cinek | 4ffd636 | 2015-12-29 15:12:23 +0100 | [diff] [blame] | 180 | } |
| 181 | ownState.recycle(); |
| 182 | } |
| 183 | } |
| 184 | } |
| 185 | |
| 186 | @Override |
| 187 | public void setVisible(boolean visible) { |
Selim Cinek | d607daf | 2016-03-11 12:34:13 -0800 | [diff] [blame] | 188 | if (mViewTransformationAnimation != null) { |
| 189 | mViewTransformationAnimation.cancel(); |
| 190 | } |
Selim Cinek | 4ffd636 | 2015-12-29 15:12:23 +0100 | [diff] [blame] | 191 | for (Integer viewType : mTransformedViews.keySet()) { |
| 192 | TransformState ownState = getCurrentState(viewType); |
| 193 | if (ownState != null) { |
Adrian Roos | 7552441 | 2016-06-21 11:56:57 -0700 | [diff] [blame] | 194 | ownState.setVisible(visible, false /* force */); |
Selim Cinek | 4ffd636 | 2015-12-29 15:12:23 +0100 | [diff] [blame] | 195 | ownState.recycle(); |
| 196 | } |
| 197 | } |
| 198 | } |
Selim Cinek | 646d205 | 2016-01-03 14:42:02 +0800 | [diff] [blame] | 199 | |
Selim Cinek | 8f2f6a6 | 2016-02-23 19:56:31 -0800 | [diff] [blame] | 200 | private void abortTransformations() { |
| 201 | for (Integer viewType : mTransformedViews.keySet()) { |
| 202 | TransformState ownState = getCurrentState(viewType); |
| 203 | if (ownState != null) { |
| 204 | ownState.abortTransformation(); |
| 205 | ownState.recycle(); |
| 206 | } |
| 207 | } |
| 208 | } |
| 209 | |
Selim Cinek | 646d205 | 2016-01-03 14:42:02 +0800 | [diff] [blame] | 210 | /** |
| 211 | * Add the remaining transformation views such that all views are being transformed correctly |
| 212 | * @param viewRoot the root below which all elements need to be transformed |
| 213 | */ |
| 214 | public void addRemainingTransformTypes(View viewRoot) { |
| 215 | // lets now tag the right views |
| 216 | int numValues = mTransformedViews.size(); |
| 217 | for (int i = 0; i < numValues; i++) { |
| 218 | View view = mTransformedViews.valueAt(i); |
| 219 | while (view != viewRoot.getParent()) { |
| 220 | view.setTag(TAG_CONTAINS_TRANSFORMED_VIEW, true); |
| 221 | view = (View) view.getParent(); |
| 222 | } |
| 223 | } |
| 224 | Stack<View> stack = new Stack<>(); |
| 225 | // Add the right views now |
| 226 | stack.push(viewRoot); |
| 227 | while (!stack.isEmpty()) { |
| 228 | View child = stack.pop(); |
Selim Cinek | 646d205 | 2016-01-03 14:42:02 +0800 | [diff] [blame] | 229 | Boolean containsView = (Boolean) child.getTag(TAG_CONTAINS_TRANSFORMED_VIEW); |
| 230 | if (containsView == null) { |
| 231 | // This one is unhandled, let's add it to our list. |
| 232 | int id = child.getId(); |
| 233 | if (id != View.NO_ID) { |
| 234 | // We only fade views with an id |
| 235 | addTransformedView(id, child); |
| 236 | continue; |
| 237 | } |
| 238 | } |
| 239 | child.setTag(TAG_CONTAINS_TRANSFORMED_VIEW, null); |
| 240 | if (child instanceof ViewGroup && !mTransformedViews.containsValue(child)){ |
| 241 | ViewGroup group = (ViewGroup) child; |
| 242 | for (int i = 0; i < group.getChildCount(); i++) { |
| 243 | stack.push(group.getChildAt(i)); |
| 244 | } |
| 245 | } |
| 246 | } |
| 247 | } |
Selim Cinek | fd3e262 | 2016-01-12 16:02:42 -0800 | [diff] [blame] | 248 | |
Adrian Roos | 7552441 | 2016-06-21 11:56:57 -0700 | [diff] [blame] | 249 | public void resetTransformedView(View view) { |
Selim Cinek | 1d6b50e | 2017-10-27 16:10:57 -0700 | [diff] [blame] | 250 | TransformState state = TransformState.createFrom(view, this); |
Adrian Roos | 7552441 | 2016-06-21 11:56:57 -0700 | [diff] [blame] | 251 | state.setVisible(true /* visible */, true /* force */); |
| 252 | state.recycle(); |
| 253 | } |
| 254 | |
| 255 | /** |
| 256 | * @return a set of all views are being transformed. |
| 257 | */ |
| 258 | public ArraySet<View> getAllTransformingViews() { |
| 259 | return new ArraySet<>(mTransformedViews.values()); |
| 260 | } |
| 261 | |
Selim Cinek | 1d6b50e | 2017-10-27 16:10:57 -0700 | [diff] [blame] | 262 | @Override |
| 263 | public boolean isAnimating() { |
| 264 | return mViewTransformationAnimation != null && mViewTransformationAnimation.isRunning(); |
| 265 | } |
| 266 | |
Selim Cinek | 8f2f6a6 | 2016-02-23 19:56:31 -0800 | [diff] [blame] | 267 | public static abstract class CustomTransformation { |
Selim Cinek | fd3e262 | 2016-01-12 16:02:42 -0800 | [diff] [blame] | 268 | /** |
| 269 | * Transform a state to the given view |
| 270 | * @param ownState the state to transform |
| 271 | * @param notification the view to transform to |
Selim Cinek | 8f2f6a6 | 2016-02-23 19:56:31 -0800 | [diff] [blame] | 272 | * @param transformationAmount how much transformation should be done |
Selim Cinek | fd3e262 | 2016-01-12 16:02:42 -0800 | [diff] [blame] | 273 | * @return whether a custom transformation is performed |
| 274 | */ |
Selim Cinek | 8f2f6a6 | 2016-02-23 19:56:31 -0800 | [diff] [blame] | 275 | public abstract boolean transformTo(TransformState ownState, |
| 276 | TransformableView notification, |
| 277 | float transformationAmount); |
Selim Cinek | fd3e262 | 2016-01-12 16:02:42 -0800 | [diff] [blame] | 278 | |
| 279 | /** |
| 280 | * Transform to this state from the given view |
| 281 | * @param ownState the state to transform to |
| 282 | * @param notification the view to transform from |
Selim Cinek | 8f2f6a6 | 2016-02-23 19:56:31 -0800 | [diff] [blame] | 283 | * @param transformationAmount how much transformation should be done |
Selim Cinek | fd3e262 | 2016-01-12 16:02:42 -0800 | [diff] [blame] | 284 | * @return whether a custom transformation is performed |
| 285 | */ |
Selim Cinek | 8f2f6a6 | 2016-02-23 19:56:31 -0800 | [diff] [blame] | 286 | public abstract boolean transformFrom(TransformState ownState, |
| 287 | TransformableView notification, |
| 288 | float transformationAmount); |
| 289 | |
| 290 | /** |
| 291 | * Perform a custom initialisation before transforming. |
| 292 | * |
| 293 | * @param ownState our own state |
| 294 | * @param otherState the other state |
| 295 | * @return whether a custom initialization is done |
| 296 | */ |
| 297 | public boolean initTransformation(TransformState ownState, |
| 298 | TransformState otherState) { |
| 299 | return false; |
| 300 | } |
| 301 | |
| 302 | public boolean customTransformTarget(TransformState ownState, |
| 303 | TransformState otherState) { |
| 304 | return false; |
| 305 | } |
Selim Cinek | 5be6f33 | 2017-01-19 11:54:04 -0800 | [diff] [blame] | 306 | |
| 307 | /** |
| 308 | * Get a custom interpolator for this animation |
| 309 | * @param interpolationType the type of the interpolation, i.e TranslationX / TranslationY |
| 310 | * @param isFrom true if this transformation from the other view |
| 311 | */ |
| 312 | public Interpolator getCustomInterpolator(int interpolationType, boolean isFrom) { |
| 313 | return null; |
| 314 | } |
Selim Cinek | fd3e262 | 2016-01-12 16:02:42 -0800 | [diff] [blame] | 315 | } |
Selim Cinek | 4ffd636 | 2015-12-29 15:12:23 +0100 | [diff] [blame] | 316 | } |