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 * Set the content of this view to be visible in an animated way. 89 * 90 * @param contentVisible True if the content should be visible or false if it should be hidden. 91 */ setContentVisible(boolean contentVisible)92 public void setContentVisible(boolean contentVisible) { 93 setContentVisible(contentVisible, true /* animate */); 94 } 95 /** 96 * Set the content of this view to be visible. 97 * @param contentVisible True if the content should be visible or false if it should be hidden. 98 * @param animate Should an animation be performed. 99 */ setContentVisible(boolean contentVisible, boolean animate)100 private void setContentVisible(boolean contentVisible, boolean animate) { 101 if (mContentVisible != contentVisible) { 102 mContentAnimating = animate; 103 mContentVisible = contentVisible; 104 setViewVisible(mContent, contentVisible, animate, mContentVisibilityEndRunnable); 105 } 106 107 if (!mContentAnimating) { 108 mContentVisibilityEndRunnable.run(); 109 } 110 } 111 isContentVisible()112 public boolean isContentVisible() { 113 return mContentVisible; 114 } 115 116 /** 117 * Make this view visible. If {@code false} is passed, the view will fade out it's content 118 * and set the view Visibility to GONE. If only the content should be changed 119 * {@link #setContentVisible(boolean)} can be used. 120 * 121 * @param nowVisible should the view be visible 122 * @param animate should the change be animated. 123 */ setVisible(boolean nowVisible, boolean animate)124 public void setVisible(boolean nowVisible, boolean animate) { 125 if (mIsVisible != nowVisible) { 126 mIsVisible = nowVisible; 127 if (animate) { 128 if (nowVisible) { 129 setVisibility(VISIBLE); 130 setWillBeGone(false); 131 notifyHeightChanged(false /* needsAnimation */); 132 } else { 133 setWillBeGone(true); 134 } 135 setContentVisible(nowVisible, true /* animate */); 136 } else { 137 setVisibility(nowVisible ? VISIBLE : GONE); 138 setContentVisible(nowVisible, false /* animate */); 139 setWillBeGone(false); 140 notifyHeightChanged(false /* needsAnimation */); 141 } 142 } 143 } 144 145 /** 146 * Set the secondary view of this layout to visible. 147 * 148 * @param nowVisible should the secondary view be visible 149 * @param animate should the change be animated 150 */ setSecondaryVisible(boolean nowVisible, boolean animate)151 public void setSecondaryVisible(boolean nowVisible, boolean animate) { 152 if (mIsSecondaryVisible != nowVisible) { 153 mSecondaryAnimating = animate; 154 mIsSecondaryVisible = nowVisible; 155 setViewVisible(mSecondaryView, nowVisible, animate, mSecondaryVisibilityEndRunnable); 156 } 157 158 if (!mSecondaryAnimating) { 159 mSecondaryVisibilityEndRunnable.run(); 160 } 161 } 162 163 @VisibleForTesting isSecondaryVisible()164 boolean isSecondaryVisible() { 165 return mIsSecondaryVisible; 166 } 167 168 /** 169 * Is this view visible. If a view is currently animating to gone, it will 170 * return {@code false}. 171 */ isVisible()172 public boolean isVisible() { 173 return mIsVisible; 174 } 175 setDuration(int duration)176 void setDuration(int duration) { 177 mDuration = duration; 178 } 179 180 /** 181 * Animate a view to a new visibility. 182 * @param view Target view, maybe content view or dismiss view. 183 * @param nowVisible Should it now be visible. 184 * @param animate Should this be done in an animated way. 185 * @param endRunnable A runnable that is run when the animation is done. 186 */ setViewVisible(View view, boolean nowVisible, boolean animate, Runnable endRunnable)187 private void setViewVisible(View view, boolean nowVisible, 188 boolean animate, Runnable endRunnable) { 189 if (view == null) { 190 return; 191 } 192 193 // Make sure we're visible so animations work 194 if (view.getVisibility() != View.VISIBLE) { 195 view.setVisibility(View.VISIBLE); 196 } 197 198 // cancel any previous animations 199 view.animate().cancel(); 200 float endValue = nowVisible ? 1.0f : 0.0f; 201 if (!animate) { 202 view.setAlpha(endValue); 203 if (endRunnable != null) { 204 endRunnable.run(); 205 } 206 return; 207 } 208 209 // Animate the view alpha 210 Interpolator interpolator = nowVisible ? Interpolators.ALPHA_IN : Interpolators.ALPHA_OUT; 211 view.animate() 212 .alpha(endValue) 213 .setInterpolator(interpolator) 214 .setDuration(mDuration) 215 .withEndAction(endRunnable); 216 } 217 218 @Override performRemoveAnimation(long duration, long delay, float translationDirection, boolean isHeadsUpAnimation, float endLocation, Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener)219 public long performRemoveAnimation(long duration, long delay, 220 float translationDirection, boolean isHeadsUpAnimation, float endLocation, 221 Runnable onFinishedRunnable, 222 AnimatorListenerAdapter animationListener) { 223 // TODO: Use duration 224 setContentVisible(false); 225 return 0; 226 } 227 228 @Override performAddAnimation(long delay, long duration, boolean isHeadsUpAppear)229 public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear) { 230 // TODO: use delay and duration 231 setContentVisible(true); 232 } 233 234 @Override needsClippingToShelf()235 public boolean needsClippingToShelf() { 236 return false; 237 } 238 239 @Override hasOverlappingRendering()240 public boolean hasOverlappingRendering() { 241 return false; 242 } 243 findContentView()244 protected abstract View findContentView(); 245 246 /** 247 * Returns a view that might not always appear while the main content view is still visible. 248 */ findSecondaryView()249 protected abstract View findSecondaryView(); 250 } 251