• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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