2 * Copyright (C) 2017 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.launcher3.popup;
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;
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;
41 * An abstract {@link FrameLayout} that supports animating an item's content
42 * (e.g. icon and text) separate from the item's background.
44 public abstract class PopupItemView extends FrameLayout
45 implements ValueAnimator.AnimatorUpdateListener {
47 protected static final Point sTempPoint = new Point();
49 protected final Rect mPillRect;
50 private float mOpenAnimationProgress;
51 protected final boolean mIsRtl;
52 protected View mIconView;
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;
58 public PopupItemView(Context context) {
59 this(context, null, 0);
62 public PopupItemView(Context context, AttributeSet attrs) {
63 this(context, attrs, 0);
66 public PopupItemView(Context context, AttributeSet attrs, int defStyle) {
67 super(context, attrs, defStyle);
69 mPillRect = new Rect();
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));
79 mIsRtl = Utilities.isRtl(getResources());
83 protected void onFinishInflate() {
84 super.onFinishInflate();
85 mIconView = findViewById(R.id.popup_item_icon);
89 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
90 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
91 mPillRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
95 protected void dispatchDraw(Canvas canvas) {
96 int saveCount = canvas.saveLayer(0, 0, getWidth(), getHeight(), null);
97 super.dispatchDraw(canvas);
99 int cornerWidth = mRoundedCornerBitmap.getWidth();
100 int cornerHeight = mRoundedCornerBitmap.getHeight();
101 // Clip top left corner.
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);
117 canvas.restoreToCount(saveCount);
121 * Creates an animator to play when the shortcut container is being opened.
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);
137 public void onAnimationUpdate(ValueAnimator valueAnimator) {
138 mOpenAnimationProgress = valueAnimator.getAnimatedFraction();
141 public boolean isOpenOrOpening() {
142 return mOpenAnimationProgress > 0;
146 * Creates an animator to play when the shortcut container is being closed.
148 public Animator createCloseAnimation(boolean isContainerAboveIcon, boolean pivotLeft,
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() {
163 public void onAnimationEnd(Animator animation) {
164 mOpenAnimationProgress = 0;
167 return closeAnimator;
171 * Returns the position of the center of the icon relative to the container.
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;
182 protected float getBackgroundRadius() {
183 return getResources().getDimensionPixelSize(R.dimen.bg_round_rect_radius);
186 public abstract int getArrowColor(boolean isArrowAttachedToBottom);
189 * Extension of {@link PillRevealOutlineProvider} which scales the icon based on the height.
191 private static class ZoomRevealOutlineProvider extends PillRevealOutlineProvider {
193 private final View mTranslateView;
194 private final View mZoomView;
196 private final float mFullHeight;
197 private final float mTranslateYMultiplier;
199 private final boolean mPivotLeft;
200 private final float mTranslateX;
201 private final float mArrowCenter;
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();
210 mTranslateYMultiplier = isContainerAboveIcon ? 0.5f : -0.5f;
212 mPivotLeft = pivotLeft;
213 mTranslateX = pivotLeft ? arrowCenter : pillRect.right - arrowCenter;
214 mArrowCenter = arrowCenter;
218 public void setProgress(float progress) {
219 super.setProgress(progress);
221 if (mZoomView != null) {
222 mZoomView.setScaleX(progress);
223 mZoomView.setScaleY(progress);
226 float height = mOutline.height();
227 mTranslateView.setTranslationY(mTranslateYMultiplier * (mFullHeight - height));
229 float offsetX = Math.min(mOutline.width(), mArrowCenter);
230 float pivotX = mPivotLeft ? (mOutline.left + offsetX) : (mOutline.right - offsetX);
231 mTranslateView.setTranslationX(mTranslateX - pivotX);
236 * An interpolator that reverses the current open animation progress.
238 private static class CloseInterpolator extends LogAccelerateInterpolator {
239 private float mStartProgress;
240 private float mRemainingProgress;
243 * @param openAnimationProgress The progress that the open interpolator ended at.
245 public CloseInterpolator(float openAnimationProgress) {
247 mStartProgress = 1f - openAnimationProgress;
248 mRemainingProgress = openAnimationProgress;
252 public float getInterpolation(float v) {
253 return mStartProgress + super.getInterpolation(v) * mRemainingProgress;