NotificationContentView.java revision b5605e58cb8080c8c887b1885336b707596c8094
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.content.Context;
20import android.graphics.Paint;
21import android.graphics.PorterDuff;
22import android.graphics.PorterDuffXfermode;
23import android.graphics.Rect;
24import android.util.AttributeSet;
25import android.view.View;
26import android.view.ViewTreeObserver;
27import android.view.animation.Interpolator;
28import android.view.animation.LinearInterpolator;
29import android.widget.FrameLayout;
30import com.android.systemui.R;
31
32/**
33 * A frame layout containing the actual payload of the notification, including the contracted and
34 * expanded layout. This class is responsible for clipping the content and and switching between the
35 * expanded and contracted view depending on its clipped size.
36 */
37public class NotificationContentView extends FrameLayout {
38
39    private static final long ANIMATION_DURATION_LENGTH = 170;
40
41    private final Rect mClipBounds = new Rect();
42
43    private View mContractedChild;
44    private View mExpandedChild;
45
46    private NotificationViewWrapper mContractedWrapper;
47
48    private int mSmallHeight;
49    private int mClipTopAmount;
50    private int mContentHeight;
51
52    private final Interpolator mLinearInterpolator = new LinearInterpolator();
53
54    private boolean mContractedVisible = true;
55    private boolean mDark;
56
57    private final Paint mFadePaint = new Paint();
58    private boolean mAnimate;
59    private ViewTreeObserver.OnPreDrawListener mEnableAnimationPredrawListener
60            = new ViewTreeObserver.OnPreDrawListener() {
61        @Override
62        public boolean onPreDraw() {
63            mAnimate = true;
64            getViewTreeObserver().removeOnPreDrawListener(this);
65            return true;
66        }
67    };
68
69    public NotificationContentView(Context context, AttributeSet attrs) {
70        super(context, attrs);
71        mFadePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD));
72        reset(true);
73    }
74
75    @Override
76    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
77        super.onLayout(changed, left, top, right, bottom);
78        updateClipping();
79    }
80
81    @Override
82    protected void onAttachedToWindow() {
83        super.onAttachedToWindow();
84        updateVisibility();
85    }
86
87    public void reset(boolean resetActualHeight) {
88        if (mContractedChild != null) {
89            mContractedChild.animate().cancel();
90        }
91        if (mExpandedChild != null) {
92            mExpandedChild.animate().cancel();
93        }
94        removeAllViews();
95        mContractedChild = null;
96        mExpandedChild = null;
97        mSmallHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height);
98        mContractedVisible = true;
99        if (resetActualHeight) {
100            mContentHeight = mSmallHeight;
101        }
102    }
103
104    public View getContractedChild() {
105        return mContractedChild;
106    }
107
108    public View getExpandedChild() {
109        return mExpandedChild;
110    }
111
112    public void setContractedChild(View child) {
113        if (mContractedChild != null) {
114            mContractedChild.animate().cancel();
115            removeView(mContractedChild);
116        }
117        sanitizeContractedLayoutParams(child);
118        addView(child);
119        mContractedChild = child;
120        mContractedWrapper = NotificationViewWrapper.wrap(getContext(), child);
121        selectLayout(false /* animate */, true /* force */);
122        mContractedWrapper.setDark(mDark, false /* animate */, 0 /* delay */);
123    }
124
125    public void setExpandedChild(View child) {
126        if (mExpandedChild != null) {
127            mExpandedChild.animate().cancel();
128            removeView(mExpandedChild);
129        }
130        addView(child);
131        mExpandedChild = child;
132        selectLayout(false /* animate */, true /* force */);
133    }
134
135    @Override
136    protected void onVisibilityChanged(View changedView, int visibility) {
137        super.onVisibilityChanged(changedView, visibility);
138        updateVisibility();
139    }
140
141    private void updateVisibility() {
142        setVisible(isShown());
143    }
144
145    private void setVisible(final boolean isVisible) {
146        if (isVisible) {
147
148            // We only animate if we are drawn at least once, otherwise the view might animate when
149            // it's shown the first time
150            getViewTreeObserver().addOnPreDrawListener(mEnableAnimationPredrawListener);
151        } else {
152            getViewTreeObserver().removeOnPreDrawListener(mEnableAnimationPredrawListener);
153            mAnimate = false;
154        }
155    }
156
157    public void setContentHeight(int contentHeight) {
158        contentHeight = Math.max(Math.min(contentHeight, getHeight()), getMinHeight());
159        mContentHeight = contentHeight;
160        selectLayout(mAnimate /* animate */, false /* force */);
161        updateClipping();
162    }
163
164    public int getContentHeight() {
165        return mContentHeight;
166    }
167
168    public int getMaxHeight() {
169
170        // The maximum height is just the laid out height.
171        return getHeight();
172    }
173
174    public int getMinHeight() {
175        return mSmallHeight;
176    }
177
178    public void setClipTopAmount(int clipTopAmount) {
179        mClipTopAmount = clipTopAmount;
180        updateClipping();
181    }
182
183    private void updateClipping() {
184        mClipBounds.set(0, mClipTopAmount, getWidth(), mContentHeight);
185        setClipBounds(mClipBounds);
186    }
187
188    private void sanitizeContractedLayoutParams(View contractedChild) {
189        LayoutParams lp = (LayoutParams) contractedChild.getLayoutParams();
190        lp.height = mSmallHeight;
191        contractedChild.setLayoutParams(lp);
192    }
193
194    private void selectLayout(boolean animate, boolean force) {
195        if (mContractedChild == null) {
196            return;
197        }
198        boolean showContractedChild = showContractedChild();
199        if (showContractedChild != mContractedVisible || force) {
200            if (animate && mExpandedChild != null) {
201                runSwitchAnimation(showContractedChild);
202            } else if (mExpandedChild != null) {
203                mContractedChild.setVisibility(showContractedChild ? View.VISIBLE : View.INVISIBLE);
204                mContractedChild.setAlpha(showContractedChild ? 1f : 0f);
205                mExpandedChild.setVisibility(showContractedChild ? View.INVISIBLE : View.VISIBLE);
206                mExpandedChild.setAlpha(showContractedChild ? 0f : 1f);
207            }
208        }
209        mContractedVisible = showContractedChild;
210    }
211
212    private void runSwitchAnimation(final boolean showContractedChild) {
213        mContractedChild.setVisibility(View.VISIBLE);
214        mExpandedChild.setVisibility(View.VISIBLE);
215        mContractedChild.setLayerType(LAYER_TYPE_HARDWARE, mFadePaint);
216        mExpandedChild.setLayerType(LAYER_TYPE_HARDWARE, mFadePaint);
217        setLayerType(LAYER_TYPE_HARDWARE, null);
218        mContractedChild.animate()
219                .alpha(showContractedChild ? 1f : 0f)
220                .setDuration(ANIMATION_DURATION_LENGTH)
221                .setInterpolator(mLinearInterpolator);
222        mExpandedChild.animate()
223                .alpha(showContractedChild ? 0f : 1f)
224                .setDuration(ANIMATION_DURATION_LENGTH)
225                .setInterpolator(mLinearInterpolator)
226                .withEndAction(new Runnable() {
227                    @Override
228                    public void run() {
229                        mContractedChild.setLayerType(LAYER_TYPE_NONE, null);
230                        mExpandedChild.setLayerType(LAYER_TYPE_NONE, null);
231                        setLayerType(LAYER_TYPE_NONE, null);
232                        mContractedChild.setVisibility(showContractedChild
233                                ? View.VISIBLE
234                                : View.INVISIBLE);
235                        mExpandedChild.setVisibility(showContractedChild
236                                ? View.INVISIBLE
237                                : View.VISIBLE);
238                    }
239                });
240    }
241
242    private boolean showContractedChild() {
243        return mContentHeight <= mSmallHeight || mExpandedChild == null;
244    }
245
246    public void notifyContentUpdated() {
247        selectLayout(false /* animate */, true /* force */);
248        if (mContractedChild != null) {
249            mContractedWrapper.notifyContentUpdated();
250            mContractedWrapper.setDark(mDark, false /* animate */, 0 /* delay */);
251        }
252    }
253
254    public boolean isContentExpandable() {
255        return mExpandedChild != null;
256    }
257
258    public void setDark(boolean dark, boolean fade, long delay) {
259        if (mDark == dark || mContractedChild == null) return;
260        mDark = dark;
261        mContractedWrapper.setDark(dark, fade, delay);
262    }
263
264    @Override
265    public boolean hasOverlappingRendering() {
266
267        // This is not really true, but good enough when fading from the contracted to the expanded
268        // layout, and saves us some layers.
269        return false;
270    }
271}
272