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