blob: c00c9f850d52a2beaf9ae791cbba9a14523e314a [file] [log] [blame]
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001/*
2 * Copyright (C) 2008 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
Daniel Sandler325dc232013-06-05 22:57:57 -040017package com.android.launcher3;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080018
Adam Cohen2801caf2011-05-13 20:57:39 -070019import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
Adam Cohen2801caf2011-05-13 20:57:39 -070021import android.animation.ValueAnimator;
22import android.animation.ValueAnimator.AnimatorUpdateListener;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080023import android.content.Context;
24import android.content.res.Resources;
Adam Cohena9cf38f2011-05-02 15:36:58 -070025import android.graphics.Canvas;
Adam Cohenf4b08912011-05-17 18:45:47 -070026import android.graphics.Color;
Adam Cohenbadf71e2011-05-26 19:08:29 -070027import android.graphics.PorterDuff;
Adam Cohen3e8f8112011-07-02 18:03:00 -070028import android.graphics.Rect;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080029import android.graphics.drawable.Drawable;
Daniel Sandler24ad0f02013-07-31 00:14:58 -040030import android.os.Looper;
Adam Cohen099f60d2011-08-23 21:07:26 -070031import android.os.Parcelable;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080032import android.util.AttributeSet;
33import android.view.LayoutInflater;
Winson Chung88f33452012-02-23 15:23:44 -080034import android.view.MotionEvent;
Adam Cohen7c693212011-05-18 15:26:57 -070035import android.view.View;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080036import android.view.ViewGroup;
Adam Cohen3e8f8112011-07-02 18:03:00 -070037import android.view.animation.AccelerateInterpolator;
38import android.view.animation.DecelerateInterpolator;
Adam Cohen76fc0852011-06-17 13:26:23 -070039import android.widget.ImageView;
40import android.widget.LinearLayout;
Adam Cohena9cf38f2011-05-02 15:36:58 -070041import android.widget.TextView;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080042
Daniel Sandler325dc232013-06-05 22:57:57 -040043import com.android.launcher3.R;
44import com.android.launcher3.DropTarget.DragObject;
45import com.android.launcher3.FolderInfo.FolderListener;
Romain Guyedcce092010-03-04 13:03:17 -080046
Adam Cohenc0dcf592011-06-01 15:30:43 -070047import java.util.ArrayList;
48
The Android Open Source Project31dd5032009-03-03 19:32:27 -080049/**
50 * An icon that can appear on in the workspace representing an {@link UserFolder}.
51 */
Adam Cohen76fc0852011-06-17 13:26:23 -070052public class FolderIcon extends LinearLayout implements FolderListener {
The Android Open Source Project31dd5032009-03-03 19:32:27 -080053 private Launcher mLauncher;
Adam Cohenfb91f302012-06-11 15:45:18 -070054 private Folder mFolder;
55 private FolderInfo mInfo;
Adam Cohen099f60d2011-08-23 21:07:26 -070056 private static boolean sStaticValuesDirty = true;
Adam Cohena9cf38f2011-05-02 15:36:58 -070057
Winson Chung88f33452012-02-23 15:23:44 -080058 private CheckLongPressHelper mLongPressHelper;
59
Adam Cohenbadf71e2011-05-26 19:08:29 -070060 // The number of icons to display in the
Adam Cohen76fc0852011-06-17 13:26:23 -070061 private static final int NUM_ITEMS_IN_PREVIEW = 3;
Adam Cohenf4b08912011-05-17 18:45:47 -070062 private static final int CONSUMPTION_ANIMATION_DURATION = 100;
Adam Cohend0445262011-07-04 23:53:22 -070063 private static final int DROP_IN_ANIMATION_DURATION = 400;
Adam Cohen8dfcba42011-07-07 16:38:18 -070064 private static final int INITIAL_ITEM_ANIMATION_DURATION = 350;
Adam Cohenfb91f302012-06-11 15:45:18 -070065 private static final int FINAL_ITEM_ANIMATION_DURATION = 200;
Adam Cohenbadf71e2011-05-26 19:08:29 -070066
67 // The degree to which the inner ring grows when accepting drop
Adam Cohen69ce2e52011-07-03 19:25:21 -070068 private static final float INNER_RING_GROWTH_FACTOR = 0.15f;
Adam Cohenbadf71e2011-05-26 19:08:29 -070069
Adam Cohenbadf71e2011-05-26 19:08:29 -070070 // The degree to which the outer ring is scaled in its natural state
Adam Cohen69ce2e52011-07-03 19:25:21 -070071 private static final float OUTER_RING_GROWTH_FACTOR = 0.3f;
Adam Cohenbadf71e2011-05-26 19:08:29 -070072
73 // The amount of vertical spread between items in the stack [0...1]
Adam Cohen76fc0852011-06-17 13:26:23 -070074 private static final float PERSPECTIVE_SHIFT_FACTOR = 0.24f;
Adam Cohenbadf71e2011-05-26 19:08:29 -070075
76 // The degree to which the item in the back of the stack is scaled [0...1]
77 // (0 means it's not scaled at all, 1 means it's scaled to nothing)
Adam Cohen76fc0852011-06-17 13:26:23 -070078 private static final float PERSPECTIVE_SCALE_FACTOR = 0.35f;
Adam Cohenbadf71e2011-05-26 19:08:29 -070079
Adam Cohenc51934b2011-07-26 21:07:43 -070080 public static Drawable sSharedFolderLeaveBehind = null;
81
Adam Cohen76fc0852011-06-17 13:26:23 -070082 private ImageView mPreviewBackground;
83 private BubbleTextView mFolderName;
Adam Cohen073a46f2011-05-17 16:28:09 -070084
Adam Cohen19072da2011-05-31 14:30:45 -070085 FolderRingAnimator mFolderRingAnimator = null;
Adam Cohen2801caf2011-05-13 20:57:39 -070086
Adam Cohend0445262011-07-04 23:53:22 -070087 // These variables are all associated with the drawing of the preview; they are stored
88 // as member variables for shared usage and to avoid computation on each frame
89 private int mIntrinsicIconSize;
90 private float mBaselineIconScale;
91 private int mBaselineIconSize;
92 private int mAvailableSpaceInPreview;
93 private int mTotalWidth = -1;
94 private int mPreviewOffsetX;
95 private int mPreviewOffsetY;
96 private float mMaxPerspectiveShift;
97 boolean mAnimating = false;
Adam Cohenfb91f302012-06-11 15:45:18 -070098
Adam Cohend0445262011-07-04 23:53:22 -070099 private PreviewItemDrawingParams mParams = new PreviewItemDrawingParams(0, 0, 0, 0);
100 private PreviewItemDrawingParams mAnimParams = new PreviewItemDrawingParams(0, 0, 0, 0);
Adam Cohenfb91f302012-06-11 15:45:18 -0700101 private ArrayList<ShortcutInfo> mHiddenItems = new ArrayList<ShortcutInfo>();
Adam Cohend0445262011-07-04 23:53:22 -0700102
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800103 public FolderIcon(Context context, AttributeSet attrs) {
104 super(context, attrs);
Winson Chung88f33452012-02-23 15:23:44 -0800105 init();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800106 }
107
108 public FolderIcon(Context context) {
109 super(context);
Winson Chung88f33452012-02-23 15:23:44 -0800110 init();
111 }
112
113 private void init() {
114 mLongPressHelper = new CheckLongPressHelper(this);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800115 }
116
Michael Jurka0280c3b2010-09-17 15:00:07 -0700117 public boolean isDropEnabled() {
Winson Chung7a1d1652011-03-18 15:56:01 -0700118 final ViewGroup cellLayoutChildren = (ViewGroup) getParent();
119 final ViewGroup cellLayout = (ViewGroup) cellLayoutChildren.getParent();
120 final Workspace workspace = (Workspace) cellLayout.getParent();
121 return !workspace.isSmall();
Michael Jurka0280c3b2010-09-17 15:00:07 -0700122 }
123
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800124 static FolderIcon fromXml(int resId, Launcher launcher, ViewGroup group,
Adam Cohendf2cc412011-04-27 16:56:57 -0700125 FolderInfo folderInfo, IconCache iconCache) {
Michael Jurka3a9fced2012-04-13 14:44:29 -0700126 @SuppressWarnings("all") // suppress dead code warning
127 final boolean error = INITIAL_ITEM_ANIMATION_DURATION >= DROP_IN_ANIMATION_DURATION;
128 if (error) {
Adam Cohend0445262011-07-04 23:53:22 -0700129 throw new IllegalStateException("DROP_IN_ANIMATION_DURATION must be greater than " +
130 "INITIAL_ITEM_ANIMATION_DURATION, as sequencing of adding first two items " +
131 "is dependent on this");
132 }
133
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800134 FolderIcon icon = (FolderIcon) LayoutInflater.from(launcher).inflate(resId, group, false);
Winson Chung5f8afe62013-08-12 16:19:28 -0700135 icon.setClipToPadding(false);
Adam Cohend2eca6b2011-07-26 22:49:13 -0700136 icon.mFolderName = (BubbleTextView) icon.findViewById(R.id.folder_icon_name);
Adam Cohen76fc0852011-06-17 13:26:23 -0700137 icon.mFolderName.setText(folderInfo.title);
138 icon.mPreviewBackground = (ImageView) icon.findViewById(R.id.preview_background);
Winson Chung5f8afe62013-08-12 16:19:28 -0700139 LauncherAppState app = LauncherAppState.getInstance();
140 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
141 // Offset the preview background to center this view accordingly
142 LinearLayout.LayoutParams lp =
143 (LinearLayout.LayoutParams) icon.mPreviewBackground.getLayoutParams();
144 lp.topMargin = grid.folderBackgroundOffset;
145 lp.width = grid.folderIconSizePx;
146 lp.height = grid.folderIconSizePx;
Adam Cohen76fc0852011-06-17 13:26:23 -0700147
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800148 icon.setTag(folderInfo);
149 icon.setOnClickListener(launcher);
150 icon.mInfo = folderInfo;
151 icon.mLauncher = launcher;
Adam Cohen3371da02011-10-25 21:38:29 -0700152 icon.setContentDescription(String.format(launcher.getString(R.string.folder_name_format),
153 folderInfo.title));
Adam Cohena9cf38f2011-05-02 15:36:58 -0700154 Folder folder = Folder.fromXml(launcher);
155 folder.setDragController(launcher.getDragController());
Adam Cohen2801caf2011-05-13 20:57:39 -0700156 folder.setFolderIcon(icon);
Adam Cohena9cf38f2011-05-02 15:36:58 -0700157 folder.bind(folderInfo);
158 icon.mFolder = folder;
Adam Cohen099f60d2011-08-23 21:07:26 -0700159
Adam Cohen099f60d2011-08-23 21:07:26 -0700160 icon.mFolderRingAnimator = new FolderRingAnimator(launcher, icon);
161 folderInfo.addListener(icon);
162
Adam Cohen19072da2011-05-31 14:30:45 -0700163 return icon;
164 }
165
Adam Cohen099f60d2011-08-23 21:07:26 -0700166 @Override
167 protected Parcelable onSaveInstanceState() {
168 sStaticValuesDirty = true;
169 return super.onSaveInstanceState();
170 }
171
Adam Cohen19072da2011-05-31 14:30:45 -0700172 public static class FolderRingAnimator {
Adam Cohen69ce2e52011-07-03 19:25:21 -0700173 public int mCellX;
174 public int mCellY;
175 private CellLayout mCellLayout;
Adam Cohen76fc0852011-06-17 13:26:23 -0700176 public float mOuterRingSize;
177 public float mInnerRingSize;
Adam Cohen19072da2011-05-31 14:30:45 -0700178 public FolderIcon mFolderIcon = null;
Adam Cohenc0dcf592011-06-01 15:30:43 -0700179 public static Drawable sSharedOuterRingDrawable = null;
180 public static Drawable sSharedInnerRingDrawable = null;
Adam Cohen76fc0852011-06-17 13:26:23 -0700181 public static int sPreviewSize = -1;
182 public static int sPreviewPadding = -1;
183
Adam Cohenc0dcf592011-06-01 15:30:43 -0700184 private ValueAnimator mAcceptAnimator;
185 private ValueAnimator mNeutralAnimator;
Adam Cohen19072da2011-05-31 14:30:45 -0700186
187 public FolderRingAnimator(Launcher launcher, FolderIcon folderIcon) {
Adam Cohen19072da2011-05-31 14:30:45 -0700188 mFolderIcon = folderIcon;
Adam Cohen76fc0852011-06-17 13:26:23 -0700189 Resources res = launcher.getResources();
Adam Cohen76fc0852011-06-17 13:26:23 -0700190
Adam Cohen099f60d2011-08-23 21:07:26 -0700191 // We need to reload the static values when configuration changes in case they are
192 // different in another configuration
193 if (sStaticValuesDirty) {
Daniel Sandler24ad0f02013-07-31 00:14:58 -0400194 if (Looper.myLooper() != Looper.getMainLooper()) {
195 throw new RuntimeException("FolderRingAnimator loading drawables on non-UI thread "
196 + Thread.currentThread());
197 }
Winson Chung5f8afe62013-08-12 16:19:28 -0700198
199 LauncherAppState app = LauncherAppState.getInstance();
200 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
201 sPreviewSize = grid.folderIconSizePx;
Adam Cohen76fc0852011-06-17 13:26:23 -0700202 sPreviewPadding = res.getDimensionPixelSize(R.dimen.folder_preview_padding);
Adam Cohen76fc0852011-06-17 13:26:23 -0700203 sSharedOuterRingDrawable = res.getDrawable(R.drawable.portal_ring_outer_holo);
Adam Cohen76fc0852011-06-17 13:26:23 -0700204 sSharedInnerRingDrawable = res.getDrawable(R.drawable.portal_ring_inner_holo);
Adam Cohendf6af572011-10-19 14:38:16 -0700205 sSharedFolderLeaveBehind = res.getDrawable(R.drawable.portal_ring_rest);
Adam Cohen099f60d2011-08-23 21:07:26 -0700206 sStaticValuesDirty = false;
Adam Cohen19072da2011-05-31 14:30:45 -0700207 }
Adam Cohen073a46f2011-05-17 16:28:09 -0700208 }
Adam Cohena9cf38f2011-05-02 15:36:58 -0700209
Adam Cohen19072da2011-05-31 14:30:45 -0700210 public void animateToAcceptState() {
Adam Cohenc0dcf592011-06-01 15:30:43 -0700211 if (mNeutralAnimator != null) {
212 mNeutralAnimator.cancel();
213 }
Michael Jurkaf1ad6082013-03-13 12:55:46 +0100214 mAcceptAnimator = LauncherAnimUtils.ofFloat(mCellLayout, 0f, 1f);
Adam Cohenc0dcf592011-06-01 15:30:43 -0700215 mAcceptAnimator.setDuration(CONSUMPTION_ANIMATION_DURATION);
Andrew Flynn850d2e72012-04-26 16:51:20 -0700216
217 final int previewSize = sPreviewSize;
Adam Cohenc0dcf592011-06-01 15:30:43 -0700218 mAcceptAnimator.addUpdateListener(new AnimatorUpdateListener() {
Adam Cohen19072da2011-05-31 14:30:45 -0700219 public void onAnimationUpdate(ValueAnimator animation) {
220 final float percent = (Float) animation.getAnimatedValue();
Andrew Flynn850d2e72012-04-26 16:51:20 -0700221 mOuterRingSize = (1 + percent * OUTER_RING_GROWTH_FACTOR) * previewSize;
222 mInnerRingSize = (1 + percent * INNER_RING_GROWTH_FACTOR) * previewSize;
Adam Cohen69ce2e52011-07-03 19:25:21 -0700223 if (mCellLayout != null) {
224 mCellLayout.invalidate();
Adam Cohen19072da2011-05-31 14:30:45 -0700225 }
226 }
227 });
Adam Cohenc0dcf592011-06-01 15:30:43 -0700228 mAcceptAnimator.addListener(new AnimatorListenerAdapter() {
Adam Cohen19072da2011-05-31 14:30:45 -0700229 @Override
Adam Cohenc0dcf592011-06-01 15:30:43 -0700230 public void onAnimationStart(Animator animation) {
Adam Cohen19072da2011-05-31 14:30:45 -0700231 if (mFolderIcon != null) {
Adam Cohen76fc0852011-06-17 13:26:23 -0700232 mFolderIcon.mPreviewBackground.setVisibility(INVISIBLE);
Adam Cohen19072da2011-05-31 14:30:45 -0700233 }
234 }
235 });
Adam Cohenc0dcf592011-06-01 15:30:43 -0700236 mAcceptAnimator.start();
Adam Cohen19072da2011-05-31 14:30:45 -0700237 }
238
239 public void animateToNaturalState() {
Adam Cohenc0dcf592011-06-01 15:30:43 -0700240 if (mAcceptAnimator != null) {
241 mAcceptAnimator.cancel();
242 }
Michael Jurkaf1ad6082013-03-13 12:55:46 +0100243 mNeutralAnimator = LauncherAnimUtils.ofFloat(mCellLayout, 0f, 1f);
Adam Cohenc0dcf592011-06-01 15:30:43 -0700244 mNeutralAnimator.setDuration(CONSUMPTION_ANIMATION_DURATION);
Andrew Flynn850d2e72012-04-26 16:51:20 -0700245
246 final int previewSize = sPreviewSize;
Adam Cohenc0dcf592011-06-01 15:30:43 -0700247 mNeutralAnimator.addUpdateListener(new AnimatorUpdateListener() {
Adam Cohen19072da2011-05-31 14:30:45 -0700248 public void onAnimationUpdate(ValueAnimator animation) {
249 final float percent = (Float) animation.getAnimatedValue();
Andrew Flynn850d2e72012-04-26 16:51:20 -0700250 mOuterRingSize = (1 + (1 - percent) * OUTER_RING_GROWTH_FACTOR) * previewSize;
251 mInnerRingSize = (1 + (1 - percent) * INNER_RING_GROWTH_FACTOR) * previewSize;
Adam Cohen69ce2e52011-07-03 19:25:21 -0700252 if (mCellLayout != null) {
253 mCellLayout.invalidate();
Adam Cohen19072da2011-05-31 14:30:45 -0700254 }
255 }
256 });
Adam Cohenc0dcf592011-06-01 15:30:43 -0700257 mNeutralAnimator.addListener(new AnimatorListenerAdapter() {
Adam Cohen19072da2011-05-31 14:30:45 -0700258 @Override
259 public void onAnimationEnd(Animator animation) {
Adam Cohen69ce2e52011-07-03 19:25:21 -0700260 if (mCellLayout != null) {
261 mCellLayout.hideFolderAccept(FolderRingAnimator.this);
262 }
Adam Cohen19072da2011-05-31 14:30:45 -0700263 if (mFolderIcon != null) {
Adam Cohen76fc0852011-06-17 13:26:23 -0700264 mFolderIcon.mPreviewBackground.setVisibility(VISIBLE);
Adam Cohen19072da2011-05-31 14:30:45 -0700265 }
Adam Cohen19072da2011-05-31 14:30:45 -0700266 }
267 });
Adam Cohenc0dcf592011-06-01 15:30:43 -0700268 mNeutralAnimator.start();
Adam Cohen19072da2011-05-31 14:30:45 -0700269 }
270
Adam Cohenc0dcf592011-06-01 15:30:43 -0700271 // Location is expressed in window coordinates
Adam Cohen69ce2e52011-07-03 19:25:21 -0700272 public void getCell(int[] loc) {
273 loc[0] = mCellX;
274 loc[1] = mCellY;
275 }
276
277 // Location is expressed in window coordinates
278 public void setCell(int x, int y) {
279 mCellX = x;
280 mCellY = y;
281 }
282
283 public void setCellLayout(CellLayout layout) {
284 mCellLayout = layout;
Adam Cohen19072da2011-05-31 14:30:45 -0700285 }
286
Adam Cohen76fc0852011-06-17 13:26:23 -0700287 public float getOuterRingSize() {
288 return mOuterRingSize;
Adam Cohen19072da2011-05-31 14:30:45 -0700289 }
290
Adam Cohen76fc0852011-06-17 13:26:23 -0700291 public float getInnerRingSize() {
292 return mInnerRingSize;
Adam Cohen19072da2011-05-31 14:30:45 -0700293 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800294 }
295
Adam Cohenfb91f302012-06-11 15:45:18 -0700296 Folder getFolder() {
297 return mFolder;
298 }
299
300 FolderInfo getFolderInfo() {
301 return mInfo;
302 }
303
Adam Cohen073a46f2011-05-17 16:28:09 -0700304 private boolean willAcceptItem(ItemInfo item) {
305 final int itemType = item.itemType;
306 return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
307 itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) &&
Adam Cohenc51934b2011-07-26 21:07:43 -0700308 !mFolder.isFull() && item != mInfo && !mInfo.opened);
Adam Cohen073a46f2011-05-17 16:28:09 -0700309 }
310
Adam Cohenc0dcf592011-06-01 15:30:43 -0700311 public boolean acceptDrop(Object dragInfo) {
312 final ItemInfo item = (ItemInfo) dragInfo;
Adam Cohenfb91f302012-06-11 15:45:18 -0700313 return !mFolder.isDestroyed() && willAcceptItem(item);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800314 }
315
Adam Cohendf035382011-04-11 17:22:04 -0700316 public void addItem(ShortcutInfo item) {
317 mInfo.add(item);
Adam Cohendf035382011-04-11 17:22:04 -0700318 }
319
Adam Cohenc0dcf592011-06-01 15:30:43 -0700320 public void onDragEnter(Object dragInfo) {
Adam Cohenfb91f302012-06-11 15:45:18 -0700321 if (mFolder.isDestroyed() || !willAcceptItem((ItemInfo) dragInfo)) return;
Adam Cohen69ce2e52011-07-03 19:25:21 -0700322 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams();
323 CellLayout layout = (CellLayout) getParent().getParent();
324 mFolderRingAnimator.setCell(lp.cellX, lp.cellY);
325 mFolderRingAnimator.setCellLayout(layout);
Adam Cohen19072da2011-05-31 14:30:45 -0700326 mFolderRingAnimator.animateToAcceptState();
Adam Cohen69ce2e52011-07-03 19:25:21 -0700327 layout.showFolderAccept(mFolderRingAnimator);
Adam Cohen073a46f2011-05-17 16:28:09 -0700328 }
329
Adam Cohenc0dcf592011-06-01 15:30:43 -0700330 public void onDragOver(Object dragInfo) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800331 }
332
Adam Cohend0445262011-07-04 23:53:22 -0700333 public void performCreateAnimation(final ShortcutInfo destInfo, final View destView,
Winson Chung7bd1bbb2012-02-13 18:29:29 -0800334 final ShortcutInfo srcInfo, final DragView srcView, Rect dstRect,
Adam Cohenac8c8762011-07-13 11:15:27 -0700335 float scaleRelativeToDragLayer, Runnable postAnimationRunnable) {
Adam Cohend0445262011-07-04 23:53:22 -0700336
Adam Cohenfb91f302012-06-11 15:45:18 -0700337 // These correspond two the drawable and view that the icon was dropped _onto_
Adam Cohend0445262011-07-04 23:53:22 -0700338 Drawable animateDrawable = ((TextView) destView).getCompoundDrawables()[1];
Adam Cohenfb91f302012-06-11 15:45:18 -0700339 computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(),
340 destView.getMeasuredWidth());
Adam Cohend0445262011-07-04 23:53:22 -0700341
342 // This will animate the first item from it's position as an icon into its
343 // position as the first item in the preview
Adam Cohenfb91f302012-06-11 15:45:18 -0700344 animateFirstItem(animateDrawable, INITIAL_ITEM_ANIMATION_DURATION, false, null);
Adam Cohen268c4752012-06-06 17:47:33 -0700345 addItem(destInfo);
Adam Cohenfb91f302012-06-11 15:45:18 -0700346
347 // This will animate the dragView (srcView) into the new folder
348 onDrop(srcInfo, srcView, dstRect, scaleRelativeToDragLayer, 1, postAnimationRunnable, null);
349 }
350
351 public void performDestroyAnimation(final View finalView, Runnable onCompleteRunnable) {
352 Drawable animateDrawable = ((TextView) finalView).getCompoundDrawables()[1];
353 computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(),
354 finalView.getMeasuredWidth());
355
356 // This will animate the first item from it's position as an icon into its
357 // position as the first item in the preview
358 animateFirstItem(animateDrawable, FINAL_ITEM_ANIMATION_DURATION, true,
359 onCompleteRunnable);
Adam Cohend0445262011-07-04 23:53:22 -0700360 }
361
Adam Cohenc0dcf592011-06-01 15:30:43 -0700362 public void onDragExit(Object dragInfo) {
Adam Cohenc6cc61d2012-04-04 12:47:08 -0700363 onDragExit();
364 }
365
366 public void onDragExit() {
Adam Cohen19072da2011-05-31 14:30:45 -0700367 mFolderRingAnimator.animateToNaturalState();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800368 }
Patrick Dubroy440c3602010-07-13 17:50:32 -0700369
Winson Chung7bd1bbb2012-02-13 18:29:29 -0800370 private void onDrop(final ShortcutInfo item, DragView animateView, Rect finalRect,
371 float scaleRelativeToDragLayer, int index, Runnable postAnimationRunnable,
372 DragObject d) {
Adam Cohend0445262011-07-04 23:53:22 -0700373 item.cellX = -1;
374 item.cellY = -1;
Adam Cohend0445262011-07-04 23:53:22 -0700375
Adam Cohen558baaf2011-08-15 15:22:57 -0700376 // Typically, the animateView corresponds to the DragView; however, if this is being done
377 // after a configuration activity (ie. for a Shortcut being dragged from AllApps) we
378 // will not have a view to animate
379 if (animateView != null) {
380 DragLayer dragLayer = mLauncher.getDragLayer();
381 Rect from = new Rect();
382 dragLayer.getViewRectRelativeToSelf(animateView, from);
383 Rect to = finalRect;
384 if (to == null) {
385 to = new Rect();
386 Workspace workspace = mLauncher.getWorkspace();
387 // Set cellLayout and this to it's final state to compute final animation locations
388 workspace.setFinalTransitionTransform((CellLayout) getParent().getParent());
389 float scaleX = getScaleX();
390 float scaleY = getScaleY();
391 setScaleX(1.0f);
392 setScaleY(1.0f);
393 scaleRelativeToDragLayer = dragLayer.getDescendantRectRelativeToSelf(this, to);
394 // Finished computing final animation locations, restore current state
395 setScaleX(scaleX);
396 setScaleY(scaleY);
397 workspace.resetTransitionTransform((CellLayout) getParent().getParent());
Adam Cohend0445262011-07-04 23:53:22 -0700398 }
Adam Cohen558baaf2011-08-15 15:22:57 -0700399
400 int[] center = new int[2];
401 float scale = getLocalCenterForIndex(index, center);
402 center[0] = (int) Math.round(scaleRelativeToDragLayer * center[0]);
403 center[1] = (int) Math.round(scaleRelativeToDragLayer * center[1]);
404
405 to.offset(center[0] - animateView.getMeasuredWidth() / 2,
Winson Chung5f8afe62013-08-12 16:19:28 -0700406 center[1] - animateView.getMeasuredHeight() / 2);
Adam Cohen558baaf2011-08-15 15:22:57 -0700407
408 float finalAlpha = index < NUM_ITEMS_IN_PREVIEW ? 0.5f : 0f;
409
Adam Cohened66b2b2012-01-23 17:28:51 -0800410 float finalScale = scale * scaleRelativeToDragLayer;
Adam Cohen558baaf2011-08-15 15:22:57 -0700411 dragLayer.animateView(animateView, from, to, finalAlpha,
Adam Cohened66b2b2012-01-23 17:28:51 -0800412 1, 1, finalScale, finalScale, DROP_IN_ANIMATION_DURATION,
Adam Cohen558baaf2011-08-15 15:22:57 -0700413 new DecelerateInterpolator(2), new AccelerateInterpolator(2),
Adam Cohened66b2b2012-01-23 17:28:51 -0800414 postAnimationRunnable, DragLayer.ANIMATION_END_DISAPPEAR, null);
Adam Cohenfb91f302012-06-11 15:45:18 -0700415 addItem(item);
416 mHiddenItems.add(item);
Adam Cohen7a8b82b2013-05-29 18:41:50 -0700417 mFolder.hideItem(item);
Adam Cohen558baaf2011-08-15 15:22:57 -0700418 postDelayed(new Runnable() {
419 public void run() {
Adam Cohenfb91f302012-06-11 15:45:18 -0700420 mHiddenItems.remove(item);
Adam Cohen7a8b82b2013-05-29 18:41:50 -0700421 mFolder.showItem(item);
Adam Cohenfb91f302012-06-11 15:45:18 -0700422 invalidate();
Adam Cohen558baaf2011-08-15 15:22:57 -0700423 }
424 }, DROP_IN_ANIMATION_DURATION);
425 } else {
426 addItem(item);
427 }
Adam Cohend0445262011-07-04 23:53:22 -0700428 }
429
Adam Cohen3e8f8112011-07-02 18:03:00 -0700430 public void onDrop(DragObject d) {
Adam Cohenc0dcf592011-06-01 15:30:43 -0700431 ShortcutInfo item;
Michael Jurkaeadbfc52013-09-04 00:45:37 +0200432 if (d.dragInfo instanceof AppInfo) {
Adam Cohenc0dcf592011-06-01 15:30:43 -0700433 // Came from all apps -- make a copy
Michael Jurkaeadbfc52013-09-04 00:45:37 +0200434 item = ((AppInfo) d.dragInfo).makeShortcut();
Adam Cohenc0dcf592011-06-01 15:30:43 -0700435 } else {
Adam Cohen3e8f8112011-07-02 18:03:00 -0700436 item = (ShortcutInfo) d.dragInfo;
Adam Cohenc0dcf592011-06-01 15:30:43 -0700437 }
Adam Cohen67bd9cc2011-07-29 14:07:04 -0700438 mFolder.notifyDrop();
Winson Chung7bd1bbb2012-02-13 18:29:29 -0800439 onDrop(item, d.dragView, null, 1.0f, mInfo.contents.size(), d.postAnimationRunnable, d);
Adam Cohenc0dcf592011-06-01 15:30:43 -0700440 }
441
Adam Cohend0445262011-07-04 23:53:22 -0700442 private void computePreviewDrawingParams(int drawableSize, int totalSize) {
443 if (mIntrinsicIconSize != drawableSize || mTotalWidth != totalSize) {
Winson Chung5f8afe62013-08-12 16:19:28 -0700444 LauncherAppState app = LauncherAppState.getInstance();
445 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
446
Adam Cohend0445262011-07-04 23:53:22 -0700447 mIntrinsicIconSize = drawableSize;
448 mTotalWidth = totalSize;
449
Winson Chung5f8afe62013-08-12 16:19:28 -0700450 final int previewSize = mPreviewBackground.getLayoutParams().height;
Adam Cohend0445262011-07-04 23:53:22 -0700451 final int previewPadding = FolderRingAnimator.sPreviewPadding;
452
453 mAvailableSpaceInPreview = (previewSize - 2 * previewPadding);
454 // cos(45) = 0.707 + ~= 0.1) = 0.8f
455 int adjustedAvailableSpace = (int) ((mAvailableSpaceInPreview / 2) * (1 + 0.8f));
456
457 int unscaledHeight = (int) (mIntrinsicIconSize * (1 + PERSPECTIVE_SHIFT_FACTOR));
458 mBaselineIconScale = (1.0f * adjustedAvailableSpace / unscaledHeight);
459
460 mBaselineIconSize = (int) (mIntrinsicIconSize * mBaselineIconScale);
461 mMaxPerspectiveShift = mBaselineIconSize * PERSPECTIVE_SHIFT_FACTOR;
462
463 mPreviewOffsetX = (mTotalWidth - mAvailableSpaceInPreview) / 2;
Winson Chung5f8afe62013-08-12 16:19:28 -0700464 mPreviewOffsetY = previewPadding + grid.folderBackgroundOffset;
Adam Cohend0445262011-07-04 23:53:22 -0700465 }
466 }
467
468 private void computePreviewDrawingParams(Drawable d) {
469 computePreviewDrawingParams(d.getIntrinsicWidth(), getMeasuredWidth());
470 }
471
472 class PreviewItemDrawingParams {
473 PreviewItemDrawingParams(float transX, float transY, float scale, int overlayAlpha) {
474 this.transX = transX;
475 this.transY = transY;
476 this.scale = scale;
477 this.overlayAlpha = overlayAlpha;
478 }
479 float transX;
480 float transY;
481 float scale;
482 int overlayAlpha;
483 Drawable drawable;
484 }
485
Adam Cohenac8c8762011-07-13 11:15:27 -0700486 private float getLocalCenterForIndex(int index, int[] center) {
Adam Cohend0445262011-07-04 23:53:22 -0700487 mParams = computePreviewItemDrawingParams(Math.min(NUM_ITEMS_IN_PREVIEW, index), mParams);
488
489 mParams.transX += mPreviewOffsetX;
490 mParams.transY += mPreviewOffsetY;
Adam Cohenac8c8762011-07-13 11:15:27 -0700491 float offsetX = mParams.transX + (mParams.scale * mIntrinsicIconSize) / 2;
492 float offsetY = mParams.transY + (mParams.scale * mIntrinsicIconSize) / 2;
Adam Cohend0445262011-07-04 23:53:22 -0700493
Adam Cohenac8c8762011-07-13 11:15:27 -0700494 center[0] = (int) Math.round(offsetX);
495 center[1] = (int) Math.round(offsetY);
Adam Cohend0445262011-07-04 23:53:22 -0700496 return mParams.scale;
497 }
498
499 private PreviewItemDrawingParams computePreviewItemDrawingParams(int index,
500 PreviewItemDrawingParams params) {
501 index = NUM_ITEMS_IN_PREVIEW - index - 1;
502 float r = (index * 1.0f) / (NUM_ITEMS_IN_PREVIEW - 1);
503 float scale = (1 - PERSPECTIVE_SCALE_FACTOR * (1 - r));
504
505 float offset = (1 - r) * mMaxPerspectiveShift;
506 float scaledSize = scale * mBaselineIconSize;
507 float scaleOffsetCorrection = (1 - scale) * mBaselineIconSize;
508
509 // We want to imagine our coordinates from the bottom left, growing up and to the
510 // right. This is natural for the x-axis, but for the y-axis, we have to invert things.
Winson Chung5f8afe62013-08-12 16:19:28 -0700511 float transY = mAvailableSpaceInPreview - (offset + scaledSize + scaleOffsetCorrection) + getPaddingTop();
Adam Cohend0445262011-07-04 23:53:22 -0700512 float transX = offset + scaleOffsetCorrection;
513 float totalScale = mBaselineIconScale * scale;
514 final int overlayAlpha = (int) (80 * (1 - r));
515
516 if (params == null) {
517 params = new PreviewItemDrawingParams(transX, transY, totalScale, overlayAlpha);
518 } else {
519 params.transX = transX;
520 params.transY = transY;
521 params.scale = totalScale;
522 params.overlayAlpha = overlayAlpha;
523 }
524 return params;
525 }
526
527 private void drawPreviewItem(Canvas canvas, PreviewItemDrawingParams params) {
528 canvas.save();
529 canvas.translate(params.transX + mPreviewOffsetX, params.transY + mPreviewOffsetY);
530 canvas.scale(params.scale, params.scale);
531 Drawable d = params.drawable;
532
533 if (d != null) {
534 d.setBounds(0, 0, mIntrinsicIconSize, mIntrinsicIconSize);
535 d.setFilterBitmap(true);
536 d.setColorFilter(Color.argb(params.overlayAlpha, 0, 0, 0), PorterDuff.Mode.SRC_ATOP);
537 d.draw(canvas);
538 d.clearColorFilter();
539 d.setFilterBitmap(false);
540 }
541 canvas.restore();
542 }
543
Adam Cohena9cf38f2011-05-02 15:36:58 -0700544 @Override
Adam Cohenc0dcf592011-06-01 15:30:43 -0700545 protected void dispatchDraw(Canvas canvas) {
Adam Cohen76fc0852011-06-17 13:26:23 -0700546 super.dispatchDraw(canvas);
547
Adam Cohena9cf38f2011-05-02 15:36:58 -0700548 if (mFolder == null) return;
Adam Cohend0445262011-07-04 23:53:22 -0700549 if (mFolder.getItemCount() == 0 && !mAnimating) return;
Adam Cohena9cf38f2011-05-02 15:36:58 -0700550
Adam Cohen7a8b82b2013-05-29 18:41:50 -0700551 ArrayList<View> items = mFolder.getItemsInReadingOrder();
Adam Cohend0445262011-07-04 23:53:22 -0700552 Drawable d;
553 TextView v;
Adam Cohenbadf71e2011-05-26 19:08:29 -0700554
Adam Cohend0445262011-07-04 23:53:22 -0700555 // Update our drawing parameters if necessary
556 if (mAnimating) {
557 computePreviewDrawingParams(mAnimParams.drawable);
558 } else {
559 v = (TextView) items.get(0);
Adam Cohena9cf38f2011-05-02 15:36:58 -0700560 d = v.getCompoundDrawables()[1];
Adam Cohend0445262011-07-04 23:53:22 -0700561 computePreviewDrawingParams(d);
Adam Cohena9cf38f2011-05-02 15:36:58 -0700562 }
Adam Cohend0445262011-07-04 23:53:22 -0700563
564 int nItemsInPreview = Math.min(items.size(), NUM_ITEMS_IN_PREVIEW);
565 if (!mAnimating) {
566 for (int i = nItemsInPreview - 1; i >= 0; i--) {
567 v = (TextView) items.get(i);
Adam Cohenfb91f302012-06-11 15:45:18 -0700568 if (!mHiddenItems.contains(v.getTag())) {
569 d = v.getCompoundDrawables()[1];
570 mParams = computePreviewItemDrawingParams(i, mParams);
571 mParams.drawable = d;
572 drawPreviewItem(canvas, mParams);
573 }
Adam Cohend0445262011-07-04 23:53:22 -0700574 }
575 } else {
576 drawPreviewItem(canvas, mAnimParams);
577 }
578 }
579
Adam Cohenfb91f302012-06-11 15:45:18 -0700580 private void animateFirstItem(final Drawable d, int duration, final boolean reverse,
581 final Runnable onCompleteRunnable) {
Adam Cohend0445262011-07-04 23:53:22 -0700582 final PreviewItemDrawingParams finalParams = computePreviewItemDrawingParams(0, null);
583
584 final float scale0 = 1.0f;
Adam Cohen1d4ee4e2011-08-12 15:51:59 -0700585 final float transX0 = (mAvailableSpaceInPreview - d.getIntrinsicWidth()) / 2;
Winson Chung5f8afe62013-08-12 16:19:28 -0700586 final float transY0 = (mAvailableSpaceInPreview - d.getIntrinsicHeight()) / 2 + getPaddingTop();
Adam Cohend0445262011-07-04 23:53:22 -0700587 mAnimParams.drawable = d;
588
Michael Jurkaf1ad6082013-03-13 12:55:46 +0100589 ValueAnimator va = LauncherAnimUtils.ofFloat(this, 0f, 1.0f);
Adam Cohend0445262011-07-04 23:53:22 -0700590 va.addUpdateListener(new AnimatorUpdateListener(){
591 public void onAnimationUpdate(ValueAnimator animation) {
592 float progress = (Float) animation.getAnimatedValue();
Adam Cohenfb91f302012-06-11 15:45:18 -0700593 if (reverse) {
594 progress = 1 - progress;
595 mPreviewBackground.setAlpha(progress);
596 }
Adam Cohend0445262011-07-04 23:53:22 -0700597
598 mAnimParams.transX = transX0 + progress * (finalParams.transX - transX0);
599 mAnimParams.transY = transY0 + progress * (finalParams.transY - transY0);
600 mAnimParams.scale = scale0 + progress * (finalParams.scale - scale0);
601 invalidate();
602 }
603 });
604 va.addListener(new AnimatorListenerAdapter() {
605 @Override
606 public void onAnimationStart(Animator animation) {
607 mAnimating = true;
608 }
609 @Override
610 public void onAnimationEnd(Animator animation) {
611 mAnimating = false;
Adam Cohenfb91f302012-06-11 15:45:18 -0700612 if (onCompleteRunnable != null) {
613 onCompleteRunnable.run();
614 }
Adam Cohend0445262011-07-04 23:53:22 -0700615 }
616 });
617 va.setDuration(duration);
618 va.start();
Adam Cohena9cf38f2011-05-02 15:36:58 -0700619 }
620
Adam Cohen099f60d2011-08-23 21:07:26 -0700621 public void setTextVisible(boolean visible) {
622 if (visible) {
623 mFolderName.setVisibility(VISIBLE);
624 } else {
625 mFolderName.setVisibility(INVISIBLE);
626 }
627 }
628
629 public boolean getTextVisible() {
630 return mFolderName.getVisibility() == VISIBLE;
631 }
632
Adam Cohen76078c42011-06-09 15:06:52 -0700633 public void onItemsChanged() {
634 invalidate();
635 requestLayout();
636 }
637
Adam Cohena9cf38f2011-05-02 15:36:58 -0700638 public void onAdd(ShortcutInfo item) {
639 invalidate();
640 requestLayout();
641 }
642
643 public void onRemove(ShortcutInfo item) {
644 invalidate();
645 requestLayout();
646 }
Adam Cohen76fc0852011-06-17 13:26:23 -0700647
648 public void onTitleChanged(CharSequence title) {
Adam Cohend63cfa92011-06-24 14:17:17 -0700649 mFolderName.setText(title.toString());
Michael Jurka8b805b12012-04-18 14:23:14 -0700650 setContentDescription(String.format(getContext().getString(R.string.folder_name_format),
Adam Cohen3371da02011-10-25 21:38:29 -0700651 title));
Adam Cohen76fc0852011-06-17 13:26:23 -0700652 }
Winson Chung88f33452012-02-23 15:23:44 -0800653
654 @Override
655 public boolean onTouchEvent(MotionEvent event) {
656 // Call the superclass onTouchEvent first, because sometimes it changes the state to
657 // isPressed() on an ACTION_UP
658 boolean result = super.onTouchEvent(event);
659
660 switch (event.getAction()) {
661 case MotionEvent.ACTION_DOWN:
662 mLongPressHelper.postCheckForLongPress();
663 break;
664 case MotionEvent.ACTION_CANCEL:
665 case MotionEvent.ACTION_UP:
666 mLongPressHelper.cancelLongPress();
667 break;
668 }
669 return result;
670 }
671
672 @Override
673 public void cancelLongPress() {
674 super.cancelLongPress();
675
676 mLongPressHelper.cancelLongPress();
677 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800678}