TaskStackViewLayoutAlgorithm.java revision 012ef36a6c5e9745d112c734aed916cab052558c
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.recents.views; 18 19import android.graphics.Rect; 20import com.android.systemui.recents.RecentsConfiguration; 21import com.android.systemui.recents.misc.Console; 22import com.android.systemui.recents.misc.Utilities; 23import com.android.systemui.recents.model.Task; 24 25import java.util.ArrayList; 26import java.util.HashMap; 27 28/* The layout logic for a TaskStackView. 29 * 30 * We are using a curve that defines the curve of the tasks as that go back in the recents list. 31 * The curve is defined such that at curve progress p = 0 is the end of the curve (the top of the 32 * stack rect), and p = 1 at the start of the curve and the bottom of the stack rect. 33 */ 34public class TaskStackViewLayoutAlgorithm { 35 36 // These are all going to change 37 static final float StackPeekMinScale = 0.825f; // The min scale of the last card in the peek area 38 39 RecentsConfiguration mConfig; 40 41 // The various rects that define the stack view 42 Rect mViewRect = new Rect(); 43 Rect mStackVisibleRect = new Rect(); 44 Rect mStackRect = new Rect(); 45 Rect mTaskRect = new Rect(); 46 47 // The min/max scroll progress 48 float mMinScrollP; 49 float mMaxScrollP; 50 float mInitialScrollP; 51 int mWithinAffiliationOffset; 52 int mBetweenAffiliationOffset; 53 HashMap<Task.TaskKey, Float> mTaskProgressMap = new HashMap<Task.TaskKey, Float>(); 54 55 // Log function 56 static final float XScale = 1.75f; // The large the XScale, the longer the flat area of the curve 57 static final float LogBase = 300; 58 static final int PrecisionSteps = 250; 59 static float[] xp; 60 static float[] px; 61 62 public TaskStackViewLayoutAlgorithm(RecentsConfiguration config) { 63 mConfig = config; 64 mWithinAffiliationOffset = mConfig.taskBarHeight; 65 mBetweenAffiliationOffset = 4 * mConfig.taskBarHeight; 66 67 // Precompute the path 68 initializeCurve(); 69 } 70 71 /** Computes the stack and task rects */ 72 public void computeRects(int windowWidth, int windowHeight, Rect taskStackBounds) { 73 // Compute the stack rects 74 mViewRect.set(0, 0, windowWidth, windowHeight); 75 mStackRect.set(taskStackBounds); 76 mStackVisibleRect.set(taskStackBounds); 77 mStackVisibleRect.bottom = mViewRect.bottom; 78 79 int widthPadding = (int) (mConfig.taskStackWidthPaddingPct * mStackRect.width()); 80 int heightPadding = mConfig.taskStackTopPaddingPx; 81 mStackRect.inset(widthPadding, heightPadding); 82 83 // Compute the task rect 84 int size = mStackRect.width(); 85 int left = mStackRect.left + (mStackRect.width() - size) / 2; 86 mTaskRect.set(left, mStackRect.top, 87 left + size, mStackRect.top + size); 88 } 89 90 /** Computes the minimum and maximum scroll progress values */ 91 void computeMinMaxScroll(ArrayList<Task> tasks) { 92 // Clear the progress map 93 mTaskProgressMap.clear(); 94 95 // Return early if we have no tasks 96 if (tasks.isEmpty()) { 97 mMinScrollP = mMaxScrollP = 0; 98 return; 99 } 100 101 int taskHeight = mTaskRect.height(); 102 float pAtBottomOfStackRect = screenYToCurveProgress(mStackVisibleRect.bottom); 103 float pWithinAffiliateOffset = pAtBottomOfStackRect - 104 screenYToCurveProgress(mStackVisibleRect.bottom - mWithinAffiliationOffset); 105 float pBetweenAffiliateOffset = pAtBottomOfStackRect - 106 screenYToCurveProgress(mStackVisibleRect.bottom - mBetweenAffiliationOffset); 107 float pTaskHeightOffset = pAtBottomOfStackRect - 108 screenYToCurveProgress(mStackVisibleRect.bottom - taskHeight); 109 float pNavBarOffset = pAtBottomOfStackRect - 110 screenYToCurveProgress(mStackVisibleRect.bottom - (mStackVisibleRect.bottom - mStackRect.bottom)); 111 112 // Update the task offsets 113 float pAtBackMostCardTop = screenYToCurveProgress(mStackVisibleRect.top + 114 (mStackVisibleRect.height() - taskHeight) / 2); 115 float pAtFrontMostCardTop = pAtBackMostCardTop; 116 float pAtSecondFrontMostCardTop = pAtBackMostCardTop; 117 int taskCount = tasks.size(); 118 for (int i = 0; i < taskCount; i++) { 119 Task task = tasks.get(i); 120 mTaskProgressMap.put(task.key, pAtFrontMostCardTop); 121 122 if (i < (taskCount - 1)) { 123 // Increment the peek height 124 float pPeek = task.group.isFrontMostTask(task) ? pBetweenAffiliateOffset : 125 pWithinAffiliateOffset; 126 pAtSecondFrontMostCardTop = pAtFrontMostCardTop; 127 pAtFrontMostCardTop += pPeek; 128 } 129 } 130 131 mMinScrollP = 0f; 132 mMaxScrollP = pAtFrontMostCardTop - ((1f - pTaskHeightOffset - pNavBarOffset)); 133 mInitialScrollP = pAtSecondFrontMostCardTop - ((1f - pTaskHeightOffset - pNavBarOffset)); 134 } 135 136 /** Update/get the transform */ 137 public TaskViewTransform getStackTransform(Task task, float stackScroll, TaskViewTransform transformOut, 138 TaskViewTransform prevTransform) { 139 // Return early if we have an invalid index 140 if (task == null) { 141 transformOut.reset(); 142 return transformOut; 143 } 144 return getStackTransform(mTaskProgressMap.get(task.key), stackScroll, transformOut, prevTransform); 145 } 146 147 /** Update/get the transform */ 148 public TaskViewTransform getStackTransform(float taskProgress, float stackScroll, TaskViewTransform transformOut, TaskViewTransform prevTransform) { 149 float pTaskRelative = taskProgress - stackScroll; 150 float pBounded = Math.max(0, Math.min(pTaskRelative, 1f)); 151 // If the task top is outside of the bounds below the screen, then immediately reset it 152 if (pTaskRelative > 1f) { 153 transformOut.reset(); 154 return transformOut; 155 } 156 // The check for the top is trickier, since we want to show the next task if it is at all 157 // visible, even if p < 0. 158 if (pTaskRelative < 0f) { 159 if (prevTransform != null && Float.compare(prevTransform.p, 0f) <= 0) { 160 transformOut.reset(); 161 return transformOut; 162 } 163 } 164 float scale = curveProgressToScale(pBounded); 165 int scaleYOffset = (int) (((1f - scale) * mTaskRect.height()) / 2); 166 int minZ = mConfig.taskViewTranslationZMinPx; 167 int maxZ = mConfig.taskViewTranslationZMaxPx; 168 transformOut.scale = scale; 169 transformOut.translationY = curveProgressToScreenY(pBounded) - mStackVisibleRect.top - 170 scaleYOffset; 171 transformOut.translationZ = Math.max(minZ, minZ + (pBounded * (maxZ - minZ))); 172 transformOut.rect.set(mTaskRect); 173 transformOut.rect.offset(0, transformOut.translationY); 174 Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale); 175 transformOut.visible = true; 176 transformOut.p = pTaskRelative; 177 return transformOut; 178 } 179 180 /** 181 * Returns the scroll to such task top = 1f; 182 */ 183 float getStackScrollForTaskIndex(Task t) { 184 return mTaskProgressMap.get(t.key); 185 } 186 187 /** Initializes the curve. */ 188 public static void initializeCurve() { 189 if (xp != null && px != null) return; 190 xp = new float[PrecisionSteps + 1]; 191 px = new float[PrecisionSteps + 1]; 192 193 // Approximate f(x) 194 float[] fx = new float[PrecisionSteps + 1]; 195 float step = 1f / PrecisionSteps; 196 float x = 0; 197 for (int xStep = 0; xStep <= PrecisionSteps; xStep++) { 198 fx[xStep] = logFunc(x); 199 x += step; 200 } 201 // Calculate the arc length for x:1->0 202 float pLength = 0; 203 float[] dx = new float[PrecisionSteps + 1]; 204 dx[0] = 0; 205 for (int xStep = 1; xStep < PrecisionSteps; xStep++) { 206 dx[xStep] = (float) Math.sqrt(Math.pow(fx[xStep] - fx[xStep - 1], 2) + Math.pow(step, 2)); 207 pLength += dx[xStep]; 208 } 209 // Approximate p(x), a function of cumulative progress with x, normalized to 0..1 210 float p = 0; 211 px[0] = 0f; 212 px[PrecisionSteps] = 1f; 213 for (int xStep = 1; xStep <= PrecisionSteps; xStep++) { 214 p += Math.abs(dx[xStep] / pLength); 215 px[xStep] = p; 216 } 217 // Given p(x), calculate the inverse function x(p). This assumes that x(p) is also a valid 218 // function. 219 int xStep = 0; 220 p = 0; 221 xp[0] = 0f; 222 xp[PrecisionSteps] = 1f; 223 for (int pStep = 0; pStep < PrecisionSteps; pStep++) { 224 // Walk forward in px and find the x where px <= p && p < px+1 225 while (xStep < PrecisionSteps) { 226 if (px[xStep] > p) break; 227 xStep++; 228 } 229 // Now, px[xStep-1] <= p < px[xStep] 230 if (xStep == 0) { 231 xp[pStep] = 0; 232 } else { 233 // Find x such that proportionally, x is correct 234 float fraction = (p - px[xStep - 1]) / (px[xStep] - px[xStep - 1]); 235 x = (xStep - 1 + fraction) * step; 236 xp[pStep] = x; 237 } 238 p += step; 239 } 240 } 241 242 /** Reverses and scales out x. */ 243 static float reverse(float x) { 244 return (-x * XScale) + 1; 245 } 246 /** The log function describing the curve. */ 247 static float logFunc(float x) { 248 return 1f - (float) (Math.pow(LogBase, reverse(x))) / (LogBase); 249 } 250 /** The inverse of the log function describing the curve. */ 251 float invLogFunc(float y) { 252 return (float) (Math.log((1f - reverse(y)) * (LogBase - 1) + 1) / Math.log(LogBase)); 253 } 254 255 /** Converts from the progress along the curve to a screen coordinate. */ 256 int curveProgressToScreenY(float p) { 257 if (p < 0 || p > 1) return mStackVisibleRect.top + (int) (p * mStackVisibleRect.height()); 258 float pIndex = p * PrecisionSteps; 259 int pFloorIndex = (int) Math.floor(pIndex); 260 int pCeilIndex = (int) Math.ceil(pIndex); 261 float xFraction = 0; 262 if (pFloorIndex < PrecisionSteps && (pCeilIndex != pFloorIndex)) { 263 float pFraction = (pIndex - pFloorIndex) / (pCeilIndex - pFloorIndex); 264 xFraction = (xp[pCeilIndex] - xp[pFloorIndex]) * pFraction; 265 } 266 float x = xp[pFloorIndex] + xFraction; 267 return mStackVisibleRect.top + (int) (x * mStackVisibleRect.height()); 268 } 269 270 /** Converts from the progress along the curve to a scale. */ 271 float curveProgressToScale(float p) { 272 if (p < 0) return StackPeekMinScale; 273 if (p > 1) return 1f; 274 float scaleRange = (1f - StackPeekMinScale); 275 float scale = StackPeekMinScale + (p * scaleRange); 276 return scale; 277 } 278 279 /** Converts from a screen coordinate to the progress along the curve. */ 280 float screenYToCurveProgress(int screenY) { 281 float x = (float) (screenY - mStackVisibleRect.top) / mStackVisibleRect.height(); 282 if (x < 0 || x > 1) return x; 283 float xIndex = x * PrecisionSteps; 284 int xFloorIndex = (int) Math.floor(xIndex); 285 int xCeilIndex = (int) Math.ceil(xIndex); 286 float pFraction = 0; 287 if (xFloorIndex < PrecisionSteps && (xCeilIndex != xFloorIndex)) { 288 float xFraction = (xIndex - xFloorIndex) / (xCeilIndex - xFloorIndex); 289 pFraction = (px[xCeilIndex] - px[xFloorIndex]) * xFraction; 290 } 291 return px[xFloorIndex] + pFraction; 292 } 293}