• 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.AnimatorListenerAdapter;
20 import android.content.Context;
21 import android.content.res.Configuration;
22 import android.graphics.Paint;
23 import android.graphics.Rect;
24 import android.util.AttributeSet;
25 import android.view.View;
26 import android.view.ViewGroup;
27 import android.widget.FrameLayout;
28 
29 import androidx.annotation.Nullable;
30 
31 import com.android.systemui.Dumpable;
32 import com.android.systemui.R;
33 import com.android.systemui.animation.Interpolators;
34 import com.android.systemui.statusbar.StatusBarIconView;
35 import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
36 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
37 
38 import java.io.FileDescriptor;
39 import java.io.PrintWriter;
40 import java.util.ArrayList;
41 import java.util.List;
42 
43 /**
44  * An abstract view for expandable views.
45  */
46 public abstract class ExpandableView extends FrameLayout implements Dumpable {
47     private static final String TAG = "ExpandableView";
48 
49     protected OnHeightChangedListener mOnHeightChangedListener;
50     private int mActualHeight;
51     protected int mClipTopAmount;
52     protected int mClipBottomAmount;
53     protected int mMinimumHeightForClipping = 0;
54     protected float mExtraWidthForClipping = 0;
55     private ArrayList<View> mMatchParentViews = new ArrayList<View>();
56     private static Rect mClipRect = new Rect();
57     private boolean mWillBeGone;
58     private int mMinClipTopAmount = 0;
59     private boolean mClipToActualHeight = true;
60     private boolean mChangingPosition = false;
61     private ViewGroup mTransientContainer;
62     private boolean mInShelf;
63     private boolean mTransformingInShelf;
64     protected float mContentTransformationAmount;
65     protected boolean mIsLastChild;
66     protected int mContentShift;
67     private final ExpandableViewState mViewState;
68     private float mContentTranslation;
69     protected boolean mLastInSection;
70     protected boolean mFirstInSection;
71 
ExpandableView(Context context, AttributeSet attrs)72     public ExpandableView(Context context, AttributeSet attrs) {
73         super(context, attrs);
74         mViewState = createExpandableViewState();
75         initDimens();
76     }
77 
initDimens()78     private void initDimens() {
79         mContentShift = getResources().getDimensionPixelSize(
80                 R.dimen.shelf_transform_content_shift);
81     }
82 
83     @Override
onConfigurationChanged(Configuration newConfig)84     protected void onConfigurationChanged(Configuration newConfig) {
85         super.onConfigurationChanged(newConfig);
86         initDimens();
87     }
88 
89     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)90     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
91         final int givenHeight = MeasureSpec.getSize(heightMeasureSpec);
92         final int viewHorizontalPadding = getPaddingStart() + getPaddingEnd();
93 
94         // Max height is as large as possible, unless otherwise requested
95         int ownMaxHeight = Integer.MAX_VALUE;
96         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
97         if (heightMode != MeasureSpec.UNSPECIFIED && givenHeight != 0) {
98             // Set our max height to what was requested from the parent
99             ownMaxHeight = Math.min(givenHeight, ownMaxHeight);
100         }
101 
102         // height of the largest child
103         int maxChildHeight = 0;
104         int atMostOwnMaxHeightSpec = MeasureSpec.makeMeasureSpec(ownMaxHeight, MeasureSpec.AT_MOST);
105         int childCount = getChildCount();
106         for (int i = 0; i < childCount; i++) {
107             View child = getChildAt(i);
108             if (child.getVisibility() == GONE) {
109                 continue;
110             }
111             int childHeightSpec = atMostOwnMaxHeightSpec;
112             ViewGroup.LayoutParams layoutParams = child.getLayoutParams();
113             if (layoutParams.height != ViewGroup.LayoutParams.MATCH_PARENT) {
114                 if (layoutParams.height >= 0) {
115                     // If an actual height is set, cap it to the max height
116                     childHeightSpec = MeasureSpec.makeMeasureSpec(
117                             Math.min(layoutParams.height, ownMaxHeight),
118                             MeasureSpec.EXACTLY);
119                 }
120                 child.measure(getChildMeasureSpec(
121                         widthMeasureSpec, viewHorizontalPadding, layoutParams.width),
122                         childHeightSpec);
123                 int childHeight = child.getMeasuredHeight();
124                 maxChildHeight = Math.max(maxChildHeight, childHeight);
125             } else {
126                 mMatchParentViews.add(child);
127             }
128         }
129 
130         // Set our own height to the given height, or the height of the largest child
131         int ownHeight = heightMode == MeasureSpec.EXACTLY
132                 ? givenHeight
133                 : Math.min(ownMaxHeight, maxChildHeight);
134         int exactlyOwnHeightSpec = MeasureSpec.makeMeasureSpec(ownHeight, MeasureSpec.EXACTLY);
135 
136         // Now that we know our own height, measure the children that are MATCH_PARENT
137         for (View child : mMatchParentViews) {
138             child.measure(getChildMeasureSpec(
139                     widthMeasureSpec, viewHorizontalPadding, child.getLayoutParams().width),
140                     exactlyOwnHeightSpec);
141         }
142         mMatchParentViews.clear();
143 
144         // Finish up
145         int width = MeasureSpec.getSize(widthMeasureSpec);
146         setMeasuredDimension(width, ownHeight);
147     }
148 
149     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)150     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
151         super.onLayout(changed, left, top, right, bottom);
152         updateClipping();
153     }
154 
155     @Override
pointInView(float localX, float localY, float slop)156     public boolean pointInView(float localX, float localY, float slop) {
157         float top = mClipTopAmount;
158         float bottom = mActualHeight;
159         return localX >= -slop && localY >= top - slop && localX < ((mRight - mLeft) + slop) &&
160                 localY < (bottom + slop);
161     }
162 
163     /**
164      * @return if this view needs to be clipped to the shelf
165      */
needsClippingToShelf()166     public boolean needsClippingToShelf() {
167         return true;
168     }
169 
170 
isPinned()171     public boolean isPinned() {
172         return false;
173     }
174 
isHeadsUpAnimatingAway()175     public boolean isHeadsUpAnimatingAway() {
176         return false;
177     }
178 
179     /**
180      * Sets the actual height of this notification. This is different than the laid out
181      * {@link View#getHeight()}, as we want to avoid layouting during scrolling and expanding.
182      *
183      * @param actualHeight The height of this notification.
184      * @param notifyListeners Whether the listener should be informed about the change.
185      */
setActualHeight(int actualHeight, boolean notifyListeners)186     public void setActualHeight(int actualHeight, boolean notifyListeners) {
187         mActualHeight = actualHeight;
188         updateClipping();
189         if (notifyListeners) {
190             notifyHeightChanged(false  /* needsAnimation */);
191         }
192     }
193 
setActualHeight(int actualHeight)194     public void setActualHeight(int actualHeight) {
195         setActualHeight(actualHeight, true /* notifyListeners */);
196     }
197 
198     /**
199      * See {@link #setActualHeight}.
200      *
201      * @return The current actual height of this notification.
202      */
getActualHeight()203     public int getActualHeight() {
204         return mActualHeight;
205     }
206 
isExpandAnimationRunning()207     public boolean isExpandAnimationRunning() {
208         return false;
209     }
210 
211     /**
212      * @return The maximum height of this notification.
213      */
getMaxContentHeight()214     public int getMaxContentHeight() {
215         return getHeight();
216     }
217 
218     /**
219      * @return The minimum content height of this notification. This also respects the temporary
220      * states of the view.
221      */
getMinHeight()222     public int getMinHeight() {
223         return getMinHeight(false /* ignoreTemporaryStates */);
224     }
225 
226     /**
227      * Get the minimum height of this view.
228      *
229      * @param ignoreTemporaryStates should temporary states be ignored like the guts or heads-up.
230      *
231      * @return The minimum height that this view needs.
232      */
getMinHeight(boolean ignoreTemporaryStates)233     public int getMinHeight(boolean ignoreTemporaryStates) {
234         return getHeight();
235     }
236 
237     /**
238      * @return The collapsed height of this view. Note that this might be different
239      * than {@link #getMinHeight()} because some elements like groups may have different sizes when
240      * they are system expanded.
241      */
getCollapsedHeight()242     public int getCollapsedHeight() {
243         return getHeight();
244     }
245 
246     /**
247      * Sets the notification as dimmed. The default implementation does nothing.
248      *
249      * @param dimmed Whether the notification should be dimmed.
250      * @param fade Whether an animation should be played to change the state.
251      */
setDimmed(boolean dimmed, boolean fade)252     public void setDimmed(boolean dimmed, boolean fade) {
253     }
254 
isRemoved()255     public boolean isRemoved() {
256         return false;
257     }
258 
259     /**
260      * See {@link #setHideSensitive}. This is a variant which notifies this view in advance about
261      * the upcoming state of hiding sensitive notifications. It gets called at the very beginning
262      * of a stack scroller update such that the updated intrinsic height (which is dependent on
263      * whether private or public layout is showing) gets taken into account into all layout
264      * calculations.
265      */
setHideSensitiveForIntrinsicHeight(boolean hideSensitive)266     public void setHideSensitiveForIntrinsicHeight(boolean hideSensitive) {
267     }
268 
269     /**
270      * Sets whether the notification should hide its private contents if it is sensitive.
271      */
setHideSensitive(boolean hideSensitive, boolean animated, long delay, long duration)272     public void setHideSensitive(boolean hideSensitive, boolean animated, long delay,
273             long duration) {
274     }
275 
276     /**
277      * @return The desired notification height.
278      */
getIntrinsicHeight()279     public int getIntrinsicHeight() {
280         return getHeight();
281     }
282 
283     /**
284      * Sets the amount this view should be clipped from the top. This is used when an expanded
285      * notification is scrolling in the top or bottom stack.
286      *
287      * @param clipTopAmount The amount of pixels this view should be clipped from top.
288      */
setClipTopAmount(int clipTopAmount)289     public void setClipTopAmount(int clipTopAmount) {
290         mClipTopAmount = clipTopAmount;
291         updateClipping();
292     }
293 
294     /**
295      * Set the amount the the notification is clipped on the bottom in addition to the regular
296      * clipping. This is mainly used to clip something in a non-animated way without changing the
297      * actual height of the notification and is purely visual.
298      *
299      * @param clipBottomAmount the amount to clip.
300      */
setClipBottomAmount(int clipBottomAmount)301     public void setClipBottomAmount(int clipBottomAmount) {
302         mClipBottomAmount = clipBottomAmount;
303         updateClipping();
304     }
305 
getClipTopAmount()306     public int getClipTopAmount() {
307         return mClipTopAmount;
308     }
309 
getClipBottomAmount()310     public int getClipBottomAmount() {
311         return mClipBottomAmount;
312     }
313 
setOnHeightChangedListener(OnHeightChangedListener listener)314     public void setOnHeightChangedListener(OnHeightChangedListener listener) {
315         mOnHeightChangedListener = listener;
316     }
317 
318     /**
319      * @return Whether we can expand this views content.
320      */
isContentExpandable()321     public boolean isContentExpandable() {
322         return false;
323     }
324 
notifyHeightChanged(boolean needsAnimation)325     public void notifyHeightChanged(boolean needsAnimation) {
326         if (mOnHeightChangedListener != null) {
327             mOnHeightChangedListener.onHeightChanged(this, needsAnimation);
328         }
329     }
330 
isTransparent()331     public boolean isTransparent() {
332         return false;
333     }
334 
335     /**
336      * Perform a remove animation on this view.
337      * @param duration The duration of the remove animation.
338      * @param delay The delay of the animation
339      * @param translationDirection The direction value from [-1 ... 1] indicating in which the
340      *                             animation should be performed. A value of -1 means that The
341      *                             remove animation should be performed upwards,
342      *                             such that the  child appears to be going away to the top. 1
343      *                             Should mean the opposite.
344      * @param isHeadsUpAnimation Is this a headsUp animation.
345      * @param endLocation The location where the horizonal heads up disappear animation should end.
346      * @param onFinishedRunnable A runnable which should be run when the animation is finished.
347      * @param animationListener An animation listener to add to the animation.
348      *
349      * @return The additional delay, in milliseconds, that this view needs to add before the
350      * animation starts.
351      */
performRemoveAnimation(long duration, long delay, float translationDirection, boolean isHeadsUpAnimation, float endLocation, Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener)352     public abstract long performRemoveAnimation(long duration,
353             long delay, float translationDirection, boolean isHeadsUpAnimation, float endLocation,
354             Runnable onFinishedRunnable,
355             AnimatorListenerAdapter animationListener);
356 
performAddAnimation(long delay, long duration, boolean isHeadsUpAppear)357     public abstract void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear);
358 
359     /**
360      * Set the notification appearance to be below the speed bump.
361      * @param below true if it is below.
362      */
setBelowSpeedBump(boolean below)363     public void setBelowSpeedBump(boolean below) {
364     }
365 
getPinnedHeadsUpHeight()366     public int getPinnedHeadsUpHeight() {
367         return getIntrinsicHeight();
368     }
369 
370 
371     /**
372      * Sets the translation of the view.
373      */
setTranslation(float translation)374     public void setTranslation(float translation) {
375         setTranslationX(translation);
376     }
377 
378     /**
379      * Gets the translation of the view.
380      */
getTranslation()381     public float getTranslation() {
382         return getTranslationX();
383     }
384 
onHeightReset()385     public void onHeightReset() {
386         if (mOnHeightChangedListener != null) {
387             mOnHeightChangedListener.onReset(this);
388         }
389     }
390 
391     /**
392      * This method returns the drawing rect for the view which is different from the regular
393      * drawing rect, since we layout all children in the {@link NotificationStackScrollLayout} at
394      * position 0 and usually the translation is neglected. Since we are manually clipping this
395      * view,we also need to subtract the clipTopAmount from the top. This is needed in order to
396      * ensure that accessibility and focusing work correctly.
397      *
398      * @param outRect The (scrolled) drawing bounds of the view.
399      */
400     @Override
getDrawingRect(Rect outRect)401     public void getDrawingRect(Rect outRect) {
402         super.getDrawingRect(outRect);
403         outRect.left += getTranslationX();
404         outRect.right += getTranslationX();
405         outRect.bottom = (int) (outRect.top + getTranslationY() + getActualHeight());
406         outRect.top += getTranslationY() + getClipTopAmount();
407     }
408 
409     @Override
getBoundsOnScreen(Rect outRect, boolean clipToParent)410     public void getBoundsOnScreen(Rect outRect, boolean clipToParent) {
411         super.getBoundsOnScreen(outRect, clipToParent);
412         if (getTop() + getTranslationY() < 0) {
413             // We got clipped to the parent here - make sure we undo that.
414             outRect.top += getTop() + getTranslationY();
415         }
416         outRect.bottom = outRect.top + getActualHeight();
417         outRect.top += getClipTopAmount();
418     }
419 
isSummaryWithChildren()420     public boolean isSummaryWithChildren() {
421         return false;
422     }
423 
areChildrenExpanded()424     public boolean areChildrenExpanded() {
425         return false;
426     }
427 
updateClipping()428     protected void updateClipping() {
429         if (mClipToActualHeight && shouldClipToActualHeight()) {
430             int top = getClipTopAmount();
431             int bottom = Math.max(Math.max(getActualHeight() + getExtraBottomPadding()
432                     - mClipBottomAmount, top), mMinimumHeightForClipping);
433             int halfExtraWidth = (int) (mExtraWidthForClipping / 2.0f);
434             mClipRect.set(-halfExtraWidth, top, getWidth() + halfExtraWidth, bottom);
435             setClipBounds(mClipRect);
436         } else {
437             setClipBounds(null);
438         }
439     }
440 
setMinimumHeightForClipping(int minimumHeightForClipping)441     public void setMinimumHeightForClipping(int minimumHeightForClipping) {
442         mMinimumHeightForClipping = minimumHeightForClipping;
443         updateClipping();
444     }
445 
setExtraWidthForClipping(float extraWidthForClipping)446     public void setExtraWidthForClipping(float extraWidthForClipping) {
447         mExtraWidthForClipping = extraWidthForClipping;
448         updateClipping();
449     }
450 
getHeaderVisibleAmount()451     public float getHeaderVisibleAmount() {
452         return 1.0f;
453     }
454 
shouldClipToActualHeight()455     protected boolean shouldClipToActualHeight() {
456         return true;
457     }
458 
setClipToActualHeight(boolean clipToActualHeight)459     public void setClipToActualHeight(boolean clipToActualHeight) {
460         mClipToActualHeight = clipToActualHeight;
461         updateClipping();
462     }
463 
willBeGone()464     public boolean willBeGone() {
465         return mWillBeGone;
466     }
467 
setWillBeGone(boolean willBeGone)468     public void setWillBeGone(boolean willBeGone) {
469         mWillBeGone = willBeGone;
470     }
471 
getMinClipTopAmount()472     public int getMinClipTopAmount() {
473         return mMinClipTopAmount;
474     }
475 
setMinClipTopAmount(int minClipTopAmount)476     public void setMinClipTopAmount(int minClipTopAmount) {
477         mMinClipTopAmount = minClipTopAmount;
478     }
479 
480     @Override
setLayerType(int layerType, Paint paint)481     public void setLayerType(int layerType, Paint paint) {
482         // Allow resetting the layerType to NONE regardless of overlappingRendering
483         if (layerType == LAYER_TYPE_NONE || hasOverlappingRendering()) {
484             super.setLayerType(layerType, paint);
485         }
486     }
487 
488     @Override
hasOverlappingRendering()489     public boolean hasOverlappingRendering() {
490         // Otherwise it will be clipped
491         return super.hasOverlappingRendering() && getActualHeight() <= getHeight();
492     }
493 
mustStayOnScreen()494     public boolean mustStayOnScreen() {
495         return false;
496     }
497 
setFakeShadowIntensity(float shadowIntensity, float outlineAlpha, int shadowYEnd, int outlineTranslation)498     public void setFakeShadowIntensity(float shadowIntensity, float outlineAlpha, int shadowYEnd,
499             int outlineTranslation) {
500     }
501 
getOutlineAlpha()502     public float getOutlineAlpha() {
503         return 0.0f;
504     }
505 
getOutlineTranslation()506     public int getOutlineTranslation() {
507         return 0;
508     }
509 
setChangingPosition(boolean changingPosition)510     public void setChangingPosition(boolean changingPosition) {
511         mChangingPosition = changingPosition;
512     }
513 
isChangingPosition()514     public boolean isChangingPosition() {
515         return mChangingPosition;
516     }
517 
setTransientContainer(ViewGroup transientContainer)518     public void setTransientContainer(ViewGroup transientContainer) {
519         mTransientContainer = transientContainer;
520     }
521 
getTransientContainer()522     public ViewGroup getTransientContainer() {
523         return mTransientContainer;
524     }
525 
526     /**
527      * @return padding used to alter how much of the view is clipped.
528      */
getExtraBottomPadding()529     public int getExtraBottomPadding() {
530         return 0;
531     }
532 
533     /**
534      * @return true if the group's expansion state is changing, false otherwise.
535      */
isGroupExpansionChanging()536     public boolean isGroupExpansionChanging() {
537         return false;
538     }
539 
isGroupExpanded()540     public boolean isGroupExpanded() {
541         return false;
542     }
543 
setHeadsUpIsVisible()544     public void setHeadsUpIsVisible() {
545     }
546 
showingPulsing()547     public boolean showingPulsing() {
548         return false;
549     }
550 
isChildInGroup()551     public boolean isChildInGroup() {
552         return false;
553     }
554 
setActualHeightAnimating(boolean animating)555     public void setActualHeightAnimating(boolean animating) {}
556 
createExpandableViewState()557     protected ExpandableViewState createExpandableViewState() {
558         return new ExpandableViewState();
559     }
560 
561     /** Sets {@link ExpandableViewState} to default state. */
resetViewState()562     public ExpandableViewState resetViewState() {
563         // initialize with the default values of the view
564         mViewState.height = getIntrinsicHeight();
565         mViewState.gone = getVisibility() == View.GONE;
566         mViewState.alpha = 1f;
567         mViewState.notGoneIndex = -1;
568         mViewState.xTranslation = getTranslationX();
569         mViewState.hidden = false;
570         mViewState.scaleX = getScaleX();
571         mViewState.scaleY = getScaleY();
572         mViewState.inShelf = false;
573         mViewState.headsUpIsVisible = false;
574 
575         // handling reset for child notifications
576         if (this instanceof ExpandableNotificationRow) {
577             ExpandableNotificationRow row = (ExpandableNotificationRow) this;
578             List<ExpandableNotificationRow> children = row.getAttachedChildren();
579             if (row.isSummaryWithChildren() && children != null) {
580                 for (ExpandableNotificationRow childRow : children) {
581                     childRow.resetViewState();
582                 }
583             }
584         }
585 
586         return mViewState;
587     }
588 
getViewState()589     @Nullable public ExpandableViewState getViewState() {
590         return mViewState;
591     }
592 
593     /** Applies internal {@link ExpandableViewState} to this view. */
applyViewState()594     public void applyViewState() {
595         if (!mViewState.gone) {
596             mViewState.applyToView(this);
597         }
598     }
599 
600     /**
601      * @return whether the current view doesn't add height to the overall content. This means that
602      * if it is added to a list of items, its content will still have the same height.
603      * An example is the notification shelf, that is always placed on top of another view.
604      */
hasNoContentHeight()605     public boolean hasNoContentHeight() {
606         return false;
607     }
608 
609     /**
610      * @param inShelf whether the view is currently fully in the notification shelf.
611      */
setInShelf(boolean inShelf)612     public void setInShelf(boolean inShelf) {
613         mInShelf = inShelf;
614     }
615 
isInShelf()616     public boolean isInShelf() {
617         return mInShelf;
618     }
619 
getShelfIcon()620     public @Nullable StatusBarIconView getShelfIcon() {
621         return null;
622     }
623 
624     /**
625      * @return get the transformation target of the shelf, which usually is the icon
626      */
getShelfTransformationTarget()627     public View getShelfTransformationTarget() {
628         return null;
629     }
630 
631     /**
632      * Get the relative top padding of a view relative to this view. This recursively walks up the
633      * hierarchy and does the corresponding measuring.
634      *
635      * @param view the view to get the padding for. The requested view has to be a child of this
636      *             notification.
637      * @return the toppadding
638      */
getRelativeTopPadding(View view)639     public int getRelativeTopPadding(View view) {
640         int topPadding = 0;
641         while (view.getParent() instanceof ViewGroup) {
642             topPadding += view.getTop();
643             view = (View) view.getParent();
644             if (view == this) {
645                 return topPadding;
646             }
647         }
648         return topPadding;
649     }
650 
651 
652     /**
653      * Get the relative start padding of a view relative to this view. This recursively walks up the
654      * hierarchy and does the corresponding measuring.
655      *
656      * @param view the view to get the padding for. The requested view has to be a child of this
657      *             notification.
658      * @return the start padding
659      */
getRelativeStartPadding(View view)660     public int getRelativeStartPadding(View view) {
661         boolean isRtl = isLayoutRtl();
662         int startPadding = 0;
663         while (view.getParent() instanceof ViewGroup) {
664             View parent = (View) view.getParent();
665             startPadding += isRtl ? parent.getWidth() - view.getRight() : view.getLeft();
666             view = parent;
667             if (view == this) {
668                 return startPadding;
669             }
670         }
671         return startPadding;
672     }
673 
674     /**
675      * Set how much this notification is transformed into the shelf.
676      *
677      * @param contentTransformationAmount A value from 0 to 1 indicating how much we are transformed
678      *                                 to the content away
679      * @param isLastChild is this the last child in the list. If true, then the transformation is
680      *                    different since its content fades out.
681      */
setContentTransformationAmount(float contentTransformationAmount, boolean isLastChild)682     public void setContentTransformationAmount(float contentTransformationAmount,
683             boolean isLastChild) {
684         boolean changeTransformation = isLastChild != mIsLastChild;
685         changeTransformation |= mContentTransformationAmount != contentTransformationAmount;
686         mIsLastChild = isLastChild;
687         mContentTransformationAmount = contentTransformationAmount;
688         if (changeTransformation) {
689             updateContentTransformation();
690         }
691     }
692 
693     /**
694      * Update the content representation based on the amount we are transformed into the shelf.
695      */
updateContentTransformation()696     protected void updateContentTransformation() {
697         float translationY = -mContentTransformationAmount * getContentTransformationShift();
698         float contentAlpha = 1.0f - mContentTransformationAmount;
699         contentAlpha = Math.min(contentAlpha / 0.5f, 1.0f);
700         contentAlpha = Interpolators.ALPHA_OUT.getInterpolation(contentAlpha);
701         if (mIsLastChild) {
702             translationY *= 0.4f;
703         }
704         mContentTranslation = translationY;
705         applyContentTransformation(contentAlpha, translationY);
706     }
707 
708     /**
709      * @return how much the content shifts up when going into the shelf
710      */
getContentTransformationShift()711     protected float getContentTransformationShift() {
712         return mContentShift;
713     }
714 
715     /**
716      * Apply the contentTransformation when going into the shelf.
717      *
718      * @param contentAlpha The alpha that should be applied
719      * @param translationY the translationY that should be applied
720      */
applyContentTransformation(float contentAlpha, float translationY)721     protected void applyContentTransformation(float contentAlpha, float translationY) {
722     }
723 
724     /**
725      * @param transformingInShelf whether the view is currently transforming into the shelf in an
726      *                            animated way
727      */
setTransformingInShelf(boolean transformingInShelf)728     public void setTransformingInShelf(boolean transformingInShelf) {
729         mTransformingInShelf = transformingInShelf;
730     }
731 
isTransformingIntoShelf()732     public boolean isTransformingIntoShelf() {
733         return mTransformingInShelf;
734     }
735 
isAboveShelf()736     public boolean isAboveShelf() {
737         return false;
738     }
739 
hasExpandingChild()740     public boolean hasExpandingChild() {
741         return false;
742     }
743 
744     @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)745     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
746     }
747 
748     /**
749      * return the amount that the content is translated
750      */
getContentTranslation()751     public float getContentTranslation() {
752         return mContentTranslation;
753     }
754 
755     /** Sets whether this view is the first notification in a section. */
setFirstInSection(boolean firstInSection)756     public void setFirstInSection(boolean firstInSection) {
757         mFirstInSection = firstInSection;
758     }
759 
760     /** Sets whether this view is the last notification in a section. */
setLastInSection(boolean lastInSection)761     public void setLastInSection(boolean lastInSection) {
762         mLastInSection = lastInSection;
763     }
764 
isLastInSection()765     public boolean isLastInSection() {
766         return mLastInSection;
767     }
768 
isFirstInSection()769     public boolean isFirstInSection() {
770         return mFirstInSection;
771     }
772 
773     /**
774      * Set the topRoundness of this view.
775      * @return Whether the roundness was changed.
776      */
setTopRoundness(float topRoundness, boolean animate)777     public boolean setTopRoundness(float topRoundness, boolean animate) {
778         return false;
779     }
780 
781     /**
782      * Set the bottom roundness of this view.
783      * @return Whether the roundness was changed.
784      */
setBottomRoundness(float bottomRoundness, boolean animate)785     public boolean setBottomRoundness(float bottomRoundness, boolean animate) {
786         return false;
787     }
788 
getHeadsUpHeightWithoutHeader()789     public int getHeadsUpHeightWithoutHeader() {
790         return getHeight();
791     }
792 
793     /**
794      * A listener notifying when {@link #getActualHeight} changes.
795      */
796     public interface OnHeightChangedListener {
797 
798         /**
799          * @param view the view for which the height changed, or {@code null} if just the top
800          *             padding or the padding between the elements changed
801          * @param needsAnimation whether the view height needs to be animated
802          */
onHeightChanged(ExpandableView view, boolean needsAnimation)803         void onHeightChanged(ExpandableView view, boolean needsAnimation);
804 
805         /**
806          * Called when the view is reset and therefore the height will change abruptly
807          *
808          * @param view The view which was reset.
809          */
onReset(ExpandableView view)810         void onReset(ExpandableView view);
811     }
812 }
813