blob: acd1c6ac212ee875927a209051d7a51c48ee22d1 [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;
Christoph Studer6e3eceb2014-04-01 18:40:27 +020020import android.util.Log;
Selim Cinek67b22602014-03-10 15:40:16 +010021import android.view.View;
22import android.view.ViewGroup;
Selim Cinek343e6e22014-04-11 21:23:30 +020023
Selim Cinek67b22602014-03-10 15:40:16 +010024import com.android.systemui.R;
Selim Cinek1685e632014-04-08 02:27:49 +020025import com.android.systemui.statusbar.ExpandableNotificationRow;
Jorim Jaggibe565df2014-04-28 17:51:23 +020026import com.android.systemui.statusbar.ExpandableView;
Selim Cinek67b22602014-03-10 15:40:16 +010027
Jorim Jaggid4a57442014-04-10 02:45:55 +020028import java.util.ArrayList;
29
Selim Cinek67b22602014-03-10 15:40:16 +010030/**
31 * The Algorithm of the {@link com.android.systemui.statusbar.stack
32 * .NotificationStackScrollLayout} which can be queried for {@link com.android.systemui.statusbar
33 * .stack.StackScrollState}
34 */
35public class StackScrollAlgorithm {
36
Christoph Studer6e3eceb2014-04-01 18:40:27 +020037 private static final String LOG_TAG = "StackScrollAlgorithm";
38
Selim Cinek67b22602014-03-10 15:40:16 +010039 private static final int MAX_ITEMS_IN_BOTTOM_STACK = 3;
40 private static final int MAX_ITEMS_IN_TOP_STACK = 3;
41
42 private int mPaddingBetweenElements;
43 private int mCollapsedSize;
44 private int mTopStackPeekSize;
45 private int mBottomStackPeekSize;
46 private int mZDistanceBetweenElements;
47 private int mZBasicHeight;
48
49 private StackIndentationFunctor mTopStackIndentationFunctor;
50 private StackIndentationFunctor mBottomStackIndentationFunctor;
51
Selim Cinek343e6e22014-04-11 21:23:30 +020052 private int mLayoutHeight;
Selim Cinek67b22602014-03-10 15:40:16 +010053 private StackScrollAlgorithmState mTempAlgorithmState = new StackScrollAlgorithmState();
Selim Cinek1685e632014-04-08 02:27:49 +020054 private boolean mIsExpansionChanging;
55 private int mFirstChildMaxHeight;
56 private boolean mIsExpanded;
Jorim Jaggibe565df2014-04-28 17:51:23 +020057 private ExpandableView mFirstChildWhileExpanding;
Selim Cinek1685e632014-04-08 02:27:49 +020058 private boolean mExpandedOnStart;
Selim Cinek343e6e22014-04-11 21:23:30 +020059 private int mTopStackTotalSize;
Selim Cinek67b22602014-03-10 15:40:16 +010060
61 public StackScrollAlgorithm(Context context) {
62 initConstants(context);
63 }
64
65 private void initConstants(Context context) {
Jorim Jaggife40f7d2014-04-28 15:20:04 +020066 mPaddingBetweenElements = context.getResources()
67 .getDimensionPixelSize(R.dimen.notification_padding);
Selim Cinek67b22602014-03-10 15:40:16 +010068 mCollapsedSize = context.getResources()
Jorim Jaggife40f7d2014-04-28 15:20:04 +020069 .getDimensionPixelSize(R.dimen.notification_min_height);
Selim Cinek67b22602014-03-10 15:40:16 +010070 mTopStackPeekSize = context.getResources()
71 .getDimensionPixelSize(R.dimen.top_stack_peek_amount);
72 mBottomStackPeekSize = context.getResources()
73 .getDimensionPixelSize(R.dimen.bottom_stack_peek_amount);
74 mZDistanceBetweenElements = context.getResources()
75 .getDimensionPixelSize(R.dimen.z_distance_between_notifications);
76 mZBasicHeight = (MAX_ITEMS_IN_BOTTOM_STACK + 1) * mZDistanceBetweenElements;
Selim Cinek343e6e22014-04-11 21:23:30 +020077 mTopStackTotalSize = mCollapsedSize + mPaddingBetweenElements;
Selim Cinek67b22602014-03-10 15:40:16 +010078 mTopStackIndentationFunctor = new PiecewiseLinearIndentationFunctor(
79 MAX_ITEMS_IN_TOP_STACK,
80 mTopStackPeekSize,
Selim Cinek343e6e22014-04-11 21:23:30 +020081 mTopStackTotalSize,
Selim Cinek67b22602014-03-10 15:40:16 +010082 0.5f);
83 mBottomStackIndentationFunctor = new PiecewiseLinearIndentationFunctor(
84 MAX_ITEMS_IN_BOTTOM_STACK,
85 mBottomStackPeekSize,
Jorim Jaggibe565df2014-04-28 17:51:23 +020086 mCollapsedSize + mBottomStackPeekSize + mPaddingBetweenElements,
Selim Cinek67b22602014-03-10 15:40:16 +010087 0.5f);
88 }
89
90
91 public void getStackScrollState(StackScrollState resultState) {
92 // The state of the local variables are saved in an algorithmState to easily subdivide it
93 // into multiple phases.
94 StackScrollAlgorithmState algorithmState = mTempAlgorithmState;
95
96 // First we reset the view states to their default values.
97 resultState.resetViewStates();
98
Selim Cinek343e6e22014-04-11 21:23:30 +020099 algorithmState.itemsInTopStack = 0.0f;
Selim Cinek67b22602014-03-10 15:40:16 +0100100 algorithmState.partialInTop = 0.0f;
101 algorithmState.lastTopStackIndex = 0;
Selim Cinek343e6e22014-04-11 21:23:30 +0200102 algorithmState.scrolledPixelsTop = 0;
Selim Cinek67b22602014-03-10 15:40:16 +0100103 algorithmState.itemsInBottomStack = 0.0f;
Selim Cinek343e6e22014-04-11 21:23:30 +0200104 algorithmState.partialInBottom = 0.0f;
Jorim Jaggibe565df2014-04-28 17:51:23 +0200105 algorithmState.scrollY = resultState.getScrollY() + mCollapsedSize;
Selim Cinek343e6e22014-04-11 21:23:30 +0200106
Jorim Jaggid4a57442014-04-10 02:45:55 +0200107 updateVisibleChildren(resultState, algorithmState);
Selim Cinek67b22602014-03-10 15:40:16 +0100108
109 // Phase 1:
110 findNumberOfItemsInTopStackAndUpdateState(resultState, algorithmState);
111
112 // Phase 2:
113 updatePositionsForState(resultState, algorithmState);
114
115 // Phase 3:
116 updateZValuesForState(resultState, algorithmState);
Selim Cinek343e6e22014-04-11 21:23:30 +0200117 }
Selim Cinek67b22602014-03-10 15:40:16 +0100118
Selim Cinek343e6e22014-04-11 21:23:30 +0200119 /**
Jorim Jaggid4a57442014-04-10 02:45:55 +0200120 * Update the visible children on the state.
121 */
122 private void updateVisibleChildren(StackScrollState resultState,
123 StackScrollAlgorithmState state) {
124 ViewGroup hostView = resultState.getHostView();
125 int childCount = hostView.getChildCount();
126 state.visibleChildren.clear();
127 state.visibleChildren.ensureCapacity(childCount);
128 for (int i = 0; i < childCount; i++) {
Jorim Jaggibe565df2014-04-28 17:51:23 +0200129 ExpandableView v = (ExpandableView) hostView.getChildAt(i);
Jorim Jaggid4a57442014-04-10 02:45:55 +0200130 if (v.getVisibility() != View.GONE) {
131 state.visibleChildren.add(v);
132 }
133 }
134 }
135
136 /**
Selim Cinek67b22602014-03-10 15:40:16 +0100137 * Determine the positions for the views. This is the main part of the algorithm.
138 *
139 * @param resultState The result state to update if a change to the properties of a child occurs
140 * @param algorithmState The state in which the current pass of the algorithm is currently in
141 * and which will be updated
142 */
143 private void updatePositionsForState(StackScrollState resultState,
144 StackScrollAlgorithmState algorithmState) {
Selim Cinek67b22602014-03-10 15:40:16 +0100145
Selim Cinek1685e632014-04-08 02:27:49 +0200146 // The starting position of the bottom stack peek
Selim Cinek343e6e22014-04-11 21:23:30 +0200147 float bottomPeekStart = mLayoutHeight - mBottomStackPeekSize;
Selim Cinek1685e632014-04-08 02:27:49 +0200148
Selim Cinek67b22602014-03-10 15:40:16 +0100149 // The position where the bottom stack starts.
Selim Cinek343e6e22014-04-11 21:23:30 +0200150 float bottomStackStart = bottomPeekStart - mCollapsedSize;
Selim Cinek67b22602014-03-10 15:40:16 +0100151
152 // The y coordinate of the current child.
153 float currentYPosition = 0.0f;
154
155 // How far in is the element currently transitioning into the bottom stack.
156 float yPositionInScrollView = 0.0f;
157
Jorim Jaggid4a57442014-04-10 02:45:55 +0200158 int childCount = algorithmState.visibleChildren.size();
Selim Cinek67b22602014-03-10 15:40:16 +0100159 int numberOfElementsCompletelyIn = (int) algorithmState.itemsInTopStack;
160 for (int i = 0; i < childCount; i++) {
Jorim Jaggibe565df2014-04-28 17:51:23 +0200161 ExpandableView child = algorithmState.visibleChildren.get(i);
Selim Cinek67b22602014-03-10 15:40:16 +0100162 StackScrollState.ViewState childViewState = resultState.getViewStateForView(child);
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200163 childViewState.location = StackScrollState.ViewState.LOCATION_UNKNOWN;
Jorim Jaggibe565df2014-04-28 17:51:23 +0200164 int childHeight = getMaxAllowedChildHeight(child);
Selim Cinek67b22602014-03-10 15:40:16 +0100165 float yPositionInScrollViewAfterElement = yPositionInScrollView
166 + childHeight
167 + mPaddingBetweenElements;
Selim Cinek343e6e22014-04-11 21:23:30 +0200168 float scrollOffset = yPositionInScrollView - algorithmState.scrollY + mCollapsedSize;
169
170 if (i == algorithmState.lastTopStackIndex + 1) {
171 // Normally the position of this child is the position in the regular scrollview,
172 // but if the two stacks are very close to each other,
173 // then have have to push it even more upwards to the position of the bottom
174 // stack start.
175 currentYPosition = Math.min(scrollOffset, bottomStackStart);
176 }
177 childViewState.yTranslation = currentYPosition;
178
179 // The y position after this element
180 float nextYPosition = currentYPosition + childHeight +
181 mPaddingBetweenElements;
182
183 if (i <= algorithmState.lastTopStackIndex) {
Selim Cinek67b22602014-03-10 15:40:16 +0100184 // Case 1:
185 // We are in the top Stack
Selim Cinek343e6e22014-04-11 21:23:30 +0200186 updateStateForTopStackChild(algorithmState,
187 numberOfElementsCompletelyIn, i, childHeight, childViewState, scrollOffset);
188 clampYTranslation(childViewState, childHeight);
189 // check if we are overlapping with the bottom stack
190 if (childViewState.yTranslation + childHeight + mPaddingBetweenElements
191 >= bottomStackStart && !mIsExpansionChanging) {
192 // TODO: handle overlapping sizes with end stack better
193 // we just collapse this element
194 childViewState.height = mCollapsedSize;
195 }
196 } else if (nextYPosition >= bottomStackStart) {
Selim Cinek67b22602014-03-10 15:40:16 +0100197 // Case 2:
Selim Cinek343e6e22014-04-11 21:23:30 +0200198 // We are in the bottom stack.
199 if (currentYPosition >= bottomStackStart) {
Selim Cinek67b22602014-03-10 15:40:16 +0100200 // According to the regular scroll view we are fully translated out of the
201 // bottom of the screen so we are fully in the bottom stack
Selim Cinek343e6e22014-04-11 21:23:30 +0200202 updateStateForChildFullyInBottomStack(algorithmState,
203 bottomStackStart, childViewState, childHeight);
Selim Cinek67b22602014-03-10 15:40:16 +0100204 } else {
Selim Cinek67b22602014-03-10 15:40:16 +0100205 // According to the regular scroll view we are currently translating out of /
206 // into the bottom of the screen
Selim Cinek343e6e22014-04-11 21:23:30 +0200207 updateStateForChildTransitioningInBottom(algorithmState,
208 bottomStackStart, bottomPeekStart, currentYPosition,
209 childViewState, childHeight);
Selim Cinek67b22602014-03-10 15:40:16 +0100210 }
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200211 } else {
Selim Cinek343e6e22014-04-11 21:23:30 +0200212 // Case 3:
213 // We are in the regular scroll area.
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200214 childViewState.location = StackScrollState.ViewState.LOCATION_MAIN_AREA;
Selim Cinek343e6e22014-04-11 21:23:30 +0200215 clampYTranslation(childViewState, childHeight);
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200216 }
Selim Cinek343e6e22014-04-11 21:23:30 +0200217
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200218 // The first card is always rendered.
219 if (i == 0) {
220 childViewState.alpha = 1.0f;
Selim Cinek343e6e22014-04-11 21:23:30 +0200221 childViewState.yTranslation = 0;
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200222 childViewState.location = StackScrollState.ViewState.LOCATION_FIRST_CARD;
223 }
224 if (childViewState.location == StackScrollState.ViewState.LOCATION_UNKNOWN) {
225 Log.wtf(LOG_TAG, "Failed to assign location for child " + i);
Selim Cinek67b22602014-03-10 15:40:16 +0100226 }
Selim Cinek343e6e22014-04-11 21:23:30 +0200227 currentYPosition = childViewState.yTranslation + childHeight + mPaddingBetweenElements;
Selim Cinek67b22602014-03-10 15:40:16 +0100228 yPositionInScrollView = yPositionInScrollViewAfterElement;
229 }
230 }
231
Selim Cinek1685e632014-04-08 02:27:49 +0200232 /**
Selim Cinek343e6e22014-04-11 21:23:30 +0200233 * Clamp the yTranslation both up and down to valid positions.
Selim Cinek1685e632014-04-08 02:27:49 +0200234 *
Selim Cinek1685e632014-04-08 02:27:49 +0200235 * @param childViewState the view state of the child
Selim Cinek343e6e22014-04-11 21:23:30 +0200236 * @param childHeight the height of this child
Selim Cinek1685e632014-04-08 02:27:49 +0200237 */
Selim Cinek343e6e22014-04-11 21:23:30 +0200238 private void clampYTranslation(StackScrollState.ViewState childViewState, int childHeight) {
239 clampPositionToBottomStackStart(childViewState, childHeight);
240 clampPositionToTopStackEnd(childViewState, childHeight);
241 }
242
243 /**
244 * Clamp the yTranslation of the child down such that its end is at most on the beginning of
245 * the bottom stack.
246 *
247 * @param childViewState the view state of the child
248 * @param childHeight the height of this child
249 */
250 private void clampPositionToBottomStackStart(StackScrollState.ViewState childViewState,
251 int childHeight) {
252 childViewState.yTranslation = Math.min(childViewState.yTranslation,
253 mLayoutHeight - mBottomStackPeekSize - childHeight);
254 }
255
256 /**
257 * Clamp the yTranslation of the child up such that its end is at lest on the end of the top
Jorim Jaggibe565df2014-04-28 17:51:23 +0200258 * stack.get
Selim Cinek343e6e22014-04-11 21:23:30 +0200259 *
260 * @param childViewState the view state of the child
261 * @param childHeight the height of this child
262 */
263 private void clampPositionToTopStackEnd(StackScrollState.ViewState childViewState,
264 int childHeight) {
265 childViewState.yTranslation = Math.max(childViewState.yTranslation,
266 mCollapsedSize - childHeight);
Selim Cinek1685e632014-04-08 02:27:49 +0200267 }
268
269 private int getMaxAllowedChildHeight(View child) {
270 if (child instanceof ExpandableNotificationRow) {
271 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
272 return row.getMaximumAllowedExpandHeight();
Jorim Jaggibe565df2014-04-28 17:51:23 +0200273 } else if (child instanceof ExpandableView) {
274 ExpandableView expandableView = (ExpandableView) child;
275 return expandableView.getActualHeight();
Selim Cinek1685e632014-04-08 02:27:49 +0200276 }
Jorim Jaggibe565df2014-04-28 17:51:23 +0200277 return child == null? mCollapsedSize : child.getHeight();
Selim Cinek1685e632014-04-08 02:27:49 +0200278 }
279
Selim Cinek343e6e22014-04-11 21:23:30 +0200280 private void updateStateForChildTransitioningInBottom(StackScrollAlgorithmState algorithmState,
281 float transitioningPositionStart, float bottomPeakStart, float currentYPosition,
282 StackScrollState.ViewState childViewState, int childHeight) {
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200283
Selim Cinek343e6e22014-04-11 21:23:30 +0200284 // This is the transitioning element on top of bottom stack, calculate how far we are in.
285 algorithmState.partialInBottom = 1.0f - (
286 (transitioningPositionStart - currentYPosition) / (childHeight +
287 mPaddingBetweenElements));
288
289 // the offset starting at the transitionPosition of the bottom stack
290 float offset = mBottomStackIndentationFunctor.getValue(algorithmState.partialInBottom);
291 algorithmState.itemsInBottomStack += algorithmState.partialInBottom;
Jorim Jaggife40f7d2014-04-28 15:20:04 +0200292 childViewState.yTranslation = transitioningPositionStart + offset - childHeight
293 - mPaddingBetweenElements;
Selim Cinek343e6e22014-04-11 21:23:30 +0200294
295 // We want at least to be at the end of the top stack when collapsing
296 clampPositionToTopStackEnd(childViewState, childHeight);
297 childViewState.location = StackScrollState.ViewState.LOCATION_MAIN_AREA;
Selim Cinek67b22602014-03-10 15:40:16 +0100298 }
299
Selim Cinek343e6e22014-04-11 21:23:30 +0200300 private void updateStateForChildFullyInBottomStack(StackScrollAlgorithmState algorithmState,
Selim Cinek67b22602014-03-10 15:40:16 +0100301 float transitioningPositionStart, StackScrollState.ViewState childViewState,
302 int childHeight) {
303
Selim Cinek343e6e22014-04-11 21:23:30 +0200304 float currentYPosition;
Selim Cinek67b22602014-03-10 15:40:16 +0100305 algorithmState.itemsInBottomStack += 1.0f;
306 if (algorithmState.itemsInBottomStack < MAX_ITEMS_IN_BOTTOM_STACK) {
307 // We are visually entering the bottom stack
Selim Cinek343e6e22014-04-11 21:23:30 +0200308 currentYPosition = transitioningPositionStart
Jorim Jaggife40f7d2014-04-28 15:20:04 +0200309 + mBottomStackIndentationFunctor.getValue(algorithmState.itemsInBottomStack)
310 - mPaddingBetweenElements;
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200311 childViewState.location = StackScrollState.ViewState.LOCATION_BOTTOM_STACK_PEEKING;
Selim Cinek67b22602014-03-10 15:40:16 +0100312 } else {
313 // we are fully inside the stack
314 if (algorithmState.itemsInBottomStack > MAX_ITEMS_IN_BOTTOM_STACK + 2) {
315 childViewState.alpha = 0.0f;
316 } else if (algorithmState.itemsInBottomStack
317 > MAX_ITEMS_IN_BOTTOM_STACK + 1) {
318 childViewState.alpha = 1.0f - algorithmState.partialInBottom;
319 }
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200320 childViewState.location = StackScrollState.ViewState.LOCATION_BOTTOM_STACK_HIDDEN;
Selim Cinek343e6e22014-04-11 21:23:30 +0200321 currentYPosition = mLayoutHeight;
Selim Cinek67b22602014-03-10 15:40:16 +0100322 }
Selim Cinek343e6e22014-04-11 21:23:30 +0200323 childViewState.yTranslation = currentYPosition - childHeight;
324 clampPositionToTopStackEnd(childViewState, childHeight);
Selim Cinek67b22602014-03-10 15:40:16 +0100325 }
326
Selim Cinek343e6e22014-04-11 21:23:30 +0200327 private void updateStateForTopStackChild(StackScrollAlgorithmState algorithmState,
328 int numberOfElementsCompletelyIn, int i, int childHeight,
329 StackScrollState.ViewState childViewState, float scrollOffset) {
Selim Cinek67b22602014-03-10 15:40:16 +0100330
Selim Cinek67b22602014-03-10 15:40:16 +0100331
332 // First we calculate the index relative to the current stack window of size at most
333 // {@link #MAX_ITEMS_IN_TOP_STACK}
Selim Cinek343e6e22014-04-11 21:23:30 +0200334 int paddedIndex = i - 1
Selim Cinek67b22602014-03-10 15:40:16 +0100335 - Math.max(numberOfElementsCompletelyIn - MAX_ITEMS_IN_TOP_STACK, 0);
336 if (paddedIndex >= 0) {
Selim Cinek343e6e22014-04-11 21:23:30 +0200337
Selim Cinek67b22602014-03-10 15:40:16 +0100338 // We are currently visually entering the top stack
Selim Cinek343e6e22014-04-11 21:23:30 +0200339 float distanceToStack = childHeight - algorithmState.scrolledPixelsTop;
340 if (i == algorithmState.lastTopStackIndex && distanceToStack > mTopStackTotalSize) {
341
342 // Child is currently translating into stack but not yet inside slow down zone.
343 // Handle it like the regular scrollview.
344 childViewState.yTranslation = scrollOffset;
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200345 } else {
Selim Cinek343e6e22014-04-11 21:23:30 +0200346 // Apply stacking logic.
347 float numItemsBefore;
348 if (i == algorithmState.lastTopStackIndex) {
349 numItemsBefore = 1.0f - (distanceToStack / mTopStackTotalSize);
350 } else {
351 numItemsBefore = algorithmState.itemsInTopStack - i;
352 }
353 // The end position of the current child
354 float currentChildEndY = mCollapsedSize + mTopStackTotalSize -
355 mTopStackIndentationFunctor.getValue(numItemsBefore);
356 childViewState.yTranslation = currentChildEndY - childHeight;
Selim Cinek67b22602014-03-10 15:40:16 +0100357 }
Selim Cinek343e6e22014-04-11 21:23:30 +0200358 childViewState.location = StackScrollState.ViewState.LOCATION_TOP_STACK_PEEKING;
Selim Cinek67b22602014-03-10 15:40:16 +0100359 } else {
Selim Cinek343e6e22014-04-11 21:23:30 +0200360 if (paddedIndex == -1) {
361 childViewState.alpha = 1.0f - algorithmState.partialInTop;
362 } else {
363 // We are hidden behind the top card and faded out, so we can hide ourselves.
364 childViewState.alpha = 0.0f;
365 }
366 childViewState.yTranslation = mCollapsedSize - childHeight;
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200367 childViewState.location = StackScrollState.ViewState.LOCATION_TOP_STACK_HIDDEN;
Selim Cinek67b22602014-03-10 15:40:16 +0100368 }
Selim Cinek343e6e22014-04-11 21:23:30 +0200369
370
Selim Cinek67b22602014-03-10 15:40:16 +0100371 }
372
373 /**
374 * Find the number of items in the top stack and update the result state if needed.
375 *
376 * @param resultState The result state to update if a height change of an child occurs
377 * @param algorithmState The state in which the current pass of the algorithm is currently in
378 * and which will be updated
379 */
380 private void findNumberOfItemsInTopStackAndUpdateState(StackScrollState resultState,
381 StackScrollAlgorithmState algorithmState) {
382
383 // The y Position if the element would be in a regular scrollView
384 float yPositionInScrollView = 0.0f;
Jorim Jaggid4a57442014-04-10 02:45:55 +0200385 int childCount = algorithmState.visibleChildren.size();
Selim Cinek67b22602014-03-10 15:40:16 +0100386
387 // find the number of elements in the top stack.
388 for (int i = 0; i < childCount; i++) {
Jorim Jaggibe565df2014-04-28 17:51:23 +0200389 ExpandableView child = algorithmState.visibleChildren.get(i);
Selim Cinek67b22602014-03-10 15:40:16 +0100390 StackScrollState.ViewState childViewState = resultState.getViewStateForView(child);
Jorim Jaggibe565df2014-04-28 17:51:23 +0200391 int childHeight = getMaxAllowedChildHeight(child);
Selim Cinek67b22602014-03-10 15:40:16 +0100392 float yPositionInScrollViewAfterElement = yPositionInScrollView
393 + childHeight
394 + mPaddingBetweenElements;
395 if (yPositionInScrollView < algorithmState.scrollY) {
Selim Cinek343e6e22014-04-11 21:23:30 +0200396 if (i == 0 && algorithmState.scrollY == mCollapsedSize) {
397
398 // The starting position of the bottom stack peek
399 int bottomPeekStart = mLayoutHeight - mBottomStackPeekSize;
400 // Collapse and expand the first child while the shade is being expanded
401 float maxHeight = mIsExpansionChanging && child == mFirstChildWhileExpanding
402 ? mFirstChildMaxHeight
403 : childHeight;
404 childViewState.height = (int) Math.max(Math.min(bottomPeekStart, maxHeight),
405 mCollapsedSize);
406 algorithmState.itemsInTopStack = 1.0f;
407
408 } else if (yPositionInScrollViewAfterElement < algorithmState.scrollY) {
Selim Cinek67b22602014-03-10 15:40:16 +0100409 // According to the regular scroll view we are fully off screen
410 algorithmState.itemsInTopStack += 1.0f;
Selim Cinek343e6e22014-04-11 21:23:30 +0200411 if (i == 0) {
Selim Cinek67b22602014-03-10 15:40:16 +0100412 childViewState.height = mCollapsedSize;
413 }
414 } else {
415 // According to the regular scroll view we are partially off screen
416 // If it is expanded we have to collapse it to a new size
417 float newSize = yPositionInScrollViewAfterElement
418 - mPaddingBetweenElements
419 - algorithmState.scrollY;
420
Selim Cinek343e6e22014-04-11 21:23:30 +0200421 if (i == 0) {
422 newSize += mCollapsedSize;
423 }
424
Selim Cinek67b22602014-03-10 15:40:16 +0100425 // How much did we scroll into this child
Selim Cinek343e6e22014-04-11 21:23:30 +0200426 algorithmState.scrolledPixelsTop = childHeight - newSize;
427 algorithmState.partialInTop = (algorithmState.scrolledPixelsTop) / (childHeight
Selim Cinek67b22602014-03-10 15:40:16 +0100428 + mPaddingBetweenElements);
429
430 // Our element can be expanded, so this can get negative
431 algorithmState.partialInTop = Math.max(0.0f, algorithmState.partialInTop);
432 algorithmState.itemsInTopStack += algorithmState.partialInTop;
Selim Cinek67b22602014-03-10 15:40:16 +0100433 newSize = Math.max(mCollapsedSize, newSize);
Selim Cinek343e6e22014-04-11 21:23:30 +0200434 if (i == 0) {
Selim Cinek67b22602014-03-10 15:40:16 +0100435 childViewState.height = (int) newSize;
Selim Cinek67b22602014-03-10 15:40:16 +0100436 }
Selim Cinek343e6e22014-04-11 21:23:30 +0200437 algorithmState.lastTopStackIndex = i;
438 break;
Selim Cinek67b22602014-03-10 15:40:16 +0100439 }
440 } else {
Selim Cinek343e6e22014-04-11 21:23:30 +0200441 algorithmState.lastTopStackIndex = i - 1;
Selim Cinek67b22602014-03-10 15:40:16 +0100442 // We are already past the stack so we can end the loop
443 break;
444 }
445 yPositionInScrollView = yPositionInScrollViewAfterElement;
446 }
447 }
448
449 /**
450 * Calculate the Z positions for all children based on the number of items in both stacks and
451 * save it in the resultState
452 *
453 * @param resultState The result state to update the zTranslation values
454 * @param algorithmState The state in which the current pass of the algorithm is currently in
455 */
456 private void updateZValuesForState(StackScrollState resultState,
457 StackScrollAlgorithmState algorithmState) {
Jorim Jaggid4a57442014-04-10 02:45:55 +0200458 int childCount = algorithmState.visibleChildren.size();
Selim Cinek67b22602014-03-10 15:40:16 +0100459 for (int i = 0; i < childCount; i++) {
Jorim Jaggid4a57442014-04-10 02:45:55 +0200460 View child = algorithmState.visibleChildren.get(i);
Selim Cinek67b22602014-03-10 15:40:16 +0100461 StackScrollState.ViewState childViewState = resultState.getViewStateForView(child);
462 if (i < algorithmState.itemsInTopStack) {
463 float stackIndex = algorithmState.itemsInTopStack - i;
464 stackIndex = Math.min(stackIndex, MAX_ITEMS_IN_TOP_STACK + 2);
465 childViewState.zTranslation = mZBasicHeight
466 + stackIndex * mZDistanceBetweenElements;
467 } else if (i > (childCount - 1 - algorithmState.itemsInBottomStack)) {
468 float numItemsAbove = i - (childCount - 1 - algorithmState.itemsInBottomStack);
469 float translationZ = mZBasicHeight
470 - numItemsAbove * mZDistanceBetweenElements;
471 childViewState.zTranslation = translationZ;
472 } else {
473 childViewState.zTranslation = mZBasicHeight;
474 }
475 }
476 }
477
Selim Cinek343e6e22014-04-11 21:23:30 +0200478 public int getLayoutHeight() {
Selim Cinek67b22602014-03-10 15:40:16 +0100479 return mLayoutHeight;
480 }
481
Selim Cinek343e6e22014-04-11 21:23:30 +0200482 public void setLayoutHeight(int layoutHeight) {
Selim Cinek67b22602014-03-10 15:40:16 +0100483 this.mLayoutHeight = layoutHeight;
484 }
485
Selim Cinek1685e632014-04-08 02:27:49 +0200486 public void onExpansionStarted(StackScrollState currentState) {
487 mIsExpansionChanging = true;
488 mExpandedOnStart = mIsExpanded;
489 ViewGroup hostView = currentState.getHostView();
490 updateFirstChildHeightWhileExpanding(hostView);
491 }
492
493 private void updateFirstChildHeightWhileExpanding(ViewGroup hostView) {
Jorim Jaggibe565df2014-04-28 17:51:23 +0200494 mFirstChildWhileExpanding = (ExpandableView) findFirstVisibleChild(hostView);
Jorim Jaggi9f347ae2014-04-11 00:47:18 +0200495 if (mFirstChildWhileExpanding != null) {
Selim Cinek1685e632014-04-08 02:27:49 +0200496 if (mExpandedOnStart) {
497
498 // We are collapsing the shade, so the first child can get as most as high as the
499 // current height.
Jorim Jaggibe565df2014-04-28 17:51:23 +0200500 mFirstChildMaxHeight = mFirstChildWhileExpanding.getActualHeight();
Selim Cinek1685e632014-04-08 02:27:49 +0200501 } else {
502
503 // We are expanding the shade, expand it to its full height.
Selim Cinekb6e0e122014-04-23 17:24:37 +0200504 if (mFirstChildWhileExpanding.getWidth() == 0) {
505
506 // This child was not layouted yet, wait for a layout pass
507 mFirstChildWhileExpanding
508 .addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
509 @Override
510 public void onLayoutChange(View v, int left, int top, int right,
511 int bottom, int oldLeft, int oldTop, int oldRight,
512 int oldBottom) {
Selim Cinek2ba5f1f2014-04-28 20:23:30 +0200513 if (mFirstChildWhileExpanding != null) {
514 mFirstChildMaxHeight = getMaxAllowedChildHeight(
515 mFirstChildWhileExpanding);
516 } else {
517 mFirstChildMaxHeight = 0;
518 }
519 v.removeOnLayoutChangeListener(this);
Selim Cinekb6e0e122014-04-23 17:24:37 +0200520 }
521 });
522 } else {
523 mFirstChildMaxHeight = getMaxAllowedChildHeight(mFirstChildWhileExpanding);
524 }
Selim Cinek1685e632014-04-08 02:27:49 +0200525 }
526 } else {
Selim Cinek1685e632014-04-08 02:27:49 +0200527 mFirstChildMaxHeight = 0;
528 }
529 }
530
Jorim Jaggi9f347ae2014-04-11 00:47:18 +0200531 private View findFirstVisibleChild(ViewGroup container) {
532 int childCount = container.getChildCount();
533 for (int i = 0; i < childCount; i++) {
534 View child = container.getChildAt(i);
535 if (child.getVisibility() != View.GONE) {
536 return child;
537 }
538 }
539 return null;
540 }
541
Selim Cinek1685e632014-04-08 02:27:49 +0200542 public void onExpansionStopped() {
543 mIsExpansionChanging = false;
544 mFirstChildWhileExpanding = null;
545 }
546
547 public void setIsExpanded(boolean isExpanded) {
548 this.mIsExpanded = isExpanded;
549 }
550
551 public void notifyChildrenChanged(ViewGroup hostView) {
552 if (mIsExpansionChanging) {
553 updateFirstChildHeightWhileExpanding(hostView);
554 }
555 }
556
Selim Cinek67b22602014-03-10 15:40:16 +0100557 class StackScrollAlgorithmState {
558
559 /**
560 * The scroll position of the algorithm
561 */
562 public int scrollY;
563
564 /**
565 * The quantity of items which are in the top stack.
566 */
567 public float itemsInTopStack;
568
569 /**
570 * how far in is the element currently transitioning into the top stack
571 */
572 public float partialInTop;
573
574 /**
Selim Cinek343e6e22014-04-11 21:23:30 +0200575 * The number of pixels the last child in the top stack has scrolled in to the stack
576 */
577 public float scrolledPixelsTop;
578
579 /**
Selim Cinek67b22602014-03-10 15:40:16 +0100580 * The last item index which is in the top stack.
Selim Cinek67b22602014-03-10 15:40:16 +0100581 */
582 public int lastTopStackIndex;
583
584 /**
585 * The quantity of items which are in the bottom stack.
586 */
587 public float itemsInBottomStack;
588
589 /**
590 * how far in is the element currently transitioning into the bottom stack
591 */
592 public float partialInBottom;
Jorim Jaggid4a57442014-04-10 02:45:55 +0200593
594 /**
595 * The children from the host view which are not gone.
596 */
Jorim Jaggibe565df2014-04-28 17:51:23 +0200597 public final ArrayList<ExpandableView> visibleChildren = new ArrayList<ExpandableView>();
Selim Cinek67b22602014-03-10 15:40:16 +0100598 }
599
600}