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.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 StackScrollerDecorView(Context context, AttributeSet attrs)50 public StackScrollerDecorView(Context context, AttributeSet attrs) { 51 super(context, attrs); 52 } 53 54 @Override onFinishInflate()55 protected void onFinishInflate() { 56 super.onFinishInflate(); 57 mContent = findContentView(); 58 mSecondaryView = findSecondaryView(); 59 setVisible(false /* nowVisible */, false /* animate */); 60 setSecondaryVisible(false /* nowVisible */, false /* animate */); 61 } 62 63 @Override onLayout(boolean changed, int left, int top, int right, int bottom)64 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 65 super.onLayout(changed, left, top, right, bottom); 66 setOutlineProvider(null); 67 } 68 69 @Override isTransparent()70 public boolean isTransparent() { 71 return true; 72 } 73 74 /** 75 * Set the content of this view to be visible in an animated way. 76 * 77 * @param contentVisible True if the content should be visible or false if it should be hidden. 78 */ setContentVisible(boolean contentVisible)79 public void setContentVisible(boolean contentVisible) { 80 setContentVisible(contentVisible, true /* animate */); 81 } 82 /** 83 * Set the content of this view to be visible. 84 * @param contentVisible True if the content should be visible or false if it should be hidden. 85 * @param animate Should an animation be performed. 86 */ setContentVisible(boolean contentVisible, boolean animate)87 private void setContentVisible(boolean contentVisible, boolean animate) { 88 if (mContentVisible != contentVisible) { 89 mContentAnimating = animate; 90 setViewVisible(mContent, contentVisible, animate, mContentVisibilityEndRunnable); 91 mContentVisible = contentVisible; 92 } if (!mContentAnimating) { 93 mContentVisibilityEndRunnable.run(); 94 } 95 } 96 isContentVisible()97 public boolean isContentVisible() { 98 return mContentVisible; 99 } 100 101 /** 102 * Make this view visible. If {@code false} is passed, the view will fade out it's content 103 * and set the view Visibility to GONE. If only the content should be changed 104 * {@link #setContentVisible(boolean)} can be used. 105 * 106 * @param nowVisible should the view be visible 107 * @param animate should the change be animated. 108 */ setVisible(boolean nowVisible, boolean animate)109 public void setVisible(boolean nowVisible, boolean animate) { 110 if (mIsVisible != nowVisible) { 111 mIsVisible = nowVisible; 112 if (animate) { 113 if (nowVisible) { 114 setVisibility(VISIBLE); 115 setWillBeGone(false); 116 notifyHeightChanged(false /* needsAnimation */); 117 } else { 118 setWillBeGone(true); 119 } 120 setContentVisible(nowVisible, true /* animate */); 121 } else { 122 setVisibility(nowVisible ? VISIBLE : GONE); 123 setContentVisible(nowVisible, false /* animate */); 124 setWillBeGone(false); 125 notifyHeightChanged(false /* needsAnimation */); 126 } 127 } 128 } 129 130 /** 131 * Set the secondary view of this layout to visible. 132 * 133 * @param nowVisible should the secondary view be visible 134 * @param animate should the change be animated 135 */ setSecondaryVisible(boolean nowVisible, boolean animate)136 public void setSecondaryVisible(boolean nowVisible, boolean animate) { 137 if (mIsSecondaryVisible != nowVisible) { 138 setViewVisible(mSecondaryView, nowVisible, animate, null /* endRunnable */); 139 mIsSecondaryVisible = nowVisible; 140 } 141 } 142 143 @VisibleForTesting isSecondaryVisible()144 boolean isSecondaryVisible() { 145 return mIsSecondaryVisible; 146 } 147 148 /** 149 * Is this view visible. If a view is currently animating to gone, it will 150 * return {@code false}. 151 */ isVisible()152 public boolean isVisible() { 153 return mIsVisible; 154 } 155 setDuration(int duration)156 void setDuration(int duration) { 157 mDuration = duration; 158 } 159 160 /** 161 * Animate a view to a new visibility. 162 * @param view Target view, maybe content view or dismiss view. 163 * @param nowVisible Should it now be visible. 164 * @param animate Should this be done in an animated way. 165 * @param endRunnable A runnable that is run when the animation is done. 166 */ setViewVisible(View view, boolean nowVisible, boolean animate, Runnable endRunnable)167 private void setViewVisible(View view, boolean nowVisible, 168 boolean animate, Runnable endRunnable) { 169 if (view == null) { 170 return; 171 } 172 // cancel any previous animations 173 view.animate().cancel(); 174 float endValue = nowVisible ? 1.0f : 0.0f; 175 if (!animate) { 176 view.setAlpha(endValue); 177 if (endRunnable != null) { 178 endRunnable.run(); 179 } 180 return; 181 } 182 183 // Animate the view alpha 184 Interpolator interpolator = nowVisible ? Interpolators.ALPHA_IN : Interpolators.ALPHA_OUT; 185 view.animate() 186 .alpha(endValue) 187 .setInterpolator(interpolator) 188 .setDuration(mDuration) 189 .withEndAction(endRunnable); 190 } 191 192 @Override performRemoveAnimation(long duration, long delay, float translationDirection, boolean isHeadsUpAnimation, float endLocation, Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener)193 public long performRemoveAnimation(long duration, long delay, 194 float translationDirection, boolean isHeadsUpAnimation, float endLocation, 195 Runnable onFinishedRunnable, 196 AnimatorListenerAdapter animationListener) { 197 // TODO: Use duration 198 setContentVisible(false); 199 return 0; 200 } 201 202 @Override performAddAnimation(long delay, long duration, boolean isHeadsUpAppear)203 public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear) { 204 // TODO: use delay and duration 205 setContentVisible(true); 206 } 207 208 @Override hasOverlappingRendering()209 public boolean hasOverlappingRendering() { 210 return false; 211 } 212 findContentView()213 protected abstract View findContentView(); 214 215 /** 216 * Returns a view that might not always appear while the main content view is still visible. 217 */ findSecondaryView()218 protected abstract View findSecondaryView(); 219 } 220