1 /* 2 * Copyright (C) 2017 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.internal.widget; 18 19 import android.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.animation.ObjectAnimator; 22 import android.util.IntProperty; 23 import android.view.View; 24 import android.view.ViewGroup; 25 import android.view.animation.Interpolator; 26 import android.view.animation.PathInterpolator; 27 28 import com.android.internal.R; 29 30 /** 31 * A listener that automatically starts animations when the layout bounds change. 32 */ 33 public class MessagingPropertyAnimator implements View.OnLayoutChangeListener { 34 private static final long APPEAR_ANIMATION_LENGTH = 210; 35 private static final Interpolator ALPHA_IN = new PathInterpolator(0.4f, 0f, 1f, 1f); 36 public static final Interpolator ALPHA_OUT = new PathInterpolator(0f, 0f, 0.8f, 1f); 37 private static final int TAG_TOP_ANIMATOR = R.id.tag_top_animator; 38 private static final int TAG_TOP = R.id.tag_top_override; 39 private static final int TAG_LAYOUT_TOP = R.id.tag_layout_top; 40 private static final int TAG_FIRST_LAYOUT = R.id.tag_is_first_layout; 41 private static final int TAG_ALPHA_ANIMATOR = R.id.tag_alpha_animator; 42 private static final ViewClippingUtil.ClippingParameters CLIPPING_PARAMETERS = 43 view -> view.getId() == com.android.internal.R.id.notification_messaging; 44 private static final IntProperty<View> TOP = 45 new IntProperty<View>("top") { 46 @Override 47 public void setValue(View object, int value) { 48 setTop(object, value); 49 } 50 51 @Override 52 public Integer get(View object) { 53 return getTop(object); 54 } 55 }; 56 57 @Override 58 public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, 59 int oldTop, int oldRight, int oldBottom) { 60 setLayoutTop(v, top); 61 if (isFirstLayout(v)) { 62 setFirstLayout(v, false /* first */); 63 setTop(v, top); 64 return; 65 } 66 startTopAnimation(v, getTop(v), top, MessagingLayout.FAST_OUT_SLOW_IN); 67 } 68 69 private static boolean isFirstLayout(View view) { 70 Boolean tag = (Boolean) view.getTag(TAG_FIRST_LAYOUT); 71 if (tag == null) { 72 return true; 73 } 74 return tag; 75 } 76 77 public static void recycle(View view) { 78 setFirstLayout(view, true /* first */); 79 } 80 81 private static void setFirstLayout(View view, boolean first) { 82 view.setTagInternal(TAG_FIRST_LAYOUT, first); 83 } 84 85 private static void setLayoutTop(View view, int top) { 86 view.setTagInternal(TAG_LAYOUT_TOP, top); 87 } 88 89 public static int getLayoutTop(View view) { 90 Integer tag = (Integer) view.getTag(TAG_LAYOUT_TOP); 91 if (tag == null) { 92 return getTop(view); 93 } 94 return tag; 95 } 96 97 /** 98 * Start a translation animation from a start offset to the laid out location 99 * @param view The view to animate 100 * @param startTranslation The starting translation to start from. 101 * @param interpolator The interpolator to use. 102 */ 103 public static void startLocalTranslationFrom(View view, int startTranslation, 104 Interpolator interpolator) { 105 startTopAnimation(view, getTop(view) + startTranslation, getLayoutTop(view), interpolator); 106 } 107 108 /** 109 * Start a translation animation from a start offset to the laid out location 110 * @param view The view to animate 111 * @param endTranslation The end translation to go to. 112 * @param interpolator The interpolator to use. 113 */ 114 public static void startLocalTranslationTo(View view, int endTranslation, 115 Interpolator interpolator) { 116 int top = getTop(view); 117 startTopAnimation(view, top, top + endTranslation, interpolator); 118 } 119 120 public static int getTop(View v) { 121 Integer tag = (Integer) v.getTag(TAG_TOP); 122 if (tag == null) { 123 return v.getTop(); 124 } 125 return tag; 126 } 127 128 private static void setTop(View v, int value) { 129 v.setTagInternal(TAG_TOP, value); 130 updateTopAndBottom(v); 131 } 132 133 private static void updateTopAndBottom(View v) { 134 int top = getTop(v); 135 int height = v.getHeight(); 136 v.setTop(top); 137 v.setBottom(height + top); 138 } 139 140 private static void startTopAnimation(final View v, int start, int end, 141 Interpolator interpolator) { 142 ObjectAnimator existing = (ObjectAnimator) v.getTag(TAG_TOP_ANIMATOR); 143 if (existing != null) { 144 existing.cancel(); 145 } 146 if (!v.isShown() || start == end 147 || (MessagingLinearLayout.isGone(v) && !isHidingAnimated(v))) { 148 setTop(v, end); 149 return; 150 } 151 ObjectAnimator animator = ObjectAnimator.ofInt(v, TOP, start, end); 152 setTop(v, start); 153 animator.setInterpolator(interpolator); 154 animator.setDuration(APPEAR_ANIMATION_LENGTH); 155 animator.addListener(new AnimatorListenerAdapter() { 156 public boolean mCancelled; 157 158 @Override 159 public void onAnimationEnd(Animator animation) { 160 v.setTagInternal(TAG_TOP_ANIMATOR, null); 161 setClippingDeactivated(v, false); 162 } 163 164 @Override 165 public void onAnimationCancel(Animator animation) { 166 mCancelled = true; 167 } 168 }); 169 setClippingDeactivated(v, true); 170 v.setTagInternal(TAG_TOP_ANIMATOR, animator); 171 animator.start(); 172 } 173 174 private static boolean isHidingAnimated(View v) { 175 if (v instanceof MessagingLinearLayout.MessagingChild) { 176 return ((MessagingLinearLayout.MessagingChild) v).isHidingAnimated(); 177 } 178 return false; 179 } 180 181 public static void fadeIn(final View v) { 182 ObjectAnimator existing = (ObjectAnimator) v.getTag(TAG_ALPHA_ANIMATOR); 183 if (existing != null) { 184 existing.cancel(); 185 } 186 if (v.getVisibility() == View.INVISIBLE) { 187 v.setVisibility(View.VISIBLE); 188 } 189 ObjectAnimator animator = ObjectAnimator.ofFloat(v, View.ALPHA, 190 0.0f, 1.0f); 191 v.setAlpha(0.0f); 192 animator.setInterpolator(ALPHA_IN); 193 animator.setDuration(APPEAR_ANIMATION_LENGTH); 194 animator.addListener(new AnimatorListenerAdapter() { 195 @Override 196 public void onAnimationEnd(Animator animation) { 197 v.setTagInternal(TAG_ALPHA_ANIMATOR, null); 198 updateLayerType(v, false /* animating */); 199 } 200 }); 201 updateLayerType(v, true /* animating */); 202 v.setTagInternal(TAG_ALPHA_ANIMATOR, animator); 203 animator.start(); 204 } 205 206 private static void updateLayerType(View view, boolean animating) { 207 if (view.hasOverlappingRendering() && animating) { 208 view.setLayerType(View.LAYER_TYPE_HARDWARE, null); 209 } else if (view.getLayerType() == View.LAYER_TYPE_HARDWARE) { 210 view.setLayerType(View.LAYER_TYPE_NONE, null); 211 } 212 } 213 214 public static void fadeOut(final View view, Runnable endAction) { 215 ObjectAnimator existing = (ObjectAnimator) view.getTag(TAG_ALPHA_ANIMATOR); 216 if (existing != null) { 217 existing.cancel(); 218 } 219 if (!view.isShown() || (MessagingLinearLayout.isGone(view) && !isHidingAnimated(view))) { 220 view.setAlpha(0.0f); 221 if (endAction != null) { 222 endAction.run(); 223 } 224 return; 225 } 226 ObjectAnimator animator = ObjectAnimator.ofFloat(view, View.ALPHA, 227 view.getAlpha(), 0.0f); 228 animator.setInterpolator(ALPHA_OUT); 229 animator.setDuration(APPEAR_ANIMATION_LENGTH); 230 animator.addListener(new AnimatorListenerAdapter() { 231 @Override 232 public void onAnimationEnd(Animator animation) { 233 view.setTagInternal(TAG_ALPHA_ANIMATOR, null); 234 updateLayerType(view, false /* animating */); 235 if (endAction != null) { 236 endAction.run(); 237 } 238 } 239 }); 240 updateLayerType(view, true /* animating */); 241 view.setTagInternal(TAG_ALPHA_ANIMATOR, animator); 242 animator.start(); 243 } 244 245 public static void setClippingDeactivated(final View transformedView, boolean deactivated) { 246 ViewClippingUtil.setClippingDeactivated(transformedView, deactivated, 247 CLIPPING_PARAMETERS); 248 } 249 250 public static boolean isAnimatingTranslation(View v) { 251 return v.getTag(TAG_TOP_ANIMATOR) != null; 252 } 253 254 public static boolean isAnimatingAlpha(View v) { 255 return v.getTag(TAG_ALPHA_ANIMATOR) != null; 256 } 257 258 public static void setToLaidOutPosition(View view) { 259 setTop(view, getLayoutTop(view)); 260 } 261 } 262