diff options
author | Selim Cinek <cinek@google.com> | 2014-05-14 10:23:33 +0200 |
---|---|---|
committer | Selim Cinek <cinek@google.com> | 2014-05-16 23:19:28 +0200 |
commit | c27437b7fd04e682ae2abdf0727a99bf5c6e409d (patch) | |
tree | 772dffd985ed65c7e35fc8c7108640bc404e66fd /packages/SystemUI/src | |
parent | 4455f54b5ba98da345c64de1614b0704022d272b (diff) | |
download | frameworks_base-c27437b7fd04e682ae2abdf0727a99bf5c6e409d.zip frameworks_base-c27437b7fd04e682ae2abdf0727a99bf5c6e409d.tar.gz frameworks_base-c27437b7fd04e682ae2abdf0727a99bf5c6e409d.tar.bz2 |
Implemented visual speed-bump for notifications.
The separation between the important and the less important
notifications has now a visual representation.
Bug: 14607473
Change-Id: I8baa0a08924ec041be2884a2834139477313ab40
Diffstat (limited to 'packages/SystemUI/src')
13 files changed, 821 insertions, 10 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java index 6090948..c1228d9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java @@ -877,6 +877,7 @@ public abstract class BaseStatusBar extends SystemUI implements entry.row = row; entry.row.setHeightRange(mRowMinHeight, mRowMaxHeight); entry.row.setOnActivatedListener(this); + entry.row.setIsBelowSpeedBump(isBelowSpeedBump(entry.notification)); entry.expanded = contentViewLocal; entry.expandedPublic = publicViewLocal; entry.setBigContentView(bigContentViewLocal); @@ -1039,8 +1040,8 @@ public abstract class BaseStatusBar extends SystemUI implements if (DEBUG) { Log.d(TAG, "addNotificationViews: added at " + pos); } - updateRowStates(); updateNotificationIcons(); + updateRowStates(); } private void addNotificationViews(IBinder key, StatusBarNotification notification) { @@ -1060,6 +1061,7 @@ public abstract class BaseStatusBar extends SystemUI implements mKeyguardIconOverflowContainer.getIconsView().removeAllViews(); int n = mNotificationData.size(); int visibleNotifications = 0; + int speedBumpIndex = -1; boolean onKeyguard = mState == StatusBarState.KEYGUARD; for (int i = n-1; i >= 0; i--) { NotificationData.Entry entry = mNotificationData.get(i); @@ -1087,6 +1089,10 @@ public abstract class BaseStatusBar extends SystemUI implements entry.row.setVisibility(View.VISIBLE); visibleNotifications++; } + if (entry.row.getVisibility() != View.GONE && speedBumpIndex == -1 + && entry.row.isBelowSpeedBump() ) { + speedBumpIndex = n - 1 - i; + } } if (onKeyguard && mKeyguardIconOverflowContainer.getIconsView().getChildCount() > 0) { @@ -1094,6 +1100,8 @@ public abstract class BaseStatusBar extends SystemUI implements } else { mKeyguardIconOverflowContainer.setVisibility(View.GONE); } + + mStackScroller.updateSpeedBumpIndex(speedBumpIndex); } private boolean shouldShowOnKeyguard(StatusBarNotification sbn) { @@ -1309,9 +1317,19 @@ public abstract class BaseStatusBar extends SystemUI implements } else { entry.row.setOnClickListener(null); } + boolean wasBelow = entry.row.isBelowSpeedBump(); + boolean nowBelow = isBelowSpeedBump(notification); + if (wasBelow != nowBelow) { + entry.row.setIsBelowSpeedBump(nowBelow); + } entry.row.notifyContentUpdated(); } + private boolean isBelowSpeedBump(StatusBarNotification notification) { + return notification.getNotification().priority == + Notification.PRIORITY_MIN; + } + protected void notifyHeadsUpScreenOn(boolean screenOn) { if (!screenOn && mInterruptingNotificationEntry != null) { mHandler.sendEmptyMessage(MSG_ESCALATE_HEADS_UP); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java index 39f2bb9..f6c80fc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java @@ -52,6 +52,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { private NotificationContentView mPublicLayout; private NotificationContentView mPrivateLayout; private int mMaxExpandHeight; + private boolean mIsBelowSpeedBump; public ExpandableNotificationRow(Context context, AttributeSet attrs) { super(context, attrs); @@ -244,6 +245,14 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { mPrivateLayout.setClipTopAmount(clipTopAmount); } + public boolean isBelowSpeedBump() { + return mIsBelowSpeedBump; + } + + public void setIsBelowSpeedBump(boolean isBelow) { + this.mIsBelowSpeedBump = isBelow; + } + public void notifyContentUpdated() { mPrivateLayout.notifyContentUpdated(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java index 4bd0e1c..061396d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java @@ -40,11 +40,15 @@ public abstract class ExpandableView extends FrameLayout { protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); if (!mActualHeightInitialized && mActualHeight == 0) { - mActualHeight = getHeight(); + mActualHeight = getInitialHeight(); } mActualHeightInitialized = true; } + protected int getInitialHeight() { + return getHeight(); + } + @Override public boolean dispatchTouchEvent(MotionEvent ev) { if (filterMotionEvent(ev)) { @@ -146,6 +150,10 @@ public abstract class ExpandableView extends FrameLayout { } } + public boolean isTransparent() { + return false; + } + /** * A listener notifying when {@link #getActualHeight} changes. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpDotView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpDotView.java new file mode 100644 index 0000000..3ca021a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpDotView.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Outline; +import android.graphics.Paint; +import android.util.AttributeSet; +import android.view.View; + +/** + * An single dot of the {@link com.android.systemui.statusbar.SpeedBumpDotsLayout} + */ +public class SpeedBumpDotView extends View { + + private final Paint mPaint = new Paint(); + + public SpeedBumpDotView(Context context, AttributeSet attrs) { + super(context, attrs); + mPaint.setAntiAlias(true); + } + + @Override + protected void onDraw(Canvas canvas) { + float radius = getWidth() / 2.0f; + canvas.drawCircle(radius, radius, radius, mPaint); + } + + public void setColor(int color) { + mPaint.setColor(color); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpDotsAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpDotsAlgorithm.java new file mode 100644 index 0000000..cac6327 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpDotsAlgorithm.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar; + +import android.content.Context; +import android.view.View; +import com.android.systemui.R; + +/** + * The Algorithm of the {@link com.android.systemui.statusbar.SpeedBumpDotsLayout} which can be + * queried for {@link * com.android.systemui.statusbar.SpeedBumpDotsState} + */ +public class SpeedBumpDotsAlgorithm { + + private final float mDotRadius; + + public SpeedBumpDotsAlgorithm(Context context) { + mDotRadius = context.getResources().getDimensionPixelSize(R.dimen.speed_bump_dots_height) + / 2.0f; + } + + public void getState(SpeedBumpDotsState resultState) { + + // First reset the current state and ensure that every View has a ViewState + resultState.resetViewStates(); + + SpeedBumpDotsLayout hostView = resultState.getHostView(); + boolean currentlyVisible = hostView.isCurrentlyVisible(); + resultState.setActiveState(currentlyVisible + ? SpeedBumpDotsState.SHOWN + : SpeedBumpDotsState.HIDDEN); + int hostWidth = hostView.getWidth(); + float layoutWidth = hostWidth - 2 * mDotRadius; + int childCount = hostView.getChildCount(); + float paddingBetween = layoutWidth / (childCount - 1); + float centerY = hostView.getHeight() / 2.0f; + for (int i = 0; i < childCount; i++) { + View child = hostView.getChildAt(i); + SpeedBumpDotsState.ViewState viewState = resultState.getViewStateForView(child); + if (currentlyVisible) { + float xTranslation = i * paddingBetween; + viewState.xTranslation = xTranslation; + viewState.yTranslation = calculateYTranslation(hostView, centerY, xTranslation, + layoutWidth); + } else { + viewState.xTranslation = layoutWidth / 2; + viewState.yTranslation = centerY - mDotRadius; + } + viewState.alpha = currentlyVisible ? 1.0f : 0.0f; + viewState.scale = currentlyVisible ? 1.0f : 0.5f; + } + } + + private float calculateYTranslation(SpeedBumpDotsLayout hostView, float centerY, + float xTranslation, float layoutWidth) { + float t = hostView.getAnimationProgress(); + if (t == 0.0f || t == 1.0f) { + return centerY - mDotRadius; + } + float damping = (0.5f -Math.abs(0.5f - t)) * 1.3f; + float partialOffset = xTranslation / layoutWidth; + float indentFactor = (float) (Math.sin((t + partialOffset * 1.5f) * - Math.PI) * damping); + return (1.0f - indentFactor) * centerY - mDotRadius; + } + +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpDotsLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpDotsLayout.java new file mode 100644 index 0000000..ddf5215 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpDotsLayout.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar; + +import android.animation.TimeAnimator; +import android.animation.ValueAnimator; +import android.content.Context; +import android.graphics.Canvas; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.AccelerateDecelerateInterpolator; +import com.android.systemui.R; + +/** + * A layout with a certain number of dots which are integrated in the + * {@link com.android.systemui.statusbar.SpeedBumpView} + */ +public class SpeedBumpDotsLayout extends ViewGroup { + + private static final float DOT_CLICK_ANIMATION_LENGTH = 300; + private final int mDotSize; + private final SpeedBumpDotsAlgorithm mAlgorithm = new SpeedBumpDotsAlgorithm(getContext()); + private final SpeedBumpDotsState mCurrentState = new SpeedBumpDotsState(this); + private boolean mIsCurrentlyVisible = true; + private final ValueAnimator mClickAnimator; + private float mAnimationProgress; + private ValueAnimator.AnimatorUpdateListener mClickUpdateListener + = new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + mAnimationProgress = animation.getAnimatedFraction(); + updateChildren(); + } + }; + + public SpeedBumpDotsLayout(Context context, AttributeSet attrs) { + super(context, attrs); + mDotSize = getResources().getDimensionPixelSize(R.dimen.speed_bump_dots_height); + createDots(context, attrs); + mClickAnimator = TimeAnimator.ofFloat(0, DOT_CLICK_ANIMATION_LENGTH); + mClickAnimator.setInterpolator(new AccelerateDecelerateInterpolator()); + mClickAnimator.addUpdateListener(mClickUpdateListener); + } + + private void createDots(Context context, AttributeSet attrs) { + SpeedBumpDotView blueDot = new SpeedBumpDotView(context, attrs); + blueDot.setColor(getResources().getColor(R.color.speed_bump_dot_blue)); + addView(blueDot); + + SpeedBumpDotView redDot = new SpeedBumpDotView(context, attrs); + redDot.setColor(getResources().getColor(R.color.speed_bump_dot_red)); + addView(redDot); + + SpeedBumpDotView yellowDot = new SpeedBumpDotView(context, attrs); + yellowDot.setColor(getResources().getColor(R.color.speed_bump_dot_yellow)); + addView(yellowDot); + + SpeedBumpDotView greenDot = new SpeedBumpDotView(context, attrs); + greenDot.setColor(getResources().getColor(R.color.speed_bump_dot_green)); + addView(greenDot); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + int childWidthSpec = MeasureSpec.makeMeasureSpec(mDotSize, + MeasureSpec.getMode(widthMeasureSpec)); + int childHeightSpec = MeasureSpec.makeMeasureSpec(mDotSize, + MeasureSpec.getMode(heightMeasureSpec)); + measureChildren(childWidthSpec, childHeightSpec); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + View child = getChildAt(i); + child.layout(0, 0, mDotSize, mDotSize); + } + if (changed) { + updateChildren(); + } + } + + private void updateChildren() { + mAlgorithm.getState(mCurrentState); + mCurrentState.apply(); + } + + public void performVisibilityAnimation(boolean visible) { + if (mClickAnimator.isRunning()) { + mClickAnimator.cancel(); + } + mIsCurrentlyVisible = visible; + mAlgorithm.getState(mCurrentState); + mCurrentState.animateToState(); + } + + public void setInvisible() { + mIsCurrentlyVisible = false; + mAlgorithm.getState(mCurrentState); + mCurrentState.apply(); + } + + public boolean isCurrentlyVisible() { + return mIsCurrentlyVisible; + } + + public void performDotClickAnimation() { + if (mClickAnimator.isRunning()) { + // don't perform an animation if it's running already + return; + } + mClickAnimator.start(); + } + + + public float getAnimationProgress() { + return mAnimationProgress; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpDotsState.java b/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpDotsState.java new file mode 100644 index 0000000..06a7f95 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpDotsState.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar; + +import android.view.View; +import android.view.ViewPropertyAnimator; +import android.view.animation.AnimationUtils; +import android.view.animation.Interpolator; + +import java.util.HashMap; +import java.util.Map; + +/** + * A state of a {@link com.android.systemui.statusbar.SpeedBumpDotsLayout} + */ +public class SpeedBumpDotsState { + + public static final int HIDDEN = 1; + public static final int SHOWN = 2; + private static final int VISIBILITY_ANIMATION_DELAY_PER_ELEMENT = 80; + + private final SpeedBumpDotsLayout mHostView; + private final HashMap<View, ViewState> mStateMap = new HashMap<View, ViewState>(); + private final Interpolator mFastOutSlowInInterpolator; + private int mActiveState = 0; + + public SpeedBumpDotsState(SpeedBumpDotsLayout hostLayout) { + mHostView = hostLayout; + mFastOutSlowInInterpolator = AnimationUtils + .loadInterpolator(hostLayout.getContext(), + android.R.interpolator.fast_out_slow_in); + } + + public SpeedBumpDotsLayout getHostView() { + return mHostView; + } + + public void resetViewStates() { + int numChildren = mHostView.getChildCount(); + for (int i = 0; i < numChildren; i++) { + View child = mHostView.getChildAt(i); + ViewState viewState = mStateMap.get(child); + if (viewState == null) { + viewState = new ViewState(); + mStateMap.put(child, viewState); + } + } + } + + public ViewState getViewStateForView(View requestedView) { + return mStateMap.get(requestedView); + } + + public void apply() { + int childCount = mHostView.getChildCount(); + for (int i = 0; i < childCount; i++) { + View child = mHostView.getChildAt(i); + ViewState viewState = mStateMap.get(child); + float translationX = child.getTranslationX(); + float translationY = child.getTranslationY(); + float scale = child.getScaleX(); + float alpha = child.getAlpha(); + if (translationX != viewState.xTranslation) { + child.setTranslationX(viewState.xTranslation); + } + if (translationY != viewState.yTranslation) { + child.setTranslationY(viewState.yTranslation); + } + if (scale != viewState.scale) { + child.setScaleX(viewState.scale); + child.setScaleY(viewState.scale); + } + if (alpha != viewState.alpha) { + child.setAlpha(viewState.alpha); + } + } + } + + public void animateToState() { + int childCount = mHostView.getChildCount(); + int middleIndex = (childCount - 1) / 2; + long delayPerElement = VISIBILITY_ANIMATION_DELAY_PER_ELEMENT; + boolean isAppearing = getActiveState() == SHOWN; + boolean isDisappearing = getActiveState() == HIDDEN; + for (int i = 0; i < childCount; i++) { + int delayIndex; + if (i <= middleIndex) { + delayIndex = i * 2; + } else { + int distToMiddle = i - middleIndex; + delayIndex = (childCount - 1) - (distToMiddle - 1) * 2; + } + long startDelay = 0; + if (isAppearing || isDisappearing) { + if (isDisappearing) { + delayIndex = childCount - 1 - delayIndex; + } + startDelay = delayIndex * delayPerElement; + } + View child = mHostView.getChildAt(i); + ViewState viewState = mStateMap.get(child); + child.animate().setInterpolator(mFastOutSlowInInterpolator) + .setStartDelay(startDelay) + .alpha(viewState.alpha).withLayer() + .translationX(viewState.xTranslation) + .translationY(viewState.yTranslation) + .scaleX(viewState.scale).scaleY(viewState.scale); + } + } + + public int getActiveState() { + return mActiveState; + } + + public void setActiveState(int mActiveState) { + this.mActiveState = mActiveState; + } + + public static class ViewState { + float xTranslation; + float yTranslation; + float alpha; + float scale; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpView.java new file mode 100644 index 0000000..8ae503a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpView.java @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.content.Context; +import android.graphics.Outline; +import android.util.AttributeSet; +import android.view.View; +import android.view.animation.AnimationUtils; +import android.view.animation.Interpolator; +import android.widget.TextView; +import com.android.systemui.R; + +/** + * The view representing the separation between important and less important notifications + */ +public class SpeedBumpView extends ExpandableView implements View.OnClickListener { + + private final int mCollapsedHeight; + private final int mDotsHeight; + private final int mTextPaddingInset; + private SpeedBumpDotsLayout mDots; + private View mLineLeft; + private View mLineRight; + private boolean mIsExpanded; + private boolean mDividerVisible = true; + private ValueAnimator mCurrentAnimator; + private final Interpolator mFastOutSlowInInterpolator; + private float mCenterX; + private TextView mExplanationText; + private boolean mExplanationTextVisible = false; + private AnimatorListenerAdapter mHideExplanationListener = new AnimatorListenerAdapter() { + private boolean mCancelled; + + @Override + public void onAnimationEnd(Animator animation) { + if (!mCancelled) { + mExplanationText.setVisibility(View.INVISIBLE); + } + } + + @Override + public void onAnimationCancel(Animator animation) { + mCancelled = true; + } + + @Override + public void onAnimationStart(Animator animation) { + mCancelled = false; + } + }; + private Animator.AnimatorListener mAnimationFinishedListener = new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mCurrentAnimator = null; + } + }; + + public SpeedBumpView(Context context, AttributeSet attrs) { + super(context, attrs); + mCollapsedHeight = getResources() + .getDimensionPixelSize(R.dimen.speed_bump_height_collapsed); + mTextPaddingInset = getResources().getDimensionPixelSize( + R.dimen.speed_bump_text_padding_inset); + mDotsHeight = getResources().getDimensionPixelSize(R.dimen.speed_bump_dots_height); + setOnClickListener(this); + mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(getContext(), + android.R.interpolator.fast_out_slow_in); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mDots = (SpeedBumpDotsLayout) findViewById(R.id.speed_bump_dots_layout); + mLineLeft = findViewById(R.id.speedbump_line_left); + mLineRight = findViewById(R.id.speedbump_line_right); + mExplanationText = (TextView) findViewById(R.id.speed_bump_text); + resetExplanationText(); + + } + + @Override + protected int getInitialHeight() { + return mCollapsedHeight; + } + + @Override + public int getIntrinsicHeight() { + return getActualHeight(); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + Outline outline = new Outline(); + mCenterX = getWidth() / 2; + float centerY = getHeight() / 2; + // TODO: hide outline better + // Temporary workaround to hide outline on a transparent view + int outlineLeft = (int) (mCenterX - getResources().getDisplayMetrics().densityDpi * 8); + int outlineTop = (int) (centerY - mDotsHeight / 2); + outline.setOval(outlineLeft, outlineTop, outlineLeft + mDotsHeight, + outlineTop + mDotsHeight); + setOutline(outline); + mLineLeft.setPivotX(mLineLeft.getWidth()); + mLineLeft.setPivotY(mLineLeft.getHeight() / 2); + mLineRight.setPivotX(0); + mLineRight.setPivotY(mLineRight.getHeight() / 2); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + measureChildren(widthMeasureSpec, heightMeasureSpec); + int height = mCollapsedHeight + mExplanationText.getMeasuredHeight() - mTextPaddingInset; + setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), height); + } + + @Override + public void onClick(View v) { + if (mCurrentAnimator != null) { + return; + } + int startValue = mIsExpanded ? getMaxHeight() : mCollapsedHeight; + int endValue = mIsExpanded ? mCollapsedHeight : getMaxHeight(); + mCurrentAnimator = ValueAnimator.ofInt(startValue, endValue); + mCurrentAnimator.setInterpolator(mFastOutSlowInInterpolator); + mCurrentAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + setActualHeight((int) animation.getAnimatedValue()); + } + }); + mCurrentAnimator.addListener(mAnimationFinishedListener); + mCurrentAnimator.start(); + mIsExpanded = !mIsExpanded; + mDots.performDotClickAnimation(); + animateExplanationTextInternal(mIsExpanded); + } + + private void animateExplanationTextInternal(boolean visible) { + if (mExplanationTextVisible != visible) { + float translationY = 0.0f; + float scale = 0.5f; + float alpha = 0.0f; + boolean needsHideListener = true; + if (visible) { + mExplanationText.setVisibility(VISIBLE); + translationY = mDots.getBottom() - mTextPaddingInset; + scale = 1.0f; + alpha = 1.0f; + needsHideListener = false; + } + mExplanationText.animate().setInterpolator(mFastOutSlowInInterpolator) + .alpha(alpha) + .scaleX(scale) + .scaleY(scale) + .translationY(translationY) + .setListener(needsHideListener ? mHideExplanationListener : null) + .withLayer(); + mExplanationTextVisible = visible; + } + } + + @Override + public boolean isTransparent() { + return true; + } + + public void performVisibilityAnimation(boolean nowVisible) { + animateDivider(nowVisible); + + // Animate explanation Text + if (mIsExpanded) { + animateExplanationTextInternal(nowVisible); + } + } + + public void animateDivider(boolean nowVisible) { + if (nowVisible != mDividerVisible) { + // Animate dividers + float endValue = nowVisible ? 1.0f : 0.0f; + float endTranslationXLeft = nowVisible ? 0.0f : mCenterX - mLineLeft.getRight(); + float endTranslationXRight = nowVisible ? 0.0f : mCenterX - mLineRight.getLeft(); + mLineLeft.animate() + .alpha(endValue) + .withLayer() + .scaleX(endValue) + .scaleY(endValue) + .translationX(endTranslationXLeft) + .setInterpolator(mFastOutSlowInInterpolator); + mLineRight.animate() + .alpha(endValue) + .withLayer() + .scaleX(endValue) + .scaleY(endValue) + .translationX(endTranslationXRight) + .setInterpolator(mFastOutSlowInInterpolator); + + // Animate dots + mDots.performVisibilityAnimation(nowVisible); + mDividerVisible = nowVisible; + } + } + + public void setInvisible() { + float endTranslationXLeft = mCenterX - mLineLeft.getRight(); + float endTranslationXRight = mCenterX - mLineRight.getLeft(); + mLineLeft.setAlpha(0.0f); + mLineLeft.setScaleX(0.0f); + mLineLeft.setScaleY(0.0f); + mLineLeft.setTranslationX(endTranslationXLeft); + mLineRight.setAlpha(0.0f); + mLineRight.setScaleX(0.0f); + mLineRight.setScaleY(0.0f); + mLineRight.setTranslationX(endTranslationXRight); + mDots.setInvisible(); + resetExplanationText(); + + mDividerVisible = false; + } + + public void collapse() { + if (mIsExpanded) { + setActualHeight(mCollapsedHeight); + mIsExpanded = false; + } + resetExplanationText(); + } + + public void animateExplanationText(boolean nowVisible) { + if (mIsExpanded) { + animateExplanationTextInternal(nowVisible); + } + } + + private void resetExplanationText() { + mExplanationText.setTranslationY(0); + mExplanationText.setVisibility(INVISIBLE); + mExplanationText.setAlpha(0.0f); + mExplanationText.setScaleX(0.5f); + mExplanationText.setScaleY(0.5f); + mExplanationTextVisible = false; + } + + public boolean isExpanded() { + return mIsExpanded; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index b9f5ab2..cd985f5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -143,8 +143,9 @@ public class NotificationPanelView extends PanelView implements } } - public void animateNextTopPaddingChange() { + public void animateToFullShade() { mAnimateNextTopPaddingChange = true; + mNotificationStackScroller.goToFullShade(); requestLayout(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index d3b5f96..4e1ffa5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -106,6 +106,7 @@ import com.android.systemui.statusbar.NotificationData; import com.android.systemui.statusbar.NotificationData.Entry; import com.android.systemui.statusbar.NotificationOverflowContainer; import com.android.systemui.statusbar.SignalClusterView; +import com.android.systemui.statusbar.SpeedBumpView; import com.android.systemui.statusbar.StatusBarIconView; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.policy.BatteryController; @@ -634,6 +635,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mKeyguardIconOverflowContainer.setOnClickListener(mOverflowClickListener); mStackScroller.addView(mKeyguardIconOverflowContainer); + SpeedBumpView speedBump = (SpeedBumpView) LayoutInflater.from(mContext).inflate( + R.layout.status_bar_notification_speed_bump, mStackScroller, false); + mStackScroller.setSpeedBumpView(speedBump); + mExpandedContents = mStackScroller; mHeader = (StatusBarHeaderView) mStatusBarWindow.findViewById(R.id.header); @@ -1150,7 +1155,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, ArrayList<View> toRemove = new ArrayList<View>(); for (int i=0; i< mStackScroller.getChildCount(); i++) { View child = mStackScroller.getChildAt(i); - if (!toShow.contains(child) && child != mKeyguardIconOverflowContainer) { + if (!toShow.contains(child) && child instanceof ExpandableNotificationRow) { toRemove.add(child); } } @@ -2722,7 +2727,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, setBarState(StatusBarState.SHADE); if (mLeaveOpenOnKeyguardHide) { mLeaveOpenOnKeyguardHide = false; - mNotificationPanel.animateNextTopPaddingChange(); + mNotificationPanel.animateToFullShade(); } else { instantCollapseNotificationPanel(); } @@ -2894,7 +2899,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mLeaveOpenOnKeyguardHide = true; showBouncer(); } else { - mNotificationPanel.animateNextTopPaddingChange(); + mNotificationPanel.animateToFullShade(); setBarState(StatusBarState.SHADE_LOCKED); updateKeyguardState(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java index deab757..b21e12c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java @@ -30,6 +30,7 @@ public class AmbientState { private View mActivatedChild; private float mOverScrollTopAmount; private float mOverScrollBottomAmount; + private int mSpeedBumpIndex = -1; public int getScrollY() { return mScrollY; @@ -86,4 +87,12 @@ public class AmbientState { public float getOverScrollAmount(boolean top) { return top ? mOverScrollTopAmount : mOverScrollBottomAmount; } + + public int getSpeedBumpIndex() { + return mSpeedBumpIndex; + } + + public void setSpeedBumpIndex(int speedBumpIndex) { + mSpeedBumpIndex = speedBumpIndex; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java index fbb6695..f125c9a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -39,6 +39,7 @@ import com.android.systemui.R; import com.android.systemui.SwipeHelper; import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.ExpandableView; +import com.android.systemui.statusbar.SpeedBumpView; import com.android.systemui.statusbar.stack.StackScrollState.ViewState; import com.android.systemui.statusbar.policy.ScrollAdapter; @@ -126,6 +127,8 @@ public class NotificationStackScrollLayout extends ViewGroup private boolean mActivateNeedsAnimation; private boolean mIsExpanded = true; private boolean mChildrenUpdateRequested; + private SpeedBumpView mSpeedBumpView; + private boolean mIsExpansionChanging; private ViewTreeObserver.OnPreDrawListener mChildrenUpdater = new ViewTreeObserver.OnPreDrawListener() { @Override @@ -244,6 +247,22 @@ public class NotificationStackScrollLayout extends ViewGroup requestChildrenUpdate(); } + public void updateSpeedBumpIndex(int newIndex) { + int currentIndex = indexOfChild(mSpeedBumpView); + + // If we are currently layouted before the new speed bump index, we have to decrease it. + boolean validIndex = newIndex > 0; + if (newIndex > getChildCount() - 1) { + validIndex = false; + newIndex = -1; + } + if (validIndex && currentIndex != newIndex) { + changeViewPosition(mSpeedBumpView, newIndex); + } + updateSpeedBump(validIndex); + mAmbientState.setSpeedBumpIndex(newIndex); + } + public void setChildLocationsChangedListener(OnChildLocationsChangedListener listener) { mListener = listener; } @@ -1044,6 +1063,10 @@ public class NotificationStackScrollLayout extends ViewGroup mCurrentStackScrollState.removeViewStateForView(child); mStackScrollAlgorithm.notifyChildrenChanged(this); updateScrollStateForRemovedChild(child); + generateRemoveAnimation(child); + } + + private void generateRemoveAnimation(View child) { if (mIsExpanded) { if (!mChildrenToAddAnimated.contains(child)) { @@ -1120,7 +1143,9 @@ public class NotificationStackScrollLayout extends ViewGroup */ public void changeViewPosition(View child, int newIndex) { if (child != null && child.getParent() == this) { - // TODO: handle this + removeView(child); + addView(child, newIndex); + // TODO: handle events } } @@ -1362,10 +1387,12 @@ public class NotificationStackScrollLayout extends ViewGroup } public void onExpansionStarted() { + mIsExpansionChanging = true; mStackScrollAlgorithm.onExpansionStarted(mCurrentStackScrollState); } public void onExpansionStopped() { + mIsExpansionChanging = false; mStackScrollAlgorithm.onExpansionStopped(); } @@ -1374,6 +1401,7 @@ public class NotificationStackScrollLayout extends ViewGroup mStackScrollAlgorithm.setIsExpanded(isExpanded); if (!isExpanded) { mOwnScrollY = 0; + mSpeedBumpView.collapse(); } } @@ -1432,6 +1460,34 @@ public class NotificationStackScrollLayout extends ViewGroup } } + public void setSpeedBumpView(SpeedBumpView speedBumpView) { + mSpeedBumpView = speedBumpView; + addView(speedBumpView); + } + + private void updateSpeedBump(boolean visible) { + int newVisibility = visible ? VISIBLE : GONE; + int oldVisibility = mSpeedBumpView.getVisibility(); + if (newVisibility != oldVisibility) { + mSpeedBumpView.setVisibility(newVisibility); + if (visible) { + mSpeedBumpView.collapse(); + // Make invisible to ensure that the appear animation is played. + mSpeedBumpView.setInvisible(); + if (!mIsExpansionChanging) { + generateAddAnimation(mSpeedBumpView); + } + } else { + mSpeedBumpView.performVisibilityAnimation(false); + generateRemoveAnimation(mSpeedBumpView); + } + } + } + + public void goToFullShade() { + updateSpeedBump(true); + } + /** * A listener that is notified when some child locations might have changed. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java index 8fc26d8..011411c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java @@ -23,6 +23,7 @@ import android.view.View; import android.view.ViewGroup; import com.android.systemui.statusbar.ExpandableView; +import com.android.systemui.statusbar.SpeedBumpView; import java.util.HashMap; import java.util.Map; @@ -167,11 +168,48 @@ public class StackScrollState { clipHeight, (int) (newHeight - (previousNotificationStart - newYTranslation))); - previousNotificationStart = newYTranslation + child.getClipTopAmount(); - previousNotificationEnd = newNotificationEnd; - previousNotificationIsSwiped = child.getTranslationX() != 0; + if (!child.isTransparent()) { + // Only update the previous values if we are not transparent, + // otherwise we would clip to a transparent view. + previousNotificationStart = newYTranslation + child.getClipTopAmount(); + previousNotificationEnd = newNotificationEnd; + previousNotificationIsSwiped = child.getTranslationX() != 0; + } + + if(child instanceof SpeedBumpView) { + performSpeedBumpAnimation(i, (SpeedBumpView) child, newNotificationEnd, + newYTranslation); + } + } + } + } + + private void performSpeedBumpAnimation(int i, SpeedBumpView speedBump, float speedBumpEnd, + float speedBumpStart) { + View nextChild = getNextChildNotGone(i); + if (nextChild != null) { + ViewState nextState = getViewStateForView(nextChild); + boolean startIsAboveNext = nextState.yTranslation > speedBumpStart; + speedBump.animateDivider(startIsAboveNext); + + // handle expanded case + if (speedBump.isExpanded()) { + boolean endIsAboveNext = nextState.yTranslation > speedBumpEnd; + speedBump.animateExplanationText(endIsAboveNext); + } + + } + } + + private View getNextChildNotGone(int childIndex) { + int childCount = mHostView.getChildCount(); + for (int i = childIndex + 1; i < childCount; i++) { + View child = mHostView.getChildAt(i); + if (child.getVisibility() != View.GONE) { + return child; } } + return null; } /** |