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