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