/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.launcher3.popup; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.content.Context; import android.content.res.ColorStateList; import android.graphics.Point; import android.graphics.Rect; import android.util.AttributeSet; import android.view.View; import android.widget.FrameLayout; import com.android.launcher3.LogAccelerateInterpolator; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.util.PillRevealOutlineProvider; import com.android.launcher3.util.PillWidthRevealOutlineProvider; /** * An abstract {@link FrameLayout} that supports animating an item's content * (e.g. icon and text) separate from the item's background. */ public abstract class PopupItemView extends FrameLayout implements ValueAnimator.AnimatorUpdateListener { protected static final Point sTempPoint = new Point(); protected final Rect mPillRect; private float mOpenAnimationProgress; protected View mIconView; public PopupItemView(Context context) { this(context, null, 0); } public PopupItemView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public PopupItemView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); mPillRect = new Rect(); } @Override protected void onFinishInflate() { super.onFinishInflate(); mIconView = findViewById(R.id.popup_item_icon); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); mPillRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight()); } protected ColorStateList getAttachedArrowColor() { return getBackgroundTintList(); } public boolean willDrawIcon() { return true; } /** * Creates an animator to play when the shortcut container is being opened. */ public Animator createOpenAnimation(boolean isContainerAboveIcon, boolean pivotLeft) { Point center = getIconCenter(); ValueAnimator openAnimator = new ZoomRevealOutlineProvider(center.x, center.y, mPillRect, this, mIconView, isContainerAboveIcon, pivotLeft) .createRevealAnimator(this, false); mOpenAnimationProgress = 0f; openAnimator.addUpdateListener(this); return openAnimator; } @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { mOpenAnimationProgress = valueAnimator.getAnimatedFraction(); } public boolean isOpenOrOpening() { return mOpenAnimationProgress > 0; } /** * Creates an animator to play when the shortcut container is being closed. */ public Animator createCloseAnimation(boolean isContainerAboveIcon, boolean pivotLeft, long duration) { Point center = getIconCenter(); ValueAnimator closeAnimator = new ZoomRevealOutlineProvider(center.x, center.y, mPillRect, this, mIconView, isContainerAboveIcon, pivotLeft) .createRevealAnimator(this, true); // Scale down the duration and interpolator according to the progress // that the open animation was at when the close started. closeAnimator.setDuration((long) (duration * mOpenAnimationProgress)); closeAnimator.setInterpolator(new CloseInterpolator(mOpenAnimationProgress)); closeAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mOpenAnimationProgress = 0; } }); return closeAnimator; } /** * Creates an animator which clips the container to form a circle around the icon. */ public Animator collapseToIcon() { int halfHeight = getMeasuredHeight() / 2; int iconCenterX = getIconCenter().x; return new PillWidthRevealOutlineProvider(mPillRect, iconCenterX - halfHeight, iconCenterX + halfHeight) .createRevealAnimator(this, true); } /** * Returns the position of the center of the icon relative to the container. */ public Point getIconCenter() { sTempPoint.y = sTempPoint.x = getMeasuredHeight() / 2; if (Utilities.isRtl(getResources())) { sTempPoint.x = getMeasuredWidth() - sTempPoint.x; } return sTempPoint; } protected float getBackgroundRadius() { return getResources().getDimensionPixelSize(R.dimen.bg_pill_radius); } /** * Extension of {@link PillRevealOutlineProvider} which scales the icon based on the height. */ private static class ZoomRevealOutlineProvider extends PillRevealOutlineProvider { private final View mTranslateView; private final View mZoomView; private final float mFullHeight; private final float mTranslateYMultiplier; private final boolean mPivotLeft; private final float mTranslateX; public ZoomRevealOutlineProvider(int x, int y, Rect pillRect, PopupItemView translateView, View zoomView, boolean isContainerAboveIcon, boolean pivotLeft) { super(x, y, pillRect, translateView.getBackgroundRadius()); mTranslateView = translateView; mZoomView = zoomView; mFullHeight = pillRect.height(); mTranslateYMultiplier = isContainerAboveIcon ? 0.5f : -0.5f; mPivotLeft = pivotLeft; mTranslateX = pivotLeft ? pillRect.height() / 2 : pillRect.right - pillRect.height() / 2; } @Override public void setProgress(float progress) { super.setProgress(progress); mZoomView.setScaleX(progress); mZoomView.setScaleY(progress); float height = mOutline.height(); mTranslateView.setTranslationY(mTranslateYMultiplier * (mFullHeight - height)); float pivotX = mPivotLeft ? (mOutline.left + height / 2) : (mOutline.right - height / 2); mTranslateView.setTranslationX(mTranslateX - pivotX); } } /** * An interpolator that reverses the current open animation progress. */ private static class CloseInterpolator extends LogAccelerateInterpolator { private float mStartProgress; private float mRemainingProgress; /** * @param openAnimationProgress The progress that the open interpolator ended at. */ public CloseInterpolator(float openAnimationProgress) { super(100, 0); mStartProgress = 1f - openAnimationProgress; mRemainingProgress = openAnimationProgress; } @Override public float getInterpolation(float v) { return mStartProgress + super.getInterpolation(v) * mRemainingProgress; } } }