Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 49014f85 authored by Selim Cinek's avatar Selim Cinek
Browse files

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
parent c40c79ac
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -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" />

    <com.android.systemui.statusbar.notification.FakeShadowView
+6 −0
Original line number Diff line number Diff line
@@ -112,6 +112,12 @@
    <!-- the padding on the start of the statusbar -->
    <dimen name="status_bar_padding_start">6dp</dimen>

    <!-- the radius of the overflow dot in the status bar -->
    <dimen name="overflow_dot_radius">1dp</dimen>

    <!-- the padding between dots in the icon overflow -->
    <dimen name="overflow_icon_dot_padding">3dp</dimen>

    <!-- The padding on the global screenshot background image -->
    <dimen name="global_screenshot_bg_padding">20dp</dimen>

+53 −32
Original line number Diff line number Diff line
@@ -48,7 +48,7 @@ public class NotificationShelf extends ActivatableNotificationView {

    private ViewInvertHelper mViewInvertHelper;
    private boolean mDark;
    private NotificationIconContainer mNotificationIconContainer;
    private NotificationIconContainer mShelfIcons;
    private ArrayList<StatusBarIconView> 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<View, IconState> 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);
        }
    }
+156 −4
Original line number Diff line number Diff line
@@ -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 {
    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 boolean mAlwaysScaleIcon;
    private static final Property<StatusBarIconView, Float> ICON_APPEAR_AMOUNT
            = new FloatProperty<StatusBarIconView>("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<StatusBarIconView, Float> DOT_APPEAR_AMOUNG
            = new FloatProperty<StatusBarIconView>("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) {
        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;
    }
}
+7 −8
Original line number Diff line number Diff line
@@ -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<NotificationData.Entry, StatusBarIconView> function,
            ViewGroup hostLayout) {
            NotificationIconContainer hostLayout) {
        ArrayList<StatusBarIconView> toShow = new ArrayList<>(
                mNotificationScrollLayout.getChildCount());

@@ -193,6 +190,7 @@ public class NotificationIconAreaController {
            }
        }


        ArrayList<View> 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);
        }
    }
}
Loading