blob: b3d7155aa0833a51ed104b5a802f456d3ab9d3e4 [file] [log] [blame]
/*
* 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;
}
}
}