blob: 9db4e773f3b5016488bb50cbfbad9b71fbd668fd [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
19import android.content.Context;
20import android.view.View;
21import android.view.ViewGroup;
22import com.android.systemui.R;
23
24/**
25 * The Algorithm of the {@link com.android.systemui.statusbar.stack
26 * .NotificationStackScrollLayout} which can be queried for {@link com.android.systemui.statusbar
27 * .stack.StackScrollState}
28 */
29public class StackScrollAlgorithm {
30
31 private static final int MAX_ITEMS_IN_BOTTOM_STACK = 3;
32 private static final int MAX_ITEMS_IN_TOP_STACK = 3;
33
34 private int mPaddingBetweenElements;
35 private int mCollapsedSize;
36 private int mTopStackPeekSize;
37 private int mBottomStackPeekSize;
38 private int mZDistanceBetweenElements;
39 private int mZBasicHeight;
40
41 private StackIndentationFunctor mTopStackIndentationFunctor;
42 private StackIndentationFunctor mBottomStackIndentationFunctor;
43
44 private float mLayoutHeight;
45 private StackScrollAlgorithmState mTempAlgorithmState = new StackScrollAlgorithmState();
46
47 public StackScrollAlgorithm(Context context) {
48 initConstants(context);
49 }
50
51 private void initConstants(Context context) {
52
53 // currently the padding is in the elements themself
54 mPaddingBetweenElements = 0;
55 mCollapsedSize = context.getResources()
56 .getDimensionPixelSize(R.dimen.notification_row_min_height);
57 mTopStackPeekSize = context.getResources()
58 .getDimensionPixelSize(R.dimen.top_stack_peek_amount);
59 mBottomStackPeekSize = context.getResources()
60 .getDimensionPixelSize(R.dimen.bottom_stack_peek_amount);
61 mZDistanceBetweenElements = context.getResources()
62 .getDimensionPixelSize(R.dimen.z_distance_between_notifications);
63 mZBasicHeight = (MAX_ITEMS_IN_BOTTOM_STACK + 1) * mZDistanceBetweenElements;
64
65 mTopStackIndentationFunctor = new PiecewiseLinearIndentationFunctor(
66 MAX_ITEMS_IN_TOP_STACK,
67 mTopStackPeekSize,
68 mCollapsedSize + mPaddingBetweenElements,
69 0.5f);
70 mBottomStackIndentationFunctor = new PiecewiseLinearIndentationFunctor(
71 MAX_ITEMS_IN_BOTTOM_STACK,
72 mBottomStackPeekSize,
73 mBottomStackPeekSize,
74 0.5f);
75 }
76
77
78 public void getStackScrollState(StackScrollState resultState) {
79 // The state of the local variables are saved in an algorithmState to easily subdivide it
80 // into multiple phases.
81 StackScrollAlgorithmState algorithmState = mTempAlgorithmState;
82
83 // First we reset the view states to their default values.
84 resultState.resetViewStates();
85
86 // The first element is always in there so it's initialized with 1.0f.
87 algorithmState.itemsInTopStack = 1.0f;
88 algorithmState.partialInTop = 0.0f;
89 algorithmState.lastTopStackIndex = 0;
90 algorithmState.scrollY = resultState.getScrollY();
91 algorithmState.itemsInBottomStack = 0.0f;
92
93 // Phase 1:
94 findNumberOfItemsInTopStackAndUpdateState(resultState, algorithmState);
95
96 // Phase 2:
97 updatePositionsForState(resultState, algorithmState);
98
99 // Phase 3:
100 updateZValuesForState(resultState, algorithmState);
101
102 // Write the algorithm state to the result.
103 resultState.setScrollY(algorithmState.scrollY);
104 }
105
106 /**
107 * Determine the positions for the views. This is the main part of the algorithm.
108 *
109 * @param resultState The result state to update if a change to the properties of a child occurs
110 * @param algorithmState The state in which the current pass of the algorithm is currently in
111 * and which will be updated
112 */
113 private void updatePositionsForState(StackScrollState resultState,
114 StackScrollAlgorithmState algorithmState) {
115 float stackHeight = getLayoutHeight();
116
117 // The position where the bottom stack starts.
118 float transitioningPositionStart = stackHeight - mCollapsedSize - mBottomStackPeekSize;
119
120 // The y coordinate of the current child.
121 float currentYPosition = 0.0f;
122
123 // How far in is the element currently transitioning into the bottom stack.
124 float yPositionInScrollView = 0.0f;
125
126 ViewGroup hostView = resultState.getHostView();
127 int childCount = hostView.getChildCount();
128 int numberOfElementsCompletelyIn = (int) algorithmState.itemsInTopStack;
129 for (int i = 0; i < childCount; i++) {
130 View child = hostView.getChildAt(i);
131 StackScrollState.ViewState childViewState = resultState.getViewStateForView(child);
132 childViewState.yTranslation = currentYPosition;
133 int childHeight = child.getHeight();
134 // The y position after this element
135 float nextYPosition = currentYPosition + childHeight + mPaddingBetweenElements;
136 float yPositionInScrollViewAfterElement = yPositionInScrollView
137 + childHeight
138 + mPaddingBetweenElements;
139 float scrollOffset = yPositionInScrollViewAfterElement - algorithmState.scrollY;
140 if (i < algorithmState.lastTopStackIndex) {
141 // Case 1:
142 // We are in the top Stack
143 nextYPosition = updateStateForTopStackChild(algorithmState,
144 numberOfElementsCompletelyIn,
145 i, childViewState);
146
147 } else if (i == algorithmState.lastTopStackIndex) {
148 // Case 2:
149 // First element of regular scrollview comes next, so the position is just the
150 // scrolling position
151 nextYPosition = scrollOffset;
152 } else if (nextYPosition >= transitioningPositionStart) {
153 if (currentYPosition >= transitioningPositionStart) {
154 // Case 3:
155 // According to the regular scroll view we are fully translated out of the
156 // bottom of the screen so we are fully in the bottom stack
157 nextYPosition = updateStateForChildFullyInBottomStack(algorithmState,
158 transitioningPositionStart, childViewState, childHeight);
159
160
161 } else {
162 // Case 4:
163 // According to the regular scroll view we are currently translating out of /
164 // into the bottom of the screen
165 nextYPosition = updateStateForChildTransitioningInBottom(
166 algorithmState, stackHeight, transitioningPositionStart,
167 currentYPosition, childViewState,
168 childHeight, nextYPosition);
169 }
170 }
171 currentYPosition = nextYPosition;
172 yPositionInScrollView = yPositionInScrollViewAfterElement;
173 }
174 }
175
176 private float updateStateForChildTransitioningInBottom(StackScrollAlgorithmState algorithmState,
177 float stackHeight, float transitioningPositionStart, float currentYPosition,
178 StackScrollState.ViewState childViewState, int childHeight, float nextYPosition) {
179 float newSize = transitioningPositionStart + mCollapsedSize - currentYPosition;
180 newSize = Math.min(childHeight, newSize);
181 // Transitioning element on top of bottom stack:
182 algorithmState.partialInBottom = 1.0f - (
183 (stackHeight - mBottomStackPeekSize - nextYPosition) / mCollapsedSize);
184 // Our element can be expanded, so we might even have to scroll further than
185 // mCollapsedSize
186 algorithmState.partialInBottom = Math.min(1.0f, algorithmState.partialInBottom);
187 float offset = mBottomStackIndentationFunctor.getValue(
188 algorithmState.partialInBottom);
189 nextYPosition = transitioningPositionStart + offset;
190 algorithmState.itemsInBottomStack += algorithmState.partialInBottom;
191 // TODO: only temporarily collapse
192 if (childHeight != (int) newSize) {
193 childViewState.height = (int) newSize;
194 }
195 return nextYPosition;
196 }
197
198 private float updateStateForChildFullyInBottomStack(StackScrollAlgorithmState algorithmState,
199 float transitioningPositionStart, StackScrollState.ViewState childViewState,
200 int childHeight) {
201
202 float nextYPosition;
203 algorithmState.itemsInBottomStack += 1.0f;
204 if (algorithmState.itemsInBottomStack < MAX_ITEMS_IN_BOTTOM_STACK) {
205 // We are visually entering the bottom stack
206 nextYPosition = transitioningPositionStart
207 + mBottomStackIndentationFunctor.getValue(
208 algorithmState.itemsInBottomStack);
209 } else {
210 // we are fully inside the stack
211 if (algorithmState.itemsInBottomStack > MAX_ITEMS_IN_BOTTOM_STACK + 2) {
212 childViewState.alpha = 0.0f;
213 } else if (algorithmState.itemsInBottomStack
214 > MAX_ITEMS_IN_BOTTOM_STACK + 1) {
215 childViewState.alpha = 1.0f - algorithmState.partialInBottom;
216 }
217 nextYPosition = transitioningPositionStart + mBottomStackPeekSize;
218 }
219 // TODO: only temporarily collapse
220 if (childHeight != mCollapsedSize) {
221 childViewState.height = mCollapsedSize;
222 }
223 return nextYPosition;
224 }
225
226 private float updateStateForTopStackChild(StackScrollAlgorithmState algorithmState,
227 int numberOfElementsCompletelyIn, int i, StackScrollState.ViewState childViewState) {
228
229 float nextYPosition = 0;
230
231 // First we calculate the index relative to the current stack window of size at most
232 // {@link #MAX_ITEMS_IN_TOP_STACK}
233 int paddedIndex = i
234 - Math.max(numberOfElementsCompletelyIn - MAX_ITEMS_IN_TOP_STACK, 0);
235 if (paddedIndex >= 0) {
236 // We are currently visually entering the top stack
237 nextYPosition = mCollapsedSize + mPaddingBetweenElements -
238 mTopStackIndentationFunctor.getValue(
239 algorithmState.itemsInTopStack - i - 1);
240 if (paddedIndex == 0 && i != 0) {
241 childViewState.alpha = 1.0f - algorithmState.partialInTop;
242 }
243 } else {
244 // We are hidden behind the top card and faded out, so we can hide ourselfs
245 if (i != 0) {
246 childViewState.alpha = 0.0f;
247 }
248 }
249 return nextYPosition;
250 }
251
252 /**
253 * Find the number of items in the top stack and update the result state if needed.
254 *
255 * @param resultState The result state to update if a height change of an child occurs
256 * @param algorithmState The state in which the current pass of the algorithm is currently in
257 * and which will be updated
258 */
259 private void findNumberOfItemsInTopStackAndUpdateState(StackScrollState resultState,
260 StackScrollAlgorithmState algorithmState) {
261
262 // The y Position if the element would be in a regular scrollView
263 float yPositionInScrollView = 0.0f;
264 ViewGroup hostView = resultState.getHostView();
265 int childCount = hostView.getChildCount();
266
267 // find the number of elements in the top stack.
268 for (int i = 0; i < childCount; i++) {
269 View child = hostView.getChildAt(i);
270 StackScrollState.ViewState childViewState = resultState.getViewStateForView(child);
271 int childHeight = child.getHeight();
272 float yPositionInScrollViewAfterElement = yPositionInScrollView
273 + childHeight
274 + mPaddingBetweenElements;
275 if (yPositionInScrollView < algorithmState.scrollY) {
276 if (yPositionInScrollViewAfterElement <= algorithmState.scrollY) {
277 // According to the regular scroll view we are fully off screen
278 algorithmState.itemsInTopStack += 1.0f;
279 if (childHeight != mCollapsedSize) {
280 childViewState.height = mCollapsedSize;
281 }
282 } else {
283 // According to the regular scroll view we are partially off screen
284 // If it is expanded we have to collapse it to a new size
285 float newSize = yPositionInScrollViewAfterElement
286 - mPaddingBetweenElements
287 - algorithmState.scrollY;
288
289 // How much did we scroll into this child
290 algorithmState.partialInTop = (mCollapsedSize - newSize) / (mCollapsedSize
291 + mPaddingBetweenElements);
292
293 // Our element can be expanded, so this can get negative
294 algorithmState.partialInTop = Math.max(0.0f, algorithmState.partialInTop);
295 algorithmState.itemsInTopStack += algorithmState.partialInTop;
296 // TODO: handle overlapping sizes with end stack
297 newSize = Math.max(mCollapsedSize, newSize);
298 // TODO: only temporarily collapse
299 if (newSize != childHeight) {
300 childViewState.height = (int) newSize;
301
302 // We decrease scrollY by the same amount we made this child smaller.
303 // The new scroll position is therefore the start of the element
304 algorithmState.scrollY = (int) yPositionInScrollView;
305 resultState.setScrollY(algorithmState.scrollY);
306 }
307 if (childHeight > mCollapsedSize) {
308 // If we are just resizing this child, this element is not treated to be
309 // transitioning into the stack and therefore it is the last element in
310 // the stack.
311 algorithmState.lastTopStackIndex = i;
312 break;
313 }
314 }
315 } else {
316 algorithmState.lastTopStackIndex = i;
317
318 // We are already past the stack so we can end the loop
319 break;
320 }
321 yPositionInScrollView = yPositionInScrollViewAfterElement;
322 }
323 }
324
325 /**
326 * Calculate the Z positions for all children based on the number of items in both stacks and
327 * save it in the resultState
328 *
329 * @param resultState The result state to update the zTranslation values
330 * @param algorithmState The state in which the current pass of the algorithm is currently in
331 */
332 private void updateZValuesForState(StackScrollState resultState,
333 StackScrollAlgorithmState algorithmState) {
334 ViewGroup hostView = resultState.getHostView();
335 int childCount = hostView.getChildCount();
336 for (int i = 0; i < childCount; i++) {
337 View child = hostView.getChildAt(i);
338 StackScrollState.ViewState childViewState = resultState.getViewStateForView(child);
339 if (i < algorithmState.itemsInTopStack) {
340 float stackIndex = algorithmState.itemsInTopStack - i;
341 stackIndex = Math.min(stackIndex, MAX_ITEMS_IN_TOP_STACK + 2);
342 childViewState.zTranslation = mZBasicHeight
343 + stackIndex * mZDistanceBetweenElements;
344 } else if (i > (childCount - 1 - algorithmState.itemsInBottomStack)) {
345 float numItemsAbove = i - (childCount - 1 - algorithmState.itemsInBottomStack);
346 float translationZ = mZBasicHeight
347 - numItemsAbove * mZDistanceBetweenElements;
348 childViewState.zTranslation = translationZ;
349 } else {
350 childViewState.zTranslation = mZBasicHeight;
351 }
352 }
353 }
354
355 public float getLayoutHeight() {
356 return mLayoutHeight;
357 }
358
359 public void setLayoutHeight(float layoutHeight) {
360 this.mLayoutHeight = layoutHeight;
361 }
362
363 class StackScrollAlgorithmState {
364
365 /**
366 * The scroll position of the algorithm
367 */
368 public int scrollY;
369
370 /**
371 * The quantity of items which are in the top stack.
372 */
373 public float itemsInTopStack;
374
375 /**
376 * how far in is the element currently transitioning into the top stack
377 */
378 public float partialInTop;
379
380 /**
381 * The last item index which is in the top stack.
382 * NOTE: In the top stack the item after the transitioning element is also in the stack!
383 * This is needed to ensure a smooth transition between the y position in the regular
384 * scrollview and the one in the stack.
385 */
386 public int lastTopStackIndex;
387
388 /**
389 * The quantity of items which are in the bottom stack.
390 */
391 public float itemsInBottomStack;
392
393 /**
394 * how far in is the element currently transitioning into the bottom stack
395 */
396 public float partialInBottom;
397 }
398
399}