StackScrollState.java revision bf370992508c55d1f2493923bdc1834a0710e4ba
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.Rect;
20import android.util.Log;
21import android.view.View;
22import android.view.ViewGroup;
23
24import com.android.systemui.statusbar.ExpandableView;
25import com.android.systemui.statusbar.SpeedBumpView;
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
42    public StackScrollState(ViewGroup hostView) {
43        mHostView = hostView;
44        mStateMap = new HashMap<ExpandableView, ViewState>();
45    }
46
47    public ViewGroup getHostView() {
48        return mHostView;
49    }
50
51    public void resetViewStates() {
52        int numChildren = mHostView.getChildCount();
53        for (int i = 0; i < numChildren; i++) {
54            ExpandableView child = (ExpandableView) mHostView.getChildAt(i);
55            ViewState viewState = mStateMap.get(child);
56            if (viewState == null) {
57                viewState = new ViewState();
58                mStateMap.put(child, viewState);
59            }
60            // initialize with the default values of the view
61            viewState.height = child.getIntrinsicHeight();
62            viewState.gone = child.getVisibility() == View.GONE;
63            viewState.alpha = 1;
64            viewState.notGoneIndex = -1;
65        }
66    }
67
68    public ViewState getViewStateForView(View requestedView) {
69        return mStateMap.get(requestedView);
70    }
71
72    public void removeViewStateForView(View child) {
73        mStateMap.remove(child);
74    }
75
76    /**
77     * Apply the properties saved in {@link #mStateMap} to the children of the {@link #mHostView}.
78     * The properties are only applied if they effectively changed.
79     */
80    public void apply() {
81        int numChildren = mHostView.getChildCount();
82        for (int i = 0; i < numChildren; i++) {
83            ExpandableView child = (ExpandableView) mHostView.getChildAt(i);
84            ViewState state = mStateMap.get(child);
85            if (state == null) {
86                Log.wtf(CHILD_NOT_FOUND_TAG, "No child state was found when applying this state " +
87                        "to the hostView");
88                continue;
89            }
90            if (!state.gone) {
91                float alpha = child.getAlpha();
92                float yTranslation = child.getTranslationY();
93                float zTranslation = child.getTranslationZ();
94                float scale = child.getScaleX();
95                int height = child.getActualHeight();
96                float newAlpha = state.alpha;
97                float newYTranslation = state.yTranslation;
98                float newZTranslation = state.zTranslation;
99                float newScale = state.scale;
100                int newHeight = state.height;
101                boolean becomesInvisible = newAlpha == 0.0f;
102                if (alpha != newAlpha) {
103                    // apply layer type
104                    boolean becomesFullyVisible = newAlpha == 1.0f;
105                    boolean newLayerTypeIsHardware = !becomesInvisible && !becomesFullyVisible;
106                    int layerType = child.getLayerType();
107                    int newLayerType = newLayerTypeIsHardware
108                            ? View.LAYER_TYPE_HARDWARE
109                            : View.LAYER_TYPE_NONE;
110                    if (layerType != newLayerType) {
111                        child.setLayerType(newLayerType, null);
112                    }
113
114                    // apply alpha
115                    if (!becomesInvisible) {
116                        child.setAlpha(newAlpha);
117                    }
118                }
119
120                // apply visibility
121                int oldVisibility = child.getVisibility();
122                int newVisibility = becomesInvisible ? View.INVISIBLE : View.VISIBLE;
123                if (newVisibility != oldVisibility) {
124                    child.setVisibility(newVisibility);
125                }
126
127                // apply yTranslation
128                if (yTranslation != newYTranslation) {
129                    child.setTranslationY(newYTranslation);
130                }
131
132                // apply zTranslation
133                if (zTranslation != newZTranslation) {
134                    child.setTranslationZ(newZTranslation);
135                }
136
137                // apply scale
138                if (scale != newScale) {
139                    child.setScaleX(newScale);
140                    child.setScaleY(newScale);
141                }
142
143                // apply height
144                if (height != newHeight) {
145                    child.setActualHeight(newHeight, false /* notifyListeners */);
146                }
147
148                // apply dimming
149                child.setDimmed(state.dimmed, false /* animate */);
150
151                // apply dark
152                child.setDark(state.dark, false /* animate */);
153
154                // apply scrimming
155                child.setScrimAmount(state.scrimAmount);
156
157                float oldClipTopAmount = child.getClipTopAmount();
158                if (oldClipTopAmount != state.clipTopAmount) {
159                    child.setClipTopAmount(state.clipTopAmount);
160                }
161
162                if (state.topOverLap != 0) {
163                    updateChildClip(child, newHeight, state.topOverLap);
164                } else {
165                    child.setClipBounds(null);
166                }
167
168                if(child instanceof SpeedBumpView) {
169                    float lineEnd = newYTranslation + newHeight / 2;
170                    performSpeedBumpAnimation(i, (SpeedBumpView) child, lineEnd);
171                }
172            }
173        }
174    }
175
176    /**
177     * Updates the clipping of a view
178     *
179     * @param child the view to update
180     * @param height the currently applied height of the view
181     * @param clipInset how much should this view be clipped from the top
182     */
183    private void updateChildClip(View child, int height, int clipInset) {
184        mClipRect.set(0,
185                clipInset,
186                child.getWidth(),
187                height);
188        child.setClipBounds(mClipRect);
189    }
190
191    private void performSpeedBumpAnimation(int i, SpeedBumpView speedBump, float speedBumpEnd) {
192        View nextChild = getNextChildNotGone(i);
193        if (nextChild != null) {
194            ViewState nextState = getViewStateForView(nextChild);
195            boolean startIsAboveNext = nextState.yTranslation > speedBumpEnd;
196            speedBump.animateDivider(startIsAboveNext, null /* onFinishedRunnable */);
197        }
198    }
199
200    private View getNextChildNotGone(int childIndex) {
201        int childCount = mHostView.getChildCount();
202        for (int i = childIndex + 1; i < childCount; i++) {
203            View child = mHostView.getChildAt(i);
204            if (child.getVisibility() != View.GONE) {
205                return child;
206            }
207        }
208        return null;
209    }
210
211    public static class ViewState {
212
213        // These are flags such that we can create masks for filtering.
214
215        public static final int LOCATION_UNKNOWN = 0x00;
216        public static final int LOCATION_FIRST_CARD = 0x01;
217        public static final int LOCATION_TOP_STACK_HIDDEN = 0x02;
218        public static final int LOCATION_TOP_STACK_PEEKING = 0x04;
219        public static final int LOCATION_MAIN_AREA = 0x08;
220        public static final int LOCATION_BOTTOM_STACK_PEEKING = 0x10;
221        public static final int LOCATION_BOTTOM_STACK_HIDDEN = 0x20;
222
223        float alpha;
224        float yTranslation;
225        float zTranslation;
226        int height;
227        boolean gone;
228        float scale;
229        boolean dimmed;
230        boolean dark;
231
232        /**
233         * A value between 0 and 1 indicating how much the view should be scrimmed.
234         * 1 means that the notifications will be darkened as much as possible.
235         */
236        float scrimAmount;
237
238        /**
239         * The amount which the view should be clipped from the top. This is calculated to
240         * perceive consistent shadows.
241         */
242        int clipTopAmount;
243
244        /**
245         * How much does the child overlap with the previous view on the top? Can be used for
246         * a clipping optimization
247         */
248        int topOverLap;
249
250        /**
251         * The index of the view, only accounting for views not equal to GONE
252         */
253        int notGoneIndex;
254
255        /**
256         * The location this view is currently rendered at.
257         *
258         * <p>See <code>LOCATION_</code> flags.</p>
259         */
260        int location;
261    }
262}
263