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