1 /* 2 * Copyright (C) 2016 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.shortcuts; 18 19 import android.animation.Animator; 20 import android.animation.ValueAnimator; 21 import android.content.Context; 22 import android.graphics.Point; 23 import android.graphics.Rect; 24 import android.text.TextUtils; 25 import android.util.AttributeSet; 26 import android.view.View; 27 import android.widget.FrameLayout; 28 29 import com.android.launcher3.IconCache; 30 import com.android.launcher3.Launcher; 31 import com.android.launcher3.LauncherAppState; 32 import com.android.launcher3.LogAccelerateInterpolator; 33 import com.android.launcher3.R; 34 import com.android.launcher3.ShortcutInfo; 35 import com.android.launcher3.Utilities; 36 import com.android.launcher3.shortcuts.DeepShortcutsContainer.UnbadgedShortcutInfo; 37 import com.android.launcher3.util.PillRevealOutlineProvider; 38 import com.android.launcher3.util.PillWidthRevealOutlineProvider; 39 40 /** 41 * A {@link android.widget.FrameLayout} that contains a {@link DeepShortcutView}. 42 * This lets us animate the DeepShortcutView (icon and text) separately from the background. 43 */ 44 public class DeepShortcutView extends FrameLayout implements ValueAnimator.AnimatorUpdateListener { 45 46 private static final Point sTempPoint = new Point(); 47 48 private final Rect mPillRect; 49 50 private DeepShortcutTextView mBubbleText; 51 private View mIconView; 52 private float mOpenAnimationProgress; 53 54 private UnbadgedShortcutInfo mInfo; 55 DeepShortcutView(Context context)56 public DeepShortcutView(Context context) { 57 this(context, null, 0); 58 } 59 DeepShortcutView(Context context, AttributeSet attrs)60 public DeepShortcutView(Context context, AttributeSet attrs) { 61 this(context, attrs, 0); 62 } 63 DeepShortcutView(Context context, AttributeSet attrs, int defStyle)64 public DeepShortcutView(Context context, AttributeSet attrs, int defStyle) { 65 super(context, attrs, defStyle); 66 67 mPillRect = new Rect(); 68 } 69 70 @Override onFinishInflate()71 protected void onFinishInflate() { 72 super.onFinishInflate(); 73 mIconView = findViewById(R.id.deep_shortcut_icon); 74 mBubbleText = (DeepShortcutTextView) findViewById(R.id.deep_shortcut); 75 } 76 getBubbleText()77 public DeepShortcutTextView getBubbleText() { 78 return mBubbleText; 79 } 80 setWillDrawIcon(boolean willDraw)81 public void setWillDrawIcon(boolean willDraw) { 82 mIconView.setVisibility(willDraw ? View.VISIBLE : View.INVISIBLE); 83 } 84 willDrawIcon()85 public boolean willDrawIcon() { 86 return mIconView.getVisibility() == View.VISIBLE; 87 } 88 89 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)90 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 91 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 92 mPillRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight()); 93 } 94 95 /** package private **/ applyShortcutInfo(UnbadgedShortcutInfo info, DeepShortcutsContainer container)96 void applyShortcutInfo(UnbadgedShortcutInfo info, DeepShortcutsContainer container) { 97 mInfo = info; 98 IconCache cache = LauncherAppState.getInstance().getIconCache(); 99 mBubbleText.applyFromShortcutInfo(info, cache); 100 mIconView.setBackground(mBubbleText.getIcon()); 101 102 // Use the long label as long as it exists and fits. 103 CharSequence longLabel = info.mDetail.getLongLabel(); 104 int availableWidth = mBubbleText.getWidth() - mBubbleText.getTotalPaddingLeft() 105 - mBubbleText.getTotalPaddingRight(); 106 boolean usingLongLabel = !TextUtils.isEmpty(longLabel) 107 && mBubbleText.getPaint().measureText(longLabel.toString()) <= availableWidth; 108 mBubbleText.setText(usingLongLabel ? longLabel : info.mDetail.getShortLabel()); 109 110 // TODO: Add the click handler to this view directly and not the child view. 111 mBubbleText.setOnClickListener(Launcher.getLauncher(getContext())); 112 mBubbleText.setOnLongClickListener(container); 113 mBubbleText.setOnTouchListener(container); 114 } 115 116 /** 117 * Returns the shortcut info that is suitable to be added on the homescreen 118 */ getFinalInfo()119 public ShortcutInfo getFinalInfo() { 120 ShortcutInfo badged = new ShortcutInfo(mInfo); 121 // Queue an update task on the worker thread. This ensures that the badged 122 // shortcut eventually gets its icon updated. 123 Launcher.getLauncher(getContext()).getModel().updateShortcutInfo(mInfo.mDetail, badged); 124 return badged; 125 } 126 getIconView()127 public View getIconView() { 128 return mIconView; 129 } 130 131 /** 132 * Creates an animator to play when the shortcut container is being opened. 133 */ createOpenAnimation(boolean isContainerAboveIcon, boolean pivotLeft)134 public Animator createOpenAnimation(boolean isContainerAboveIcon, boolean pivotLeft) { 135 Point center = getIconCenter(); 136 ValueAnimator openAnimator = new ZoomRevealOutlineProvider(center.x, center.y, 137 mPillRect, this, mIconView, isContainerAboveIcon, pivotLeft) 138 .createRevealAnimator(this, false); 139 mOpenAnimationProgress = 0f; 140 openAnimator.addUpdateListener(this); 141 return openAnimator; 142 } 143 144 @Override onAnimationUpdate(ValueAnimator valueAnimator)145 public void onAnimationUpdate(ValueAnimator valueAnimator) { 146 mOpenAnimationProgress = valueAnimator.getAnimatedFraction(); 147 } 148 isOpenOrOpening()149 public boolean isOpenOrOpening() { 150 return mOpenAnimationProgress > 0; 151 } 152 153 /** 154 * Creates an animator to play when the shortcut container is being closed. 155 */ createCloseAnimation(boolean isContainerAboveIcon, boolean pivotLeft, long duration)156 public Animator createCloseAnimation(boolean isContainerAboveIcon, boolean pivotLeft, 157 long duration) { 158 Point center = getIconCenter(); 159 ValueAnimator closeAnimator = new ZoomRevealOutlineProvider(center.x, center.y, 160 mPillRect, this, mIconView, isContainerAboveIcon, pivotLeft) 161 .createRevealAnimator(this, true); 162 // Scale down the duration and interpolator according to the progress 163 // that the open animation was at when the close started. 164 closeAnimator.setDuration((long) (duration * mOpenAnimationProgress)); 165 closeAnimator.setInterpolator(new CloseInterpolator(mOpenAnimationProgress)); 166 return closeAnimator; 167 } 168 169 /** 170 * Creates an animator which clips the container to form a circle around the icon. 171 */ collapseToIcon()172 public Animator collapseToIcon() { 173 int halfHeight = getMeasuredHeight() / 2; 174 int iconCenterX = getIconCenter().x; 175 return new PillWidthRevealOutlineProvider(mPillRect, 176 iconCenterX - halfHeight, iconCenterX + halfHeight) 177 .createRevealAnimator(this, true); 178 } 179 180 /** 181 * Returns the position of the center of the icon relative to the container. 182 */ getIconCenter()183 public Point getIconCenter() { 184 sTempPoint.y = sTempPoint.x = getMeasuredHeight() / 2; 185 if (Utilities.isRtl(getResources())) { 186 sTempPoint.x = getMeasuredWidth() - sTempPoint.x; 187 } 188 return sTempPoint; 189 } 190 191 /** 192 * Extension of {@link PillRevealOutlineProvider} which scales the icon based on the height. 193 */ 194 private static class ZoomRevealOutlineProvider extends PillRevealOutlineProvider { 195 196 private final View mTranslateView; 197 private final View mZoomView; 198 199 private final float mFullHeight; 200 private final float mTranslateYMultiplier; 201 202 private final boolean mPivotLeft; 203 private final float mTranslateX; 204 ZoomRevealOutlineProvider(int x, int y, Rect pillRect, View translateView, View zoomView, boolean isContainerAboveIcon, boolean pivotLeft)205 public ZoomRevealOutlineProvider(int x, int y, Rect pillRect, 206 View translateView, View zoomView, boolean isContainerAboveIcon, boolean pivotLeft) { 207 super(x, y, pillRect); 208 mTranslateView = translateView; 209 mZoomView = zoomView; 210 mFullHeight = pillRect.height(); 211 212 mTranslateYMultiplier = isContainerAboveIcon ? 0.5f : -0.5f; 213 214 mPivotLeft = pivotLeft; 215 mTranslateX = pivotLeft ? pillRect.height() / 2 : pillRect.right - pillRect.height() / 2; 216 } 217 218 @Override setProgress(float progress)219 public void setProgress(float progress) { 220 super.setProgress(progress); 221 222 mZoomView.setScaleX(progress); 223 mZoomView.setScaleY(progress); 224 225 float height = mOutline.height(); 226 mTranslateView.setTranslationY(mTranslateYMultiplier * (mFullHeight - height)); 227 228 float pivotX = mPivotLeft ? (mOutline.left + height / 2) : (mOutline.right - height / 2); 229 mTranslateView.setTranslationX(mTranslateX - pivotX); 230 } 231 } 232 233 /** 234 * An interpolator that reverses the current open animation progress. 235 */ 236 private static class CloseInterpolator extends LogAccelerateInterpolator { 237 private float mStartProgress; 238 private float mRemainingProgress; 239 240 /** 241 * @param openAnimationProgress The progress that the open interpolator ended at. 242 */ CloseInterpolator(float openAnimationProgress)243 public CloseInterpolator(float openAnimationProgress) { 244 super(100, 0); 245 mStartProgress = 1f - openAnimationProgress; 246 mRemainingProgress = openAnimationProgress; 247 } 248 249 @Override getInterpolation(float v)250 public float getInterpolation(float v) { 251 return mStartProgress + super.getInterpolation(v) * mRemainingProgress; 252 } 253 } 254 } 255