ButtonDropTarget.java revision 94b510cc683a6436ae82c6d323cbd8b429561b06
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 17package com.android.launcher3; 18 19import android.animation.AnimatorSet; 20import android.animation.FloatArrayEvaluator; 21import android.animation.ObjectAnimator; 22import android.animation.ValueAnimator; 23import android.animation.ValueAnimator.AnimatorUpdateListener; 24import android.annotation.TargetApi; 25import android.content.Context; 26import android.content.res.ColorStateList; 27import android.content.res.Resources; 28import android.content.res.TypedArray; 29import android.graphics.ColorMatrix; 30import android.graphics.ColorMatrixColorFilter; 31import android.graphics.PointF; 32import android.graphics.Rect; 33import android.graphics.drawable.Drawable; 34import android.os.Build; 35import android.util.AttributeSet; 36import android.view.View; 37import android.view.View.OnClickListener; 38import android.view.ViewGroup; 39import android.view.accessibility.AccessibilityEvent; 40import android.view.animation.DecelerateInterpolator; 41import android.view.animation.LinearInterpolator; 42import android.widget.TextView; 43 44import com.android.launcher3.dragndrop.DragController; 45import com.android.launcher3.dragndrop.DragLayer; 46import com.android.launcher3.dragndrop.DragOptions; 47import com.android.launcher3.dragndrop.DragView; 48import com.android.launcher3.util.Thunk; 49 50/** 51 * Implements a DropTarget. 52 */ 53public 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 80 public ButtonDropTarget(Context context, AttributeSet attrs) { 81 this(context, attrs, 0); 82 } 83 84 public ButtonDropTarget(Context context, AttributeSet attrs, int defStyle) { 85 super(context, attrs, defStyle); 86 mLauncher = (Launcher) 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 99 protected void onFinishInflate() { 100 super.onFinishInflate(); 101 mOriginalTextColor = getTextColors(); 102 } 103 104 @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) 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 117 public void setDropTargetBar(DropTargetBar dropTargetBar) { 118 mDropTargetBar = dropTargetBar; 119 } 120 121 @Override 122 public void onFlingToDelete(DragObject d, PointF vec) { } 123 124 @Override 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 144 public void onDragOver(DragObject d) { 145 // Do nothing 146 } 147 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) 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 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 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 219 public final boolean acceptDrop(DragObject dragObject) { 220 return supportsDrop(dragObject.dragSource, dragObject.dragInfo); 221 } 222 223 protected abstract boolean supportsDrop(DragSource source, ItemInfo info); 224 225 @Override 226 public boolean isDropEnabled() { 227 return mActive && (mAccessibleDrag || 228 mLauncher.getDragController().getDistanceDragged() >= mDragDistanceThreshold); 229 } 230 231 @Override 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 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 DRAG_VIEW_DROP_DURATION, new DecelerateInterpolator(2), 263 new LinearInterpolator(), onAnimationEndRunnable, 264 DragLayer.ANIMATION_END_DISAPPEAR, null); 265 } 266 267 @Override 268 public void prepareAccessibilityDrop() { } 269 270 @Thunk abstract void completeDrop(DragObject d); 271 272 @Override 273 public void getHitRectRelativeToDragLayer(android.graphics.Rect outRect) { 274 super.getHitRect(outRect); 275 outRect.bottom += mBottomDragPadding; 276 277 int[] coords = new int[2]; 278 mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, coords); 279 outRect.offsetTo(coords[0], coords[1]); 280 } 281 282 protected Rect getIconRect(int viewWidth, int viewHeight, int drawableWidth, int drawableHeight) { 283 DragLayer dragLayer = mLauncher.getDragLayer(); 284 285 // Find the rect to animate to (the view is center aligned) 286 Rect to = new Rect(); 287 dragLayer.getViewRectRelativeToSelf(this, to); 288 289 final int width = drawableWidth; 290 final int height = drawableHeight; 291 292 final int left; 293 final int right; 294 295 if (Utilities.isRtl(getResources())) { 296 right = to.right - getPaddingRight(); 297 left = right - width; 298 } else { 299 left = to.left + getPaddingLeft(); 300 right = left + width; 301 } 302 303 final int top = to.top + (getMeasuredHeight() - height) / 2; 304 final int bottom = top + height; 305 306 to.set(left, top, right, bottom); 307 308 // Center the destination rect about the trash icon 309 final int xOffset = (int) -(viewWidth - width) / 2; 310 final int yOffset = (int) -(viewHeight - height) / 2; 311 to.offset(xOffset, yOffset); 312 313 return to; 314 } 315 316 @Override 317 public void onClick(View v) { 318 mLauncher.getAccessibilityDelegate().handleAccessibleDrop(this, null, null); 319 } 320 321 public int getTextColor() { 322 return getTextColors().getDefaultColor(); 323 } 324} 325