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