ExpandableView.java revision a5e211b1f2a8d055b369dadc464dc5d5bc3dd9c1
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.util.AttributeSet;
21import android.view.MotionEvent;
22import android.view.View;
23import android.view.ViewGroup;
24import android.widget.FrameLayout;
25import com.android.systemui.R;
26
27import java.util.ArrayList;
28
29/**
30 * An abstract view for expandable views.
31 */
32public abstract class ExpandableView extends FrameLayout {
33
34    private final int mMaxNotificationHeight;
35
36    private OnHeightChangedListener mOnHeightChangedListener;
37    protected int mActualHeight;
38    protected int mClipTopAmount;
39    private boolean mActualHeightInitialized;
40    private ArrayList<View> mMatchParentViews = new ArrayList<View>();
41
42    public ExpandableView(Context context, AttributeSet attrs) {
43        super(context, attrs);
44        mMaxNotificationHeight = getResources().getDimensionPixelSize(
45                R.dimen.notification_max_height);
46    }
47
48    @Override
49    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
50        int ownMaxHeight = mMaxNotificationHeight;
51        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
52        boolean hasFixedHeight = heightMode == MeasureSpec.EXACTLY;
53        boolean isHeightLimited = heightMode == MeasureSpec.AT_MOST;
54        if (hasFixedHeight || isHeightLimited) {
55            int size = MeasureSpec.getSize(heightMeasureSpec);
56            ownMaxHeight = Math.min(ownMaxHeight, size);
57        }
58        int newHeightSpec = MeasureSpec.makeMeasureSpec(ownMaxHeight, MeasureSpec.AT_MOST);
59        int maxChildHeight = 0;
60        int childCount = getChildCount();
61        for (int i = 0; i < childCount; i++) {
62            View child = getChildAt(i);
63            int childHeightSpec = newHeightSpec;
64            ViewGroup.LayoutParams layoutParams = child.getLayoutParams();
65            if (layoutParams.height != ViewGroup.LayoutParams.MATCH_PARENT) {
66                if (layoutParams.height >= 0) {
67                    // An actual height is set
68                    childHeightSpec = layoutParams.height > ownMaxHeight
69                        ? MeasureSpec.makeMeasureSpec(ownMaxHeight, MeasureSpec.EXACTLY)
70                        : MeasureSpec.makeMeasureSpec(layoutParams.height, MeasureSpec.EXACTLY);
71                }
72                child.measure(
73                        getChildMeasureSpec(widthMeasureSpec, 0 /* padding */, layoutParams.width),
74                        childHeightSpec);
75                int childHeight = child.getMeasuredHeight();
76                maxChildHeight = Math.max(maxChildHeight, childHeight);
77            } else {
78                mMatchParentViews.add(child);
79            }
80        }
81        int ownHeight = hasFixedHeight ? ownMaxHeight : maxChildHeight;
82        newHeightSpec = MeasureSpec.makeMeasureSpec(ownHeight, MeasureSpec.EXACTLY);
83        for (View child : mMatchParentViews) {
84            child.measure(getChildMeasureSpec(
85                    widthMeasureSpec, 0 /* padding */, child.getLayoutParams().width),
86                    newHeightSpec);
87        }
88        mMatchParentViews.clear();
89        int width = MeasureSpec.getSize(widthMeasureSpec);
90        setMeasuredDimension(width, ownHeight);
91    }
92
93    @Override
94    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
95        super.onLayout(changed, left, top, right, bottom);
96        if (!mActualHeightInitialized && mActualHeight == 0) {
97            setActualHeight(getInitialHeight());
98        }
99    }
100
101    protected int getInitialHeight() {
102        return getHeight();
103    }
104
105    @Override
106    public boolean dispatchTouchEvent(MotionEvent ev) {
107        if (filterMotionEvent(ev)) {
108            return super.dispatchTouchEvent(ev);
109        }
110        return false;
111    }
112
113    private boolean filterMotionEvent(MotionEvent event) {
114        return event.getActionMasked() != MotionEvent.ACTION_DOWN
115                || event.getY() > mClipTopAmount && event.getY() < mActualHeight;
116    }
117
118    /**
119     * Sets the actual height of this notification. This is different than the laid out
120     * {@link View#getHeight()}, as we want to avoid layouting during scrolling and expanding.
121     *
122     * @param actualHeight The height of this notification.
123     * @param notifyListeners Whether the listener should be informed about the change.
124     */
125    public void setActualHeight(int actualHeight, boolean notifyListeners) {
126        mActualHeightInitialized = true;
127        mActualHeight = actualHeight;
128        if (notifyListeners) {
129            notifyHeightChanged();
130        }
131    }
132
133    public void setActualHeight(int actualHeight) {
134        setActualHeight(actualHeight, true);
135    }
136
137    /**
138     * See {@link #setActualHeight}.
139     *
140     * @return The current actual height of this notification.
141     */
142    public int getActualHeight() {
143        return mActualHeight;
144    }
145
146    /**
147     * @return The maximum height of this notification.
148     */
149    public int getMaxHeight() {
150        return getHeight();
151    }
152
153    /**
154     * @return The minimum height of this notification.
155     */
156    public int getMinHeight() {
157        return getHeight();
158    }
159
160    /**
161     * Sets the notification as dimmed. The default implementation does nothing.
162     *
163     * @param dimmed Whether the notification should be dimmed.
164     * @param fade Whether an animation should be played to change the state.
165     */
166    public void setDimmed(boolean dimmed, boolean fade) {
167    }
168
169    /**
170     * Sets the notification as dark. The default implementation does nothing.
171     *
172     * @param dark Whether the notification should be dark.
173     * @param fade Whether an animation should be played to change the state.
174     */
175    public void setDark(boolean dark, boolean fade) {
176    }
177
178    /**
179     * See {@link #setHideSensitive}. This is a variant which notifies this view in advance about
180     * the upcoming state of hiding sensitive notifications. It gets called at the very beginning
181     * of a stack scroller update such that the updated intrinsic height (which is dependent on
182     * whether private or public layout is showing) gets taken into account into all layout
183     * calculations.
184     */
185    public void setHideSensitiveForIntrinsicHeight(boolean hideSensitive) {
186    }
187
188    /**
189     * Sets whether the notification should hide its private contents if it is sensitive.
190     */
191    public void setHideSensitive(boolean hideSensitive, boolean animated, long delay,
192            long duration) {
193    }
194
195    /**
196     * @return The desired notification height.
197     */
198    public int getIntrinsicHeight() {
199        return getHeight();
200    }
201
202    /**
203     * Sets the amount this view should be clipped from the top. This is used when an expanded
204     * notification is scrolling in the top or bottom stack.
205     *
206     * @param clipTopAmount The amount of pixels this view should be clipped from top.
207     */
208    public void setClipTopAmount(int clipTopAmount) {
209        mClipTopAmount = clipTopAmount;
210    }
211
212    public int getClipTopAmount() {
213        return mClipTopAmount;
214    }
215
216    public void setOnHeightChangedListener(OnHeightChangedListener listener) {
217        mOnHeightChangedListener = listener;
218    }
219
220    /**
221     * @return Whether we can expand this views content.
222     */
223    public boolean isContentExpandable() {
224        return false;
225    }
226
227    public void notifyHeightChanged() {
228        if (mOnHeightChangedListener != null) {
229            mOnHeightChangedListener.onHeightChanged(this);
230        }
231    }
232
233    public boolean isTransparent() {
234        return false;
235    }
236
237    /**
238     * Perform a remove animation on this view.
239     *
240     * @param duration The duration of the remove animation.
241     * @param translationDirection The direction value from [-1 ... 1] indicating in which the
242     *                             animation should be performed. A value of -1 means that The
243     *                             remove animation should be performed upwards,
244     *                             such that the  child appears to be going away to the top. 1
245     *                             Should mean the opposite.
246     * @param onFinishedRunnable A runnable which should be run when the animation is finished.
247     */
248    public abstract void performRemoveAnimation(long duration, float translationDirection,
249            Runnable onFinishedRunnable);
250
251    public abstract void performAddAnimation(long delay, long duration);
252
253    public abstract void setScrimAmount(float scrimAmount);
254
255    public void setBelowSpeedBump(boolean below) {
256    }
257
258    public void reset() {
259        mOnHeightChangedListener.onReset(this);
260    }
261
262    /**
263     * A listener notifying when {@link #getActualHeight} changes.
264     */
265    public interface OnHeightChangedListener {
266
267        /**
268         * @param view the view for which the height changed, or {@code null} if just the top
269         *             padding or the padding between the elements changed
270         */
271        void onHeightChanged(ExpandableView view);
272
273        /**
274         * Called when the view is reset and therefore the height will change abruptly
275         *
276         * @param view The view which was reset.
277         */
278        void onReset(ExpandableView view);
279    }
280}
281