blob: 8c75adc95b265999fdb85e2bc59674bd56076ba6 [file] [log] [blame]
Selim Cinek67b22602014-03-10 15:40:16 +01001/*
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
Selim Cinek343e6e22014-04-11 21:23:30 +020019import android.graphics.Outline;
20import android.graphics.Rect;
Selim Cinek67b22602014-03-10 15:40:16 +010021import android.util.Log;
22import android.view.View;
23import android.view.ViewGroup;
24
Jorim Jaggibe565df2014-04-28 17:51:23 +020025import com.android.systemui.statusbar.ExpandableView;
Selim Cinek343e6e22014-04-11 21:23:30 +020026
Selim Cinek67b22602014-03-10 15:40:16 +010027import 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;
Jorim Jaggibe565df2014-04-28 17:51:23 +020039 private Map<ExpandableView, ViewState> mStateMap;
Selim Cinek67b22602014-03-10 15:40:16 +010040 private int mScrollY;
Selim Cinek343e6e22014-04-11 21:23:30 +020041 private final Rect mClipRect = new Rect();
42 private int mBackgroundRoundedRectCornerRadius;
43 private final Outline mChildOutline = new Outline();
Selim Cinek67b22602014-03-10 15:40:16 +010044
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;
Jorim Jaggibe565df2014-04-28 17:51:23 +020055 mStateMap = new HashMap<ExpandableView, ViewState>();
Selim Cinek343e6e22014-04-11 21:23:30 +020056 mBackgroundRoundedRectCornerRadius = hostView.getResources().getDimensionPixelSize(
57 com.android.internal.R.dimen.notification_quantum_rounded_rect_radius);
Selim Cinek67b22602014-03-10 15:40:16 +010058 }
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++) {
Jorim Jaggibe565df2014-04-28 17:51:23 +020067 ExpandableView child = (ExpandableView) mHostView.getChildAt(i);
Selim Cinek67b22602014-03-10 15:40:16 +010068 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
Jorim Jaggibe565df2014-04-28 17:51:23 +020074 viewState.height = child.getActualHeight();
Jorim Jaggid4a57442014-04-10 02:45:55 +020075 viewState.gone = child.getVisibility() == View.GONE;
Selim Cinek572bbd42014-04-25 16:43:27 +020076 viewState.alpha = 1;
Selim Cinek67b22602014-03-10 15:40:16 +010077 }
78 }
79
Selim Cinek67b22602014-03-10 15:40:16 +010080 public ViewState getViewStateForView(View requestedView) {
81 return mStateMap.get(requestedView);
82 }
83
Christoph Studer068f5922014-04-08 17:43:07 -040084 public void removeViewStateForView(View child) {
85 mStateMap.remove(child);
86 }
87
Selim Cinek67b22602014-03-10 15:40:16 +010088 /**
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();
Selim Cinek343e6e22014-04-11 21:23:30 +020094 float previousNotificationEnd = 0;
95 float previousNotificationStart = 0;
Selim Cinek67b22602014-03-10 15:40:16 +010096 for (int i = 0; i < numChildren; i++) {
Jorim Jaggibe565df2014-04-28 17:51:23 +020097 ExpandableView child = (ExpandableView) mHostView.getChildAt(i);
Selim Cinek67b22602014-03-10 15:40:16 +010098 ViewState state = mStateMap.get(child);
Selim Cinek343e6e22014-04-11 21:23:30 +020099 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) {
Selim Cinek67b22602014-03-10 15:40:16 +0100105 float alpha = child.getAlpha();
106 float yTranslation = child.getTranslationY();
107 float zTranslation = child.getTranslationZ();
Jorim Jaggibe565df2014-04-28 17:51:23 +0200108 int height = child.getActualHeight();
Selim Cinek67b22602014-03-10 15:40:16 +0100109 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;
Selim Cinek343e6e22014-04-11 21:23:30 +0200135 if (newVisibility != oldVisibility) {
Selim Cinek67b22602014-03-10 15:40:16 +0100136 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) {
Jorim Jaggibe565df2014-04-28 17:51:23 +0200151 child.setActualHeight(newHeight);
Selim Cinek67b22602014-03-10 15:40:16 +0100152 }
Selim Cinek343e6e22014-04-11 21:23:30 +0200153
154 // apply clipping and shadow
155 float newNotificationEnd = newYTranslation + newHeight;
Jorim Jaggibe565df2014-04-28 17:51:23 +0200156 updateChildClippingAndBackground(child, newHeight,
157 newNotificationEnd - (previousNotificationEnd),
158 (int) (newHeight - (previousNotificationStart - newYTranslation)));
Selim Cinek343e6e22014-04-11 21:23:30 +0200159
160 previousNotificationStart = newYTranslation;
161 previousNotificationEnd = newNotificationEnd;
Selim Cinek67b22602014-03-10 15:40:16 +0100162 }
163 }
164 }
165
Selim Cinek343e6e22014-04-11 21:23:30 +0200166 /**
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
Jorim Jaggibe565df2014-04-28 17:51:23 +0200172 * @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
Selim Cinek343e6e22014-04-11 21:23:30 +0200174 */
Jorim Jaggibe565df2014-04-28 17:51:23 +0200175 private void updateChildClippingAndBackground(ExpandableView child, int realHeight,
176 float clipHeight, int backgroundHeight) {
Selim Cinek343e6e22014-04-11 21:23:30 +0200177 if (realHeight > clipHeight) {
178 updateChildClip(child, realHeight, clipHeight);
179 } else {
180 child.setClipBounds(null);
181 }
Jorim Jaggibe565df2014-04-28 17:51:23 +0200182 if (realHeight > backgroundHeight) {
183 child.setClipTopAmount(realHeight - backgroundHeight);
184 } else {
185 child.setClipTopAmount(0);
186 }
Selim Cinek343e6e22014-04-11 21:23:30 +0200187 }
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) {
Jorim Jaggife40f7d2014-04-28 15:20:04 +0200197 int clipInset = (int) (height - clipHeight);
198 mClipRect.set(0,
199 clipInset,
200 child.getWidth(),
201 height);
202 child.setClipBounds(mClipRect);
Selim Cinek343e6e22014-04-11 21:23:30 +0200203 }
204
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200205 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
Selim Cinek67b22602014-03-10 15:40:16 +0100217 float alpha;
218 float yTranslation;
219 float zTranslation;
220 int height;
Jorim Jaggid4a57442014-04-10 02:45:55 +0200221 boolean gone;
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200222
223 /**
224 * The location this view is currently rendered at.
225 *
226 * <p>See <code>LOCATION_</code> flags.</p>
227 */
228 int location;
Selim Cinek67b22602014-03-10 15:40:16 +0100229 }
230}