From 49014f85220eb064f7cb9326ab84117c53c8e43b Mon Sep 17 00:00:00 2001 From: Selim Cinek Date: Fri, 4 Nov 2016 14:55:30 -0700 Subject: [PATCH] Implemented a nicer transition when the icons overflow The overflowing icons are now represented as dots and animate in and out nicer. The shelf also animates much nicer from the regular statusbar size if there are a lot of notifications. Test: Add a lot of notifications, observe them nicely overflowing into dots Bug: 32437839 Change-Id: I5906c076bbf5d48cbabdbacfd21234bed55c6caa --- .../layout/status_bar_notification_shelf.xml | 4 +- packages/SystemUI/res/values/dimens.xml | 6 + .../systemui/statusbar/NotificationShelf.java | 85 +++++---- .../systemui/statusbar/StatusBarIconView.java | 162 +++++++++++++++++- .../phone/NotificationIconAreaController.java | 15 +- .../phone/NotificationIconContainer.java | 152 ++++++++++++++-- 6 files changed, 367 insertions(+), 57 deletions(-) diff --git a/packages/SystemUI/res/layout/status_bar_notification_shelf.xml b/packages/SystemUI/res/layout/status_bar_notification_shelf.xml index fde61ebc77b9a..088deba807f55 100644 --- a/packages/SystemUI/res/layout/status_bar_notification_shelf.xml +++ b/packages/SystemUI/res/layout/status_bar_notification_shelf.xml @@ -35,8 +35,8 @@ android:id="@+id/content" android:layout_width="match_parent" android:layout_height="match_parent" - android:layout_marginStart="13dp" - android:layout_marginEnd="13dp" + android:paddingStart="13dp" + android:paddingEnd="13dp" android:gravity="center_vertical" /> 6dp + + 1dp + + + 3dp + 20dp diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java index 9a1e0649c805f..de88e6650245f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java @@ -48,7 +48,7 @@ public class NotificationShelf extends ActivatableNotificationView { private ViewInvertHelper mViewInvertHelper; private boolean mDark; - private NotificationIconContainer mNotificationIconContainer; + private NotificationIconContainer mShelfIcons; private ArrayList mIcons = new ArrayList<>(); private ShelfState mShelfState; private int[] mTmp = new int[2]; @@ -62,6 +62,7 @@ public class NotificationShelf extends ActivatableNotificationView { private int mPaddingBetweenElements; private int mNotGoneIndex; private boolean mHasItemsInStableShelf; + private NotificationIconContainer mCollapsedIcons; public NotificationShelf(Context context, AttributeSet attrs) { super(context, attrs); @@ -70,14 +71,15 @@ public class NotificationShelf extends ActivatableNotificationView { @Override protected void onFinishInflate() { super.onFinishInflate(); - mNotificationIconContainer = (NotificationIconContainer) findViewById(R.id.content); - mNotificationIconContainer.setClipChildren(false); - mNotificationIconContainer.setClipToPadding(false); + mShelfIcons = (NotificationIconContainer) findViewById(R.id.content); + mShelfIcons.setClipChildren(false); + mShelfIcons.setClipToPadding(false); + setClipToActualHeight(false); setClipChildren(false); setClipToPadding(false); - mNotificationIconContainer.setShowAllIcons(false); - mViewInvertHelper = new ViewInvertHelper(mNotificationIconContainer, + mShelfIcons.setShowAllIcons(false); + mViewInvertHelper = new ViewInvertHelper(mShelfIcons, NotificationPanelView.DOZE_ANIMATION_DURATION); mShelfState = new ShelfState(); initDimens(); @@ -118,11 +120,11 @@ public class NotificationShelf extends ActivatableNotificationView { @Override protected View getContentView() { - return mNotificationIconContainer; + return mShelfIcons; } - public NotificationIconContainer getNotificationIconContainer() { - return mNotificationIconContainer; + public NotificationIconContainer getShelfIcons() { + return mShelfIcons; } @Override @@ -142,13 +144,13 @@ public class NotificationShelf extends ActivatableNotificationView { float viewEnd = lastViewState.yTranslation + lastViewState.height; mShelfState.copyFrom(lastViewState); mShelfState.height = getIntrinsicHeight(); - mShelfState.yTranslation = Math.min(viewEnd, maxShelfEnd) - mShelfState.height; + mShelfState.yTranslation = Math.max(Math.min(viewEnd, maxShelfEnd) - mShelfState.height, + getFullyClosedTranslation()); mShelfState.zTranslation = ambientState.getBaseZHeight(); float openedAmount = (mShelfState.yTranslation - getFullyClosedTranslation()) / (getIntrinsicHeight() * 2); openedAmount = Math.min(1.0f, openedAmount); - mShelfState.iconContainerTranslation = (1.0f - openedAmount) - * (mStatusBarPaddingStart - mNotificationIconContainer.getLeft()); + mShelfState.openedAmount = openedAmount; mShelfState.clipTopAmount = 0; mShelfState.alpha = 1.0f; mShelfState.belowShelf = false; @@ -172,10 +174,16 @@ public class NotificationShelf extends ActivatableNotificationView { */ public void updateAppearance() { WeakHashMap iconStates = - mNotificationIconContainer.resetViewStates(); + mShelfIcons.resetViewStates(); float numIconsInShelf = 0.0f; int shelfIndex = mAmbientState.getShelfIndex(); mNotGoneIndex = -1; + float interpolationStart = mMaxLayoutHeight - getIntrinsicHeight() * 2; + float expandAmount = 0.0f; + if (getTranslationY() >= interpolationStart) { + expandAmount = (getTranslationY() - interpolationStart) / getIntrinsicHeight(); + expandAmount = Math.min(1.0f, expandAmount); + } // find the first view that doesn't overlap with the shelf int notificationIndex = 0; int notGoneNotifications = 0; @@ -205,7 +213,7 @@ public class NotificationShelf extends ActivatableNotificationView { } } updateNotificationClipHeight(row, notificationClipEnd); - updateIconAppearance(row, iconState, icon); + updateIconAppearance(row, iconState, icon, expandAmount); numIconsInShelf += iconState.iconAppearAmount; if (row.getTranslationY() >= getTranslationY() && mNotGoneIndex == -1) { mNotGoneIndex = notGoneNotifications; @@ -228,8 +236,8 @@ public class NotificationShelf extends ActivatableNotificationView { } notificationIndex++; } - mNotificationIconContainer.calculateIconTranslations(); - mNotificationIconContainer.applyIconStates(); + mShelfIcons.calculateIconTranslations(); + mShelfIcons.applyIconStates(); setVisibility(numIconsInShelf == 0.0f || !mAmbientState.isShadeExpanded() ? INVISIBLE : VISIBLE); setHideBackground(numIconsInShelf < 1.0f); @@ -247,8 +255,8 @@ public class NotificationShelf extends ActivatableNotificationView { } } - private void updateIconAppearance(ExpandableNotificationRow row, - IconState iconState, StatusBarIconView icon) { + private void updateIconAppearance(ExpandableNotificationRow row, IconState iconState, + StatusBarIconView icon, float expandAmount) { // Let calculate how much the view is in the shelf float viewStart = row.getTranslationY(); int transformHeight = row.getActualHeight() + mPaddingBetweenElements; @@ -259,12 +267,6 @@ public class NotificationShelf extends ActivatableNotificationView { float linearAmount = (getTranslationY() - viewStart) / transformHeight; float interpolatedAmount = Interpolators.ACCELERATE_DECELERATE.getInterpolation( linearAmount); - float interpolationStart = mMaxLayoutHeight - getIntrinsicHeight() * 2; - float expandAmount = 0.0f; - if (getTranslationY() >= interpolationStart) { - expandAmount = (getTranslationY() - interpolationStart) / getIntrinsicHeight(); - expandAmount = Math.min(1.0f, expandAmount); - } interpolatedAmount = NotificationUtils.interpolate( interpolatedAmount, linearAmount, expandAmount); iconState.iconAppearAmount = 1.0f - interpolatedAmount; @@ -318,7 +320,7 @@ public class NotificationShelf extends ActivatableNotificationView { // The notification size is different from the size in the shelf / statusbar float newSize = NotificationUtils.interpolate(notificationIconSize, shelfIconSize, transitionAmount); - iconState.scaleX = newSize / icon.getHeight(); + iconState.scaleX = newSize / icon.getHeight() / icon.getIconScale(); iconState.scaleY = iconState.scaleX; iconState.hidden = transitionAmount == 0.0f; row.setIconTransformationAmount(transitionAmount); @@ -326,8 +328,8 @@ public class NotificationShelf extends ActivatableNotificationView { if (row.isInShelf() && !row.isTransformingIntoShelf()) { iconState.iconAppearAmount = 1.0f; iconState.alpha = 1.0f; - iconState.scaleX = shelfIconSize / icon.getHeight(); - iconState.scaleY = iconState.scaleX; + iconState.scaleX = 1.0f; + iconState.scaleY = 1.0f; iconState.hidden = false; } } @@ -378,8 +380,23 @@ public class NotificationShelf extends ActivatableNotificationView { return super.shouldHideBackground() || mHideBackground; } - private void setIconContainerTranslation(float iconContainerTranslation) { - mNotificationIconContainer.setTranslationX(iconContainerTranslation); + private void setOpenedAmount(float openedAmount) { + mCollapsedIcons.getLocationOnScreen(mTmp); + int start = mTmp[0]; + if (isLayoutRtl()) { + start = getWidth() - start - mCollapsedIcons.getWidth(); + } + int width = (int) NotificationUtils.interpolate(start + mCollapsedIcons.getWidth(), + mShelfIcons.getWidth(), + openedAmount); + mShelfIcons.setActualLayoutWidth(width); + float padding = NotificationUtils.interpolate(mCollapsedIcons.getPaddingEnd(), + mShelfIcons.getPaddingEnd(), + openedAmount); + mShelfIcons.setActualPaddingEnd(padding); + float paddingStart = NotificationUtils.interpolate(start, + mShelfIcons.getPaddingStart(), openedAmount); + mShelfIcons.setActualPaddingStart(paddingStart); } public void setMaxLayoutHeight(int maxLayoutHeight) { @@ -405,23 +422,27 @@ public class NotificationShelf extends ActivatableNotificationView { return mHasItemsInStableShelf; } + public void setCollapsedIcons(NotificationIconContainer collapsedIcons) { + mCollapsedIcons = collapsedIcons; + } + private class ShelfState extends ExpandableViewState { - private float iconContainerTranslation; + private float openedAmount; private boolean hasItemsInStableShelf; @Override public void applyToView(View view) { super.applyToView(view); updateAppearance(); - setIconContainerTranslation(iconContainerTranslation); + setOpenedAmount(openedAmount); setHasItemsInStableShelf(hasItemsInStableShelf); } @Override public void animateTo(View child, AnimationProperties properties) { super.animateTo(child, properties); + setOpenedAmount(openedAmount); updateAppearance(); - setIconContainerTranslation(iconContainerTranslation); setHasItemsInStableShelf(hasItemsInStableShelf); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java index 03e366283b4ed..543f899283d4b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java @@ -16,6 +16,10 @@ package com.android.systemui.statusbar; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; import android.app.Notification; import android.content.Context; import android.content.pm.ApplicationInfo; @@ -30,20 +34,55 @@ import android.os.Parcelable; import android.os.UserHandle; import android.text.TextUtils; import android.util.AttributeSet; +import android.util.FloatProperty; import android.util.Log; +import android.util.Property; import android.util.TypedValue; import android.view.ViewDebug; import android.view.accessibility.AccessibilityEvent; +import android.view.animation.Interpolator; import com.android.internal.statusbar.StatusBarIcon; +import com.android.systemui.Interpolators; import com.android.systemui.R; +import com.android.systemui.statusbar.notification.NotificationUtils; import java.text.NumberFormat; public class StatusBarIconView extends AnimatedImageView { - private static final String TAG = "StatusBarIconView"; - private boolean mAlwaysScaleIcon; + public static final int STATE_ICON = 0; + public static final int STATE_DOT = 1; + public static final int STATE_HIDDEN = 2; + private static final String TAG = "StatusBarIconView"; + private static final Property ICON_APPEAR_AMOUNT + = new FloatProperty("iconAppearAmount") { + + @Override + public void setValue(StatusBarIconView object, float value) { + object.setIconAppearAmount(value); + } + + @Override + public Float get(StatusBarIconView object) { + return object.getIconAppearAmount(); + } + }; + private static final Property DOT_APPEAR_AMOUNG + = new FloatProperty("dot_appear_amount") { + + @Override + public void setValue(StatusBarIconView object, float value) { + object.setDotAppearAmount(value); + } + + @Override + public Float get(StatusBarIconView object) { + return object.getDotAppearAmount(); + } + }; + + private boolean mAlwaysScaleIcon; private StatusBarIcon mIcon; @ViewDebug.ExportedProperty private String mSlot; private Drawable mNumberBackground; @@ -55,6 +94,15 @@ public class StatusBarIconView extends AnimatedImageView { private final boolean mBlocked; private int mDensity; private float mIconScale = 1.0f; + private final Paint mDotPaint = new Paint(); + private boolean mDotVisible; + private float mDotRadius; + private int mStaticDotRadius; + private int mVisibleState = STATE_ICON; + private float mIconAppearAmount = 1.0f; + private ObjectAnimator mIconAppearAnimator; + private ObjectAnimator mDotAnimator; + private float mDotAppearAmount; public StatusBarIconView(Context context, String slot, Notification notification) { this(context, slot, notification, false); @@ -73,6 +121,11 @@ public class StatusBarIconView extends AnimatedImageView { maybeUpdateIconScale(); setScaleType(ScaleType.CENTER); mDensity = context.getResources().getDisplayMetrics().densityDpi; + if (mNotification != null) { + setIconTint(getContext().getColor( + com.android.internal.R.color.notification_icon_default_color)); + } + reloadDimens(); } private void maybeUpdateIconScale() { @@ -88,8 +141,6 @@ public class StatusBarIconView extends AnimatedImageView { final int outerBounds = res.getDimensionPixelSize(R.dimen.status_bar_icon_size); final int imageBounds = res.getDimensionPixelSize(R.dimen.status_bar_icon_drawing_size); mIconScale = (float)imageBounds / (float)outerBounds; - setScaleX(mIconScale); - setScaleY(mIconScale); } public float getIconScale() { @@ -104,6 +155,15 @@ public class StatusBarIconView extends AnimatedImageView { mDensity = density; maybeUpdateIconScale(); updateDrawable(); + reloadDimens(); + } + } + + private void reloadDimens() { + boolean applyRadius = mDotRadius == mStaticDotRadius; + mStaticDotRadius = getResources().getDimensionPixelSize(R.dimen.overflow_dot_radius); + if (applyRadius) { + mDotRadius = mStaticDotRadius; } } @@ -264,12 +324,32 @@ public class StatusBarIconView extends AnimatedImageView { @Override protected void onDraw(Canvas canvas) { - super.onDraw(canvas); + if (mIconAppearAmount > 0.0f) { + canvas.save(); + canvas.scale(mIconScale * mIconAppearAmount, mIconScale * mIconAppearAmount, + getWidth() / 2, getHeight() / 2); + super.onDraw(canvas); + canvas.restore(); + } if (mNumberBackground != null) { mNumberBackground.draw(canvas); canvas.drawText(mNumberText, mNumberX, mNumberY, mNumberPain); } + if (mDotAppearAmount != 0.0f) { + float radius; + float alpha; + if (mDotAppearAmount <= 1.0f) { + radius = mDotRadius * mDotAppearAmount; + alpha = 1.0f; + } else { + float fadeOutAmount = mDotAppearAmount - 1.0f; + alpha = 1.0f - fadeOutAmount; + radius = NotificationUtils.interpolate(mDotRadius, getWidth() / 4, fadeOutAmount); + } + mDotPaint.setAlpha((int) (alpha * 255)); + canvas.drawCircle(getWidth() / 2, getHeight() / 2, radius, mDotPaint); + } } @Override @@ -356,4 +436,76 @@ public class StatusBarIconView extends AnimatedImageView { return c.getString(R.string.accessibility_desc_notification_icon, appName, desc); } + public void setIconTint(int iconTint) { + mDotPaint.setColor(iconTint); + } + + public void setVisibleState(int visibleState) { + if (visibleState != mVisibleState) { + mVisibleState = visibleState; + if (mIconAppearAnimator != null) { + mIconAppearAnimator.cancel(); + } + float targetAmount = 0.0f; + Interpolator interpolator = Interpolators.FAST_OUT_LINEAR_IN; + if (visibleState == STATE_ICON) { + targetAmount = 1.0f; + interpolator = Interpolators.LINEAR_OUT_SLOW_IN; + } + mIconAppearAnimator = ObjectAnimator.ofFloat(this, ICON_APPEAR_AMOUNT, + targetAmount); + mIconAppearAnimator.setInterpolator(interpolator); + mIconAppearAnimator.setDuration(100); + mIconAppearAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mIconAppearAnimator = null; + } + }); + mIconAppearAnimator.start(); + + if (mDotAnimator != null) { + mDotAnimator.cancel(); + } + targetAmount = visibleState == STATE_ICON ? 2.0f : 0.0f; + interpolator = Interpolators.FAST_OUT_LINEAR_IN; + if (visibleState == STATE_DOT) { + targetAmount = 1.0f; + interpolator = Interpolators.LINEAR_OUT_SLOW_IN; + } + mDotAnimator = ObjectAnimator.ofFloat(this, DOT_APPEAR_AMOUNG, + targetAmount); + mDotAnimator.setInterpolator(interpolator); + mDotAnimator.setDuration(100); + mDotAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mDotAnimator = null; + } + }); + mDotAnimator.start(); + } + } + + public void setIconAppearAmount(Float iconAppearAmount) { + mIconAppearAmount = iconAppearAmount; + invalidate(); + } + + public float getIconAppearAmount() { + return mIconAppearAmount; + } + + public int getVisibleState() { + return mVisibleState; + } + + public void setDotAppearAmount(float dotAppearAmount) { + mDotAppearAmount = dotAppearAmount; + invalidate(); + } + + public float getDotAppearAmount() { + return mDotAppearAmount; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java index 72e84a464fd28..7b67f31b7a4a5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java @@ -8,7 +8,6 @@ import android.graphics.Rect; import android.support.annotation.NonNull; import android.view.LayoutInflater; import android.view.View; -import android.view.ViewGroup; import android.widget.LinearLayout; import com.android.internal.util.NotificationColorUtil; @@ -36,7 +35,6 @@ public class NotificationIconAreaController { private PhoneStatusBar mPhoneStatusBar; protected View mNotificationIconArea; - private NotificationShelf mNotificationIconAreaScroller; private NotificationIconContainer mNotificationIcons; private NotificationIconContainer mNotificationIconsScroller; private final Rect mTintArea = new Rect(); @@ -66,8 +64,9 @@ public class NotificationIconAreaController { mNotificationIcons = (NotificationIconContainer) mNotificationIconArea.findViewById( R.id.notificationIcons); - mNotificationIconAreaScroller = mPhoneStatusBar.getNotificationShelf(); - mNotificationIconsScroller = mNotificationIconAreaScroller.getNotificationIconContainer(); + NotificationShelf shelf = mPhoneStatusBar.getNotificationShelf(); + mNotificationIconsScroller = shelf.getShelfIcons(); + shelf.setCollapsedIcons(mNotificationIcons); mNotificationScrollLayout = mPhoneStatusBar.getNotificationScrollLayout(); } @@ -115,12 +114,10 @@ public class NotificationIconAreaController { } /** - * Sets the color that should be used to tint any icons in the notification area. If this - * method is not called, the default tint is {@link Color#WHITE}. + * Sets the color that should be used to tint any icons in the notification area. */ public void setIconTint(int iconTint) { mIconTint = iconTint; - mNotificationIcons.setIconTint(mIconTint); applyNotificationIconsTint(); } @@ -178,7 +175,7 @@ public class NotificationIconAreaController { */ private void updateIconsForLayout(NotificationData notificationData, Function function, - ViewGroup hostLayout) { + NotificationIconContainer hostLayout) { ArrayList toShow = new ArrayList<>( mNotificationScrollLayout.getChildCount()); @@ -193,6 +190,7 @@ public class NotificationIconAreaController { } } + ArrayList toRemove = new ArrayList<>(); for (int i = 0; i < hostLayout.getChildCount(); i++) { View child = hostLayout.getChildAt(i); @@ -239,6 +237,7 @@ public class NotificationIconAreaController { v.setImageTintList(ColorStateList.valueOf( StatusBarIconController.getTint(mTintArea, v, mIconTint))); } + v.setIconTint(mIconTint); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java index 11563d5945cd6..0f41e31d9b366 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java @@ -17,10 +17,18 @@ package com.android.systemui.statusbar.phone; import android.content.Context; +import android.content.res.Configuration; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; import android.util.AttributeSet; import android.view.View; +import com.android.systemui.R; import com.android.systemui.statusbar.AlphaOptimizedFrameLayout; +import com.android.systemui.statusbar.StatusBarIconView; +import com.android.systemui.statusbar.stack.AnimationFilter; +import com.android.systemui.statusbar.stack.AnimationProperties; import com.android.systemui.statusbar.stack.ViewState; import java.util.WeakHashMap; @@ -31,15 +39,48 @@ import java.util.WeakHashMap; */ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { private static final String TAG = "NotificationIconContainer"; + private static final boolean DEBUG = false; + private static final AnimationFilter DOT_ANIMATION_FILTER = new AnimationFilter().animateX(); + private static final AnimationProperties DOT_ANIMATION_PROPERTIES = new AnimationProperties() { + @Override + public AnimationFilter getAnimationFilter() { + return DOT_ANIMATION_FILTER; + } + }.setDuration(200); private boolean mShowAllIcons = true; - private int mIconTint; private WeakHashMap mIconStates = new WeakHashMap<>(); + private int mDotPadding; + private int mStaticDotRadius; + private int mActualLayoutWidth = -1; + private float mActualPaddingEnd = -1; + private float mActualPaddingStart = -1; public NotificationIconContainer(Context context, AttributeSet attrs) { super(context, attrs); + initDimens(); + setWillNotDraw(!DEBUG); } + private void initDimens() { + mDotPadding = getResources().getDimensionPixelSize(R.dimen.overflow_icon_dot_padding); + mStaticDotRadius = getResources().getDimensionPixelSize(R.dimen.overflow_dot_radius); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + Paint paint = new Paint(); + paint.setColor(Color.RED); + paint.setStyle(Paint.Style.STROKE); + canvas.drawRect(getActualPaddingStart(), 0, getLayoutEnd(), getHeight(), paint); + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + initDimens(); + } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { float centerY = getHeight() / 2.0f; @@ -82,10 +123,6 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { mIconStates.remove(child); } - public void setIconTint(int iconTint) { - mIconTint = iconTint; - } - public WeakHashMap resetViewStates() { for (int i = 0; i < getChildCount(); i++) { View view = getChildAt(i); @@ -100,17 +137,75 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { * are inserted into the notification container. * If this is not a whole number, the fraction means by how much the icon is appearing. */ - public WeakHashMap calculateIconTranslations() { + public void calculateIconTranslations() { + float translationX = getActualPaddingStart(); + int overflowingIconIndex = -1; + int lastTwoIconWidth = 0; int childCount = getChildCount(); - float visibleIconStart = childCount - numberOfVisibleIcons; - float translationX = 0.0f; for (int i = 0; i < childCount; i++) { View view = getChildAt(i); IconState iconState = mIconStates.get(view); iconState.xTranslation = translationX; + iconState.visibleState = StatusBarIconView.STATE_ICON; translationX += iconState.iconAppearAmount * view.getWidth(); + if (translationX > getLayoutEnd()) { + // we are overflowing it with this icon + overflowingIconIndex = i - 1; + lastTwoIconWidth = view.getWidth(); + break; + } } - return mIconStates; + if (overflowingIconIndex != -1) { + int numDots = 1; + View overflowIcon = getChildAt(overflowingIconIndex); + IconState overflowState = mIconStates.get(overflowIcon); + lastTwoIconWidth += overflowIcon.getWidth(); + int dotWidth = mStaticDotRadius * 2 + mDotPadding; + int totalDotLength = mStaticDotRadius * 6 + 2 * mDotPadding; + translationX = (getLayoutEnd() - lastTwoIconWidth / 2 - totalDotLength / 2) + - overflowIcon.getWidth() * 0.3f + mStaticDotRadius; + float overflowStart = getLayoutEnd() - lastTwoIconWidth; + float overlapAmount = (overflowState.xTranslation - overflowStart) + / overflowIcon.getWidth(); + translationX += overlapAmount * dotWidth; + for (int i = overflowingIconIndex; i < childCount; i++) { + View view = getChildAt(i); + IconState iconState = mIconStates.get(view); + iconState.xTranslation = translationX; + if (numDots <= 3) { + iconState.visibleState = StatusBarIconView.STATE_DOT; + translationX += numDots == 3 ? 3 * dotWidth : dotWidth; + } else { + iconState.visibleState = StatusBarIconView.STATE_HIDDEN; + } + numDots++; + } + } + if (isLayoutRtl()) { + for (int i = 0; i < childCount; i++) { + View view = getChildAt(i); + IconState iconState = mIconStates.get(view); + iconState.xTranslation = getWidth() - iconState.xTranslation - view.getWidth(); + } + } + } + + private float getLayoutEnd() { + return getActualWidth() - getActualPaddingEnd(); + } + + private float getActualPaddingEnd() { + if (mActualPaddingEnd < 0) { + return getPaddingEnd(); + } + return mActualPaddingEnd; + } + + private float getActualPaddingStart() { + if (mActualPaddingStart < 0) { + return getPaddingStart(); + } + return mActualPaddingStart; } /** @@ -123,12 +218,49 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { mShowAllIcons = showAllIcons; } + public void setActualLayoutWidth(int actualLayoutWidth) { + mActualLayoutWidth = actualLayoutWidth; + if (DEBUG) { + invalidate(); + } + } + + public void setActualPaddingEnd(float paddingEnd) { + mActualPaddingEnd = paddingEnd; + if (DEBUG) { + invalidate(); + } + } + + public void setActualPaddingStart(float paddingStart) { + mActualPaddingStart = paddingStart; + if (DEBUG) { + invalidate(); + } + } + + public int getActualWidth() { + if (mActualLayoutWidth < 0) { + return getWidth(); + } + return mActualLayoutWidth; + } + public static class IconState extends ViewState { public float iconAppearAmount = 1.0f; + public int visibleState; @Override public void applyToView(View view) { - super.applyToView(view); + if (view instanceof StatusBarIconView) { + StatusBarIconView icon = (StatusBarIconView) view; + if (visibleState != icon.getVisibleState()) { + icon.setVisibleState(visibleState); + animateTo(icon, DOT_ANIMATION_PROPERTIES); + } else { + super.applyToView(view); + } + } } } }