• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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.folder;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.ValueAnimator;
22 import android.graphics.Canvas;
23 import android.graphics.Rect;
24 import android.graphics.drawable.Drawable;
25 import android.support.annotation.NonNull;
26 import android.view.View;
27 import android.widget.TextView;
28 
29 import com.android.launcher3.BubbleTextView;
30 import com.android.launcher3.ShortcutInfo;
31 import com.android.launcher3.Utilities;
32 
33 import java.util.ArrayList;
34 import java.util.List;
35 
36 import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ENTER_INDEX;
37 import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.EXIT_INDEX;
38 import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
39 import static com.android.launcher3.folder.FolderIcon.DROP_IN_ANIMATION_DURATION;
40 
41 /**
42  * Manages the drawing and animations of {@link PreviewItemDrawingParams} for a {@link FolderIcon}.
43  */
44 public class PreviewItemManager {
45 
46     private FolderIcon mIcon;
47 
48     // These variables are all associated with the drawing of the preview; they are stored
49     // as member variables for shared usage and to avoid computation on each frame
50     private float mIntrinsicIconSize = -1;
51     private int mTotalWidth = -1;
52     private int mPrevTopPadding = -1;
53     private Drawable mReferenceDrawable = null;
54 
55     // These hold the first page preview items
56     private ArrayList<PreviewItemDrawingParams> mFirstPageParams = new ArrayList<>();
57     // These hold the current page preview items. It is empty if the current page is the first page.
58     private ArrayList<PreviewItemDrawingParams> mCurrentPageParams = new ArrayList<>();
59 
60     private float mCurrentPageItemsTransX = 0;
61     private boolean mShouldSlideInFirstPage;
62 
63     static final int INITIAL_ITEM_ANIMATION_DURATION = 350;
64     private static final int FINAL_ITEM_ANIMATION_DURATION = 200;
65 
66     private static final int SLIDE_IN_FIRST_PAGE_ANIMATION_DURATION_DELAY = 100;
67     private static final int SLIDE_IN_FIRST_PAGE_ANIMATION_DURATION = 300;
68     private static final int ITEM_SLIDE_IN_OUT_DISTANCE_PX = 200;
69 
PreviewItemManager(FolderIcon icon)70     public PreviewItemManager(FolderIcon icon) {
71         mIcon = icon;
72     }
73 
74     /**
75      * @param reverse If true, animates the final item in the preview to be full size. If false,
76      *                animates the first item to its position in the preview.
77      */
createFirstItemAnimation(final boolean reverse, final Runnable onCompleteRunnable)78     public FolderPreviewItemAnim createFirstItemAnimation(final boolean reverse,
79             final Runnable onCompleteRunnable) {
80         return reverse
81                 ? new FolderPreviewItemAnim(this, mFirstPageParams.get(0), 0, 2, -1, -1,
82                         FINAL_ITEM_ANIMATION_DURATION, onCompleteRunnable)
83                 : new FolderPreviewItemAnim(this, mFirstPageParams.get(0), -1, -1, 0, 2,
84                         INITIAL_ITEM_ANIMATION_DURATION, onCompleteRunnable);
85     }
86 
prepareCreateAnimation(final View destView)87     Drawable prepareCreateAnimation(final View destView) {
88         Drawable animateDrawable = ((TextView) destView).getCompoundDrawables()[1];
89         computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(),
90                 destView.getMeasuredWidth());
91         mReferenceDrawable = animateDrawable;
92         return animateDrawable;
93     }
94 
recomputePreviewDrawingParams()95     public void recomputePreviewDrawingParams() {
96         if (mReferenceDrawable != null) {
97             computePreviewDrawingParams(mReferenceDrawable.getIntrinsicWidth(),
98                     mIcon.getMeasuredWidth());
99         }
100     }
101 
computePreviewDrawingParams(int drawableSize, int totalSize)102     private void computePreviewDrawingParams(int drawableSize, int totalSize) {
103         if (mIntrinsicIconSize != drawableSize || mTotalWidth != totalSize ||
104                 mPrevTopPadding != mIcon.getPaddingTop()) {
105             mIntrinsicIconSize = drawableSize;
106             mTotalWidth = totalSize;
107             mPrevTopPadding = mIcon.getPaddingTop();
108 
109             mIcon.mBackground.setup(mIcon.mLauncher, mIcon, mTotalWidth, mIcon.getPaddingTop());
110             mIcon.mPreviewLayoutRule.init(mIcon.mBackground.previewSize, mIntrinsicIconSize,
111                     Utilities.isRtl(mIcon.getResources()));
112 
113             updatePreviewItems(false);
114         }
115     }
116 
computePreviewItemDrawingParams(int index, int curNumItems, PreviewItemDrawingParams params)117     PreviewItemDrawingParams computePreviewItemDrawingParams(int index, int curNumItems,
118             PreviewItemDrawingParams params) {
119         // We use an index of -1 to represent an icon on the workspace for the destroy and
120         // create animations
121         if (index == -1) {
122             return getFinalIconParams(params);
123         }
124         return mIcon.mPreviewLayoutRule.computePreviewItemDrawingParams(index, curNumItems, params);
125     }
126 
getFinalIconParams(PreviewItemDrawingParams params)127     private PreviewItemDrawingParams getFinalIconParams(PreviewItemDrawingParams params) {
128         float iconSize = mIcon.mLauncher.getDeviceProfile().iconSizePx;
129 
130         final float scale = iconSize / mReferenceDrawable.getIntrinsicWidth();
131         final float trans = (mIcon.mBackground.previewSize - iconSize) / 2;
132 
133         params.update(trans, trans, scale);
134         return params;
135     }
136 
drawParams(Canvas canvas, ArrayList<PreviewItemDrawingParams> params, float transX)137     public void drawParams(Canvas canvas, ArrayList<PreviewItemDrawingParams> params,
138             float transX) {
139         canvas.translate(transX, 0);
140         // The first item should be drawn last (ie. on top of later items)
141         for (int i = params.size() - 1; i >= 0; i--) {
142             PreviewItemDrawingParams p = params.get(i);
143             if (!p.hidden) {
144                 drawPreviewItem(canvas, p);
145             }
146         }
147         canvas.translate(-transX, 0);
148     }
149 
draw(Canvas canvas)150     public void draw(Canvas canvas) {
151         // The items are drawn in coordinates relative to the preview offset
152         PreviewBackground bg = mIcon.getFolderBackground();
153         canvas.translate(bg.basePreviewOffsetX, bg.basePreviewOffsetY);
154 
155         float firstPageItemsTransX = 0;
156         if (mShouldSlideInFirstPage) {
157             drawParams(canvas, mCurrentPageParams, mCurrentPageItemsTransX);
158 
159             firstPageItemsTransX = -ITEM_SLIDE_IN_OUT_DISTANCE_PX + mCurrentPageItemsTransX;
160         }
161 
162         drawParams(canvas, mFirstPageParams, firstPageItemsTransX);
163         canvas.translate(-bg.basePreviewOffsetX, -bg.basePreviewOffsetY);
164     }
165 
onParamsChanged()166     public void onParamsChanged() {
167         mIcon.invalidate();
168     }
169 
drawPreviewItem(Canvas canvas, PreviewItemDrawingParams params)170     private void drawPreviewItem(Canvas canvas, PreviewItemDrawingParams params) {
171         canvas.save();
172         canvas.translate(params.transX, params.transY);
173         canvas.scale(params.scale, params.scale);
174         Drawable d = params.drawable;
175 
176         if (d != null) {
177             Rect bounds = d.getBounds();
178             canvas.save();
179             canvas.translate(-bounds.left, -bounds.top);
180             canvas.scale(mIntrinsicIconSize / bounds.width(), mIntrinsicIconSize / bounds.height());
181             d.draw(canvas);
182             canvas.restore();
183         }
184         canvas.restore();
185     }
186 
hidePreviewItem(int index, boolean hidden)187     public void hidePreviewItem(int index, boolean hidden) {
188         // If there are more params than visible in the preview, they are used for enter/exit
189         // animation purposes and they were added to the front of the list.
190         // To index the params properly, we need to skip these params.
191         index = index + Math.max(mFirstPageParams.size() - MAX_NUM_ITEMS_IN_PREVIEW, 0);
192 
193         PreviewItemDrawingParams params = index < mFirstPageParams.size() ?
194                 mFirstPageParams.get(index) : null;
195         if (params != null) {
196             params.hidden = hidden;
197         }
198     }
199 
200     void buildParamsForPage(int page, ArrayList<PreviewItemDrawingParams> params, boolean animate) {
201         List<BubbleTextView> items = mIcon.getPreviewItemsOnPage(page);
202         int prevNumItems = params.size();
203 
204         // We adjust the size of the list to match the number of items in the preview.
205         while (items.size() < params.size()) {
206             params.remove(params.size() - 1);
207         }
208         while (items.size() > params.size()) {
209             params.add(new PreviewItemDrawingParams(0, 0, 0, 0));
210         }
211 
212         int numItemsInFirstPagePreview = page == 0 ? items.size() : MAX_NUM_ITEMS_IN_PREVIEW;
213         for (int i = 0; i < params.size(); i++) {
214             PreviewItemDrawingParams p = params.get(i);
215             p.drawable = items.get(i).getCompoundDrawables()[1];
216 
217             if (p.drawable != null && !mIcon.mFolder.isOpen()) {
218                 // Set the callback to FolderIcon as it is responsible to drawing the icon. The
219                 // callback will be released when the folder is opened.
220                 p.drawable.setCallback(mIcon);
221             }
222 
223             if (!animate) {
224                 computePreviewItemDrawingParams(i, numItemsInFirstPagePreview, p);
225                 if (mReferenceDrawable == null) {
226                     mReferenceDrawable = p.drawable;
227                 }
228             } else {
229                 FolderPreviewItemAnim anim = new FolderPreviewItemAnim(this, p, i, prevNumItems, i,
230                         numItemsInFirstPagePreview, DROP_IN_ANIMATION_DURATION, null);
231 
232                 if (p.anim != null) {
233                     if (p.anim.hasEqualFinalState(anim)) {
234                         // do nothing, let the current animation finish
235                         continue;
236                     }
237                     p.anim.cancel();
238                 }
239                 p.anim = anim;
240                 p.anim.start();
241             }
242         }
243     }
244 
245     void onFolderClose(int currentPage) {
246         // If we are not closing on the first page, we animate the current page preview items
247         // out, and animate the first page preview items in.
248         mShouldSlideInFirstPage = currentPage != 0;
249         if (mShouldSlideInFirstPage) {
250             mCurrentPageItemsTransX = 0;
251             buildParamsForPage(currentPage, mCurrentPageParams, false);
252             onParamsChanged();
253 
254             ValueAnimator slideAnimator = ValueAnimator.ofFloat(0, ITEM_SLIDE_IN_OUT_DISTANCE_PX);
255             slideAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
256                 @Override
257                 public void onAnimationUpdate(ValueAnimator valueAnimator) {
258                     mCurrentPageItemsTransX = (float) valueAnimator.getAnimatedValue();
259                     onParamsChanged();
260                 }
261             });
262             slideAnimator.addListener(new AnimatorListenerAdapter() {
263                 @Override
264                 public void onAnimationEnd(Animator animation) {
265                     mCurrentPageParams.clear();
266                 }
267             });
268             slideAnimator.setStartDelay(SLIDE_IN_FIRST_PAGE_ANIMATION_DURATION_DELAY);
269             slideAnimator.setDuration(SLIDE_IN_FIRST_PAGE_ANIMATION_DURATION);
270             slideAnimator.start();
271         }
272     }
273 
274     void updatePreviewItems(boolean animate) {
275         buildParamsForPage(0, mFirstPageParams, animate);
276     }
277 
278     boolean verifyDrawable(@NonNull Drawable who) {
279         for (int i = 0; i < mFirstPageParams.size(); i++) {
280             if (mFirstPageParams.get(i).drawable == who) {
281                 return true;
282             }
283         }
284         return false;
285     }
286 
287     float getIntrinsicIconSize() {
288         return mIntrinsicIconSize;
289     }
290 
291     /**
292      * Handles the case where items in the preview are either:
293      *  - Moving into the preview
294      *  - Moving into a new position
295      *  - Moving out of the preview
296      *
297      * @param oldParams The list of items in the old preview.
298      * @param newParams The list of items in the new preview.
299      * @param dropped The item that was dropped onto the FolderIcon.
300      */
301     public void onDrop(List<BubbleTextView> oldParams, List<BubbleTextView> newParams,
302             ShortcutInfo dropped) {
303         int numItems = newParams.size();
304         final ArrayList<PreviewItemDrawingParams> params = mFirstPageParams;
305         buildParamsForPage(0, params, false);
306 
307         // New preview items for items that are moving in (except for the dropped item).
308         List<BubbleTextView> moveIn = new ArrayList<>();
309         for (BubbleTextView btv : newParams) {
310             if (!oldParams.contains(btv) && !btv.getTag().equals(dropped)) {
311                 moveIn.add(btv);
312             }
313         }
314         for (int i = 0; i < moveIn.size(); ++i) {
315             int prevIndex = newParams.indexOf(moveIn.get(i));
316             PreviewItemDrawingParams p = params.get(prevIndex);
317             computePreviewItemDrawingParams(prevIndex, numItems, p);
318             updateTransitionParam(p, moveIn.get(i), ENTER_INDEX, newParams.indexOf(moveIn.get(i)),
319                     numItems);
320         }
321 
322         // Items that are moving into new positions within the preview.
323         for (int newIndex = 0; newIndex < newParams.size(); ++newIndex) {
324             int oldIndex = oldParams.indexOf(newParams.get(newIndex));
325             if (oldIndex >= 0 && newIndex != oldIndex) {
326                 PreviewItemDrawingParams p = params.get(newIndex);
327                 updateTransitionParam(p, newParams.get(newIndex), oldIndex, newIndex, numItems);
328             }
329         }
330 
331         // Old preview items that need to be moved out.
332         List<BubbleTextView> moveOut = new ArrayList<>(oldParams);
333         moveOut.removeAll(newParams);
334         for (int i = 0; i < moveOut.size(); ++i) {
335             BubbleTextView item = moveOut.get(i);
336             int oldIndex = oldParams.indexOf(item);
337             PreviewItemDrawingParams p = computePreviewItemDrawingParams(oldIndex, numItems, null);
338             updateTransitionParam(p, item, oldIndex, EXIT_INDEX, numItems);
339             params.add(0, p); // We want these items first so that they are on drawn last.
340         }
341 
342         for (int i = 0; i < params.size(); ++i) {
343             if (params.get(i).anim != null) {
344                 params.get(i).anim.start();
345             }
346         }
347     }
348 
349     private void updateTransitionParam(final PreviewItemDrawingParams p, BubbleTextView btv,
350             int prevIndex, int newIndex, int numItems) {
351         p.drawable = btv.getCompoundDrawables()[1];
352         if (!mIcon.mFolder.isOpen()) {
353             // Set the callback to FolderIcon as it is responsible to drawing the icon. The
354             // callback will be released when the folder is opened.
355             p.drawable.setCallback(mIcon);
356         }
357 
358         FolderPreviewItemAnim anim = new FolderPreviewItemAnim(this, p, prevIndex, numItems,
359                 newIndex, numItems, DROP_IN_ANIMATION_DURATION, null);
360         if (p.anim != null && !p.anim.hasEqualFinalState(anim)) {
361             p.anim.cancel();
362         }
363         p.anim = anim;
364     }
365 }
366