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