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 android.animation.AnimatorSet; 20 import android.animation.FloatArrayEvaluator; 21 import android.animation.ObjectAnimator; 22 import android.animation.ValueAnimator; 23 import android.animation.ValueAnimator.AnimatorUpdateListener; 24 import android.annotation.TargetApi; 25 import android.content.Context; 26 import android.content.res.ColorStateList; 27 import android.content.res.Resources; 28 import android.content.res.TypedArray; 29 import android.graphics.ColorMatrix; 30 import android.graphics.ColorMatrixColorFilter; 31 import android.graphics.PointF; 32 import android.graphics.Rect; 33 import android.graphics.drawable.Drawable; 34 import android.os.Build; 35 import android.util.AttributeSet; 36 import android.view.View; 37 import android.view.View.OnClickListener; 38 import android.view.ViewGroup; 39 import android.view.accessibility.AccessibilityEvent; 40 import android.view.animation.DecelerateInterpolator; 41 import android.view.animation.LinearInterpolator; 42 import android.widget.TextView; 43 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.util.Thunk; 49 50 /** 51 * Implements a DropTarget. 52 */ 53 public abstract class ButtonDropTarget extends TextView 54 implements DropTarget, DragController.DragListener, OnClickListener { 55 56 private static final int DRAG_VIEW_DROP_DURATION = 285; 57 58 private final boolean mHideParentOnDisable; 59 protected final Launcher mLauncher; 60 61 private int mBottomDragPadding; 62 protected DropTargetBar mDropTargetBar; 63 64 /** Whether this drop target is active for the current drag */ 65 protected boolean mActive; 66 /** Whether an accessible drag is in progress */ 67 private boolean mAccessibleDrag; 68 /** An item must be dragged at least this many pixels before this drop target is enabled. */ 69 private final int mDragDistanceThreshold; 70 71 /** The paint applied to the drag view on hover */ 72 protected int mHoverColor = 0; 73 74 protected ColorStateList mOriginalTextColor; 75 protected Drawable mDrawable; 76 77 private AnimatorSet mCurrentColorAnim; 78 @Thunk ColorMatrix mSrcFilter, mDstFilter, mCurrentFilter; 79 ButtonDropTarget(Context context, AttributeSet attrs)80 public ButtonDropTarget(Context context, AttributeSet attrs) { 81 this(context, attrs, 0); 82 } 83 ButtonDropTarget(Context context, AttributeSet attrs, int defStyle)84 public ButtonDropTarget(Context context, AttributeSet attrs, int defStyle) { 85 super(context, attrs, defStyle); 86 mLauncher = Launcher.getLauncher(context); 87 88 Resources resources = getResources(); 89 mBottomDragPadding = resources.getDimensionPixelSize(R.dimen.drop_target_drag_padding); 90 91 TypedArray a = context.obtainStyledAttributes(attrs, 92 R.styleable.ButtonDropTarget, defStyle, 0); 93 mHideParentOnDisable = a.getBoolean(R.styleable.ButtonDropTarget_hideParentOnDisable, false); 94 a.recycle(); 95 mDragDistanceThreshold = resources.getDimensionPixelSize(R.dimen.drag_distanceThreshold); 96 } 97 98 @Override onFinishInflate()99 protected void onFinishInflate() { 100 super.onFinishInflate(); 101 mOriginalTextColor = getTextColors(); 102 } 103 104 @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) setDrawable(int resId)105 protected void setDrawable(int resId) { 106 // We do not set the drawable in the xml as that inflates two drawables corresponding to 107 // drawableLeft and drawableStart. 108 mDrawable = getResources().getDrawable(resId); 109 110 if (Utilities.ATLEAST_JB_MR1) { 111 setCompoundDrawablesRelativeWithIntrinsicBounds(mDrawable, null, null, null); 112 } else { 113 setCompoundDrawablesWithIntrinsicBounds(mDrawable, null, null, null); 114 } 115 } 116 setDropTargetBar(DropTargetBar dropTargetBar)117 public void setDropTargetBar(DropTargetBar dropTargetBar) { 118 mDropTargetBar = dropTargetBar; 119 } 120 121 @Override onFlingToDelete(DragObject d, PointF vec)122 public void onFlingToDelete(DragObject d, PointF vec) { } 123 124 @Override onDragEnter(DragObject d)125 public final void onDragEnter(DragObject d) { 126 d.dragView.setColor(mHoverColor); 127 if (Utilities.ATLEAST_LOLLIPOP) { 128 animateTextColor(mHoverColor); 129 } else { 130 if (mCurrentFilter == null) { 131 mCurrentFilter = new ColorMatrix(); 132 } 133 DragView.setColorScale(mHoverColor, mCurrentFilter); 134 mDrawable.setColorFilter(new ColorMatrixColorFilter(mCurrentFilter)); 135 setTextColor(mHoverColor); 136 } 137 if (d.stateAnnouncer != null) { 138 d.stateAnnouncer.cancel(); 139 } 140 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); 141 } 142 143 @Override onDragOver(DragObject d)144 public void onDragOver(DragObject d) { 145 // Do nothing 146 } 147 resetHoverColor()148 protected void resetHoverColor() { 149 if (Utilities.ATLEAST_LOLLIPOP) { 150 animateTextColor(mOriginalTextColor.getDefaultColor()); 151 } else { 152 mDrawable.setColorFilter(null); 153 setTextColor(mOriginalTextColor); 154 } 155 } 156 157 @TargetApi(Build.VERSION_CODES.LOLLIPOP) animateTextColor(int targetColor)158 private void animateTextColor(int targetColor) { 159 if (mCurrentColorAnim != null) { 160 mCurrentColorAnim.cancel(); 161 } 162 163 mCurrentColorAnim = new AnimatorSet(); 164 mCurrentColorAnim.setDuration(DragView.COLOR_CHANGE_DURATION); 165 166 if (mSrcFilter == null) { 167 mSrcFilter = new ColorMatrix(); 168 mDstFilter = new ColorMatrix(); 169 mCurrentFilter = new ColorMatrix(); 170 } 171 172 DragView.setColorScale(getTextColor(), mSrcFilter); 173 DragView.setColorScale(targetColor, mDstFilter); 174 ValueAnimator anim1 = ValueAnimator.ofObject( 175 new FloatArrayEvaluator(mCurrentFilter.getArray()), 176 mSrcFilter.getArray(), mDstFilter.getArray()); 177 anim1.addUpdateListener(new AnimatorUpdateListener() { 178 179 @Override 180 public void onAnimationUpdate(ValueAnimator animation) { 181 mDrawable.setColorFilter(new ColorMatrixColorFilter(mCurrentFilter)); 182 invalidate(); 183 } 184 }); 185 186 mCurrentColorAnim.play(anim1); 187 mCurrentColorAnim.play(ObjectAnimator.ofArgb(this, "textColor", targetColor)); 188 mCurrentColorAnim.start(); 189 } 190 191 @Override onDragExit(DragObject d)192 public final void onDragExit(DragObject d) { 193 if (!d.dragComplete) { 194 d.dragView.setColor(0); 195 resetHoverColor(); 196 } else { 197 // Restore the hover color 198 d.dragView.setColor(mHoverColor); 199 } 200 } 201 202 @Override onDragStart(DropTarget.DragObject dragObject, DragOptions options)203 public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) { 204 mActive = supportsDrop(dragObject.dragSource, dragObject.dragInfo); 205 mDrawable.setColorFilter(null); 206 if (mCurrentColorAnim != null) { 207 mCurrentColorAnim.cancel(); 208 mCurrentColorAnim = null; 209 } 210 setTextColor(mOriginalTextColor); 211 (mHideParentOnDisable ? ((ViewGroup) getParent()) : this) 212 .setVisibility(mActive ? View.VISIBLE : View.GONE); 213 214 mAccessibleDrag = options.isAccessibleDrag; 215 setOnClickListener(mAccessibleDrag ? this : null); 216 } 217 218 @Override acceptDrop(DragObject dragObject)219 public final boolean acceptDrop(DragObject dragObject) { 220 return supportsDrop(dragObject.dragSource, dragObject.dragInfo); 221 } 222 supportsDrop(DragSource source, ItemInfo info)223 protected abstract boolean supportsDrop(DragSource source, ItemInfo info); 224 225 @Override isDropEnabled()226 public boolean isDropEnabled() { 227 return mActive && (mAccessibleDrag || 228 mLauncher.getDragController().getDistanceDragged() >= mDragDistanceThreshold); 229 } 230 231 @Override onDragEnd()232 public void onDragEnd() { 233 mActive = false; 234 setOnClickListener(null); 235 } 236 237 /** 238 * On drop animate the dropView to the icon. 239 */ 240 @Override onDrop(final DragObject d)241 public void onDrop(final DragObject d) { 242 final DragLayer dragLayer = mLauncher.getDragLayer(); 243 final Rect from = new Rect(); 244 dragLayer.getViewRectRelativeToSelf(d.dragView, from); 245 246 int width = mDrawable.getIntrinsicWidth(); 247 int height = mDrawable.getIntrinsicHeight(); 248 final Rect to = getIconRect(d.dragView.getMeasuredWidth(), d.dragView.getMeasuredHeight(), 249 width, height); 250 final float scale = (float) to.width() / from.width(); 251 mDropTargetBar.deferOnDragEnd(); 252 253 Runnable onAnimationEndRunnable = new Runnable() { 254 @Override 255 public void run() { 256 completeDrop(d); 257 mDropTargetBar.onDragEnd(); 258 mLauncher.exitSpringLoadedDragModeDelayed(true, 0, null); 259 } 260 }; 261 dragLayer.animateView(d.dragView, from, to, scale, 1f, 1f, 0.1f, 0.1f, 262 mLauncher.getDragController().isExternalDrag() ? 1 : DRAG_VIEW_DROP_DURATION, 263 new DecelerateInterpolator(2), 264 new LinearInterpolator(), onAnimationEndRunnable, 265 DragLayer.ANIMATION_END_DISAPPEAR, null); 266 } 267 268 @Override prepareAccessibilityDrop()269 public void prepareAccessibilityDrop() { } 270 completeDrop(DragObject d)271 @Thunk abstract void completeDrop(DragObject d); 272 273 @Override getHitRectRelativeToDragLayer(android.graphics.Rect outRect)274 public void getHitRectRelativeToDragLayer(android.graphics.Rect outRect) { 275 super.getHitRect(outRect); 276 outRect.bottom += mBottomDragPadding; 277 278 int[] coords = new int[2]; 279 mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, coords); 280 outRect.offsetTo(coords[0], coords[1]); 281 } 282 getIconRect(int viewWidth, int viewHeight, int drawableWidth, int drawableHeight)283 protected Rect getIconRect(int viewWidth, int viewHeight, int drawableWidth, int drawableHeight) { 284 DragLayer dragLayer = mLauncher.getDragLayer(); 285 286 // Find the rect to animate to (the view is center aligned) 287 Rect to = new Rect(); 288 dragLayer.getViewRectRelativeToSelf(this, to); 289 290 final int width = drawableWidth; 291 final int height = drawableHeight; 292 293 final int left; 294 final int right; 295 296 if (Utilities.isRtl(getResources())) { 297 right = to.right - getPaddingRight(); 298 left = right - width; 299 } else { 300 left = to.left + getPaddingLeft(); 301 right = left + width; 302 } 303 304 final int top = to.top + (getMeasuredHeight() - height) / 2; 305 final int bottom = top + height; 306 307 to.set(left, top, right, bottom); 308 309 // Center the destination rect about the trash icon 310 final int xOffset = (int) -(viewWidth - width) / 2; 311 final int yOffset = (int) -(viewHeight - height) / 2; 312 to.offset(xOffset, yOffset); 313 314 return to; 315 } 316 317 @Override onClick(View v)318 public void onClick(View v) { 319 mLauncher.getAccessibilityDelegate().handleAccessibleDrop(this, null, null); 320 } 321 getTextColor()322 public int getTextColor() { 323 return getTextColors().getDefaultColor(); 324 } 325 } 326