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