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