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

Commit c383fd05 authored by Selim Cinek's avatar Selim Cinek
Browse files

Refactored shelf transformation code to support animations

The shelf positions are now applied in each frame if there
is an animation in order to not have weird transitions
when a notifications moves in / out of the shelf.

Test: Add notifications, swipe one away see animation out of the shelf
Bug: 32437839
Change-Id: Ie50362c85ec2fb2a9822de6a387167913d7a58dd
parent 48ff9b48
Loading
Loading
Loading
Loading
+0 −2
Original line number Diff line number Diff line
@@ -1765,7 +1765,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
    public static class NotificationViewState extends ExpandableViewState {

        private final StackScrollState mOverallState;
        public float iconTransformationAmount;


        private NotificationViewState(StackScrollState stackScrollState) {
@@ -1781,7 +1780,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
                    row.setClipToActualHeight(true);
                }
                row.applyChildrenState(mOverallState);
                row.setIconTransformationAmount(iconTransformationAmount);
            }
        }
    }
+77 −65
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.systemui.statusbar;

import static com.android.systemui.statusbar.phone.NotificationIconContainer.IconState;

import android.content.Context;
import android.content.res.Configuration;
import android.util.AttributeSet;
@@ -29,9 +31,9 @@ import com.android.systemui.statusbar.phone.NotificationIconContainer;
import com.android.systemui.statusbar.phone.NotificationPanelView;
import com.android.systemui.statusbar.stack.AmbientState;
import com.android.systemui.statusbar.stack.ExpandableViewState;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.stack.StackScrollAlgorithm;
import com.android.systemui.statusbar.stack.StackScrollState;
import com.android.systemui.statusbar.stack.ViewState;

import java.util.ArrayList;
import java.util.WeakHashMap;
@@ -52,6 +54,8 @@ public class NotificationShelf extends ActivatableNotificationView {
    private int mIconAppearTopPadding;
    private int mStatusBarHeight;
    private int mStatusBarPaddingStart;
    private AmbientState mAmbientState;
    private NotificationStackScrollLayout mHostLayout;

    public NotificationShelf(Context context, AttributeSet attrs) {
        super(context, attrs);
@@ -70,10 +74,14 @@ public class NotificationShelf extends ActivatableNotificationView {
        mViewInvertHelper = new ViewInvertHelper(mNotificationIconContainer,
                NotificationPanelView.DOZE_ANIMATION_DURATION);
        mShelfState = new ShelfState();
        mShelfState.iconStates = mNotificationIconContainer.getIconStates();
        initDimens();
    }

    public void bind(AmbientState ambientState, NotificationStackScrollLayout hostLayout) {
        mAmbientState = ambientState;
        mHostLayout = hostLayout;
    }

    private void initDimens() {
        mIconAppearTopPadding = getResources().getDimensionPixelSize(
                R.dimen.notification_icon_appear_padding);
@@ -140,60 +148,63 @@ public class NotificationShelf extends ActivatableNotificationView {
            mShelfState.shadowAlpha = 1.0f;
            mShelfState.isBottomClipped = false;
            mShelfState.hideSensitive = false;
        } else {
            mShelfState.hidden = true;
            mShelfState.location = ExpandableViewState.LOCATION_GONE;
        }
    }

            mShelfState.resetIcons();
    /**
     * Update the shelf appearance based on the other notifications around it. This transforms
     * the icons from the notification area into the shelf.
     */
    public void updateAppearance() {
        WeakHashMap<View, IconState> iconStates =
                mNotificationIconContainer.resetViewStates();
        float numIconsInShelf = 0.0f;
            float viewStart;
            float maxShelfStart = maxShelfEnd - mShelfState.height;
        float maxShelfStart = getTranslationY();
        int shelfIndex = mAmbientState.getShelfIndex() - 1;
        //  find the first view that doesn't overlap with the shelf
        for (int i = shelfIndex; i >= 0; i--) {
                lastView = algorithmState.visibleChildren.get(i);
                lastViewState = resultState.getViewStateForView(lastView);
                ExpandableNotificationRow row = null;
                if (lastView instanceof ExpandableNotificationRow) {
                    row = (ExpandableNotificationRow) lastView;
                }
                viewStart = lastViewState.yTranslation;
                viewEnd = viewStart + lastView.getIntrinsicHeight();
            ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i);
            if (!(child instanceof ExpandableNotificationRow)
                    || child.getVisibility() == GONE) {
                continue;
            }
            ExpandableNotificationRow row = (ExpandableNotificationRow) child;
            StatusBarIconView icon = row.getEntry().expandedIcon;
            IconState iconState = iconStates.get(icon);
            updateIconAppearance(maxShelfStart, row, iconState, icon);
            numIconsInShelf += iconState.iconAppearAmount;
        }
        mNotificationIconContainer.calculateIconTranslations();
        mNotificationIconContainer.applyIconStates();
        setVisibility(numIconsInShelf == 0.0f ? INVISIBLE : VISIBLE);
        setHideBackground(numIconsInShelf < 1.0f);
    }

    private void updateIconAppearance(float maxShelfStart, ExpandableNotificationRow row,
            IconState iconState, StatusBarIconView icon) {

        // Let calculate how much the view is in the shelf
        float viewStart = row.getTranslationY();
        float viewEnd = viewStart + row.getIntrinsicHeight();
        if (viewEnd > maxShelfStart) {
            if (viewStart < maxShelfStart) {
                        float transitionAmount = 1.0f - ((maxShelfStart - viewStart) /
                                lastView.getIntrinsicHeight());
                        numIconsInShelf += transitionAmount;
                iconState.iconAppearAmount = 1.0f - ((maxShelfStart - viewStart) /
                        row.getIntrinsicHeight());
            } else {
                        numIconsInShelf += 1.0f;
                        lastViewState.hidden = true;
                    }
                }
                if (row != null){
                    // Not in the shelf yet, Icon needs to be placed on top of the notification icon
                    updateIconAppearance(row.getEntry(),
                            (ExpandableNotificationRow.NotificationViewState) lastViewState,
                            mShelfState);
                }
                iconState.iconAppearAmount = 1.0f;
            }
            mShelfState.iconStates = mNotificationIconContainer.calculateIconStates(
                    numIconsInShelf);
            mShelfState.hidden = numIconsInShelf == 0.0f;
            mShelfState.hideBackground = numIconsInShelf < 1.0f;
        } else {
            mShelfState.hideBackground = true;
            mShelfState.hidden = true;
            mShelfState.location = ExpandableViewState.LOCATION_GONE;
        }
    }

    private float getFullyClosedTranslation() {
        return - (getIntrinsicHeight() - mStatusBarHeight) / 2;
            iconState.iconAppearAmount = 0.0f;
        }

    private void updateIconAppearance(NotificationData.Entry entry,
            ExpandableNotificationRow.NotificationViewState rowState,
            ShelfState shelfState) {
        StatusBarIconView icon = entry.expandedIcon;
        ViewState iconState = shelfState.iconStates.get(icon);
        View rowIcon = entry.row.getNotificationIcon();
        float notificationIconPosition = rowState.yTranslation;
        // Lets now calculate how much of the transformation has already happened. This is different
        // from the above, since we only start transforming when the view is already quite a bit
        // pushed in.
        View rowIcon = row.getNotificationIcon();
        float notificationIconPosition = viewStart;
        float notificationIconSize = 0.0f;
        int iconTopPadding;
        if (rowIcon != null) {
@@ -203,31 +214,39 @@ public class NotificationShelf extends ActivatableNotificationView {
            iconTopPadding = mIconAppearTopPadding;
        }
        notificationIconPosition += iconTopPadding;
        float shelfIconPosition = mShelfState.yTranslation + icon.getTop();
        float shelfIconPosition = getTranslationY() + icon.getTop();
        shelfIconPosition += ((1.0f - icon.getIconScale()) * icon.getHeight()) / 2.0f;
        float transitionDistance = getIntrinsicHeight() * 1.5f;
        float transformationStartPosition = mShelfState.yTranslation - transitionDistance;
        float transformationStartPosition = getTranslationY() - transitionDistance;
        float transitionAmount = 0.0f;
        if (rowState.yTranslation < transformationStartPosition) {
        if (viewStart < transformationStartPosition) {
            // We simply place it on the icon of the notification
            iconState.yTranslation = notificationIconPosition - shelfIconPosition;
        } else {
            transitionAmount = (rowState.yTranslation - transformationStartPosition)
            transitionAmount = (viewStart - transformationStartPosition)
                    / transitionDistance;
            float startPosition = transformationStartPosition + iconTopPadding;
            iconState.yTranslation = NotificationUtils.interpolate(
                    startPosition - shelfIconPosition, 0, transitionAmount);
        }
        float shelfIconSize = icon.getHeight() * icon.getIconScale();
        Float newSize = NotificationUtils.interpolate(notificationIconSize, shelfIconSize,
        if (!row.isShowingIcon()) {
            // The view currently doesn't have an icon, lets transform it in!
            iconState.alpha = transitionAmount;
            notificationIconSize = shelfIconSize / 2.0f;
        }
        // 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.scaleY = iconState.scaleX;
        iconState.hidden = transitionAmount == 0.0f;
        rowState.iconTransformationAmount = transitionAmount;
        if (!entry.row.isShowingIcon()) {
            iconState.alpha = transitionAmount;
        row.setIconTransformationAmount(transitionAmount);
        icon.setVisibility(transitionAmount == 0.0f ? INVISIBLE : VISIBLE);
    }

    private float getFullyClosedTranslation() {
        return - (getIntrinsicHeight() - mStatusBarHeight) / 2;
    }

    private int getIconTopPadding(View icon) {
@@ -273,20 +292,13 @@ public class NotificationShelf extends ActivatableNotificationView {
    }

    private class ShelfState extends ExpandableViewState {
        private WeakHashMap<View, ViewState> iconStates = new WeakHashMap<>();
        private boolean hideBackground;
        private float iconContainerTranslation;

        @Override
        public void applyToView(View view) {
            super.applyToView(view);
            mNotificationIconContainer.applyIconStates(iconStates);
            setHideBackground(hideBackground);
            updateAppearance();
            setIconContainerTranslation(iconContainerTranslation);
        }

        public void resetIcons() {
            mNotificationIconContainer.resetViewStates(iconStates);
        }
    }
}
+24 −24
Original line number Diff line number Diff line
@@ -34,7 +34,7 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {

    private boolean mShowAllIcons = true;
    private int mIconTint;
    private WeakHashMap<View, ViewState> mIconStates = new WeakHashMap<>();
    private WeakHashMap<View, IconState> mIconStates = new WeakHashMap<>();

    public NotificationIconContainer(Context context, AttributeSet attrs) {
        super(context, attrs);
@@ -54,16 +54,16 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
            child.layout(0, top, width, top + height);
        }
        if (mShowAllIcons) {
            resetViewStates(mIconStates);
            calculateIconStates(getChildCount());
            applyIconStates(mIconStates);
            resetViewStates();
            calculateIconTranslations();
            applyIconStates();
        }
    }

    public void applyIconStates(WeakHashMap<View, ViewState> iconStates) {
    public void applyIconStates() {
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            ViewState childState = iconStates.get(child);
            ViewState childState = mIconStates.get(child);
            if (childState != null) {
                childState.applyToView(child);
            }
@@ -73,7 +73,7 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
    @Override
    public void onViewAdded(View child) {
        super.onViewAdded(child);
        mIconStates.put(child, new ViewState());
        mIconStates.put(child, new IconState());
    }

    @Override
@@ -86,39 +86,30 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
        mIconTint = iconTint;
    }

    public void resetViewStates(WeakHashMap<View, ViewState> viewStates) {
    public WeakHashMap<View, IconState> resetViewStates() {
        for (int i = 0; i < getChildCount(); i++) {
            View view = getChildAt(i);
            ViewState iconState = mIconStates.get(view);
            iconState.initFrom(view);
        }
        return mIconStates;
    }

    /**
     * Gets a new state based on the number of visible icons starting from the right.
     * Calulate the horizontal translations for each notification based on how much the icons
     * 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<View, ViewState> calculateIconStates(float numberOfVisibleIcons) {
    public WeakHashMap<View, IconState> calculateIconTranslations() {
        int childCount = getChildCount();
        float visibleIconStart = childCount - numberOfVisibleIcons;
        int firstIconIndex = (int) visibleIconStart;
        float translationX = 0.0f;
        for (int i = 0; i < childCount; i++) {
            View view = getChildAt(i);
            ViewState iconState = mIconStates.get(view);
            if (i >= firstIconIndex) {
            IconState iconState = mIconStates.get(view);
            iconState.xTranslation = translationX;
                float appearAmount = 1.0f;
                if (i == firstIconIndex) {
                    appearAmount = 1.0f - (visibleIconStart - firstIconIndex);
                }
                translationX += appearAmount * view.getWidth();
            }
        }
        return mIconStates;
            translationX += iconState.iconAppearAmount * view.getWidth();
        }

    public WeakHashMap<View, ViewState> getIconStates() {
        return mIconStates;
    }

@@ -131,4 +122,13 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
    public void setShowAllIcons(boolean showAllIcons) {
        mShowAllIcons = showAllIcons;
    }

    public static class IconState extends ViewState {
        public float iconAppearAmount = 1.0f;

        @Override
        public void applyToView(View view) {
            super.applyToView(view);
        }
    }
}
+12 −7
Original line number Diff line number Diff line
@@ -264,14 +264,11 @@ public class NotificationStackScrollLayout extends ViewGroup
    private final ArrayList<Pair<ExpandableNotificationRow, Boolean>> mTmpList = new ArrayList<>();
    private FalsingManager mFalsingManager;
    private boolean mAnimationRunning;
    private ViewTreeObserver.OnPreDrawListener mBackgroundUpdater
    private ViewTreeObserver.OnPreDrawListener mRunningAnimationUpdater
            = new ViewTreeObserver.OnPreDrawListener() {
        @Override
        public boolean onPreDraw() {
            // if it needs animation
            if (!mNeedsAnimation && !mChildrenUpdateRequested) {
                updateBackground();
            }
            onPreDrawDuringAnimation();
            return true;
        }
    };
@@ -594,6 +591,13 @@ public class NotificationStackScrollLayout extends ViewGroup
        }
    }

    private void onPreDrawDuringAnimation() {
        if (!mNeedsAnimation && !mChildrenUpdateRequested) {
            updateBackground();
        }
        mShelf.updateAppearance();
    }

    private void updateScrollStateForAddedChildren() {
        if (mChildrenToAddAnimated.isEmpty()) {
            return;
@@ -3873,9 +3877,9 @@ public class NotificationStackScrollLayout extends ViewGroup
    public void setAnimationRunning(boolean animationRunning) {
        if (animationRunning != mAnimationRunning) {
            if (animationRunning) {
                getViewTreeObserver().addOnPreDrawListener(mBackgroundUpdater);
                getViewTreeObserver().addOnPreDrawListener(mRunningAnimationUpdater);
            } else {
                getViewTreeObserver().removeOnPreDrawListener(mBackgroundUpdater);
                getViewTreeObserver().removeOnPreDrawListener(mRunningAnimationUpdater);
            }
            mAnimationRunning = animationRunning;
            updateContinuousShadowDrawing();
@@ -3950,6 +3954,7 @@ public class NotificationStackScrollLayout extends ViewGroup
        }
        addView(mShelf, index);
        mAmbientState.setShelf(shelf);
        shelf.bind(mAmbientState, this);
    }

    public NotificationShelf getNotificationShelf() {
+3 −0
Original line number Diff line number Diff line
@@ -506,6 +506,9 @@ public class StackScrollAlgorithm {
            }
            childViewState.height = (int) newHeight;
        }
        if (childViewState.yTranslation >= shelfStart) {
            childViewState.hidden = true;
        }
    }

    protected int getMaxAllowedChildHeight(View child) {