OSDN Git Service

Visual updates for popup
[android-x86/packages-apps-Launcher3.git] / src / com / android / launcher3 / popup / PopupItemView.java
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
58     public PopupItemView(Context context) {
59         this(context, null, 0);
60     }
61
62     public PopupItemView(Context context, AttributeSet attrs) {
63         this(context, attrs, 0);
64     }
65
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
83     protected void onFinishInflate() {
84         super.onFinishInflate();
85         mIconView = findViewById(R.id.popup_item_icon);
86     }
87
88     @Override
89     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
90         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
91         mPillRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
92     }
93
94     @Override
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      */
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
137     public void onAnimationUpdate(ValueAnimator valueAnimator) {
138         mOpenAnimationProgress = valueAnimator.getAnimatedFraction();
139     }
140
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      */
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      */
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
182     protected float getBackgroundRadius() {
183         return getResources().getDimensionPixelSize(R.dimen.bg_round_rect_radius);
184     }
185
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
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
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          */
245         public CloseInterpolator(float openAnimationProgress) {
246             super(100, 0);
247             mStartProgress = 1f - openAnimationProgress;
248             mRemainingProgress = openAnimationProgress;
249         }
250
251         @Override
252         public float getInterpolation(float v) {
253             return mStartProgress + super.getInterpolation(v) * mRemainingProgress;
254         }
255     }
256 }