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