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 * Make this view visible. If {@code false} is passed, the view will fade out its content 76 * and set the view Visibility to GONE. If only the content should be changed, 77 * {@link #setContentVisibleAnimated(boolean)} can be used. 78 * 79 * @param visible True if the contents should be visible. 80 * @param animate True if we should fade to new visibility. 81 */ setVisible(boolean visible, boolean animate)82 public void setVisible(boolean visible, boolean animate) { 83 if (mIsVisible != visible) { 84 mIsVisible = visible; 85 if (animate) { 86 if (visible) { 87 setVisibility(VISIBLE); 88 setWillBeGone(false); 89 notifyHeightChanged(false /* needsAnimation */); 90 } else { 91 setWillBeGone(true); 92 } 93 setContentVisible(visible, true /* animate */, null /* onAnimationEnded */); 94 } else { 95 setVisibility(visible ? VISIBLE : GONE); 96 setContentVisible(visible, false /* animate */, null /* onAnimationEnded */); 97 setWillBeGone(false); 98 notifyHeightChanged(false /* needsAnimation */); 99 } 100 } 101 } 102 isContentVisible()103 public boolean isContentVisible() { 104 return mContentVisible; 105 } 106 107 /** 108 * Change content visibility to {@code visible}, animated. 109 */ setContentVisibleAnimated(boolean visible)110 public void setContentVisibleAnimated(boolean visible) { 111 setContentVisible(visible, true /* animate */, null /* onAnimationEnded */); 112 } 113 114 /** 115 * @param visible True if the contents should be visible. 116 * @param animate True if we should fade to new visibility. 117 * @param onAnimationEnded Callback to run after visibility updates, takes a boolean as a 118 * parameter that represents whether the animation was cancelled. 119 */ setContentVisible(boolean visible, boolean animate, Consumer<Boolean> onAnimationEnded)120 public void setContentVisible(boolean visible, boolean animate, 121 Consumer<Boolean> onAnimationEnded) { 122 if (mContentVisible != visible) { 123 mContentAnimating = animate; 124 mContentVisible = visible; 125 Consumer<Boolean> onAnimationEndedWrapper = (cancelled) -> { 126 onContentVisibilityAnimationEnd(); 127 if (onAnimationEnded != null) { 128 onAnimationEnded.accept(cancelled); 129 } 130 }; 131 setViewVisible(mContent, visible, animate, onAnimationEndedWrapper); 132 } else if (onAnimationEnded != null) { 133 // Execute onAnimationEnded immediately if there's no animation to perform. 134 onAnimationEnded.accept(true /* cancelled */); 135 } 136 137 if (!mContentAnimating) { 138 onContentVisibilityAnimationEnd(); 139 } 140 } 141 onContentVisibilityAnimationEnd()142 private void onContentVisibilityAnimationEnd() { 143 mContentAnimating = false; 144 if (getVisibility() != View.GONE && !mIsVisible) { 145 setVisibility(GONE); 146 setWillBeGone(false); 147 notifyHeightChanged(false /* needsAnimation */); 148 } 149 } 150 isSecondaryVisible()151 protected boolean isSecondaryVisible() { 152 return mIsSecondaryVisible; 153 } 154 155 /** 156 * Set the secondary view of this layout to visible. 157 * 158 * @param visible True if the contents should be visible. 159 * @param animate True if we should fade to new visibility. 160 * @param onAnimationEnded Callback to run after visibility updates, takes a boolean as a 161 * parameter that represents whether the animation was cancelled. 162 */ setSecondaryVisible(boolean visible, boolean animate, Consumer<Boolean> onAnimationEnded)163 protected void setSecondaryVisible(boolean visible, boolean animate, 164 Consumer<Boolean> onAnimationEnded) { 165 if (mIsSecondaryVisible != visible) { 166 mSecondaryAnimating = animate; 167 mIsSecondaryVisible = visible; 168 Consumer<Boolean> onAnimationEndedWrapper = (cancelled) -> { 169 onContentVisibilityAnimationEnd(); 170 if (onAnimationEnded != null) { 171 onAnimationEnded.accept(cancelled); 172 } 173 }; 174 setViewVisible(mSecondaryView, visible, animate, onAnimationEndedWrapper); 175 } 176 177 if (!mSecondaryAnimating) { 178 onSecondaryVisibilityAnimationEnd(); 179 } 180 } 181 onSecondaryVisibilityAnimationEnd()182 private void onSecondaryVisibilityAnimationEnd() { 183 mSecondaryAnimating = false; 184 // If we were on screen, become GONE to avoid touches 185 if (mSecondaryView == null) return; 186 if (getVisibility() != View.GONE 187 && mSecondaryView.getVisibility() != View.GONE 188 && !mIsSecondaryVisible) { 189 mSecondaryView.setVisibility(View.GONE); 190 } 191 } 192 193 /** 194 * Animate a view to a new visibility. 195 * 196 * @param view Target view, maybe content view or dismiss view. 197 * @param visible Should it now be visible. 198 * @param animate Should this be done in an animated way. 199 * @param onAnimationEnded Callback to run after visibility updates, takes a boolean as a 200 * parameter that represents whether the animation was cancelled. 201 */ setViewVisible(View view, boolean visible, boolean animate, Consumer<Boolean> onAnimationEnded)202 private void setViewVisible(View view, boolean visible, 203 boolean animate, Consumer<Boolean> onAnimationEnded) { 204 if (view == null) { 205 return; 206 } 207 208 // Make sure we're visible so animations work 209 if (view.getVisibility() != View.VISIBLE) { 210 view.setVisibility(View.VISIBLE); 211 } 212 213 // cancel any previous animations 214 view.animate().cancel(); 215 float endValue = visible ? 1.0f : 0.0f; 216 if (!animate) { 217 view.setAlpha(endValue); 218 if (onAnimationEnded != null) { 219 onAnimationEnded.accept(true); 220 } 221 return; 222 } 223 224 // Animate the view alpha 225 Interpolator interpolator = visible ? Interpolators.ALPHA_IN : Interpolators.ALPHA_OUT; 226 view.animate() 227 .alpha(endValue) 228 .setInterpolator(interpolator) 229 .setDuration(mAnimationDuration) 230 .setListener(new AnimatorListenerAdapter() { 231 boolean mCancelled; 232 233 @Override 234 public void onAnimationCancel(Animator animation) { 235 mCancelled = true; 236 } 237 238 @Override 239 public void onAnimationEnd(Animator animation) { 240 onAnimationEnded.accept(mCancelled); 241 } 242 }); 243 } 244 245 @VisibleForTesting setAnimationDuration(int animationDuration)246 public void setAnimationDuration(int animationDuration) { 247 mAnimationDuration = animationDuration; 248 } 249 250 @Override performRemoveAnimation(long duration, long delay, float translationDirection, boolean isHeadsUpAnimation, Runnable onStartedRunnable, Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener, ClipSide clipSide)251 public long performRemoveAnimation(long duration, long delay, 252 float translationDirection, boolean isHeadsUpAnimation, 253 Runnable onStartedRunnable, 254 Runnable onFinishedRunnable, 255 AnimatorListenerAdapter animationListener, ClipSide clipSide) { 256 // TODO: Use duration 257 if (onStartedRunnable != null) { 258 onStartedRunnable.run(); 259 } 260 setContentVisible(false, true /* animate */, (cancelled) -> onFinishedRunnable.run()); 261 return 0; 262 } 263 264 @Override performAddAnimation(long delay, long duration, boolean isHeadsUpAppear)265 public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear) { 266 // TODO: use delay and duration 267 setContentVisibleAnimated(true); 268 } 269 270 @Override performAddAnimation(long delay, long duration, boolean isHeadsUpAppear, Runnable endRunnable)271 public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear, 272 Runnable endRunnable) { 273 // TODO: use delay and duration 274 setContentVisibleAnimated(true); 275 } 276 277 @Override needsClippingToShelf()278 public boolean needsClippingToShelf() { 279 return false; 280 } 281 282 @Override hasOverlappingRendering()283 public boolean hasOverlappingRendering() { 284 return false; 285 } 286 findContentView()287 protected abstract View findContentView(); 288 289 /** 290 * Returns a view that might not always appear while the main content view is still visible. 291 */ findSecondaryView()292 protected abstract View findSecondaryView(); 293 } 294