StackScrollState.java revision d552d9d8e964c102e6832610be46cf2c041e8829
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.stack;
18
19import android.graphics.Outline;
20import android.graphics.Rect;
21import android.util.Log;
22import android.view.View;
23import android.view.ViewGroup;
24
25import com.android.systemui.statusbar.ExpandableView;
26
27import java.util.HashMap;
28import java.util.Map;
29
30/**
31 * A state of a {@link com.android.systemui.statusbar.stack.NotificationStackScrollLayout} which
32 * can be applied to a viewGroup.
33 */
34public class StackScrollState {
35
36    private static final String CHILD_NOT_FOUND_TAG = "StackScrollStateNoSuchChild";
37
38    private final ViewGroup mHostView;
39    private Map<ExpandableView, ViewState> mStateMap;
40    private final Rect mClipRect = new Rect();
41    private int mBackgroundRoundedRectCornerRadius;
42    private final Outline mChildOutline = new Outline();
43
44    public StackScrollState(ViewGroup hostView) {
45        mHostView = hostView;
46        mStateMap = new HashMap<ExpandableView, ViewState>();
47        mBackgroundRoundedRectCornerRadius = hostView.getResources().getDimensionPixelSize(
48                com.android.internal.R.dimen.notification_quantum_rounded_rect_radius);
49    }
50
51    public ViewGroup getHostView() {
52        return mHostView;
53    }
54
55    public void resetViewStates() {
56        int numChildren = mHostView.getChildCount();
57        for (int i = 0; i < numChildren; i++) {
58            ExpandableView child = (ExpandableView) mHostView.getChildAt(i);
59            ViewState viewState = mStateMap.get(child);
60            if (viewState == null) {
61                viewState = new ViewState();
62                mStateMap.put(child, viewState);
63            }
64            // initialize with the default values of the view
65            viewState.height = child.getIntrinsicHeight();
66            viewState.gone = child.getVisibility() == View.GONE;
67            viewState.alpha = 1;
68        }
69    }
70
71    public ViewState getViewStateForView(View requestedView) {
72        return mStateMap.get(requestedView);
73    }
74
75    public void removeViewStateForView(View child) {
76        mStateMap.remove(child);
77    }
78
79    /**
80     * Apply the properties saved in {@link #mStateMap} to the children of the {@link #mHostView}.
81     * The properties are only applied if they effectively changed.
82     */
83    public void apply() {
84        int numChildren = mHostView.getChildCount();
85        float previousNotificationEnd = 0;
86        float previousNotificationStart = 0;
87        boolean previousNotificationIsSwiped = false;
88        for (int i = 0; i < numChildren; i++) {
89            ExpandableView child = (ExpandableView) mHostView.getChildAt(i);
90            ViewState state = mStateMap.get(child);
91            if (state == null) {
92                Log.wtf(CHILD_NOT_FOUND_TAG, "No child state was found when applying this state " +
93                        "to the hostView");
94                continue;
95            }
96            if (!state.gone) {
97                float alpha = child.getAlpha();
98                float yTranslation = child.getTranslationY();
99                float zTranslation = child.getTranslationZ();
100                float scale = child.getScaleX();
101                int height = child.getActualHeight();
102                float newAlpha = state.alpha;
103                float newYTranslation = state.yTranslation;
104                float newZTranslation = state.zTranslation;
105                float newScale = state.scale;
106                int newHeight = state.height;
107                boolean becomesInvisible = newAlpha == 0.0f;
108                if (alpha != newAlpha) {
109                    // apply layer type
110                    boolean becomesFullyVisible = newAlpha == 1.0f;
111                    boolean newLayerTypeIsHardware = !becomesInvisible && !becomesFullyVisible;
112                    int layerType = child.getLayerType();
113                    int newLayerType = newLayerTypeIsHardware
114                            ? View.LAYER_TYPE_HARDWARE
115                            : View.LAYER_TYPE_NONE;
116                    if (layerType != newLayerType) {
117                        child.setLayerType(newLayerType, null);
118                    }
119
120                    // apply alpha
121                    if (!becomesInvisible) {
122                        child.setAlpha(newAlpha);
123                    }
124                }
125
126                // apply visibility
127                int oldVisibility = child.getVisibility();
128                int newVisibility = becomesInvisible ? View.INVISIBLE : View.VISIBLE;
129                if (newVisibility != oldVisibility) {
130                    child.setVisibility(newVisibility);
131                }
132
133                // apply yTranslation
134                if (yTranslation != newYTranslation) {
135                    child.setTranslationY(newYTranslation);
136                }
137
138                // apply zTranslation
139                if (zTranslation != newZTranslation) {
140                    child.setTranslationZ(newZTranslation);
141                }
142
143                // apply scale
144                if (scale != newScale) {
145                    child.setScaleX(newScale);
146                    child.setScaleY(newScale);
147                }
148
149                // apply height
150                if (height != newHeight) {
151                    child.setActualHeight(newHeight, false /* notifyListeners */);
152                }
153
154                // apply dimming
155                child.setDimmed(state.dimmed, false /* animate */);
156
157                // apply clipping and shadow
158                float newNotificationEnd = newYTranslation + newHeight;
159
160                // When the previous notification is swiped, we don't clip the content to the
161                // bottom of it.
162                float clipHeight = previousNotificationIsSwiped
163                        ? newHeight
164                        : newNotificationEnd - (previousNotificationEnd);
165
166                updateChildClippingAndBackground(child, newHeight,
167                        clipHeight,
168                        (int) (newHeight - (previousNotificationStart - newYTranslation)));
169
170                previousNotificationStart = newYTranslation + child.getClipTopAmount();
171                previousNotificationEnd = newNotificationEnd;
172                previousNotificationIsSwiped = child.getTranslationX() != 0;
173            }
174        }
175    }
176
177    /**
178     * Updates the shadow outline and the clipping for a view.
179     *
180     * @param child the view to update
181     * @param realHeight the currently applied height of the view
182     * @param clipHeight the desired clip height, the rest of the view will be clipped from the top
183     * @param backgroundHeight the desired background height. The shadows of the view will be
184     *                         based on this height and the content will be clipped from the top
185     */
186    private void updateChildClippingAndBackground(ExpandableView child, int realHeight,
187            float clipHeight, int backgroundHeight) {
188        if (realHeight > clipHeight) {
189            updateChildClip(child, realHeight, clipHeight);
190        } else {
191            child.setClipBounds(null);
192        }
193        if (realHeight > backgroundHeight) {
194            child.setClipTopAmount(realHeight - backgroundHeight);
195        } else {
196            child.setClipTopAmount(0);
197        }
198    }
199
200    /**
201     * Updates the clipping of a view
202     *
203     * @param child the view to update
204     * @param height the currently applied height of the view
205     * @param clipHeight the desired clip height, the rest of the view will be clipped from the top
206     */
207    private void updateChildClip(View child, int height, float clipHeight) {
208        int clipInset = (int) (height - clipHeight);
209        mClipRect.set(0,
210                clipInset,
211                child.getWidth(),
212                height);
213        child.setClipBounds(mClipRect);
214    }
215
216    public static class ViewState {
217
218        // These are flags such that we can create masks for filtering.
219
220        public static final int LOCATION_UNKNOWN = 0x00;
221        public static final int LOCATION_FIRST_CARD = 0x01;
222        public static final int LOCATION_TOP_STACK_HIDDEN = 0x02;
223        public static final int LOCATION_TOP_STACK_PEEKING = 0x04;
224        public static final int LOCATION_MAIN_AREA = 0x08;
225        public static final int LOCATION_BOTTOM_STACK_PEEKING = 0x10;
226        public static final int LOCATION_BOTTOM_STACK_HIDDEN = 0x20;
227
228        float alpha;
229        float yTranslation;
230        float zTranslation;
231        int height;
232        boolean gone;
233        float scale;
234        boolean dimmed;
235
236        /**
237         * The location this view is currently rendered at.
238         *
239         * <p>See <code>LOCATION_</code> flags.</p>
240         */
241        int location;
242    }
243}
244