1 /* 2 * Copyright (C) 2014 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.notification.row; 18 19 import android.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.content.Context; 22 import android.util.AttributeSet; 23 import android.view.View; 24 import android.view.animation.Interpolator; 25 26 import com.android.app.animation.Interpolators; 27 import com.android.internal.annotations.VisibleForTesting; 28 29 import java.util.function.Consumer; 30 31 /** 32 * A common base class for all views in the notification stack scroller which don't have a 33 * background. 34 */ 35 public abstract class StackScrollerDecorView extends ExpandableView { 36 37 protected View mContent; 38 protected View mSecondaryView; 39 private boolean mIsVisible = true; 40 private boolean mContentVisible = true; 41 private boolean mIsSecondaryVisible = true; 42 private int mAnimationDuration = 260; 43 private boolean mContentAnimating; 44 private boolean mSecondaryAnimating = false; 45 StackScrollerDecorView(Context context, AttributeSet attrs)46 public StackScrollerDecorView(Context context, AttributeSet attrs) { 47 super(context, attrs); 48 setClipChildren(false); 49 } 50 51 @Override onFinishInflate()52 protected void onFinishInflate() { 53 super.onFinishInflate(); 54 mContent = findContentView(); 55 mSecondaryView = findSecondaryView(); 56 setVisible(false /* visible */, false /* animate */); 57 setSecondaryVisible(false /* visible */, false /* animate */, null /* onAnimationEnd */); 58 setOutlineProvider(null); 59 } 60 61 @Override isTransparent()62 public boolean isTransparent() { 63 return true; 64 } 65 66 /** 67 * Is this view visible. If a view is currently animating to gone, it will 68 * return {@code false}. 69 */ isVisible()70 public boolean isVisible() { 71 return mIsVisible; 72 } 73 74 /** 75 * See {@link #setVisible(boolean, boolean, Consumer)}. 76 */ setVisible(boolean visible, boolean animate)77 public void setVisible(boolean visible, boolean animate) { 78 setVisible(visible, animate, null /* onAnimationEnded */); 79 } 80 81 /** 82 * Make this view visible. If {@code false} is passed, the view will fade out its content 83 * and set the view Visibility to GONE. If only the content should be changed, 84 * {@link #setContentVisibleAnimated(boolean)} can be used. 85 * 86 * @param visible True if the contents should be visible. 87 * @param animate True if we should fade to new visibility. 88 * @param onAnimationEnded Callback to run after visibility updates, takes a boolean as a 89 * parameter that represents whether the animation was cancelled. 90 */ setVisible(boolean visible, boolean animate, Consumer<Boolean> onAnimationEnded)91 public void setVisible(boolean visible, boolean animate, 92 Consumer<Boolean> onAnimationEnded) { 93 if (mIsVisible != visible) { 94 mIsVisible = visible; 95 if (animate) { 96 if (visible) { 97 setVisibility(VISIBLE); 98 setWillBeGone(false); 99 notifyHeightChanged(false /* needsAnimation */); 100 } else { 101 setWillBeGone(true); 102 } 103 setContentVisible(visible, true /* animate */, onAnimationEnded); 104 } else { 105 setVisibility(visible ? VISIBLE : GONE); 106 setContentVisible(visible, false /* animate */, onAnimationEnded); 107 setWillBeGone(false); 108 notifyHeightChanged(false /* needsAnimation */); 109 } 110 } 111 } 112 isContentVisible()113 public boolean isContentVisible() { 114 return mContentVisible; 115 } 116 117 /** 118 * Change content visibility to {@code visible}, animated. 119 */ setContentVisibleAnimated(boolean visible)120 public void setContentVisibleAnimated(boolean visible) { 121 setContentVisible(visible, true /* animate */, null /* onAnimationEnded */); 122 } 123 124 /** 125 * @param visible True if the contents should be visible. 126 * @param animate True if we should fade to new visibility. 127 * @param onAnimationEnded Callback to run after visibility updates, takes a boolean as a 128 * parameter that represents whether the animation was cancelled. 129 */ setContentVisible(boolean visible, boolean animate, Consumer<Boolean> onAnimationEnded)130 public void setContentVisible(boolean visible, boolean animate, 131 Consumer<Boolean> onAnimationEnded) { 132 if (mContentVisible != visible) { 133 mContentAnimating = animate; 134 mContentVisible = visible; 135 Consumer<Boolean> onAnimationEndedWrapper = (cancelled) -> { 136 onContentVisibilityAnimationEnd(); 137 if (onAnimationEnded != null) { 138 onAnimationEnded.accept(cancelled); 139 } 140 }; 141 setViewVisible(mContent, visible, animate, onAnimationEndedWrapper); 142 } else if (onAnimationEnded != null) { 143 // Execute onAnimationEnded immediately if there's no animation to perform. 144 onAnimationEnded.accept(true /* cancelled */); 145 } 146 147 if (!mContentAnimating) { 148 onContentVisibilityAnimationEnd(); 149 } 150 } 151 onContentVisibilityAnimationEnd()152 private void onContentVisibilityAnimationEnd() { 153 mContentAnimating = false; 154 if (getVisibility() != View.GONE && !mIsVisible) { 155 setVisibility(GONE); 156 setWillBeGone(false); 157 notifyHeightChanged(false /* needsAnimation */); 158 } 159 } 160 isSecondaryVisible()161 protected boolean isSecondaryVisible() { 162 return mIsSecondaryVisible; 163 } 164 165 /** 166 * Set the secondary view of this layout to visible. 167 * 168 * @param visible True if the contents should be visible. 169 * @param animate True if we should fade to new visibility. 170 * @param onAnimationEnded Callback to run after visibility updates, takes a boolean as a 171 * parameter that represents whether the animation was cancelled. 172 */ setSecondaryVisible(boolean visible, boolean animate, Consumer<Boolean> onAnimationEnded)173 protected void setSecondaryVisible(boolean visible, boolean animate, 174 Consumer<Boolean> onAnimationEnded) { 175 if (mIsSecondaryVisible != visible) { 176 mSecondaryAnimating = animate; 177 mIsSecondaryVisible = visible; 178 Consumer<Boolean> onAnimationEndedWrapper = (cancelled) -> { 179 onContentVisibilityAnimationEnd(); 180 if (onAnimationEnded != null) { 181 onAnimationEnded.accept(cancelled); 182 } 183 }; 184 setViewVisible(mSecondaryView, visible, animate, onAnimationEndedWrapper); 185 } 186 187 if (!mSecondaryAnimating) { 188 onSecondaryVisibilityAnimationEnd(); 189 } 190 } 191 onSecondaryVisibilityAnimationEnd()192 private void onSecondaryVisibilityAnimationEnd() { 193 mSecondaryAnimating = false; 194 // If we were on screen, become GONE to avoid touches 195 if (mSecondaryView == null) return; 196 if (getVisibility() != View.GONE 197 && mSecondaryView.getVisibility() != View.GONE 198 && !mIsSecondaryVisible) { 199 mSecondaryView.setVisibility(View.GONE); 200 } 201 } 202 203 /** 204 * Animate a view to a new visibility. 205 * 206 * @param view Target view, maybe content view or dismiss view. 207 * @param visible Should it now be visible. 208 * @param animate Should this be done in an animated way. 209 * @param onAnimationEnded Callback to run after visibility updates, takes a boolean as a 210 * parameter that represents whether the animation was cancelled. 211 */ setViewVisible(View view, boolean visible, boolean animate, Consumer<Boolean> onAnimationEnded)212 private void setViewVisible(View view, boolean visible, 213 boolean animate, Consumer<Boolean> onAnimationEnded) { 214 if (view == null) { 215 return; 216 } 217 218 // Make sure we're visible so animations work 219 if (view.getVisibility() != View.VISIBLE) { 220 view.setVisibility(View.VISIBLE); 221 } 222 223 // cancel any previous animations 224 view.animate().cancel(); 225 float endValue = visible ? 1.0f : 0.0f; 226 if (!animate) { 227 view.setAlpha(endValue); 228 if (onAnimationEnded != null) { 229 onAnimationEnded.accept(true); 230 } 231 return; 232 } 233 234 // Animate the view alpha 235 Interpolator interpolator = visible ? Interpolators.ALPHA_IN : Interpolators.ALPHA_OUT; 236 view.animate() 237 .alpha(endValue) 238 .setInterpolator(interpolator) 239 .setDuration(mAnimationDuration) 240 .setListener(new AnimatorListenerAdapter() { 241 boolean mCancelled; 242 243 @Override 244 public void onAnimationCancel(Animator animation) { 245 mCancelled = true; 246 } 247 248 @Override 249 public void onAnimationEnd(Animator animation) { 250 onAnimationEnded.accept(mCancelled); 251 } 252 }); 253 } 254 255 @VisibleForTesting setAnimationDuration(int animationDuration)256 public void setAnimationDuration(int animationDuration) { 257 mAnimationDuration = animationDuration; 258 } 259 260 @Override performRemoveAnimation(long duration, long delay, float translationDirection, boolean isHeadsUpAnimation, boolean isHeadsUpCycling, Runnable onStartedRunnable, Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener, ClipSide clipSide)261 public long performRemoveAnimation(long duration, long delay, 262 float translationDirection, boolean isHeadsUpAnimation, 263 boolean isHeadsUpCycling, Runnable onStartedRunnable, 264 Runnable onFinishedRunnable, 265 AnimatorListenerAdapter animationListener, ClipSide clipSide) { 266 // TODO: Use duration 267 if (onStartedRunnable != null) { 268 onStartedRunnable.run(); 269 } 270 setContentVisible(false, true /* animate */, (cancelled) -> onFinishedRunnable.run()); 271 return 0; 272 } 273 274 @Override performAddAnimation(long delay, long duration, boolean isHeadsUpAppear)275 public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear) { 276 // TODO: use delay and duration 277 setContentVisibleAnimated(true); 278 } 279 280 @Override performAddAnimation(long delay, long duration, boolean isHeadsUpAppear, boolean isHeadsUpCycling, Runnable endRunnable)281 public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear, 282 boolean isHeadsUpCycling, Runnable endRunnable) { 283 // TODO: use delay and duration 284 setContentVisibleAnimated(true); 285 } 286 287 @Override needsClippingToShelf()288 public boolean needsClippingToShelf() { 289 return false; 290 } 291 292 @Override hasOverlappingRendering()293 public boolean hasOverlappingRendering() { 294 return false; 295 } 296 findContentView()297 protected abstract View findContentView(); 298 299 /** 300 * Returns a view that might not always appear while the main content view is still visible. 301 */ findSecondaryView()302 protected abstract View findSecondaryView(); 303 } 304