2 * Copyright (C) 2008 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.launcher3.dragndrop;
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.TimeInterpolator;
22 import android.animation.ValueAnimator;
23 import android.animation.ValueAnimator.AnimatorUpdateListener;
24 import android.content.Context;
25 import android.content.res.Resources;
26 import android.graphics.Canvas;
27 import android.graphics.Color;
28 import android.graphics.Rect;
29 import android.graphics.Region;
30 import android.util.AttributeSet;
31 import android.view.KeyEvent;
32 import android.view.LayoutInflater;
33 import android.view.MotionEvent;
34 import android.view.View;
35 import android.view.ViewGroup;
36 import android.view.accessibility.AccessibilityEvent;
37 import android.view.accessibility.AccessibilityManager;
38 import android.view.animation.DecelerateInterpolator;
39 import android.view.animation.Interpolator;
40 import android.widget.FrameLayout;
41 import android.widget.TextView;
43 import com.android.launcher3.AbstractFloatingView;
44 import com.android.launcher3.AppWidgetResizeFrame;
45 import com.android.launcher3.CellLayout;
46 import com.android.launcher3.DropTargetBar;
47 import com.android.launcher3.ExtendedEditText;
48 import com.android.launcher3.InsettableFrameLayout;
49 import com.android.launcher3.Launcher;
50 import com.android.launcher3.LauncherAppWidgetHostView;
51 import com.android.launcher3.PinchToOverviewListener;
52 import com.android.launcher3.R;
53 import com.android.launcher3.ShortcutAndWidgetContainer;
54 import com.android.launcher3.Utilities;
55 import com.android.launcher3.allapps.AllAppsTransitionController;
56 import com.android.launcher3.config.FeatureFlags;
57 import com.android.launcher3.folder.Folder;
58 import com.android.launcher3.folder.FolderIcon;
59 import com.android.launcher3.keyboard.ViewGroupFocusHelper;
60 import com.android.launcher3.logging.LoggerUtils;
61 import com.android.launcher3.userevent.nano.LauncherLogProto;
62 import com.android.launcher3.util.Thunk;
63 import com.android.launcher3.util.TouchController;
64 import com.android.launcher3.widget.WidgetsAndMore;
66 import java.util.ArrayList;
69 * A ViewGroup that coordinates dragging across its descendants
71 public class DragLayer extends InsettableFrameLayout {
73 public static final int ANIMATION_END_DISAPPEAR = 0;
74 public static final int ANIMATION_END_REMAIN_VISIBLE = 2;
76 // Scrim color without any alpha component.
77 private static final int SCRIM_COLOR = Color.BLACK & 0x00FFFFFF;
79 private final int[] mTmpXY = new int[2];
81 @Thunk DragController mDragController;
83 private Launcher mLauncher;
85 // Variables relating to resizing widgets
86 private final boolean mIsRtl;
87 private AppWidgetResizeFrame mCurrentResizeFrame;
89 // Variables relating to animation of views after drop
90 private ValueAnimator mDropAnim = null;
91 private final TimeInterpolator mCubicEaseOutInterpolator = new DecelerateInterpolator(1.5f);
92 @Thunk DragView mDropView = null;
93 @Thunk int mAnchorViewInitialScrollX = 0;
94 @Thunk View mAnchorView = null;
96 private boolean mHoverPointClosesFolder = false;
97 private final Rect mHitRect = new Rect();
98 private final Rect mHighlightRect = new Rect();
100 private TouchCompleteListener mTouchCompleteListener;
102 private int mTopViewIndex;
103 private int mChildCountOnLastUpdate = -1;
106 private float mBackgroundAlpha = 0;
108 // Related to adjacent page hints
109 private final Rect mScrollChildPosition = new Rect();
110 private final ViewGroupFocusHelper mFocusIndicatorHelper;
112 // Related to pinch-to-go-to-overview gesture.
113 private PinchToOverviewListener mPinchListener = null;
115 // Handles all apps pull up interaction
116 private AllAppsTransitionController mAllAppsController;
118 private TouchController mActiveController;
120 * Used to create a new DragLayer from XML.
122 * @param context The application's context.
123 * @param attrs The attributes set containing the Workspace's customization values.
125 public DragLayer(Context context, AttributeSet attrs) {
126 super(context, attrs);
128 // Disable multitouch across the workspace/all apps/customize tray
129 setMotionEventSplittingEnabled(false);
130 setChildrenDrawingOrderEnabled(true);
132 mIsRtl = Utilities.isRtl(getResources());
133 mFocusIndicatorHelper = new ViewGroupFocusHelper(this);
136 public void setup(Launcher launcher, DragController dragController,
137 AllAppsTransitionController allAppsTransitionController) {
138 mLauncher = launcher;
139 mDragController = dragController;
140 mAllAppsController = allAppsTransitionController;
142 boolean isAccessibilityEnabled = ((AccessibilityManager) mLauncher.getSystemService(
143 Context.ACCESSIBILITY_SERVICE)).isEnabled();
144 onAccessibilityStateChanged(isAccessibilityEnabled);
147 public ViewGroupFocusHelper getFocusIndicatorHelper() {
148 return mFocusIndicatorHelper;
152 public boolean dispatchKeyEvent(KeyEvent event) {
153 return mDragController.dispatchKeyEvent(event) || super.dispatchKeyEvent(event);
156 public void onAccessibilityStateChanged(boolean isAccessibilityEnabled) {
157 mPinchListener = FeatureFlags.LAUNCHER3_DISABLE_PINCH_TO_OVERVIEW || isAccessibilityEnabled
158 ? null : new PinchToOverviewListener(mLauncher);
161 public boolean isEventOverPageIndicator(MotionEvent ev) {
162 return isEventOverView(mLauncher.getWorkspace().getPageIndicator(), ev);
165 public boolean isEventOverHotseat(MotionEvent ev) {
166 return isEventOverView(mLauncher.getHotseat(), ev);
169 private boolean isEventOverFolder(Folder folder, MotionEvent ev) {
170 return isEventOverView(folder, ev);
173 private boolean isEventOverDropTargetBar(MotionEvent ev) {
174 return isEventOverView(mLauncher.getDropTargetBar(), ev);
177 public boolean isEventOverView(View view, MotionEvent ev) {
178 getDescendantRectRelativeToSelf(view, mHitRect);
179 return mHitRect.contains((int) ev.getX(), (int) ev.getY());
182 private boolean handleTouchDown(MotionEvent ev, boolean intercept) {
183 AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mLauncher);
184 if (topView != null && intercept) {
185 ExtendedEditText textView = topView.getActiveTextView();
186 if (textView != null) {
187 if (!isEventOverView(textView, ev)) {
188 textView.dispatchBackKey();
191 } else if (!isEventOverView(topView, ev)) {
192 if (isInAccessibleDrag()) {
193 // Do not close the container if in drag and drop.
194 if (!isEventOverDropTargetBar(ev)) {
198 mLauncher.getUserEventDispatcher().logActionTapOutside(
199 LoggerUtils.newContainerTarget(topView.getLogContainerType()));
202 // We let touches on the original icon go through so that users can launch
203 // the app with one tap if they don't find a shortcut they want.
204 View extendedTouch = topView.getExtendedTouchView();
205 return extendedTouch == null || !isEventOverView(extendedTouch, ev);
213 public boolean onInterceptTouchEvent(MotionEvent ev) {
214 int action = ev.getAction();
216 if (action == MotionEvent.ACTION_DOWN) {
217 // Cancel discovery bounce animation when a user start interacting on anywhere on
218 // dray layer even if mAllAppsController is NOT the active controller.
219 // TODO: handle other input other than touch
220 mAllAppsController.cancelDiscoveryAnimation();
221 if (handleTouchDown(ev, true)) {
224 } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
225 if (mTouchCompleteListener != null) {
226 mTouchCompleteListener.onTouchComplete();
228 mTouchCompleteListener = null;
230 mActiveController = null;
232 if (mCurrentResizeFrame != null
233 && mCurrentResizeFrame.onControllerInterceptTouchEvent(ev)) {
234 mActiveController = mCurrentResizeFrame;
240 if (mDragController.onControllerInterceptTouchEvent(ev)) {
241 mActiveController = mDragController;
245 if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && mAllAppsController.onControllerInterceptTouchEvent(ev)) {
246 mActiveController = mAllAppsController;
250 WidgetsAndMore widgetsAndMore = WidgetsAndMore.getOpen(mLauncher);
251 if (widgetsAndMore != null && widgetsAndMore.onControllerInterceptTouchEvent(ev)) {
252 mActiveController = widgetsAndMore;
256 if (mPinchListener != null && mPinchListener.onControllerInterceptTouchEvent(ev)) {
257 // Stop listening for scrolling etc. (onTouchEvent() handles the rest of the pinch.)
258 mActiveController = mPinchListener;
265 public boolean onInterceptHoverEvent(MotionEvent ev) {
266 if (mLauncher == null || mLauncher.getWorkspace() == null) {
269 Folder currentFolder = Folder.getOpen(mLauncher);
270 if (currentFolder == null) {
273 AccessibilityManager accessibilityManager = (AccessibilityManager)
274 getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
275 if (accessibilityManager.isTouchExplorationEnabled()) {
276 final int action = ev.getAction();
277 boolean isOverFolderOrSearchBar;
279 case MotionEvent.ACTION_HOVER_ENTER:
280 isOverFolderOrSearchBar = isEventOverFolder(currentFolder, ev) ||
281 (isInAccessibleDrag() && isEventOverDropTargetBar(ev));
282 if (!isOverFolderOrSearchBar) {
283 sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName());
284 mHoverPointClosesFolder = true;
287 mHoverPointClosesFolder = false;
289 case MotionEvent.ACTION_HOVER_MOVE:
290 isOverFolderOrSearchBar = isEventOverFolder(currentFolder, ev) ||
291 (isInAccessibleDrag() && isEventOverDropTargetBar(ev));
292 if (!isOverFolderOrSearchBar && !mHoverPointClosesFolder) {
293 sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName());
294 mHoverPointClosesFolder = true;
296 } else if (!isOverFolderOrSearchBar) {
299 mHoverPointClosesFolder = false;
306 private void sendTapOutsideFolderAccessibilityEvent(boolean isEditingName) {
307 int stringId = isEditingName ? R.string.folder_tap_to_rename : R.string.folder_tap_to_close;
308 Utilities.sendCustomAccessibilityEvent(
309 this, AccessibilityEvent.TYPE_VIEW_FOCUSED, getContext().getString(stringId));
312 private boolean isInAccessibleDrag() {
313 return mLauncher.getAccessibilityDelegate().isInAccessibleDrag();
317 public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
318 // Shortcuts can appear above folder
319 View topView = AbstractFloatingView.getTopOpenView(mLauncher);
320 if (topView != null) {
321 if (child == topView) {
322 return super.onRequestSendAccessibilityEvent(child, event);
324 if (isInAccessibleDrag() && child instanceof DropTargetBar) {
325 return super.onRequestSendAccessibilityEvent(child, event);
327 // Skip propagating onRequestSendAccessibilityEvent for all other children
328 // which are not topView
331 return super.onRequestSendAccessibilityEvent(child, event);
335 public void addChildrenForAccessibility(ArrayList<View> childrenForAccessibility) {
336 View topView = AbstractFloatingView.getTopOpenView(mLauncher);
337 if (topView != null) {
338 // Only add the top view as a child for accessibility when it is open
339 childrenForAccessibility.add(topView);
341 if (isInAccessibleDrag()) {
342 childrenForAccessibility.add(mLauncher.getDropTargetBar());
345 super.addChildrenForAccessibility(childrenForAccessibility);
350 public boolean onHoverEvent(MotionEvent ev) {
351 // If we've received this, we've already done the necessary handling
352 // in onInterceptHoverEvent. Return true to consume the event.
357 public boolean onTouchEvent(MotionEvent ev) {
358 int action = ev.getAction();
360 if (action == MotionEvent.ACTION_DOWN) {
361 if (handleTouchDown(ev, false)) {
364 } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
365 if (mTouchCompleteListener != null) {
366 mTouchCompleteListener.onTouchComplete();
368 mTouchCompleteListener = null;
371 if (mActiveController != null) {
372 return mActiveController.onControllerTouchEvent(ev);
378 * Determine the rect of the descendant in this DragLayer's coordinates
380 * @param descendant The descendant whose coordinates we want to find.
381 * @param r The rect into which to place the results.
382 * @return The factor by which this descendant is scaled relative to this DragLayer.
384 public float getDescendantRectRelativeToSelf(View descendant, Rect r) {
387 float scale = getDescendantCoordRelativeToSelf(descendant, mTmpXY);
389 r.set(mTmpXY[0], mTmpXY[1],
390 (int) (mTmpXY[0] + scale * descendant.getMeasuredWidth()),
391 (int) (mTmpXY[1] + scale * descendant.getMeasuredHeight()));
395 public float getLocationInDragLayer(View child, int[] loc) {
398 return getDescendantCoordRelativeToSelf(child, loc);
401 public float getDescendantCoordRelativeToSelf(View descendant, int[] coord) {
402 return getDescendantCoordRelativeToSelf(descendant, coord, false);
406 * Given a coordinate relative to the descendant, find the coordinate in this DragLayer's
409 * @param descendant The descendant to which the passed coordinate is relative.
410 * @param coord The coordinate that we want mapped.
411 * @param includeRootScroll Whether or not to account for the scroll of the root descendant:
412 * sometimes this is relevant as in a child's coordinates within the root descendant.
413 * @return The factor by which this descendant is scaled relative to this DragLayer. Caution
414 * this scale factor is assumed to be equal in X and Y, and so if at any point this
415 * assumption fails, we will need to return a pair of scale factors.
417 public float getDescendantCoordRelativeToSelf(View descendant, int[] coord,
418 boolean includeRootScroll) {
419 return Utilities.getDescendantCoordRelativeToAncestor(descendant, this,
420 coord, includeRootScroll);
424 * Inverse of {@link #getDescendantCoordRelativeToSelf(View, int[])}.
426 public void mapCoordInSelfToDescendant(View descendant, int[] coord) {
427 Utilities.mapCoordInSelfToDescendant(descendant, this, coord);
430 public void getViewRectRelativeToSelf(View v, Rect r) {
431 int[] loc = new int[2];
432 getLocationInWindow(loc);
436 v.getLocationInWindow(loc);
442 r.set(left, top, left + v.getMeasuredWidth(), top + v.getMeasuredHeight());
446 public boolean dispatchUnhandledMove(View focused, int direction) {
447 // Consume the unhandled move if a container is open, to avoid switching pages underneath.
448 boolean isContainerOpen = AbstractFloatingView.getTopOpenView(mLauncher) != null;
449 return isContainerOpen || mDragController.dispatchUnhandledMove(focused, direction);
453 public LayoutParams generateLayoutParams(AttributeSet attrs) {
454 return new LayoutParams(getContext(), attrs);
458 protected LayoutParams generateDefaultLayoutParams() {
459 return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
462 // Override to allow type-checking of LayoutParams.
464 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
465 return p instanceof LayoutParams;
469 protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
470 return new LayoutParams(p);
473 public static class LayoutParams extends InsettableFrameLayout.LayoutParams {
475 public boolean customPosition = false;
477 public LayoutParams(Context c, AttributeSet attrs) {
481 public LayoutParams(int width, int height) {
482 super(width, height);
485 public LayoutParams(ViewGroup.LayoutParams lp) {
489 public void setWidth(int width) {
493 public int getWidth() {
497 public void setHeight(int height) {
498 this.height = height;
501 public int getHeight() {
505 public void setX(int x) {
513 public void setY(int y) {
522 protected void onLayout(boolean changed, int l, int t, int r, int b) {
523 super.onLayout(changed, l, t, r, b);
524 int count = getChildCount();
525 for (int i = 0; i < count; i++) {
526 View child = getChildAt(i);
527 final FrameLayout.LayoutParams flp = (FrameLayout.LayoutParams) child.getLayoutParams();
528 if (flp instanceof LayoutParams) {
529 final LayoutParams lp = (LayoutParams) flp;
530 if (lp.customPosition) {
531 child.layout(lp.x, lp.y, lp.x + lp.width, lp.y + lp.height);
537 public void clearResizeFrame() {
538 if (mCurrentResizeFrame != null) {
539 mCurrentResizeFrame.commitResize();
540 removeView(mCurrentResizeFrame);
541 mCurrentResizeFrame = null;
545 public void addResizeFrame(LauncherAppWidgetHostView widget, CellLayout cellLayout) {
548 mCurrentResizeFrame = (AppWidgetResizeFrame) LayoutInflater.from(mLauncher)
549 .inflate(R.layout.app_widget_resize_frame, this, false);
550 mCurrentResizeFrame.setupForWidget(widget, cellLayout, this);
551 ((LayoutParams) mCurrentResizeFrame.getLayoutParams()).customPosition = true;
553 addView(mCurrentResizeFrame);
554 mCurrentResizeFrame.snapToWidget(false);
557 public void animateViewIntoPosition(DragView dragView, final int[] pos, float alpha,
558 float scaleX, float scaleY, int animationEndStyle, Runnable onFinishRunnable,
561 getViewRectRelativeToSelf(dragView, r);
562 final int fromX = r.left;
563 final int fromY = r.top;
565 animateViewIntoPosition(dragView, fromX, fromY, pos[0], pos[1], alpha, 1, 1, scaleX, scaleY,
566 onFinishRunnable, animationEndStyle, duration, null);
569 public void animateViewIntoPosition(DragView dragView, final View child,
570 final Runnable onFinishAnimationRunnable, View anchorView) {
571 animateViewIntoPosition(dragView, child, -1, onFinishAnimationRunnable, anchorView);
574 public void animateViewIntoPosition(DragView dragView, final View child, int duration,
575 final Runnable onFinishAnimationRunnable, View anchorView) {
576 ShortcutAndWidgetContainer parentChildren = (ShortcutAndWidgetContainer) child.getParent();
577 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
578 parentChildren.measureChild(child);
581 getViewRectRelativeToSelf(dragView, r);
583 int coord[] = new int[2];
584 float childScale = child.getScaleX();
585 coord[0] = lp.x + (int) (child.getMeasuredWidth() * (1 - childScale) / 2);
586 coord[1] = lp.y + (int) (child.getMeasuredHeight() * (1 - childScale) / 2);
588 // Since the child hasn't necessarily been laid out, we force the lp to be updated with
589 // the correct coordinates (above) and use these to determine the final location
590 float scale = getDescendantCoordRelativeToSelf((View) child.getParent(), coord);
591 // We need to account for the scale of the child itself, as the above only accounts for
592 // for the scale in parents.
596 float toScale = scale;
597 if (child instanceof TextView) {
598 TextView tv = (TextView) child;
599 // Account for the source scale of the icon (ie. from AllApps to Workspace, in which
600 // the workspace may have smaller icon bounds).
601 toScale = scale / dragView.getIntrinsicIconScaleFactor();
603 // The child may be scaled (always about the center of the view) so to account for it,
604 // we have to offset the position by the scaled size. Once we do that, we can center
605 // the drag view about the scaled child view.
606 toY += Math.round(toScale * tv.getPaddingTop());
607 toY -= dragView.getMeasuredHeight() * (1 - toScale) / 2;
608 if (dragView.getDragVisualizeOffset() != null) {
609 toY -= Math.round(toScale * dragView.getDragVisualizeOffset().y);
612 toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2;
613 } else if (child instanceof FolderIcon) {
614 // Account for holographic blur padding on the drag view
615 toY += Math.round(scale * (child.getPaddingTop() - dragView.getDragRegionTop()));
616 toY -= scale * dragView.getBlurSizeOutline() / 2;
617 toY -= (1 - scale) * dragView.getMeasuredHeight() / 2;
618 // Center in the x coordinate about the target's drawable
619 toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2;
621 toY -= (Math.round(scale * (dragView.getHeight() - child.getMeasuredHeight()))) / 2;
622 toX -= (Math.round(scale * (dragView.getMeasuredWidth()
623 - child.getMeasuredWidth()))) / 2;
626 final int fromX = r.left;
627 final int fromY = r.top;
628 child.setVisibility(INVISIBLE);
629 Runnable onCompleteRunnable = new Runnable() {
631 child.setVisibility(VISIBLE);
632 if (onFinishAnimationRunnable != null) {
633 onFinishAnimationRunnable.run();
637 animateViewIntoPosition(dragView, fromX, fromY, toX, toY, 1, 1, 1, toScale, toScale,
638 onCompleteRunnable, ANIMATION_END_DISAPPEAR, duration, anchorView);
641 public void animateViewIntoPosition(final DragView view, final int fromX, final int fromY,
642 final int toX, final int toY, float finalAlpha, float initScaleX, float initScaleY,
643 float finalScaleX, float finalScaleY, Runnable onCompleteRunnable,
644 int animationEndStyle, int duration, View anchorView) {
645 Rect from = new Rect(fromX, fromY, fromX +
646 view.getMeasuredWidth(), fromY + view.getMeasuredHeight());
647 Rect to = new Rect(toX, toY, toX + view.getMeasuredWidth(), toY + view.getMeasuredHeight());
648 animateView(view, from, to, finalAlpha, initScaleX, initScaleY, finalScaleX, finalScaleY, duration,
649 null, null, onCompleteRunnable, animationEndStyle, anchorView);
653 * This method animates a view at the end of a drag and drop animation.
655 * @param view The view to be animated. This view is drawn directly into DragLayer, and so
656 * doesn't need to be a child of DragLayer.
657 * @param from The initial location of the view. Only the left and top parameters are used.
658 * @param to The final location of the view. Only the left and top parameters are used. This
659 * location doesn't account for scaling, and so should be centered about the desired
660 * final location (including scaling).
661 * @param finalAlpha The final alpha of the view, in case we want it to fade as it animates.
662 * @param finalScaleX The final scale of the view. The view is scaled about its center.
663 * @param finalScaleY The final scale of the view. The view is scaled about its center.
664 * @param duration The duration of the animation.
665 * @param motionInterpolator The interpolator to use for the location of the view.
666 * @param alphaInterpolator The interpolator to use for the alpha of the view.
667 * @param onCompleteRunnable Optional runnable to run on animation completion.
668 * @param animationEndStyle Whether or not to fade out the view once the animation completes.
669 * {@link #ANIMATION_END_DISAPPEAR} or {@link #ANIMATION_END_REMAIN_VISIBLE}.
670 * @param anchorView If not null, this represents the view which the animated view stays
671 * anchored to in case scrolling is currently taking place. Note: currently this is
672 * only used for the X dimension for the case of the workspace.
674 public void animateView(final DragView view, final Rect from, final Rect to,
675 final float finalAlpha, final float initScaleX, final float initScaleY,
676 final float finalScaleX, final float finalScaleY, int duration,
677 final Interpolator motionInterpolator, final Interpolator alphaInterpolator,
678 final Runnable onCompleteRunnable, final int animationEndStyle, View anchorView) {
680 // Calculate the duration of the animation based on the object's distance
681 final float dist = (float) Math.hypot(to.left - from.left, to.top - from.top);
682 final Resources res = getResources();
683 final float maxDist = (float) res.getInteger(R.integer.config_dropAnimMaxDist);
685 // If duration < 0, this is a cue to compute the duration based on the distance
687 duration = res.getInteger(R.integer.config_dropAnimMaxDuration);
688 if (dist < maxDist) {
689 duration *= mCubicEaseOutInterpolator.getInterpolation(dist / maxDist);
691 duration = Math.max(duration, res.getInteger(R.integer.config_dropAnimMinDuration));
694 // Fall back to cubic ease out interpolator for the animation if none is specified
695 TimeInterpolator interpolator = null;
696 if (alphaInterpolator == null || motionInterpolator == null) {
697 interpolator = mCubicEaseOutInterpolator;
701 final float initAlpha = view.getAlpha();
702 final float dropViewScale = view.getScaleX();
703 AnimatorUpdateListener updateCb = new AnimatorUpdateListener() {
705 public void onAnimationUpdate(ValueAnimator animation) {
706 final float percent = (Float) animation.getAnimatedValue();
707 final int width = view.getMeasuredWidth();
708 final int height = view.getMeasuredHeight();
710 float alphaPercent = alphaInterpolator == null ? percent :
711 alphaInterpolator.getInterpolation(percent);
712 float motionPercent = motionInterpolator == null ? percent :
713 motionInterpolator.getInterpolation(percent);
715 float initialScaleX = initScaleX * dropViewScale;
716 float initialScaleY = initScaleY * dropViewScale;
717 float scaleX = finalScaleX * percent + initialScaleX * (1 - percent);
718 float scaleY = finalScaleY * percent + initialScaleY * (1 - percent);
719 float alpha = finalAlpha * alphaPercent + initAlpha * (1 - alphaPercent);
721 float fromLeft = from.left + (initialScaleX - 1f) * width / 2;
722 float fromTop = from.top + (initialScaleY - 1f) * height / 2;
724 int x = (int) (fromLeft + Math.round(((to.left - fromLeft) * motionPercent)));
725 int y = (int) (fromTop + Math.round(((to.top - fromTop) * motionPercent)));
727 int anchorAdjust = mAnchorView == null ? 0 : (int) (mAnchorView.getScaleX() *
728 (mAnchorViewInitialScrollX - mAnchorView.getScrollX()));
730 int xPos = x - mDropView.getScrollX() + anchorAdjust;
731 int yPos = y - mDropView.getScrollY();
733 mDropView.setTranslationX(xPos);
734 mDropView.setTranslationY(yPos);
735 mDropView.setScaleX(scaleX);
736 mDropView.setScaleY(scaleY);
737 mDropView.setAlpha(alpha);
740 animateView(view, updateCb, duration, interpolator, onCompleteRunnable, animationEndStyle,
744 public void animateView(final DragView view, AnimatorUpdateListener updateCb, int duration,
745 TimeInterpolator interpolator, final Runnable onCompleteRunnable,
746 final int animationEndStyle, View anchorView) {
747 // Clean up the previous animations
748 if (mDropAnim != null) mDropAnim.cancel();
750 // Show the drop view if it was previously hidden
752 mDropView.cancelAnimation();
753 mDropView.requestLayout();
755 // Set the anchor view if the page is scrolling
756 if (anchorView != null) {
757 mAnchorViewInitialScrollX = anchorView.getScrollX();
759 mAnchorView = anchorView;
761 // Create and start the animation
762 mDropAnim = new ValueAnimator();
763 mDropAnim.setInterpolator(interpolator);
764 mDropAnim.setDuration(duration);
765 mDropAnim.setFloatValues(0f, 1f);
766 mDropAnim.addUpdateListener(updateCb);
767 mDropAnim.addListener(new AnimatorListenerAdapter() {
768 public void onAnimationEnd(Animator animation) {
769 if (onCompleteRunnable != null) {
770 onCompleteRunnable.run();
772 switch (animationEndStyle) {
773 case ANIMATION_END_DISAPPEAR:
776 case ANIMATION_END_REMAIN_VISIBLE:
784 public void clearAnimatedView() {
785 if (mDropAnim != null) {
788 if (mDropView != null) {
789 mDragController.onDeferredEndDrag(mDropView);
795 public View getAnimatedView() {
800 public void onChildViewAdded(View parent, View child) {
801 super.onChildViewAdded(parent, child);
802 updateChildIndices();
806 public void onChildViewRemoved(View parent, View child) {
807 updateChildIndices();
811 public void bringChildToFront(View child) {
812 super.bringChildToFront(child);
813 updateChildIndices();
816 private void updateChildIndices() {
818 int childCount = getChildCount();
819 for (int i = 0; i < childCount; i++) {
820 if (getChildAt(i) instanceof DragView) {
824 mChildCountOnLastUpdate = childCount;
828 protected int getChildDrawingOrder(int childCount, int i) {
829 if (mChildCountOnLastUpdate != childCount) {
830 // between platform versions 17 and 18, behavior for onChildViewRemoved / Added changed.
831 // Pre-18, the child was not added / removed by the time of those callbacks. We need to
832 // force update our representation of things here to avoid crashing on pre-18 devices
833 // in certain instances.
834 updateChildIndices();
837 // i represents the current draw iteration
838 if (mTopViewIndex == -1) {
839 // in general we do nothing
841 } else if (i == childCount - 1) {
842 // if we have a top index, we return it when drawing last item (highest z-order)
843 return mTopViewIndex;
844 } else if (i < mTopViewIndex) {
847 // for indexes greater than the top index, we fetch one item above to shift for the
848 // displacement of the top index
853 public void invalidateScrim() {
854 if (mBackgroundAlpha > 0.0f) {
860 protected void dispatchDraw(Canvas canvas) {
861 // Draw the background below children.
862 if (mBackgroundAlpha > 0.0f) {
863 // Update the scroll position first to ensure scrim cutout is in the right place.
864 mLauncher.getWorkspace().computeScrollWithoutInvalidation();
866 int alpha = (int) (mBackgroundAlpha * 255);
867 CellLayout currCellLayout = mLauncher.getWorkspace().getCurrentDragOverlappingLayout();
869 if (currCellLayout != null && currCellLayout != mLauncher.getHotseat().getLayout()) {
870 // Cut a hole in the darkening scrim on the page that should be highlighted, if any.
871 getDescendantRectRelativeToSelf(currCellLayout, mHighlightRect);
872 canvas.clipRect(mHighlightRect, Region.Op.DIFFERENCE);
874 canvas.drawColor((alpha << 24) | SCRIM_COLOR);
878 mFocusIndicatorHelper.draw(canvas);
879 super.dispatchDraw(canvas);
882 public void setBackgroundAlpha(float alpha) {
883 if (alpha != mBackgroundAlpha) {
884 mBackgroundAlpha = alpha;
889 public float getBackgroundAlpha() {
890 return mBackgroundAlpha;
894 protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
895 View topView = AbstractFloatingView.getTopOpenView(mLauncher);
896 if (topView != null) {
897 return topView.requestFocus(direction, previouslyFocusedRect);
899 return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
904 public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
905 View topView = AbstractFloatingView.getTopOpenView(mLauncher);
906 if (topView != null) {
907 topView.addFocusables(views, direction);
909 super.addFocusables(views, direction, focusableMode);
913 public void setTouchCompleteListener(TouchCompleteListener listener) {
914 mTouchCompleteListener = listener;
917 public interface TouchCompleteListener {
918 public void onTouchComplete();