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