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