| /* |
| * Copyright (C) 2016 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.shortcuts; |
| |
| import android.animation.Animator; |
| import android.animation.ValueAnimator; |
| import android.content.Context; |
| import android.graphics.Point; |
| import android.graphics.Rect; |
| import android.text.TextUtils; |
| import android.util.AttributeSet; |
| import android.view.View; |
| import android.widget.FrameLayout; |
| |
| import com.android.launcher3.IconCache; |
| import com.android.launcher3.Launcher; |
| import com.android.launcher3.LauncherAppState; |
| import com.android.launcher3.LogAccelerateInterpolator; |
| import com.android.launcher3.R; |
| import com.android.launcher3.ShortcutInfo; |
| import com.android.launcher3.Utilities; |
| import com.android.launcher3.shortcuts.DeepShortcutsContainer.UnbadgedShortcutInfo; |
| import com.android.launcher3.util.PillRevealOutlineProvider; |
| import com.android.launcher3.util.PillWidthRevealOutlineProvider; |
| |
| /** |
| * A {@link android.widget.FrameLayout} that contains a {@link DeepShortcutView}. |
| * This lets us animate the DeepShortcutView (icon and text) separately from the background. |
| */ |
| public class DeepShortcutView extends FrameLayout implements ValueAnimator.AnimatorUpdateListener { |
| |
| private static final Point sTempPoint = new Point(); |
| |
| private final Rect mPillRect; |
| |
| private DeepShortcutTextView mBubbleText; |
| private View mIconView; |
| private float mOpenAnimationProgress; |
| |
| private UnbadgedShortcutInfo mInfo; |
| |
| public DeepShortcutView(Context context) { |
| this(context, null, 0); |
| } |
| |
| public DeepShortcutView(Context context, AttributeSet attrs) { |
| this(context, attrs, 0); |
| } |
| |
| public DeepShortcutView(Context context, AttributeSet attrs, int defStyle) { |
| super(context, attrs, defStyle); |
| |
| mPillRect = new Rect(); |
| } |
| |
| @Override |
| protected void onFinishInflate() { |
| super.onFinishInflate(); |
| mIconView = findViewById(R.id.deep_shortcut_icon); |
| mBubbleText = (DeepShortcutTextView) findViewById(R.id.deep_shortcut); |
| } |
| |
| public DeepShortcutTextView getBubbleText() { |
| return mBubbleText; |
| } |
| |
| public void setWillDrawIcon(boolean willDraw) { |
| mIconView.setVisibility(willDraw ? View.VISIBLE : View.INVISIBLE); |
| } |
| |
| public boolean willDrawIcon() { |
| return mIconView.getVisibility() == View.VISIBLE; |
| } |
| |
| @Override |
| protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
| super.onMeasure(widthMeasureSpec, heightMeasureSpec); |
| mPillRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight()); |
| } |
| |
| /** package private **/ |
| void applyShortcutInfo(UnbadgedShortcutInfo info, DeepShortcutsContainer container) { |
| mInfo = info; |
| IconCache cache = LauncherAppState.getInstance().getIconCache(); |
| mBubbleText.applyFromShortcutInfo(info, cache); |
| mIconView.setBackground(mBubbleText.getIcon()); |
| |
| // Use the long label as long as it exists and fits. |
| CharSequence longLabel = info.mDetail.getLongLabel(); |
| int availableWidth = mBubbleText.getWidth() - mBubbleText.getTotalPaddingLeft() |
| - mBubbleText.getTotalPaddingRight(); |
| boolean usingLongLabel = !TextUtils.isEmpty(longLabel) |
| && mBubbleText.getPaint().measureText(longLabel.toString()) <= availableWidth; |
| mBubbleText.setText(usingLongLabel ? longLabel : info.mDetail.getShortLabel()); |
| |
| // TODO: Add the click handler to this view directly and not the child view. |
| mBubbleText.setOnClickListener(Launcher.getLauncher(getContext())); |
| mBubbleText.setOnLongClickListener(container); |
| mBubbleText.setOnTouchListener(container); |
| } |
| |
| /** |
| * Returns the shortcut info that is suitable to be added on the homescreen |
| */ |
| public ShortcutInfo getFinalInfo() { |
| ShortcutInfo badged = new ShortcutInfo(mInfo); |
| // Queue an update task on the worker thread. This ensures that the badged |
| // shortcut eventually gets its icon updated. |
| Launcher.getLauncher(getContext()).getModel().updateShortcutInfo(mInfo.mDetail, badged); |
| return badged; |
| } |
| |
| public View getIconView() { |
| return mIconView; |
| } |
| |
| /** |
| * 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)); |
| 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; |
| } |
| |
| /** |
| * 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, |
| View translateView, View zoomView, boolean isContainerAboveIcon, boolean pivotLeft) { |
| super(x, y, pillRect); |
| 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; |
| } |
| } |
| } |