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