1 2 /* 3 * Copyright (C) 2008 The Android Open Source Project 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package com.android.launcher3.dragndrop; 19 20 import static android.view.View.MeasureSpec.EXACTLY; 21 import static android.view.View.MeasureSpec.getMode; 22 import static android.view.View.MeasureSpec.getSize; 23 24 import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent; 25 26 import android.animation.Animator; 27 import android.animation.AnimatorListenerAdapter; 28 import android.animation.TimeInterpolator; 29 import android.animation.ValueAnimator; 30 import android.animation.ValueAnimator.AnimatorUpdateListener; 31 import android.content.Context; 32 import android.content.res.Resources; 33 import android.graphics.Canvas; 34 import android.graphics.Rect; 35 import android.util.AttributeSet; 36 import android.view.Gravity; 37 import android.view.KeyEvent; 38 import android.view.MotionEvent; 39 import android.view.View; 40 import android.view.accessibility.AccessibilityEvent; 41 import android.view.accessibility.AccessibilityManager; 42 import android.view.animation.Interpolator; 43 import android.widget.FrameLayout; 44 import android.widget.TextView; 45 46 import com.android.launcher3.AbstractFloatingView; 47 import com.android.launcher3.CellLayout; 48 import com.android.launcher3.DropTargetBar; 49 import com.android.launcher3.Launcher; 50 import com.android.launcher3.R; 51 import com.android.launcher3.graphics.RotationMode; 52 import com.android.launcher3.ShortcutAndWidgetContainer; 53 import com.android.launcher3.Workspace; 54 import com.android.launcher3.anim.Interpolators; 55 import com.android.launcher3.folder.Folder; 56 import com.android.launcher3.folder.FolderIcon; 57 import com.android.launcher3.graphics.WorkspaceAndHotseatScrim; 58 import com.android.launcher3.keyboard.ViewGroupFocusHelper; 59 import com.android.launcher3.uioverrides.UiFactory; 60 import com.android.launcher3.util.Thunk; 61 import com.android.launcher3.views.BaseDragLayer; 62 import com.android.launcher3.views.Transposable; 63 64 import java.util.ArrayList; 65 66 /** 67 * A ViewGroup that coordinates dragging across its descendants 68 */ 69 public class DragLayer extends BaseDragLayer<Launcher> { 70 71 public static final int ALPHA_INDEX_OVERLAY = 0; 72 public static final int ALPHA_INDEX_LAUNCHER_LOAD = 1; 73 public static final int ALPHA_INDEX_TRANSITIONS = 2; 74 private static final int ALPHA_CHANNEL_COUNT = 3; 75 76 public static final int ANIMATION_END_DISAPPEAR = 0; 77 public static final int ANIMATION_END_REMAIN_VISIBLE = 2; 78 79 @Thunk DragController mDragController; 80 81 // Variables relating to animation of views after drop 82 private ValueAnimator mDropAnim = null; 83 private final TimeInterpolator mCubicEaseOutInterpolator = Interpolators.DEACCEL_1_5; 84 @Thunk DragView mDropView = null; 85 @Thunk int mAnchorViewInitialScrollX = 0; 86 @Thunk View mAnchorView = null; 87 88 private boolean mHoverPointClosesFolder = false; 89 90 private int mTopViewIndex; 91 private int mChildCountOnLastUpdate = -1; 92 93 // Related to adjacent page hints 94 private final ViewGroupFocusHelper mFocusIndicatorHelper; 95 private final WorkspaceAndHotseatScrim mScrim; 96 97 /** 98 * Used to create a new DragLayer from XML. 99 * 100 * @param context The application's context. 101 * @param attrs The attributes set containing the Workspace's customization values. 102 */ DragLayer(Context context, AttributeSet attrs)103 public DragLayer(Context context, AttributeSet attrs) { 104 super(context, attrs, ALPHA_CHANNEL_COUNT); 105 106 // Disable multitouch across the workspace/all apps/customize tray 107 setMotionEventSplittingEnabled(false); 108 setChildrenDrawingOrderEnabled(true); 109 110 mFocusIndicatorHelper = new ViewGroupFocusHelper(this); 111 mScrim = new WorkspaceAndHotseatScrim(this); 112 } 113 setup(DragController dragController, Workspace workspace)114 public void setup(DragController dragController, Workspace workspace) { 115 mDragController = dragController; 116 mScrim.setWorkspace(workspace); 117 recreateControllers(); 118 } 119 recreateControllers()120 public void recreateControllers() { 121 mControllers = UiFactory.createTouchControllers(mActivity); 122 } 123 getFocusIndicatorHelper()124 public ViewGroupFocusHelper getFocusIndicatorHelper() { 125 return mFocusIndicatorHelper; 126 } 127 128 @Override dispatchKeyEvent(KeyEvent event)129 public boolean dispatchKeyEvent(KeyEvent event) { 130 return mDragController.dispatchKeyEvent(event) || super.dispatchKeyEvent(event); 131 } 132 isEventOverAccessibleDropTargetBar(MotionEvent ev)133 private boolean isEventOverAccessibleDropTargetBar(MotionEvent ev) { 134 return isInAccessibleDrag() && isEventOverView(mActivity.getDropTargetBar(), ev); 135 } 136 137 @Override onInterceptHoverEvent(MotionEvent ev)138 public boolean onInterceptHoverEvent(MotionEvent ev) { 139 if (mActivity == null || mActivity.getWorkspace() == null) { 140 return false; 141 } 142 AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity); 143 if (!(topView instanceof Folder)) { 144 return false; 145 } else { 146 AccessibilityManager accessibilityManager = (AccessibilityManager) 147 getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); 148 if (accessibilityManager.isTouchExplorationEnabled()) { 149 Folder currentFolder = (Folder) topView; 150 final int action = ev.getAction(); 151 boolean isOverFolderOrSearchBar; 152 switch (action) { 153 case MotionEvent.ACTION_HOVER_ENTER: 154 isOverFolderOrSearchBar = isEventOverView(topView, ev) || 155 isEventOverAccessibleDropTargetBar(ev); 156 if (!isOverFolderOrSearchBar) { 157 sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName()); 158 mHoverPointClosesFolder = true; 159 return true; 160 } 161 mHoverPointClosesFolder = false; 162 break; 163 case MotionEvent.ACTION_HOVER_MOVE: 164 isOverFolderOrSearchBar = isEventOverView(topView, ev) || 165 isEventOverAccessibleDropTargetBar(ev); 166 if (!isOverFolderOrSearchBar && !mHoverPointClosesFolder) { 167 sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName()); 168 mHoverPointClosesFolder = true; 169 return true; 170 } else if (!isOverFolderOrSearchBar) { 171 return true; 172 } 173 mHoverPointClosesFolder = false; 174 } 175 } 176 } 177 return false; 178 } 179 sendTapOutsideFolderAccessibilityEvent(boolean isEditingName)180 private void sendTapOutsideFolderAccessibilityEvent(boolean isEditingName) { 181 int stringId = isEditingName ? R.string.folder_tap_to_rename : R.string.folder_tap_to_close; 182 sendCustomAccessibilityEvent( 183 this, AccessibilityEvent.TYPE_VIEW_FOCUSED, getContext().getString(stringId)); 184 } 185 186 @Override onHoverEvent(MotionEvent ev)187 public boolean onHoverEvent(MotionEvent ev) { 188 // If we've received this, we've already done the necessary handling 189 // in onInterceptHoverEvent. Return true to consume the event. 190 return false; 191 } 192 193 isInAccessibleDrag()194 private boolean isInAccessibleDrag() { 195 return mActivity.getAccessibilityDelegate().isInAccessibleDrag(); 196 } 197 198 @Override onRequestSendAccessibilityEvent(View child, AccessibilityEvent event)199 public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) { 200 if (isInAccessibleDrag() && child instanceof DropTargetBar) { 201 return true; 202 } 203 return super.onRequestSendAccessibilityEvent(child, event); 204 } 205 206 @Override addChildrenForAccessibility(ArrayList<View> childrenForAccessibility)207 public void addChildrenForAccessibility(ArrayList<View> childrenForAccessibility) { 208 View topView = AbstractFloatingView.getTopOpenViewWithType(mActivity, 209 AbstractFloatingView.TYPE_ACCESSIBLE); 210 if (topView != null) { 211 addAccessibleChildToList(topView, childrenForAccessibility); 212 if (isInAccessibleDrag()) { 213 addAccessibleChildToList(mActivity.getDropTargetBar(), childrenForAccessibility); 214 } 215 } else { 216 super.addChildrenForAccessibility(childrenForAccessibility); 217 } 218 } 219 220 @Override dispatchUnhandledMove(View focused, int direction)221 public boolean dispatchUnhandledMove(View focused, int direction) { 222 return super.dispatchUnhandledMove(focused, direction) 223 || mDragController.dispatchUnhandledMove(focused, direction); 224 } 225 226 @Override dispatchTouchEvent(MotionEvent ev)227 public boolean dispatchTouchEvent(MotionEvent ev) { 228 ev.offsetLocation(getTranslationX(), 0); 229 try { 230 return super.dispatchTouchEvent(ev); 231 } finally { 232 ev.offsetLocation(-getTranslationX(), 0); 233 } 234 } 235 animateViewIntoPosition(DragView dragView, final int[] pos, float alpha, float scaleX, float scaleY, int animationEndStyle, Runnable onFinishRunnable, int duration)236 public void animateViewIntoPosition(DragView dragView, final int[] pos, float alpha, 237 float scaleX, float scaleY, int animationEndStyle, Runnable onFinishRunnable, 238 int duration) { 239 Rect r = new Rect(); 240 getViewRectRelativeToSelf(dragView, r); 241 final int fromX = r.left; 242 final int fromY = r.top; 243 244 animateViewIntoPosition(dragView, fromX, fromY, pos[0], pos[1], alpha, 1, 1, scaleX, scaleY, 245 onFinishRunnable, animationEndStyle, duration, null); 246 } 247 animateViewIntoPosition(DragView dragView, final View child, View anchorView)248 public void animateViewIntoPosition(DragView dragView, final View child, View anchorView) { 249 animateViewIntoPosition(dragView, child, -1, anchorView); 250 } 251 animateViewIntoPosition(DragView dragView, final View child, int duration, View anchorView)252 public void animateViewIntoPosition(DragView dragView, final View child, int duration, 253 View anchorView) { 254 ShortcutAndWidgetContainer parentChildren = (ShortcutAndWidgetContainer) child.getParent(); 255 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); 256 parentChildren.measureChild(child); 257 258 Rect r = new Rect(); 259 getViewRectRelativeToSelf(dragView, r); 260 261 float coord[] = new float[2]; 262 float childScale = child.getScaleX(); 263 coord[0] = lp.x + (child.getMeasuredWidth() * (1 - childScale) / 2); 264 coord[1] = lp.y + (child.getMeasuredHeight() * (1 - childScale) / 2); 265 266 // Since the child hasn't necessarily been laid out, we force the lp to be updated with 267 // the correct coordinates (above) and use these to determine the final location 268 float scale = getDescendantCoordRelativeToSelf((View) child.getParent(), coord); 269 // We need to account for the scale of the child itself, as the above only accounts for 270 // for the scale in parents. 271 scale *= childScale; 272 int toX = Math.round(coord[0]); 273 int toY = Math.round(coord[1]); 274 float toScale = scale; 275 if (child instanceof TextView) { 276 TextView tv = (TextView) child; 277 // Account for the source scale of the icon (ie. from AllApps to Workspace, in which 278 // the workspace may have smaller icon bounds). 279 toScale = scale / dragView.getIntrinsicIconScaleFactor(); 280 281 // The child may be scaled (always about the center of the view) so to account for it, 282 // we have to offset the position by the scaled size. Once we do that, we can center 283 // the drag view about the scaled child view. 284 toY += Math.round(toScale * tv.getPaddingTop()); 285 toY -= dragView.getMeasuredHeight() * (1 - toScale) / 2; 286 if (dragView.getDragVisualizeOffset() != null) { 287 toY -= Math.round(toScale * dragView.getDragVisualizeOffset().y); 288 } 289 290 toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2; 291 } else if (child instanceof FolderIcon) { 292 // Account for holographic blur padding on the drag view 293 toY += Math.round(scale * (child.getPaddingTop() - dragView.getDragRegionTop())); 294 toY -= scale * dragView.getBlurSizeOutline() / 2; 295 toY -= (1 - scale) * dragView.getMeasuredHeight() / 2; 296 // Center in the x coordinate about the target's drawable 297 toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2; 298 } else { 299 toY -= (Math.round(scale * (dragView.getHeight() - child.getMeasuredHeight()))) / 2; 300 toX -= (Math.round(scale * (dragView.getMeasuredWidth() 301 - child.getMeasuredWidth()))) / 2; 302 } 303 304 final int fromX = r.left; 305 final int fromY = r.top; 306 child.setVisibility(INVISIBLE); 307 Runnable onCompleteRunnable = () -> child.setVisibility(VISIBLE); 308 animateViewIntoPosition(dragView, fromX, fromY, toX, toY, 1, 1, 1, toScale, toScale, 309 onCompleteRunnable, ANIMATION_END_DISAPPEAR, duration, anchorView); 310 } 311 animateViewIntoPosition(final DragView view, final int fromX, final int fromY, final int toX, final int toY, float finalAlpha, float initScaleX, float initScaleY, float finalScaleX, float finalScaleY, Runnable onCompleteRunnable, int animationEndStyle, int duration, View anchorView)312 public void animateViewIntoPosition(final DragView view, final int fromX, final int fromY, 313 final int toX, final int toY, float finalAlpha, float initScaleX, float initScaleY, 314 float finalScaleX, float finalScaleY, Runnable onCompleteRunnable, 315 int animationEndStyle, int duration, View anchorView) { 316 Rect from = new Rect(fromX, fromY, fromX + 317 view.getMeasuredWidth(), fromY + view.getMeasuredHeight()); 318 Rect to = new Rect(toX, toY, toX + view.getMeasuredWidth(), toY + view.getMeasuredHeight()); 319 animateView(view, from, to, finalAlpha, initScaleX, initScaleY, finalScaleX, finalScaleY, duration, 320 null, null, onCompleteRunnable, animationEndStyle, anchorView); 321 } 322 323 /** 324 * This method animates a view at the end of a drag and drop animation. 325 * 326 * @param view The view to be animated. This view is drawn directly into DragLayer, and so 327 * doesn't need to be a child of DragLayer. 328 * @param from The initial location of the view. Only the left and top parameters are used. 329 * @param to The final location of the view. Only the left and top parameters are used. This 330 * location doesn't account for scaling, and so should be centered about the desired 331 * final location (including scaling). 332 * @param finalAlpha The final alpha of the view, in case we want it to fade as it animates. 333 * @param finalScaleX The final scale of the view. The view is scaled about its center. 334 * @param finalScaleY The final scale of the view. The view is scaled about its center. 335 * @param duration The duration of the animation. 336 * @param motionInterpolator The interpolator to use for the location of the view. 337 * @param alphaInterpolator The interpolator to use for the alpha of the view. 338 * @param onCompleteRunnable Optional runnable to run on animation completion. 339 * @param animationEndStyle Whether or not to fade out the view once the animation completes. 340 * {@link #ANIMATION_END_DISAPPEAR} or {@link #ANIMATION_END_REMAIN_VISIBLE}. 341 * @param anchorView If not null, this represents the view which the animated view stays 342 * anchored to in case scrolling is currently taking place. Note: currently this is 343 * only used for the X dimension for the case of the workspace. 344 */ animateView(final DragView view, final Rect from, final Rect to, final float finalAlpha, final float initScaleX, final float initScaleY, final float finalScaleX, final float finalScaleY, int duration, final Interpolator motionInterpolator, final Interpolator alphaInterpolator, final Runnable onCompleteRunnable, final int animationEndStyle, View anchorView)345 public void animateView(final DragView view, final Rect from, final Rect to, 346 final float finalAlpha, final float initScaleX, final float initScaleY, 347 final float finalScaleX, final float finalScaleY, int duration, 348 final Interpolator motionInterpolator, final Interpolator alphaInterpolator, 349 final Runnable onCompleteRunnable, final int animationEndStyle, View anchorView) { 350 351 // Calculate the duration of the animation based on the object's distance 352 final float dist = (float) Math.hypot(to.left - from.left, to.top - from.top); 353 final Resources res = getResources(); 354 final float maxDist = (float) res.getInteger(R.integer.config_dropAnimMaxDist); 355 356 // If duration < 0, this is a cue to compute the duration based on the distance 357 if (duration < 0) { 358 duration = res.getInteger(R.integer.config_dropAnimMaxDuration); 359 if (dist < maxDist) { 360 duration *= mCubicEaseOutInterpolator.getInterpolation(dist / maxDist); 361 } 362 duration = Math.max(duration, res.getInteger(R.integer.config_dropAnimMinDuration)); 363 } 364 365 // Fall back to cubic ease out interpolator for the animation if none is specified 366 TimeInterpolator interpolator = null; 367 if (alphaInterpolator == null || motionInterpolator == null) { 368 interpolator = mCubicEaseOutInterpolator; 369 } 370 371 // Animate the view 372 final float initAlpha = view.getAlpha(); 373 final float dropViewScale = view.getScaleX(); 374 AnimatorUpdateListener updateCb = new AnimatorUpdateListener() { 375 @Override 376 public void onAnimationUpdate(ValueAnimator animation) { 377 final float percent = (Float) animation.getAnimatedValue(); 378 final int width = view.getMeasuredWidth(); 379 final int height = view.getMeasuredHeight(); 380 381 float alphaPercent = alphaInterpolator == null ? percent : 382 alphaInterpolator.getInterpolation(percent); 383 float motionPercent = motionInterpolator == null ? percent : 384 motionInterpolator.getInterpolation(percent); 385 386 float initialScaleX = initScaleX * dropViewScale; 387 float initialScaleY = initScaleY * dropViewScale; 388 float scaleX = finalScaleX * percent + initialScaleX * (1 - percent); 389 float scaleY = finalScaleY * percent + initialScaleY * (1 - percent); 390 float alpha = finalAlpha * alphaPercent + initAlpha * (1 - alphaPercent); 391 392 float fromLeft = from.left + (initialScaleX - 1f) * width / 2; 393 float fromTop = from.top + (initialScaleY - 1f) * height / 2; 394 395 int x = (int) (fromLeft + Math.round(((to.left - fromLeft) * motionPercent))); 396 int y = (int) (fromTop + Math.round(((to.top - fromTop) * motionPercent))); 397 398 int anchorAdjust = mAnchorView == null ? 0 : (int) (mAnchorView.getScaleX() * 399 (mAnchorViewInitialScrollX - mAnchorView.getScrollX())); 400 401 int xPos = x - mDropView.getScrollX() + anchorAdjust; 402 int yPos = y - mDropView.getScrollY(); 403 404 mDropView.setTranslationX(xPos); 405 mDropView.setTranslationY(yPos); 406 mDropView.setScaleX(scaleX); 407 mDropView.setScaleY(scaleY); 408 mDropView.setAlpha(alpha); 409 } 410 }; 411 animateView(view, updateCb, duration, interpolator, onCompleteRunnable, animationEndStyle, 412 anchorView); 413 } 414 animateView(final DragView view, AnimatorUpdateListener updateCb, int duration, TimeInterpolator interpolator, final Runnable onCompleteRunnable, final int animationEndStyle, View anchorView)415 public void animateView(final DragView view, AnimatorUpdateListener updateCb, int duration, 416 TimeInterpolator interpolator, final Runnable onCompleteRunnable, 417 final int animationEndStyle, View anchorView) { 418 // Clean up the previous animations 419 if (mDropAnim != null) mDropAnim.cancel(); 420 421 // Show the drop view if it was previously hidden 422 mDropView = view; 423 mDropView.cancelAnimation(); 424 mDropView.requestLayout(); 425 426 // Set the anchor view if the page is scrolling 427 if (anchorView != null) { 428 mAnchorViewInitialScrollX = anchorView.getScrollX(); 429 } 430 mAnchorView = anchorView; 431 432 // Create and start the animation 433 mDropAnim = new ValueAnimator(); 434 mDropAnim.setInterpolator(interpolator); 435 mDropAnim.setDuration(duration); 436 mDropAnim.setFloatValues(0f, 1f); 437 mDropAnim.addUpdateListener(updateCb); 438 mDropAnim.addListener(new AnimatorListenerAdapter() { 439 public void onAnimationEnd(Animator animation) { 440 if (onCompleteRunnable != null) { 441 onCompleteRunnable.run(); 442 } 443 switch (animationEndStyle) { 444 case ANIMATION_END_DISAPPEAR: 445 clearAnimatedView(); 446 break; 447 case ANIMATION_END_REMAIN_VISIBLE: 448 break; 449 } 450 mDropAnim = null; 451 } 452 }); 453 mDropAnim.start(); 454 } 455 clearAnimatedView()456 public void clearAnimatedView() { 457 if (mDropAnim != null) { 458 mDropAnim.cancel(); 459 } 460 mDropAnim = null; 461 if (mDropView != null) { 462 mDragController.onDeferredEndDrag(mDropView); 463 } 464 mDropView = null; 465 invalidate(); 466 } 467 getAnimatedView()468 public View getAnimatedView() { 469 return mDropView; 470 } 471 472 @Override onViewAdded(View child)473 public void onViewAdded(View child) { 474 super.onViewAdded(child); 475 updateChildIndices(); 476 UiFactory.onLauncherStateOrFocusChanged(mActivity); 477 } 478 479 @Override onViewRemoved(View child)480 public void onViewRemoved(View child) { 481 super.onViewRemoved(child); 482 updateChildIndices(); 483 UiFactory.onLauncherStateOrFocusChanged(mActivity); 484 } 485 486 @Override bringChildToFront(View child)487 public void bringChildToFront(View child) { 488 super.bringChildToFront(child); 489 updateChildIndices(); 490 } 491 updateChildIndices()492 private void updateChildIndices() { 493 mTopViewIndex = -1; 494 int childCount = getChildCount(); 495 for (int i = 0; i < childCount; i++) { 496 if (getChildAt(i) instanceof DragView) { 497 mTopViewIndex = i; 498 } 499 } 500 mChildCountOnLastUpdate = childCount; 501 } 502 503 @Override getChildDrawingOrder(int childCount, int i)504 protected int getChildDrawingOrder(int childCount, int i) { 505 if (mChildCountOnLastUpdate != childCount) { 506 // between platform versions 17 and 18, behavior for onChildViewRemoved / Added changed. 507 // Pre-18, the child was not added / removed by the time of those callbacks. We need to 508 // force update our representation of things here to avoid crashing on pre-18 devices 509 // in certain instances. 510 updateChildIndices(); 511 } 512 513 // i represents the current draw iteration 514 if (mTopViewIndex == -1) { 515 // in general we do nothing 516 return i; 517 } else if (i == childCount - 1) { 518 // if we have a top index, we return it when drawing last item (highest z-order) 519 return mTopViewIndex; 520 } else if (i < mTopViewIndex) { 521 return i; 522 } else { 523 // for indexes greater than the top index, we fetch one item above to shift for the 524 // displacement of the top index 525 return i + 1; 526 } 527 } 528 529 @Override dispatchDraw(Canvas canvas)530 protected void dispatchDraw(Canvas canvas) { 531 // Draw the background below children. 532 mScrim.draw(canvas); 533 mFocusIndicatorHelper.draw(canvas); 534 super.dispatchDraw(canvas); 535 } 536 537 @Override onSizeChanged(int w, int h, int oldw, int oldh)538 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 539 super.onSizeChanged(w, h, oldw, oldh); 540 mScrim.setSize(w, h); 541 } 542 543 @Override setInsets(Rect insets)544 public void setInsets(Rect insets) { 545 super.setInsets(insets); 546 mScrim.onInsetsChanged(insets); 547 } 548 getScrim()549 public WorkspaceAndHotseatScrim getScrim() { 550 return mScrim; 551 } 552 553 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)554 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 555 RotationMode rotation = mActivity.getRotationMode(); 556 int count = getChildCount(); 557 558 if (!rotation.isTransposed 559 || getMode(widthMeasureSpec) != EXACTLY 560 || getMode(heightMeasureSpec) != EXACTLY) { 561 562 for (int i = 0; i < count; i++) { 563 final View child = getChildAt(i); 564 child.setRotation(rotation.surfaceRotation); 565 } 566 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 567 } else { 568 569 for (int i = 0; i < count; i++) { 570 final View child = getChildAt(i); 571 if (child.getVisibility() == GONE) { 572 continue; 573 } 574 if (!(child instanceof Transposable)) { 575 measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); 576 } else { 577 measureChildWithMargins(child, heightMeasureSpec, 0, widthMeasureSpec, 0); 578 579 child.setPivotX(child.getMeasuredWidth() / 2); 580 child.setPivotY(child.getMeasuredHeight() / 2); 581 child.setRotation(rotation.surfaceRotation); 582 } 583 } 584 setMeasuredDimension(getSize(widthMeasureSpec), getSize(heightMeasureSpec)); 585 } 586 } 587 588 @Override onLayout(boolean changed, int left, int top, int right, int bottom)589 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 590 RotationMode rotation = mActivity.getRotationMode(); 591 if (!rotation.isTransposed) { 592 super.onLayout(changed, left, top, right, bottom); 593 return; 594 } 595 596 final int count = getChildCount(); 597 598 final int parentWidth = right - left; 599 final int parentHeight = bottom - top; 600 601 for (int i = 0; i < count; i++) { 602 final View child = getChildAt(i); 603 if (child.getVisibility() == GONE) { 604 continue; 605 } 606 607 final FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) child.getLayoutParams(); 608 609 if (lp instanceof LayoutParams) { 610 final LayoutParams dlp = (LayoutParams) lp; 611 if (dlp.customPosition) { 612 child.layout(dlp.x, dlp.y, dlp.x + dlp.width, dlp.y + dlp.height); 613 continue; 614 } 615 } 616 617 final int width = child.getMeasuredWidth(); 618 final int height = child.getMeasuredHeight(); 619 620 int childLeft; 621 int childTop; 622 623 int gravity = lp.gravity; 624 if (gravity == -1) { 625 gravity = Gravity.TOP | Gravity.START; 626 } 627 628 final int layoutDirection = getLayoutDirection(); 629 630 int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); 631 632 if (child instanceof Transposable) { 633 absoluteGravity = rotation.toNaturalGravity(absoluteGravity); 634 635 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { 636 case Gravity.CENTER_HORIZONTAL: 637 childTop = (parentHeight - height) / 2 + 638 lp.topMargin - lp.bottomMargin; 639 break; 640 case Gravity.RIGHT: 641 childTop = width / 2 + lp.rightMargin - height / 2; 642 break; 643 case Gravity.LEFT: 644 default: 645 childTop = parentHeight - lp.leftMargin - width / 2 - height / 2; 646 } 647 648 switch (absoluteGravity & Gravity.VERTICAL_GRAVITY_MASK) { 649 case Gravity.CENTER_VERTICAL: 650 childLeft = (parentWidth - width) / 2 + 651 lp.leftMargin - lp.rightMargin; 652 break; 653 case Gravity.BOTTOM: 654 childLeft = parentWidth - width / 2 - height / 2 - lp.bottomMargin; 655 break; 656 case Gravity.TOP: 657 default: 658 childLeft = height / 2 - width / 2 + lp.topMargin; 659 } 660 } else { 661 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { 662 case Gravity.CENTER_HORIZONTAL: 663 childLeft = (parentWidth - width) / 2 + 664 lp.leftMargin - lp.rightMargin; 665 break; 666 case Gravity.RIGHT: 667 childLeft = parentWidth - width - lp.rightMargin; 668 break; 669 case Gravity.LEFT: 670 default: 671 childLeft = lp.leftMargin; 672 } 673 674 switch (absoluteGravity & Gravity.VERTICAL_GRAVITY_MASK) { 675 case Gravity.TOP: 676 childTop = lp.topMargin; 677 break; 678 case Gravity.CENTER_VERTICAL: 679 childTop = (parentHeight - height) / 2 + 680 lp.topMargin - lp.bottomMargin; 681 break; 682 case Gravity.BOTTOM: 683 childTop = parentHeight - height - lp.bottomMargin; 684 break; 685 default: 686 childTop = lp.topMargin; 687 } 688 } 689 690 child.layout(childLeft, childTop, childLeft + width, childTop + height); 691 } 692 } 693 } 694