• 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.popup;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.ValueAnimator;
22 import android.content.Context;
23 import android.graphics.Bitmap;
24 import android.graphics.Canvas;
25 import android.graphics.Matrix;
26 import android.graphics.Paint;
27 import android.graphics.Point;
28 import android.graphics.PorterDuff;
29 import android.graphics.PorterDuffXfermode;
30 import android.graphics.Rect;
31 import android.util.AttributeSet;
32 import android.view.View;
33 import android.widget.FrameLayout;
34 
35 import com.android.launcher3.LogAccelerateInterpolator;
36 import com.android.launcher3.R;
37 import com.android.launcher3.Utilities;
38 import com.android.launcher3.util.PillRevealOutlineProvider;
39 
40 /**
41  * An abstract {@link FrameLayout} that supports animating an item's content
42  * (e.g. icon and text) separate from the item's background.
43  */
44 public abstract class PopupItemView extends FrameLayout
45         implements ValueAnimator.AnimatorUpdateListener {
46 
47     protected static final Point sTempPoint = new Point();
48 
49     protected final Rect mPillRect;
50     private float mOpenAnimationProgress;
51     protected final boolean mIsRtl;
52     protected View mIconView;
53 
54     private final Paint mBackgroundClipPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
55     private final Matrix mMatrix = new Matrix();
56     private Bitmap mRoundedCornerBitmap;
57 
PopupItemView(Context context)58     public PopupItemView(Context context) {
59         this(context, null, 0);
60     }
61 
PopupItemView(Context context, AttributeSet attrs)62     public PopupItemView(Context context, AttributeSet attrs) {
63         this(context, attrs, 0);
64     }
65 
PopupItemView(Context context, AttributeSet attrs, int defStyle)66     public PopupItemView(Context context, AttributeSet attrs, int defStyle) {
67         super(context, attrs, defStyle);
68 
69         mPillRect = new Rect();
70 
71         // Initialize corner clipping Bitmap and Paint.
72         int radius = (int) getBackgroundRadius();
73         mRoundedCornerBitmap = Bitmap.createBitmap(radius, radius, Bitmap.Config.ALPHA_8);
74         Canvas canvas = new Canvas();
75         canvas.setBitmap(mRoundedCornerBitmap);
76         canvas.drawArc(0, 0, radius*2, radius*2, 180, 90, true, mBackgroundClipPaint);
77         mBackgroundClipPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
78 
79         mIsRtl = Utilities.isRtl(getResources());
80     }
81 
82     @Override
onFinishInflate()83     protected void onFinishInflate() {
84         super.onFinishInflate();
85         mIconView = findViewById(R.id.popup_item_icon);
86     }
87 
88     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)89     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
90         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
91         mPillRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
92     }
93 
94     @Override
dispatchDraw(Canvas canvas)95     protected void dispatchDraw(Canvas canvas) {
96         int saveCount = canvas.saveLayer(0, 0, getWidth(), getHeight(), null);
97         super.dispatchDraw(canvas);
98 
99         int cornerWidth = mRoundedCornerBitmap.getWidth();
100         int cornerHeight = mRoundedCornerBitmap.getHeight();
101         // Clip top left corner.
102         mMatrix.reset();
103         canvas.drawBitmap(mRoundedCornerBitmap, mMatrix, mBackgroundClipPaint);
104         // Clip top right corner.
105         mMatrix.setRotate(90, cornerWidth / 2, cornerHeight / 2);
106         mMatrix.postTranslate(canvas.getWidth() - cornerWidth, 0);
107         canvas.drawBitmap(mRoundedCornerBitmap, mMatrix, mBackgroundClipPaint);
108         // Clip bottom right corner.
109         mMatrix.setRotate(180, cornerWidth / 2, cornerHeight / 2);
110         mMatrix.postTranslate(canvas.getWidth() - cornerWidth, canvas.getHeight() - cornerHeight);
111         canvas.drawBitmap(mRoundedCornerBitmap, mMatrix, mBackgroundClipPaint);
112         // Clip bottom left corner.
113         mMatrix.setRotate(270, cornerWidth / 2, cornerHeight / 2);
114         mMatrix.postTranslate(0, canvas.getHeight() - cornerHeight);
115         canvas.drawBitmap(mRoundedCornerBitmap, mMatrix, mBackgroundClipPaint);
116 
117         canvas.restoreToCount(saveCount);
118     }
119 
120     /**
121      * Creates an animator to play when the shortcut container is being opened.
122      */
createOpenAnimation(boolean isContainerAboveIcon, boolean pivotLeft)123     public Animator createOpenAnimation(boolean isContainerAboveIcon, boolean pivotLeft) {
124         Point center = getIconCenter();
125         int arrowCenter = getResources().getDimensionPixelSize(pivotLeft ^ mIsRtl ?
126                 R.dimen.popup_arrow_horizontal_center_start:
127                 R.dimen.popup_arrow_horizontal_center_end);
128         ValueAnimator openAnimator =  new ZoomRevealOutlineProvider(center.x, center.y,
129                 mPillRect, this, mIconView, isContainerAboveIcon, pivotLeft, arrowCenter)
130                         .createRevealAnimator(this, false);
131         mOpenAnimationProgress = 0f;
132         openAnimator.addUpdateListener(this);
133         return openAnimator;
134     }
135 
136     @Override
onAnimationUpdate(ValueAnimator valueAnimator)137     public void onAnimationUpdate(ValueAnimator valueAnimator) {
138         mOpenAnimationProgress = valueAnimator.getAnimatedFraction();
139     }
140 
isOpenOrOpening()141     public boolean isOpenOrOpening() {
142         return mOpenAnimationProgress > 0;
143     }
144 
145     /**
146      * Creates an animator to play when the shortcut container is being closed.
147      */
createCloseAnimation(boolean isContainerAboveIcon, boolean pivotLeft, long duration)148     public Animator createCloseAnimation(boolean isContainerAboveIcon, boolean pivotLeft,
149             long duration) {
150         Point center = getIconCenter();
151         int arrowCenter = getResources().getDimensionPixelSize(pivotLeft ^ mIsRtl ?
152                 R.dimen.popup_arrow_horizontal_center_start :
153                 R.dimen.popup_arrow_horizontal_center_end);
154         ValueAnimator closeAnimator = new ZoomRevealOutlineProvider(center.x, center.y,
155                 mPillRect, this, mIconView, isContainerAboveIcon, pivotLeft, arrowCenter)
156                         .createRevealAnimator(this, true);
157         // Scale down the duration and interpolator according to the progress
158         // that the open animation was at when the close started.
159         closeAnimator.setDuration((long) (duration * mOpenAnimationProgress));
160         closeAnimator.setInterpolator(new CloseInterpolator(mOpenAnimationProgress));
161         closeAnimator.addListener(new AnimatorListenerAdapter() {
162             @Override
163             public void onAnimationEnd(Animator animation) {
164                 mOpenAnimationProgress = 0;
165             }
166         });
167         return closeAnimator;
168     }
169 
170     /**
171      * Returns the position of the center of the icon relative to the container.
172      */
getIconCenter()173     public Point getIconCenter() {
174         sTempPoint.y = getMeasuredHeight() / 2;
175         sTempPoint.x = getResources().getDimensionPixelSize(R.dimen.bg_popup_item_height) / 2;
176         if (Utilities.isRtl(getResources())) {
177             sTempPoint.x = getMeasuredWidth() - sTempPoint.x;
178         }
179         return sTempPoint;
180     }
181 
getBackgroundRadius()182     protected float getBackgroundRadius() {
183         return getResources().getDimensionPixelSize(R.dimen.bg_round_rect_radius);
184     }
185 
getArrowColor(boolean isArrowAttachedToBottom)186     public abstract int getArrowColor(boolean isArrowAttachedToBottom);
187 
188     /**
189      * Extension of {@link PillRevealOutlineProvider} which scales the icon based on the height.
190      */
191     private static class ZoomRevealOutlineProvider extends PillRevealOutlineProvider {
192 
193         private final View mTranslateView;
194         private final View mZoomView;
195 
196         private final float mFullHeight;
197         private final float mTranslateYMultiplier;
198 
199         private final boolean mPivotLeft;
200         private final float mTranslateX;
201         private final float mArrowCenter;
202 
ZoomRevealOutlineProvider(int x, int y, Rect pillRect, PopupItemView translateView, View zoomView, boolean isContainerAboveIcon, boolean pivotLeft, float arrowCenter)203         public ZoomRevealOutlineProvider(int x, int y, Rect pillRect, PopupItemView translateView,
204                 View zoomView, boolean isContainerAboveIcon, boolean pivotLeft, float arrowCenter) {
205             super(x, y, pillRect, translateView.getBackgroundRadius());
206             mTranslateView = translateView;
207             mZoomView = zoomView;
208             mFullHeight = pillRect.height();
209 
210             mTranslateYMultiplier = isContainerAboveIcon ? 0.5f : -0.5f;
211 
212             mPivotLeft = pivotLeft;
213             mTranslateX = pivotLeft ? arrowCenter : pillRect.right - arrowCenter;
214             mArrowCenter = arrowCenter;
215         }
216 
217         @Override
setProgress(float progress)218         public void setProgress(float progress) {
219             super.setProgress(progress);
220 
221             if (mZoomView != null) {
222                 mZoomView.setScaleX(progress);
223                 mZoomView.setScaleY(progress);
224             }
225 
226             float height = mOutline.height();
227             mTranslateView.setTranslationY(mTranslateYMultiplier * (mFullHeight - height));
228 
229             float offsetX = Math.min(mOutline.width(), mArrowCenter);
230             float pivotX = mPivotLeft ? (mOutline.left + offsetX) : (mOutline.right - offsetX);
231             mTranslateView.setTranslationX(mTranslateX - pivotX);
232         }
233     }
234 
235     /**
236      * An interpolator that reverses the current open animation progress.
237      */
238     private static class CloseInterpolator extends LogAccelerateInterpolator {
239         private float mStartProgress;
240         private float mRemainingProgress;
241 
242         /**
243          * @param openAnimationProgress The progress that the open interpolator ended at.
244          */
CloseInterpolator(float openAnimationProgress)245         public CloseInterpolator(float openAnimationProgress) {
246             super(100, 0);
247             mStartProgress = 1f - openAnimationProgress;
248             mRemainingProgress = openAnimationProgress;
249         }
250 
251         @Override
getInterpolation(float v)252         public float getInterpolation(float v) {
253             return mStartProgress + super.getInterpolation(v) * mRemainingProgress;
254         }
255     }
256 }
257