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