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