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 /** The padding, in pixels, between the text and drawable. */ 70 private final int mDrawablePadding; 71 72 protected CharSequence mText; 73 protected Drawable mDrawable; 74 private boolean mTextVisible = true; 75 76 private PopupWindow mToolTip; 77 private int mToolTipLocation; 78 ButtonDropTarget(Context context, AttributeSet attrs)79 public ButtonDropTarget(Context context, AttributeSet attrs) { 80 this(context, attrs, 0); 81 } 82 ButtonDropTarget(Context context, AttributeSet attrs, int defStyle)83 public ButtonDropTarget(Context context, AttributeSet attrs, int defStyle) { 84 super(context, attrs, defStyle); 85 mLauncher = Launcher.getLauncher(context); 86 87 Resources resources = getResources(); 88 mDragDistanceThreshold = resources.getDimensionPixelSize(R.dimen.drag_distanceThreshold); 89 mDrawableSize = resources.getDimensionPixelSize(R.dimen.drop_target_text_size); 90 mDrawablePadding = resources.getDimensionPixelSize( 91 R.dimen.drop_target_button_drawable_padding); 92 } 93 94 @Override onFinishInflate()95 protected void onFinishInflate() { 96 super.onFinishInflate(); 97 mText = getText(); 98 setContentDescription(mText); 99 } 100 updateText(int resId)101 protected void updateText(int resId) { 102 setText(resId); 103 mText = getText(); 104 setContentDescription(mText); 105 } 106 setDrawable(int resId)107 protected void setDrawable(int resId) { 108 // We do not set the drawable in the xml as that inflates two drawables corresponding to 109 // drawableLeft and drawableStart. 110 mDrawable = getContext().getDrawable(resId).mutate(); 111 mDrawable.setTintList(getTextColors()); 112 centerIcon(); 113 setCompoundDrawablesRelative(mDrawable, null, null, null); 114 } 115 setDropTargetBar(DropTargetBar dropTargetBar)116 public void setDropTargetBar(DropTargetBar dropTargetBar) { 117 mDropTargetBar = dropTargetBar; 118 } 119 hideTooltip()120 private void hideTooltip() { 121 if (mToolTip != null) { 122 mToolTip.dismiss(); 123 mToolTip = null; 124 } 125 } 126 127 @Override onDragEnter(DragObject d)128 public final void onDragEnter(DragObject d) { 129 if (!mAccessibleDrag && !mTextVisible) { 130 // Show tooltip 131 hideTooltip(); 132 133 TextView message = (TextView) LayoutInflater.from(getContext()).inflate( 134 R.layout.drop_target_tool_tip, null); 135 message.setText(mText); 136 137 mToolTip = new PopupWindow(message, WRAP_CONTENT, WRAP_CONTENT); 138 int x = 0, y = 0; 139 if (mToolTipLocation != TOOLTIP_DEFAULT) { 140 y = -getMeasuredHeight(); 141 message.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); 142 if (mToolTipLocation == TOOLTIP_LEFT) { 143 x = - getMeasuredWidth() - message.getMeasuredWidth() / 2; 144 } else { 145 x = getMeasuredWidth() / 2 + message.getMeasuredWidth() / 2; 146 } 147 } 148 mToolTip.showAsDropDown(this, x, y); 149 } 150 151 d.dragView.setAlpha(DRAG_VIEW_HOVER_OVER_OPACITY); 152 setSelected(true); 153 if (d.stateAnnouncer != null) { 154 d.stateAnnouncer.cancel(); 155 } 156 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); 157 } 158 159 @Override onDragOver(DragObject d)160 public void onDragOver(DragObject d) { 161 // Do nothing 162 } 163 164 @Override onDragExit(DragObject d)165 public final void onDragExit(DragObject d) { 166 hideTooltip(); 167 168 if (!d.dragComplete) { 169 d.dragView.setAlpha(1f); 170 setSelected(false); 171 } else { 172 d.dragView.setAlpha(DRAG_VIEW_HOVER_OVER_OPACITY); 173 } 174 } 175 176 @Override onDragStart(DropTarget.DragObject dragObject, DragOptions options)177 public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) { 178 mActive = !options.isKeyboardDrag && supportsDrop(dragObject.dragInfo); 179 setVisibility(mActive ? View.VISIBLE : View.GONE); 180 181 mAccessibleDrag = options.isAccessibleDrag; 182 setOnClickListener(mAccessibleDrag ? this : null); 183 } 184 185 @Override acceptDrop(DragObject dragObject)186 public final boolean acceptDrop(DragObject dragObject) { 187 return supportsDrop(dragObject.dragInfo); 188 } 189 supportsDrop(ItemInfo info)190 protected abstract boolean supportsDrop(ItemInfo info); 191 supportsAccessibilityDrop(ItemInfo info, View view)192 public abstract boolean supportsAccessibilityDrop(ItemInfo info, View view); 193 194 @Override isDropEnabled()195 public boolean isDropEnabled() { 196 return mActive && (mAccessibleDrag || 197 mLauncher.getDragController().getDistanceDragged() >= mDragDistanceThreshold); 198 } 199 200 @Override onDragEnd()201 public void onDragEnd() { 202 mActive = false; 203 setOnClickListener(null); 204 setSelected(false); 205 } 206 207 /** 208 * On drop animate the dropView to the icon. 209 */ 210 @Override onDrop(final DragObject d, final DragOptions options)211 public void onDrop(final DragObject d, final DragOptions options) { 212 if (options.isFlingToDelete) { 213 // FlingAnimation handles the animation and then calls completeDrop(). 214 return; 215 } 216 final DragLayer dragLayer = mLauncher.getDragLayer(); 217 final DragView dragView = d.dragView; 218 final Rect to = getIconRect(d); 219 final float scale = (float) to.width() / dragView.getMeasuredWidth(); 220 dragView.detachContentView(/* reattachToPreviousParent= */ true); 221 mDropTargetBar.deferOnDragEnd(); 222 223 Runnable onAnimationEndRunnable = () -> { 224 completeDrop(d); 225 mDropTargetBar.onDragEnd(); 226 mLauncher.getStateManager().goToState(NORMAL); 227 }; 228 229 dragLayer.animateView(d.dragView, to, scale, 0.1f, 0.1f, 230 DRAG_VIEW_DROP_DURATION, 231 Interpolators.DEACCEL_2, onAnimationEndRunnable, 232 DragLayer.ANIMATION_END_DISAPPEAR, null); 233 } 234 getAccessibilityAction()235 public abstract int getAccessibilityAction(); 236 237 @Override prepareAccessibilityDrop()238 public void prepareAccessibilityDrop() { } 239 onAccessibilityDrop(View view, ItemInfo item)240 public abstract void onAccessibilityDrop(View view, ItemInfo item); 241 completeDrop(DragObject d)242 public abstract void completeDrop(DragObject d); 243 244 @Override getHitRectRelativeToDragLayer(android.graphics.Rect outRect)245 public void getHitRectRelativeToDragLayer(android.graphics.Rect outRect) { 246 super.getHitRect(outRect); 247 outRect.bottom += mLauncher.getDeviceProfile().dropTargetDragPaddingPx; 248 249 sTempCords[0] = sTempCords[1] = 0; 250 mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, sTempCords); 251 outRect.offsetTo(sTempCords[0], sTempCords[1]); 252 } 253 getIconRect(DragObject dragObject)254 public Rect getIconRect(DragObject dragObject) { 255 int viewWidth = dragObject.dragView.getMeasuredWidth(); 256 int viewHeight = dragObject.dragView.getMeasuredHeight(); 257 int drawableWidth = mDrawable.getIntrinsicWidth(); 258 int drawableHeight = mDrawable.getIntrinsicHeight(); 259 DragLayer dragLayer = mLauncher.getDragLayer(); 260 261 // Find the rect to animate to (the view is center aligned) 262 Rect to = new Rect(); 263 dragLayer.getViewRectRelativeToSelf(this, to); 264 265 final int width = drawableWidth; 266 final int height = drawableHeight; 267 268 final int left; 269 final int right; 270 271 if (Utilities.isRtl(getResources())) { 272 right = to.right - getPaddingRight(); 273 left = right - width; 274 } else { 275 left = to.left + getPaddingLeft(); 276 right = left + width; 277 } 278 279 final int top = to.top + (getMeasuredHeight() - height) / 2; 280 final int bottom = top + height; 281 282 to.set(left, top, right, bottom); 283 284 // Center the destination rect about the trash icon 285 final int xOffset = -(viewWidth - width) / 2; 286 final int yOffset = -(viewHeight - height) / 2; 287 to.offset(xOffset, yOffset); 288 289 return to; 290 } 291 centerIcon()292 private void centerIcon() { 293 int x = mTextVisible ? 0 294 : (getWidth() - getPaddingLeft() - getPaddingRight()) / 2 - mDrawableSize / 2; 295 mDrawable.setBounds(x, 0, x + mDrawableSize, mDrawableSize); 296 } 297 298 @Override onClick(View v)299 public void onClick(View v) { 300 mLauncher.getAccessibilityDelegate().handleAccessibleDrop(this, null, null); 301 } 302 setTextVisible(boolean isVisible)303 public void setTextVisible(boolean isVisible) { 304 CharSequence newText = isVisible ? mText : ""; 305 if (mTextVisible != isVisible || !TextUtils.equals(newText, getText())) { 306 mTextVisible = isVisible; 307 setText(newText); 308 centerIcon(); 309 setCompoundDrawablesRelative(mDrawable, null, null, null); 310 int drawablePadding = mTextVisible ? mDrawablePadding : 0; 311 setCompoundDrawablePadding(drawablePadding); 312 } 313 } 314 315 @Override onSizeChanged(int w, int h, int oldw, int oldh)316 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 317 super.onSizeChanged(w, h, oldw, oldh); 318 centerIcon(); 319 } 320 setToolTipLocation(int location)321 public void setToolTipLocation(int location) { 322 mToolTipLocation = location; 323 hideTooltip(); 324 } 325 isTextTruncated(int availableWidth)326 public boolean isTextTruncated(int availableWidth) { 327 availableWidth -= (getPaddingLeft() + getPaddingRight() + mDrawable.getIntrinsicWidth() 328 + getCompoundDrawablePadding()); 329 CharSequence displayedText = TextUtils.ellipsize(mText, getPaint(), availableWidth, 330 TextUtils.TruncateAt.END); 331 return !mText.equals(displayedText); 332 } 333 } 334