1 /* 2 * Copyright (C) 2010 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 17 package com.android.launcher3; 18 19 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; 20 21 import static com.android.launcher3.LauncherState.NORMAL; 22 23 import android.content.Context; 24 import android.content.res.Resources; 25 import android.graphics.Rect; 26 import android.graphics.drawable.Drawable; 27 import android.text.TextUtils; 28 import android.util.AttributeSet; 29 import android.view.LayoutInflater; 30 import android.view.View; 31 import android.view.View.OnClickListener; 32 import android.view.accessibility.AccessibilityEvent; 33 import android.widget.PopupWindow; 34 import android.widget.TextView; 35 36 import com.android.launcher3.anim.Interpolators; 37 import com.android.launcher3.dragndrop.DragController; 38 import com.android.launcher3.dragndrop.DragLayer; 39 import com.android.launcher3.dragndrop.DragOptions; 40 import com.android.launcher3.dragndrop.DragView; 41 import com.android.launcher3.model.data.ItemInfo; 42 43 /** 44 * Implements a DropTarget. 45 */ 46 public abstract class ButtonDropTarget extends TextView 47 implements DropTarget, DragController.DragListener, OnClickListener { 48 49 private static final int[] sTempCords = new int[2]; 50 private static final int DRAG_VIEW_DROP_DURATION = 285; 51 private static final float DRAG_VIEW_HOVER_OVER_OPACITY = 0.65f; 52 53 public static final int TOOLTIP_DEFAULT = 0; 54 public static final int TOOLTIP_LEFT = 1; 55 public static final int TOOLTIP_RIGHT = 2; 56 57 protected final Launcher mLauncher; 58 59 protected DropTargetBar mDropTargetBar; 60 61 /** Whether this drop target is active for the current drag */ 62 protected boolean mActive; 63 /** Whether an accessible drag is in progress */ 64 private boolean mAccessibleDrag; 65 /** An item must be dragged at least this many pixels before this drop target is enabled. */ 66 private final int mDragDistanceThreshold; 67 /** The size of the drawable shown in the drop target. */ 68 private final int mDrawableSize; 69 70 protected CharSequence mText; 71 protected Drawable mDrawable; 72 private boolean mTextVisible = true; 73 74 private PopupWindow mToolTip; 75 private int mToolTipLocation; 76 ButtonDropTarget(Context context, AttributeSet attrs)77 public ButtonDropTarget(Context context, AttributeSet attrs) { 78 this(context, attrs, 0); 79 } 80 ButtonDropTarget(Context context, AttributeSet attrs, int defStyle)81 public ButtonDropTarget(Context context, AttributeSet attrs, int defStyle) { 82 super(context, attrs, defStyle); 83 mLauncher = Launcher.getLauncher(context); 84 85 Resources resources = getResources(); 86 mDragDistanceThreshold = resources.getDimensionPixelSize(R.dimen.drag_distanceThreshold); 87 mDrawableSize = resources.getDimensionPixelSize(R.dimen.drop_target_text_size); 88 } 89 90 @Override onFinishInflate()91 protected void onFinishInflate() { 92 super.onFinishInflate(); 93 mText = getText(); 94 setContentDescription(mText); 95 } 96 updateText(int resId)97 protected void updateText(int resId) { 98 setText(resId); 99 mText = getText(); 100 setContentDescription(mText); 101 } 102 setDrawable(int resId)103 protected void setDrawable(int resId) { 104 // We do not set the drawable in the xml as that inflates two drawables corresponding to 105 // drawableLeft and drawableStart. 106 mDrawable = getContext().getDrawable(resId).mutate(); 107 mDrawable.setBounds(0, 0, mDrawableSize, mDrawableSize); 108 mDrawable.setTintList(getTextColors()); 109 setCompoundDrawablesRelative(mDrawable, null, null, null); 110 } 111 setDropTargetBar(DropTargetBar dropTargetBar)112 public void setDropTargetBar(DropTargetBar dropTargetBar) { 113 mDropTargetBar = dropTargetBar; 114 } 115 hideTooltip()116 private void hideTooltip() { 117 if (mToolTip != null) { 118 mToolTip.dismiss(); 119 mToolTip = null; 120 } 121 } 122 123 @Override onDragEnter(DragObject d)124 public final void onDragEnter(DragObject d) { 125 if (!mAccessibleDrag && !mTextVisible) { 126 // Show tooltip 127 hideTooltip(); 128 129 TextView message = (TextView) LayoutInflater.from(getContext()).inflate( 130 R.layout.drop_target_tool_tip, null); 131 message.setText(mText); 132 133 mToolTip = new PopupWindow(message, WRAP_CONTENT, WRAP_CONTENT); 134 int x = 0, y = 0; 135 if (mToolTipLocation != TOOLTIP_DEFAULT) { 136 y = -getMeasuredHeight(); 137 message.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); 138 if (mToolTipLocation == TOOLTIP_LEFT) { 139 x = - getMeasuredWidth() - message.getMeasuredWidth() / 2; 140 } else { 141 x = getMeasuredWidth() / 2 + message.getMeasuredWidth() / 2; 142 } 143 } 144 mToolTip.showAsDropDown(this, x, y); 145 } 146 147 d.dragView.setAlpha(DRAG_VIEW_HOVER_OVER_OPACITY); 148 setSelected(true); 149 if (d.stateAnnouncer != null) { 150 d.stateAnnouncer.cancel(); 151 } 152 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); 153 } 154 155 @Override onDragOver(DragObject d)156 public void onDragOver(DragObject d) { 157 // Do nothing 158 } 159 160 @Override onDragExit(DragObject d)161 public final void onDragExit(DragObject d) { 162 hideTooltip(); 163 164 if (!d.dragComplete) { 165 d.dragView.setAlpha(1f); 166 setSelected(false); 167 } else { 168 d.dragView.setAlpha(DRAG_VIEW_HOVER_OVER_OPACITY); 169 } 170 } 171 172 @Override onDragStart(DropTarget.DragObject dragObject, DragOptions options)173 public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) { 174 mActive = !options.isKeyboardDrag && supportsDrop(dragObject.dragInfo); 175 setVisibility(mActive ? View.VISIBLE : View.GONE); 176 177 mAccessibleDrag = options.isAccessibleDrag; 178 setOnClickListener(mAccessibleDrag ? this : null); 179 } 180 181 @Override acceptDrop(DragObject dragObject)182 public final boolean acceptDrop(DragObject dragObject) { 183 return supportsDrop(dragObject.dragInfo); 184 } 185 supportsDrop(ItemInfo info)186 protected abstract boolean supportsDrop(ItemInfo info); 187 supportsAccessibilityDrop(ItemInfo info, View view)188 public abstract boolean supportsAccessibilityDrop(ItemInfo info, View view); 189 190 @Override isDropEnabled()191 public boolean isDropEnabled() { 192 return mActive && (mAccessibleDrag || 193 mLauncher.getDragController().getDistanceDragged() >= mDragDistanceThreshold); 194 } 195 196 @Override onDragEnd()197 public void onDragEnd() { 198 mActive = false; 199 setOnClickListener(null); 200 setSelected(false); 201 } 202 203 /** 204 * On drop animate the dropView to the icon. 205 */ 206 @Override onDrop(final DragObject d, final DragOptions options)207 public void onDrop(final DragObject d, final DragOptions options) { 208 if (options.isFlingToDelete) { 209 // FlingAnimation handles the animation and then calls completeDrop(). 210 return; 211 } 212 final DragLayer dragLayer = mLauncher.getDragLayer(); 213 final DragView dragView = d.dragView; 214 final Rect from = new Rect(); 215 dragLayer.getViewRectRelativeToSelf(d.dragView, from); 216 217 final Rect to = getIconRect(d); 218 final float scale = (float) to.width() / from.width(); 219 dragView.disableColorExtraction(); 220 dragView.detachContentView(/* reattachToPreviousParent= */ true); 221 mDropTargetBar.deferOnDragEnd(); 222 223 Runnable onAnimationEndRunnable = () -> { 224 completeDrop(d); 225 mDropTargetBar.onDragEnd(); 226 mLauncher.getStateManager().goToState(NORMAL); 227 // Only re-enable updates once the workspace is back to normal, which will be after the 228 // current frame. 229 post(dragView::resumeColorExtraction); 230 }; 231 232 dragLayer.animateView(d.dragView, from, to, scale, 1f, 1f, 0.1f, 0.1f, 233 DRAG_VIEW_DROP_DURATION, 234 Interpolators.DEACCEL_2, Interpolators.LINEAR, onAnimationEndRunnable, 235 DragLayer.ANIMATION_END_DISAPPEAR, null); 236 } 237 getAccessibilityAction()238 public abstract int getAccessibilityAction(); 239 240 @Override prepareAccessibilityDrop()241 public void prepareAccessibilityDrop() { } 242 onAccessibilityDrop(View view, ItemInfo item)243 public abstract void onAccessibilityDrop(View view, ItemInfo item); 244 completeDrop(DragObject d)245 public abstract void completeDrop(DragObject d); 246 247 @Override getHitRectRelativeToDragLayer(android.graphics.Rect outRect)248 public void getHitRectRelativeToDragLayer(android.graphics.Rect outRect) { 249 super.getHitRect(outRect); 250 outRect.bottom += mLauncher.getDeviceProfile().dropTargetDragPaddingPx; 251 252 sTempCords[0] = sTempCords[1] = 0; 253 mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, sTempCords); 254 outRect.offsetTo(sTempCords[0], sTempCords[1]); 255 } 256 getIconRect(DragObject dragObject)257 public Rect getIconRect(DragObject dragObject) { 258 int viewWidth = dragObject.dragView.getMeasuredWidth(); 259 int viewHeight = dragObject.dragView.getMeasuredHeight(); 260 int drawableWidth = mDrawable.getIntrinsicWidth(); 261 int drawableHeight = mDrawable.getIntrinsicHeight(); 262 DragLayer dragLayer = mLauncher.getDragLayer(); 263 264 // Find the rect to animate to (the view is center aligned) 265 Rect to = new Rect(); 266 dragLayer.getViewRectRelativeToSelf(this, to); 267 268 final int width = drawableWidth; 269 final int height = drawableHeight; 270 271 final int left; 272 final int right; 273 274 if (Utilities.isRtl(getResources())) { 275 right = to.right - getPaddingRight(); 276 left = right - width; 277 } else { 278 left = to.left + getPaddingLeft(); 279 right = left + width; 280 } 281 282 final int top = to.top + (getMeasuredHeight() - height) / 2; 283 final int bottom = top + height; 284 285 to.set(left, top, right, bottom); 286 287 // Center the destination rect about the trash icon 288 final int xOffset = -(viewWidth - width) / 2; 289 final int yOffset = -(viewHeight - height) / 2; 290 to.offset(xOffset, yOffset); 291 292 return to; 293 } 294 295 @Override onClick(View v)296 public void onClick(View v) { 297 mLauncher.getAccessibilityDelegate().handleAccessibleDrop(this, null, null); 298 } 299 setTextVisible(boolean isVisible)300 public void setTextVisible(boolean isVisible) { 301 CharSequence newText = isVisible ? mText : ""; 302 if (mTextVisible != isVisible || !TextUtils.equals(newText, getText())) { 303 mTextVisible = isVisible; 304 setText(newText); 305 setCompoundDrawablesRelative(mDrawable, null, null, null); 306 } 307 } 308 setToolTipLocation(int location)309 public void setToolTipLocation(int location) { 310 mToolTipLocation = location; 311 hideTooltip(); 312 } 313 isTextTruncated(int availableWidth)314 public boolean isTextTruncated(int availableWidth) { 315 availableWidth -= (getPaddingLeft() + getPaddingRight() + mDrawable.getIntrinsicWidth() 316 + getCompoundDrawablePadding()); 317 CharSequence displayedText = TextUtils.ellipsize(mText, getPaint(), availableWidth, 318 TextUtils.TruncateAt.END); 319 return !mText.equals(displayedText); 320 } 321 } 322