• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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.shortcuts;
18 
19 import android.animation.Animator;
20 import android.animation.ValueAnimator;
21 import android.content.Context;
22 import android.graphics.Point;
23 import android.graphics.Rect;
24 import android.text.TextUtils;
25 import android.util.AttributeSet;
26 import android.view.View;
27 import android.widget.FrameLayout;
28 
29 import com.android.launcher3.IconCache;
30 import com.android.launcher3.Launcher;
31 import com.android.launcher3.LauncherAppState;
32 import com.android.launcher3.LogAccelerateInterpolator;
33 import com.android.launcher3.R;
34 import com.android.launcher3.ShortcutInfo;
35 import com.android.launcher3.Utilities;
36 import com.android.launcher3.shortcuts.DeepShortcutsContainer.UnbadgedShortcutInfo;
37 import com.android.launcher3.util.PillRevealOutlineProvider;
38 import com.android.launcher3.util.PillWidthRevealOutlineProvider;
39 
40 /**
41  * A {@link android.widget.FrameLayout} that contains a {@link DeepShortcutView}.
42  * This lets us animate the DeepShortcutView (icon and text) separately from the background.
43  */
44 public class DeepShortcutView extends FrameLayout implements ValueAnimator.AnimatorUpdateListener {
45 
46     private static final Point sTempPoint = new Point();
47 
48     private final Rect mPillRect;
49 
50     private DeepShortcutTextView mBubbleText;
51     private View mIconView;
52     private float mOpenAnimationProgress;
53 
54     private UnbadgedShortcutInfo mInfo;
55 
DeepShortcutView(Context context)56     public DeepShortcutView(Context context) {
57         this(context, null, 0);
58     }
59 
DeepShortcutView(Context context, AttributeSet attrs)60     public DeepShortcutView(Context context, AttributeSet attrs) {
61         this(context, attrs, 0);
62     }
63 
DeepShortcutView(Context context, AttributeSet attrs, int defStyle)64     public DeepShortcutView(Context context, AttributeSet attrs, int defStyle) {
65         super(context, attrs, defStyle);
66 
67         mPillRect = new Rect();
68     }
69 
70     @Override
onFinishInflate()71     protected void onFinishInflate() {
72         super.onFinishInflate();
73         mIconView = findViewById(R.id.deep_shortcut_icon);
74         mBubbleText = (DeepShortcutTextView) findViewById(R.id.deep_shortcut);
75     }
76 
getBubbleText()77     public DeepShortcutTextView getBubbleText() {
78         return mBubbleText;
79     }
80 
setWillDrawIcon(boolean willDraw)81     public void setWillDrawIcon(boolean willDraw) {
82         mIconView.setVisibility(willDraw ? View.VISIBLE : View.INVISIBLE);
83     }
84 
willDrawIcon()85     public boolean willDrawIcon() {
86         return mIconView.getVisibility() == View.VISIBLE;
87     }
88 
89     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)90     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
91         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
92         mPillRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
93     }
94 
95     /** package private **/
applyShortcutInfo(UnbadgedShortcutInfo info, DeepShortcutsContainer container)96     void applyShortcutInfo(UnbadgedShortcutInfo info, DeepShortcutsContainer container) {
97         mInfo = info;
98         IconCache cache = LauncherAppState.getInstance().getIconCache();
99         mBubbleText.applyFromShortcutInfo(info, cache);
100         mIconView.setBackground(mBubbleText.getIcon());
101 
102         // Use the long label as long as it exists and fits.
103         CharSequence longLabel = info.mDetail.getLongLabel();
104         int availableWidth = mBubbleText.getWidth() - mBubbleText.getTotalPaddingLeft()
105                 - mBubbleText.getTotalPaddingRight();
106         boolean usingLongLabel = !TextUtils.isEmpty(longLabel)
107                 && mBubbleText.getPaint().measureText(longLabel.toString()) <= availableWidth;
108         mBubbleText.setText(usingLongLabel ? longLabel : info.mDetail.getShortLabel());
109 
110         // TODO: Add the click handler to this view directly and not the child view.
111         mBubbleText.setOnClickListener(Launcher.getLauncher(getContext()));
112         mBubbleText.setOnLongClickListener(container);
113         mBubbleText.setOnTouchListener(container);
114     }
115 
116     /**
117      * Returns the shortcut info that is suitable to be added on the homescreen
118      */
getFinalInfo()119     public ShortcutInfo getFinalInfo() {
120         ShortcutInfo badged = new ShortcutInfo(mInfo);
121         // Queue an update task on the worker thread. This ensures that the badged
122         // shortcut eventually gets its icon updated.
123         Launcher.getLauncher(getContext()).getModel().updateShortcutInfo(mInfo.mDetail, badged);
124         return badged;
125     }
126 
getIconView()127     public View getIconView() {
128         return mIconView;
129     }
130 
131     /**
132      * Creates an animator to play when the shortcut container is being opened.
133      */
createOpenAnimation(boolean isContainerAboveIcon, boolean pivotLeft)134     public Animator createOpenAnimation(boolean isContainerAboveIcon, boolean pivotLeft) {
135         Point center = getIconCenter();
136         ValueAnimator openAnimator =  new ZoomRevealOutlineProvider(center.x, center.y,
137                 mPillRect, this, mIconView, isContainerAboveIcon, pivotLeft)
138                         .createRevealAnimator(this, false);
139         mOpenAnimationProgress = 0f;
140         openAnimator.addUpdateListener(this);
141         return openAnimator;
142     }
143 
144     @Override
onAnimationUpdate(ValueAnimator valueAnimator)145     public void onAnimationUpdate(ValueAnimator valueAnimator) {
146         mOpenAnimationProgress = valueAnimator.getAnimatedFraction();
147     }
148 
isOpenOrOpening()149     public boolean isOpenOrOpening() {
150         return mOpenAnimationProgress > 0;
151     }
152 
153     /**
154      * Creates an animator to play when the shortcut container is being closed.
155      */
createCloseAnimation(boolean isContainerAboveIcon, boolean pivotLeft, long duration)156     public Animator createCloseAnimation(boolean isContainerAboveIcon, boolean pivotLeft,
157             long duration) {
158         Point center = getIconCenter();
159         ValueAnimator closeAnimator =  new ZoomRevealOutlineProvider(center.x, center.y,
160                 mPillRect, this, mIconView, isContainerAboveIcon, pivotLeft)
161                         .createRevealAnimator(this, true);
162         // Scale down the duration and interpolator according to the progress
163         // that the open animation was at when the close started.
164         closeAnimator.setDuration((long) (duration * mOpenAnimationProgress));
165         closeAnimator.setInterpolator(new CloseInterpolator(mOpenAnimationProgress));
166         return closeAnimator;
167     }
168 
169     /**
170      * Creates an animator which clips the container to form a circle around the icon.
171      */
collapseToIcon()172     public Animator collapseToIcon() {
173         int halfHeight = getMeasuredHeight() / 2;
174         int iconCenterX = getIconCenter().x;
175         return new PillWidthRevealOutlineProvider(mPillRect,
176                 iconCenterX - halfHeight, iconCenterX + halfHeight)
177                         .createRevealAnimator(this, true);
178     }
179 
180     /**
181      * Returns the position of the center of the icon relative to the container.
182      */
getIconCenter()183     public Point getIconCenter() {
184         sTempPoint.y = sTempPoint.x = getMeasuredHeight() / 2;
185         if (Utilities.isRtl(getResources())) {
186             sTempPoint.x = getMeasuredWidth() - sTempPoint.x;
187         }
188         return sTempPoint;
189     }
190 
191     /**
192      * Extension of {@link PillRevealOutlineProvider} which scales the icon based on the height.
193      */
194     private static class ZoomRevealOutlineProvider extends PillRevealOutlineProvider {
195 
196         private final View mTranslateView;
197         private final View mZoomView;
198 
199         private final float mFullHeight;
200         private final float mTranslateYMultiplier;
201 
202         private final boolean mPivotLeft;
203         private final float mTranslateX;
204 
ZoomRevealOutlineProvider(int x, int y, Rect pillRect, View translateView, View zoomView, boolean isContainerAboveIcon, boolean pivotLeft)205         public ZoomRevealOutlineProvider(int x, int y, Rect pillRect,
206                 View translateView, View zoomView, boolean isContainerAboveIcon, boolean pivotLeft) {
207             super(x, y, pillRect);
208             mTranslateView = translateView;
209             mZoomView = zoomView;
210             mFullHeight = pillRect.height();
211 
212             mTranslateYMultiplier = isContainerAboveIcon ? 0.5f : -0.5f;
213 
214             mPivotLeft = pivotLeft;
215             mTranslateX = pivotLeft ? pillRect.height() / 2 : pillRect.right - pillRect.height() / 2;
216         }
217 
218         @Override
setProgress(float progress)219         public void setProgress(float progress) {
220             super.setProgress(progress);
221 
222             mZoomView.setScaleX(progress);
223             mZoomView.setScaleY(progress);
224 
225             float height = mOutline.height();
226             mTranslateView.setTranslationY(mTranslateYMultiplier * (mFullHeight - height));
227 
228             float pivotX = mPivotLeft ? (mOutline.left + height / 2) : (mOutline.right - height / 2);
229             mTranslateView.setTranslationX(mTranslateX - pivotX);
230         }
231     }
232 
233     /**
234      * An interpolator that reverses the current open animation progress.
235      */
236     private static class CloseInterpolator extends LogAccelerateInterpolator {
237         private float mStartProgress;
238         private float mRemainingProgress;
239 
240         /**
241          * @param openAnimationProgress The progress that the open interpolator ended at.
242          */
CloseInterpolator(float openAnimationProgress)243         public CloseInterpolator(float openAnimationProgress) {
244             super(100, 0);
245             mStartProgress = 1f - openAnimationProgress;
246             mRemainingProgress = openAnimationProgress;
247         }
248 
249         @Override
getInterpolation(float v)250         public float getInterpolation(float v) {
251             return mStartProgress + super.getInterpolation(v) * mRemainingProgress;
252         }
253     }
254 }
255