| /* |
| * 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() { |
| if (mCurrentAnimator != null) { |
| // expand animation is running |
| return getActualHeight(); |
| } |
| return mIsExpanded ? getHeight() : mCollapsedHeight; |
| } |
| |
| @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, null /* onFinishedRunnable */); |
| |
| // Animate explanation Text |
| if (mIsExpanded) { |
| animateExplanationTextInternal(nowVisible); |
| } |
| } |
| |
| /** |
| * Animate the divider to a new visibility. |
| * |
| * @param nowVisible should it now be visible |
| * @param onFinishedRunnable A runnable which should be run when the animation is |
| * finished. |
| */ |
| public void animateDivider(boolean nowVisible, Runnable onFinishedRunnable) { |
| 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) |
| .withEndAction(onFinishedRunnable); |
| mLineRight.animate() |
| .alpha(endValue) |
| .withLayer() |
| .scaleX(endValue) |
| .scaleY(endValue) |
| .translationX(endTranslationXRight) |
| .setInterpolator(mFastOutSlowInInterpolator); |
| |
| // Animate dots |
| mDots.performVisibilityAnimation(nowVisible); |
| mDividerVisible = nowVisible; |
| } else { |
| if (onFinishedRunnable != null) { |
| onFinishedRunnable.run(); |
| } |
| } |
| } |
| |
| 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); |
| } |
| } |
| |
| @Override |
| public void performRemoveAnimation(float translationDirection, Runnable onFinishedRunnable) { |
| performVisibilityAnimation(false); |
| } |
| |
| @Override |
| public void performAddAnimation(long delay) { |
| performVisibilityAnimation(true); |
| } |
| |
| 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; |
| } |
| } |