SpeedBumpView.java revision 0045cc1bbd71e8be1ef15658f82d659f6d0ba47a
1/* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17package com.android.systemui.statusbar; 18 19import android.animation.Animator; 20import android.animation.AnimatorListenerAdapter; 21import android.animation.ValueAnimator; 22import android.content.Context; 23import android.graphics.Outline; 24import android.util.AttributeSet; 25import android.view.View; 26import android.view.animation.AnimationUtils; 27import android.view.animation.Interpolator; 28import android.widget.TextView; 29import com.android.systemui.R; 30 31/** 32 * The view representing the separation between important and less important notifications 33 */ 34public class SpeedBumpView extends ExpandableView implements View.OnClickListener { 35 36 private final int mCollapsedHeight; 37 private final int mDotsHeight; 38 private final int mTextPaddingInset; 39 private SpeedBumpDotsLayout mDots; 40 private View mLineLeft; 41 private View mLineRight; 42 private boolean mIsExpanded; 43 private boolean mDividerVisible = true; 44 private ValueAnimator mCurrentAnimator; 45 private final Interpolator mFastOutSlowInInterpolator; 46 private float mCenterX; 47 private TextView mExplanationText; 48 private boolean mExplanationTextVisible = false; 49 private AnimatorListenerAdapter mHideExplanationListener = new AnimatorListenerAdapter() { 50 private boolean mCancelled; 51 52 @Override 53 public void onAnimationEnd(Animator animation) { 54 if (!mCancelled) { 55 mExplanationText.setVisibility(View.INVISIBLE); 56 } 57 } 58 59 @Override 60 public void onAnimationCancel(Animator animation) { 61 mCancelled = true; 62 } 63 64 @Override 65 public void onAnimationStart(Animator animation) { 66 mCancelled = false; 67 } 68 }; 69 private Animator.AnimatorListener mAnimationFinishedListener = new AnimatorListenerAdapter() { 70 @Override 71 public void onAnimationEnd(Animator animation) { 72 mCurrentAnimator = null; 73 } 74 }; 75 76 public SpeedBumpView(Context context, AttributeSet attrs) { 77 super(context, attrs); 78 mCollapsedHeight = getResources() 79 .getDimensionPixelSize(R.dimen.speed_bump_height_collapsed); 80 mTextPaddingInset = getResources().getDimensionPixelSize( 81 R.dimen.speed_bump_text_padding_inset); 82 mDotsHeight = getResources().getDimensionPixelSize(R.dimen.speed_bump_dots_height); 83 setOnClickListener(this); 84 mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(getContext(), 85 android.R.interpolator.fast_out_slow_in); 86 } 87 88 @Override 89 protected void onFinishInflate() { 90 super.onFinishInflate(); 91 mDots = (SpeedBumpDotsLayout) findViewById(R.id.speed_bump_dots_layout); 92 mLineLeft = findViewById(R.id.speedbump_line_left); 93 mLineRight = findViewById(R.id.speedbump_line_right); 94 mExplanationText = (TextView) findViewById(R.id.speed_bump_text); 95 resetExplanationText(); 96 97 } 98 99 @Override 100 protected int getInitialHeight() { 101 return mCollapsedHeight; 102 } 103 104 @Override 105 public int getIntrinsicHeight() { 106 if (mCurrentAnimator != null) { 107 // expand animation is running 108 return getActualHeight(); 109 } 110 return mIsExpanded ? getHeight() : mCollapsedHeight; 111 } 112 113 @Override 114 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 115 super.onLayout(changed, left, top, right, bottom); 116 Outline outline = new Outline(); 117 mCenterX = getWidth() / 2; 118 float centerY = getHeight() / 2; 119 // TODO: hide outline better 120 // Temporary workaround to hide outline on a transparent view 121 int outlineLeft = (int) (mCenterX - getResources().getDisplayMetrics().densityDpi * 8); 122 int outlineTop = (int) (centerY - mDotsHeight / 2); 123 outline.setOval(outlineLeft, outlineTop, outlineLeft + mDotsHeight, 124 outlineTop + mDotsHeight); 125 setOutline(outline); 126 mLineLeft.setPivotX(mLineLeft.getWidth()); 127 mLineLeft.setPivotY(mLineLeft.getHeight() / 2); 128 mLineRight.setPivotX(0); 129 mLineRight.setPivotY(mLineRight.getHeight() / 2); 130 } 131 132 @Override 133 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 134 measureChildren(widthMeasureSpec, heightMeasureSpec); 135 int height = mCollapsedHeight + mExplanationText.getMeasuredHeight() - mTextPaddingInset; 136 setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), height); 137 } 138 139 @Override 140 public void onClick(View v) { 141 if (mCurrentAnimator != null) { 142 return; 143 } 144 int startValue = mIsExpanded ? getMaxHeight() : mCollapsedHeight; 145 int endValue = mIsExpanded ? mCollapsedHeight : getMaxHeight(); 146 mCurrentAnimator = ValueAnimator.ofInt(startValue, endValue); 147 mCurrentAnimator.setInterpolator(mFastOutSlowInInterpolator); 148 mCurrentAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 149 @Override 150 public void onAnimationUpdate(ValueAnimator animation) { 151 setActualHeight((int) animation.getAnimatedValue()); 152 } 153 }); 154 mCurrentAnimator.addListener(mAnimationFinishedListener); 155 mCurrentAnimator.start(); 156 mIsExpanded = !mIsExpanded; 157 mDots.performDotClickAnimation(); 158 animateExplanationTextInternal(mIsExpanded); 159 } 160 161 private void animateExplanationTextInternal(boolean visible) { 162 if (mExplanationTextVisible != visible) { 163 float translationY = 0.0f; 164 float scale = 0.5f; 165 float alpha = 0.0f; 166 boolean needsHideListener = true; 167 if (visible) { 168 mExplanationText.setVisibility(VISIBLE); 169 translationY = mDots.getBottom() - mTextPaddingInset; 170 scale = 1.0f; 171 alpha = 1.0f; 172 needsHideListener = false; 173 } 174 mExplanationText.animate().setInterpolator(mFastOutSlowInInterpolator) 175 .alpha(alpha) 176 .scaleX(scale) 177 .scaleY(scale) 178 .translationY(translationY) 179 .setListener(needsHideListener ? mHideExplanationListener : null) 180 .withLayer(); 181 mExplanationTextVisible = visible; 182 } 183 } 184 185 @Override 186 public boolean isTransparent() { 187 return true; 188 } 189 190 public void performVisibilityAnimation(boolean nowVisible) { 191 animateDivider(nowVisible, null /* onFinishedRunnable */); 192 193 // Animate explanation Text 194 if (mIsExpanded) { 195 animateExplanationTextInternal(nowVisible); 196 } 197 } 198 199 /** 200 * Animate the divider to a new visibility. 201 * 202 * @param nowVisible should it now be visible 203 * @param onFinishedRunnable A runnable which should be run when the animation is 204 * finished. 205 */ 206 public void animateDivider(boolean nowVisible, Runnable onFinishedRunnable) { 207 if (nowVisible != mDividerVisible) { 208 // Animate dividers 209 float endValue = nowVisible ? 1.0f : 0.0f; 210 float endTranslationXLeft = nowVisible ? 0.0f : mCenterX - mLineLeft.getRight(); 211 float endTranslationXRight = nowVisible ? 0.0f : mCenterX - mLineRight.getLeft(); 212 mLineLeft.animate() 213 .alpha(endValue) 214 .withLayer() 215 .scaleX(endValue) 216 .scaleY(endValue) 217 .translationX(endTranslationXLeft) 218 .setInterpolator(mFastOutSlowInInterpolator) 219 .withEndAction(onFinishedRunnable); 220 mLineRight.animate() 221 .alpha(endValue) 222 .withLayer() 223 .scaleX(endValue) 224 .scaleY(endValue) 225 .translationX(endTranslationXRight) 226 .setInterpolator(mFastOutSlowInInterpolator); 227 228 // Animate dots 229 mDots.performVisibilityAnimation(nowVisible); 230 mDividerVisible = nowVisible; 231 } else { 232 if (onFinishedRunnable != null) { 233 onFinishedRunnable.run(); 234 } 235 } 236 } 237 238 public void setInvisible() { 239 float endTranslationXLeft = mCenterX - mLineLeft.getRight(); 240 float endTranslationXRight = mCenterX - mLineRight.getLeft(); 241 mLineLeft.setAlpha(0.0f); 242 mLineLeft.setScaleX(0.0f); 243 mLineLeft.setScaleY(0.0f); 244 mLineLeft.setTranslationX(endTranslationXLeft); 245 mLineRight.setAlpha(0.0f); 246 mLineRight.setScaleX(0.0f); 247 mLineRight.setScaleY(0.0f); 248 mLineRight.setTranslationX(endTranslationXRight); 249 mDots.setInvisible(); 250 resetExplanationText(); 251 252 mDividerVisible = false; 253 } 254 255 public void collapse() { 256 if (mIsExpanded) { 257 setActualHeight(mCollapsedHeight); 258 mIsExpanded = false; 259 } 260 resetExplanationText(); 261 } 262 263 public void animateExplanationText(boolean nowVisible) { 264 if (mIsExpanded) { 265 animateExplanationTextInternal(nowVisible); 266 } 267 } 268 269 @Override 270 public void performRemoveAnimation(float translationDirection, Runnable onFinishedRunnable) { 271 performVisibilityAnimation(false); 272 } 273 274 @Override 275 public void performAddAnimation(long delay) { 276 performVisibilityAnimation(true); 277 } 278 279 private void resetExplanationText() { 280 mExplanationText.setTranslationY(0); 281 mExplanationText.setVisibility(INVISIBLE); 282 mExplanationText.setAlpha(0.0f); 283 mExplanationText.setScaleX(0.5f); 284 mExplanationText.setScaleY(0.5f); 285 mExplanationTextVisible = false; 286 } 287 288 public boolean isExpanded() { 289 return mIsExpanded; 290 } 291} 292