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.animation.AnimatorSet; 24 import android.animation.FloatArrayEvaluator; 25 import android.animation.ObjectAnimator; 26 import android.animation.ValueAnimator; 27 import android.content.Context; 28 import android.content.res.ColorStateList; 29 import android.content.res.Resources; 30 import android.graphics.ColorMatrix; 31 import android.graphics.ColorMatrixColorFilter; 32 import android.graphics.Rect; 33 import android.graphics.drawable.Drawable; 34 import android.text.TextUtils; 35 import android.util.AttributeSet; 36 import android.view.LayoutInflater; 37 import android.view.View; 38 import android.view.View.OnClickListener; 39 import android.view.accessibility.AccessibilityEvent; 40 import android.widget.PopupWindow; 41 import android.widget.TextView; 42 43 import com.android.launcher3.anim.Interpolators; 44 import com.android.launcher3.dragndrop.DragController; 45 import com.android.launcher3.dragndrop.DragLayer; 46 import com.android.launcher3.dragndrop.DragOptions; 47 import com.android.launcher3.dragndrop.DragView; 48 import com.android.launcher3.userevent.nano.LauncherLogProto.Target; 49 import com.android.launcher3.util.Themes; 50 import com.android.launcher3.util.Thunk; 51 52 /** 53 * Implements a DropTarget. 54 */ 55 public abstract class ButtonDropTarget extends TextView 56 implements DropTarget, DragController.DragListener, OnClickListener { 57 58 private static final int[] sTempCords = new int[2]; 59 private static final int DRAG_VIEW_DROP_DURATION = 285; 60 61 public static final int TOOLTIP_DEFAULT = 0; 62 public static final int TOOLTIP_LEFT = 1; 63 public static final int TOOLTIP_RIGHT = 2; 64 65 protected final Launcher mLauncher; 66 67 private int mBottomDragPadding; 68 protected DropTargetBar mDropTargetBar; 69 70 /** Whether this drop target is active for the current drag */ 71 protected boolean mActive; 72 /** Whether an accessible drag is in progress */ 73 private boolean mAccessibleDrag; 74 /** An item must be dragged at least this many pixels before this drop target is enabled. */ 75 private final int mDragDistanceThreshold; 76 77 /** The paint applied to the drag view on hover */ 78 protected int mHoverColor = 0; 79 80 protected CharSequence mText; 81 protected ColorStateList mOriginalTextColor; 82 protected Drawable mDrawable; 83 private boolean mTextVisible = true; 84 85 private PopupWindow mToolTip; 86 private int mToolTipLocation; 87 88 private AnimatorSet mCurrentColorAnim; 89 @Thunk ColorMatrix mSrcFilter, mDstFilter, mCurrentFilter; 90 ButtonDropTarget(Context context, AttributeSet attrs)91 public ButtonDropTarget(Context context, AttributeSet attrs) { 92 this(context, attrs, 0); 93 } 94 ButtonDropTarget(Context context, AttributeSet attrs, int defStyle)95 public ButtonDropTarget(Context context, AttributeSet attrs, int defStyle) { 96 super(context, attrs, defStyle); 97 mLauncher = Launcher.getLauncher(context); 98 99 Resources resources = getResources(); 100 mBottomDragPadding = resources.getDimensionPixelSize(R.dimen.drop_target_drag_padding); 101 mDragDistanceThreshold = resources.getDimensionPixelSize(R.dimen.drag_distanceThreshold); 102 } 103 104 @Override onFinishInflate()105 protected void onFinishInflate() { 106 super.onFinishInflate(); 107 mText = getText(); 108 mOriginalTextColor = getTextColors(); 109 setContentDescription(mText); 110 } 111 updateText(int resId)112 protected void updateText(int resId) { 113 setText(resId); 114 mText = getText(); 115 setContentDescription(mText); 116 } 117 setDrawable(int resId)118 protected void setDrawable(int resId) { 119 // We do not set the drawable in the xml as that inflates two drawables corresponding to 120 // drawableLeft and drawableStart. 121 if (mTextVisible) { 122 setCompoundDrawablesRelativeWithIntrinsicBounds(resId, 0, 0, 0); 123 mDrawable = getCompoundDrawablesRelative()[0]; 124 } else { 125 setCompoundDrawablesRelativeWithIntrinsicBounds(0, resId, 0, 0); 126 mDrawable = getCompoundDrawablesRelative()[1]; 127 } 128 } 129 setDropTargetBar(DropTargetBar dropTargetBar)130 public void setDropTargetBar(DropTargetBar dropTargetBar) { 131 mDropTargetBar = dropTargetBar; 132 } 133 hideTooltip()134 private void hideTooltip() { 135 if (mToolTip != null) { 136 mToolTip.dismiss(); 137 mToolTip = null; 138 } 139 } 140 141 @Override onDragEnter(DragObject d)142 public final void onDragEnter(DragObject d) { 143 if (!d.accessibleDrag && !mTextVisible) { 144 // Show tooltip 145 hideTooltip(); 146 147 TextView message = (TextView) LayoutInflater.from(getContext()).inflate( 148 R.layout.drop_target_tool_tip, null); 149 message.setText(mText); 150 151 mToolTip = new PopupWindow(message, WRAP_CONTENT, WRAP_CONTENT); 152 int x = 0, y = 0; 153 if (mToolTipLocation != TOOLTIP_DEFAULT) { 154 y = -getMeasuredHeight(); 155 message.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); 156 if (mToolTipLocation == TOOLTIP_LEFT) { 157 x = - getMeasuredWidth() - message.getMeasuredWidth() / 2; 158 } else { 159 x = getMeasuredWidth() / 2 + message.getMeasuredWidth() / 2; 160 } 161 } 162 mToolTip.showAsDropDown(this, x, y); 163 } 164 165 d.dragView.setColor(mHoverColor); 166 animateTextColor(mHoverColor); 167 if (d.stateAnnouncer != null) { 168 d.stateAnnouncer.cancel(); 169 } 170 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); 171 } 172 173 @Override onDragOver(DragObject d)174 public void onDragOver(DragObject d) { 175 // Do nothing 176 } 177 resetHoverColor()178 protected void resetHoverColor() { 179 animateTextColor(mOriginalTextColor.getDefaultColor()); 180 } 181 animateTextColor(int targetColor)182 private void animateTextColor(int targetColor) { 183 if (mCurrentColorAnim != null) { 184 mCurrentColorAnim.cancel(); 185 } 186 187 mCurrentColorAnim = new AnimatorSet(); 188 mCurrentColorAnim.setDuration(DragView.COLOR_CHANGE_DURATION); 189 190 if (mSrcFilter == null) { 191 mSrcFilter = new ColorMatrix(); 192 mDstFilter = new ColorMatrix(); 193 mCurrentFilter = new ColorMatrix(); 194 } 195 196 int defaultTextColor = mOriginalTextColor.getDefaultColor(); 197 Themes.setColorChangeOnMatrix(defaultTextColor, getTextColor(), mSrcFilter); 198 Themes.setColorChangeOnMatrix(defaultTextColor, targetColor, mDstFilter); 199 200 ValueAnimator anim1 = ValueAnimator.ofObject( 201 new FloatArrayEvaluator(mCurrentFilter.getArray()), 202 mSrcFilter.getArray(), mDstFilter.getArray()); 203 anim1.addUpdateListener((anim) -> { 204 mDrawable.setColorFilter(new ColorMatrixColorFilter(mCurrentFilter)); 205 invalidate(); 206 }); 207 208 mCurrentColorAnim.play(anim1); 209 mCurrentColorAnim.play(ObjectAnimator.ofArgb(this, "textColor", targetColor)); 210 mCurrentColorAnim.start(); 211 } 212 213 @Override onDragExit(DragObject d)214 public final void onDragExit(DragObject d) { 215 hideTooltip(); 216 217 if (!d.dragComplete) { 218 d.dragView.setColor(0); 219 resetHoverColor(); 220 } else { 221 // Restore the hover color 222 d.dragView.setColor(mHoverColor); 223 } 224 } 225 226 @Override onDragStart(DropTarget.DragObject dragObject, DragOptions options)227 public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) { 228 mActive = supportsDrop(dragObject.dragInfo); 229 mDrawable.setColorFilter(null); 230 if (mCurrentColorAnim != null) { 231 mCurrentColorAnim.cancel(); 232 mCurrentColorAnim = null; 233 } 234 setTextColor(mOriginalTextColor); 235 setVisibility(mActive ? View.VISIBLE : View.GONE); 236 237 mAccessibleDrag = options.isAccessibleDrag; 238 setOnClickListener(mAccessibleDrag ? this : null); 239 } 240 241 @Override acceptDrop(DragObject dragObject)242 public final boolean acceptDrop(DragObject dragObject) { 243 return supportsDrop(dragObject.dragInfo); 244 } 245 supportsDrop(ItemInfo info)246 protected abstract boolean supportsDrop(ItemInfo info); 247 supportsAccessibilityDrop(ItemInfo info, View view)248 public abstract boolean supportsAccessibilityDrop(ItemInfo info, View view); 249 250 @Override isDropEnabled()251 public boolean isDropEnabled() { 252 return mActive && (mAccessibleDrag || 253 mLauncher.getDragController().getDistanceDragged() >= mDragDistanceThreshold); 254 } 255 256 @Override onDragEnd()257 public void onDragEnd() { 258 mActive = false; 259 setOnClickListener(null); 260 } 261 262 /** 263 * On drop animate the dropView to the icon. 264 */ 265 @Override onDrop(final DragObject d, final DragOptions options)266 public void onDrop(final DragObject d, final DragOptions options) { 267 final DragLayer dragLayer = mLauncher.getDragLayer(); 268 final Rect from = new Rect(); 269 dragLayer.getViewRectRelativeToSelf(d.dragView, from); 270 271 final Rect to = getIconRect(d); 272 final float scale = (float) to.width() / from.width(); 273 mDropTargetBar.deferOnDragEnd(); 274 275 Runnable onAnimationEndRunnable = () -> { 276 completeDrop(d); 277 mDropTargetBar.onDragEnd(); 278 mLauncher.getStateManager().goToState(NORMAL); 279 }; 280 281 dragLayer.animateView(d.dragView, from, to, scale, 1f, 1f, 0.1f, 0.1f, 282 DRAG_VIEW_DROP_DURATION, 283 Interpolators.DEACCEL_2, Interpolators.LINEAR, onAnimationEndRunnable, 284 DragLayer.ANIMATION_END_DISAPPEAR, null); 285 } 286 getAccessibilityAction()287 public abstract int getAccessibilityAction(); 288 289 @Override prepareAccessibilityDrop()290 public void prepareAccessibilityDrop() { } 291 onAccessibilityDrop(View view, ItemInfo item)292 public abstract void onAccessibilityDrop(View view, ItemInfo item); 293 completeDrop(DragObject d)294 public abstract void completeDrop(DragObject d); 295 296 @Override getHitRectRelativeToDragLayer(android.graphics.Rect outRect)297 public void getHitRectRelativeToDragLayer(android.graphics.Rect outRect) { 298 super.getHitRect(outRect); 299 outRect.bottom += mBottomDragPadding; 300 301 sTempCords[0] = sTempCords[1] = 0; 302 mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, sTempCords); 303 outRect.offsetTo(sTempCords[0], sTempCords[1]); 304 } 305 getIconRect(DragObject dragObject)306 public Rect getIconRect(DragObject dragObject) { 307 int viewWidth = dragObject.dragView.getMeasuredWidth(); 308 int viewHeight = dragObject.dragView.getMeasuredHeight(); 309 int drawableWidth = mDrawable.getIntrinsicWidth(); 310 int drawableHeight = mDrawable.getIntrinsicHeight(); 311 DragLayer dragLayer = mLauncher.getDragLayer(); 312 313 // Find the rect to animate to (the view is center aligned) 314 Rect to = new Rect(); 315 dragLayer.getViewRectRelativeToSelf(this, to); 316 317 final int width = drawableWidth; 318 final int height = drawableHeight; 319 320 final int left; 321 final int right; 322 323 if (Utilities.isRtl(getResources())) { 324 right = to.right - getPaddingRight(); 325 left = right - width; 326 } else { 327 left = to.left + getPaddingLeft(); 328 right = left + width; 329 } 330 331 final int top = to.top + (getMeasuredHeight() - height) / 2; 332 final int bottom = top + height; 333 334 to.set(left, top, right, bottom); 335 336 // Center the destination rect about the trash icon 337 final int xOffset = -(viewWidth - width) / 2; 338 final int yOffset = -(viewHeight - height) / 2; 339 to.offset(xOffset, yOffset); 340 341 return to; 342 } 343 344 @Override onClick(View v)345 public void onClick(View v) { 346 mLauncher.getAccessibilityDelegate().handleAccessibleDrop(this, null, null); 347 } 348 getTextColor()349 public int getTextColor() { 350 return getTextColors().getDefaultColor(); 351 } 352 setTextVisible(boolean isVisible)353 public void setTextVisible(boolean isVisible) { 354 CharSequence newText = isVisible ? mText : ""; 355 if (mTextVisible != isVisible || !TextUtils.equals(newText, getText())) { 356 mTextVisible = isVisible; 357 setText(newText); 358 if (mTextVisible) { 359 setCompoundDrawablesRelativeWithIntrinsicBounds(mDrawable, null, null, null); 360 } else { 361 setCompoundDrawablesRelativeWithIntrinsicBounds(null, mDrawable, null, null); 362 } 363 } 364 } 365 setToolTipLocation(int location)366 public void setToolTipLocation(int location) { 367 mToolTipLocation = location; 368 hideTooltip(); 369 } 370 isTextTruncated(int availableWidth)371 public boolean isTextTruncated(int availableWidth) { 372 availableWidth -= (getPaddingLeft() + getPaddingRight() + mDrawable.getIntrinsicWidth() 373 + getCompoundDrawablePadding()); 374 CharSequence displayedText = TextUtils.ellipsize(mText, getPaint(), availableWidth, 375 TextUtils.TruncateAt.END); 376 return !mText.equals(displayedText); 377 } 378 getDropTargetForLogging()379 public abstract Target getDropTargetForLogging(); 380 } 381