StackScrollState.java revision eceda3d83814e20cabddc4f0755d475fa2f3d8ff
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.R;
25import com.android.systemui.statusbar.DismissView;
26import com.android.systemui.statusbar.ExpandableView;
27import com.android.systemui.statusbar.SpeedBumpView;
28
29import java.util.HashMap;
30import java.util.Map;
31
32/**
33 * A state of a {@link com.android.systemui.statusbar.stack.NotificationStackScrollLayout} which
34 * can be applied to a viewGroup.
35 */
36public class StackScrollState {
37
38    private static final String CHILD_NOT_FOUND_TAG = "StackScrollStateNoSuchChild";
39
40    private final ViewGroup mHostView;
41    private Map<ExpandableView, ViewState> mStateMap;
42    private final Rect mClipRect = new Rect();
43    private final int mClearAllTopPadding;
44
45    public StackScrollState(ViewGroup hostView) {
46        mHostView = hostView;
47        mStateMap = new HashMap<ExpandableView, ViewState>();
48        mClearAllTopPadding = hostView.getContext().getResources().getDimensionPixelSize(
49                R.dimen.clear_all_padding_top);
50    }
51
52    public ViewGroup getHostView() {
53        return mHostView;
54    }
55
56    public void resetViewStates() {
57        int numChildren = mHostView.getChildCount();
58        for (int i = 0; i < numChildren; i++) {
59            ExpandableView child = (ExpandableView) mHostView.getChildAt(i);
60            ViewState viewState = mStateMap.get(child);
61            if (viewState == null) {
62                viewState = new ViewState();
63                mStateMap.put(child, viewState);
64            }
65            // initialize with the default values of the view
66            viewState.height = child.getIntrinsicHeight();
67            viewState.gone = child.getVisibility() == View.GONE;
68            viewState.alpha = 1;
69            viewState.notGoneIndex = -1;
70        }
71    }
72
73    public ViewState getViewStateForView(View requestedView) {
74        return mStateMap.get(requestedView);
75    }
76
77    public void removeViewStateForView(View child) {
78        mStateMap.remove(child);
79    }
80
81    /**
82     * Apply the properties saved in {@link #mStateMap} to the children of the {@link #mHostView}.
83     * The properties are only applied if they effectively changed.
84     */
85    public void apply() {
86        int numChildren = mHostView.getChildCount();
87        for (int i = 0; i < numChildren; i++) {
88            ExpandableView child = (ExpandableView) mHostView.getChildAt(i);
89            ViewState state = mStateMap.get(child);
90            if (state == null) {
91                Log.wtf(CHILD_NOT_FOUND_TAG, "No child state was found when applying this state " +
92                        "to the hostView");
93                continue;
94            }
95            if (!state.gone) {
96                float alpha = child.getAlpha();
97                float yTranslation = child.getTranslationY();
98                float xTranslation = child.getTranslationX();
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 && xTranslation == 0) {
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 dark
158                child.setDark(state.dark, false /* animate */);
159
160                // apply speed bump state
161                child.setBelowSpeedBump(state.belowSpeedBump);
162
163                // apply scrimming
164                child.setScrimAmount(state.scrimAmount);
165
166                // apply clipping
167                float oldClipTopAmount = child.getClipTopAmount();
168                if (oldClipTopAmount != state.clipTopAmount) {
169                    child.setClipTopAmount(state.clipTopAmount);
170                }
171                updateChildClip(child, newHeight, state.topOverLap);
172
173                if(child instanceof SpeedBumpView) {
174                    float lineEnd = newYTranslation + newHeight / 2;
175                    performSpeedBumpAnimation(i, (SpeedBumpView) child, lineEnd);
176                } else if (child instanceof DismissView) {
177                    DismissView dismissView = (DismissView) child;
178                    boolean visible = state.topOverLap < mClearAllTopPadding;
179                    dismissView.performVisibilityAnimation(visible);
180                }
181            }
182        }
183    }
184
185    /**
186     * Updates the clipping of a view
187     *
188     * @param child the view to update
189     * @param height the currently applied height of the view
190     * @param clipInset how much should this view be clipped from the top
191     */
192    private void updateChildClip(View child, int height, int clipInset) {
193        mClipRect.set(0,
194                clipInset,
195                child.getWidth(),
196                height);
197        child.setClipBounds(mClipRect);
198    }
199
200    private void performSpeedBumpAnimation(int i, SpeedBumpView speedBump, float speedBumpEnd) {
201        View nextChild = getNextChildNotGone(i);
202        if (nextChild != null) {
203            ViewState nextState = getViewStateForView(nextChild);
204            boolean startIsAboveNext = nextState.yTranslation > speedBumpEnd;
205            speedBump.animateDivider(startIsAboveNext, null /* onFinishedRunnable */);
206        }
207    }
208
209    private View getNextChildNotGone(int childIndex) {
210        int childCount = mHostView.getChildCount();
211        for (int i = childIndex + 1; i < childCount; i++) {
212            View child = mHostView.getChildAt(i);
213            if (child.getVisibility() != View.GONE) {
214                return child;
215            }
216        }
217        return null;
218    }
219
220    public static class ViewState {
221
222        // These are flags such that we can create masks for filtering.
223
224        public static final int LOCATION_UNKNOWN = 0x00;
225        public static final int LOCATION_FIRST_CARD = 0x01;
226        public static final int LOCATION_TOP_STACK_HIDDEN = 0x02;
227        public static final int LOCATION_TOP_STACK_PEEKING = 0x04;
228        public static final int LOCATION_MAIN_AREA = 0x08;
229        public static final int LOCATION_BOTTOM_STACK_PEEKING = 0x10;
230        public static final int LOCATION_BOTTOM_STACK_HIDDEN = 0x20;
231
232        float alpha;
233        float yTranslation;
234        float zTranslation;
235        int height;
236        boolean gone;
237        float scale;
238        boolean dimmed;
239        boolean dark;
240        boolean belowSpeedBump;
241
242        /**
243         * A value between 0 and 1 indicating how much the view should be scrimmed.
244         * 1 means that the notifications will be darkened as much as possible.
245         */
246        float scrimAmount;
247
248        /**
249         * The amount which the view should be clipped from the top. This is calculated to
250         * perceive consistent shadows.
251         */
252        int clipTopAmount;
253
254        /**
255         * How much does the child overlap with the previous view on the top? Can be used for
256         * a clipping optimization
257         */
258        int topOverLap;
259
260        /**
261         * The index of the view, only accounting for views not equal to GONE
262         */
263        int notGoneIndex;
264
265        /**
266         * The location this view is currently rendered at.
267         *
268         * <p>See <code>LOCATION_</code> flags.</p>
269         */
270        int location;
271    }
272}
273